refactor: migrate Gson serialization to kotlinx.serialization (#475)
* refactor: migrate Gson serialization to kotlinx.serialization * fix(dto): resolve serializing Any? and Map<String, Any?> with JsonElement * test(refactor): fix unit test issues under kotlinx.serialization and update nullable DTO defaults * fix(chat): support flexible numeric and string decoding for message timestamps * fix(chat): sequentialize cache loading to avoid overwriting network state
This commit is contained in:
64 files changed
+863
-577
No files matched your search
@@ -4,6 +4,9 @@ import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import androidx.security.crypto.EncryptedSharedPreferences
|
||||
import androidx.security.crypto.MasterKeys
|
||||
import com.m57.hermescontrol.data.model.ConnectionProfile
|
||||
import com.m57.hermescontrol.data.model.PinnedModel
|
||||
import com.m57.hermescontrol.data.remote.OkHttpProvider
|
||||
import com.m57.hermescontrol.theme.BottomNavDisplayMode
|
||||
import com.m57.hermescontrol.theme.ThemePreference
|
||||
import com.m57.hermescontrol.theme.ThemePreset
|
||||
@@ -15,6 +18,8 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.serialization.decodeFromString
|
||||
import kotlinx.serialization.encodeToString
|
||||
|
||||
/**
|
||||
* Singleton that manages encrypted storage of the Hermes dashboard token
|
||||
@@ -69,8 +74,6 @@ object AuthManager {
|
||||
private val _tokenFlow = MutableStateFlow<String?>(null)
|
||||
val tokenFlow: StateFlow<String?> = _tokenFlow.asStateFlow()
|
||||
|
||||
private val gson = com.m57.hermescontrol.data.remote.OkHttpProvider.gson
|
||||
|
||||
/**
|
||||
* Initialise the encrypted preferences.
|
||||
* Call this once from Application.onCreate() or MainActivity.onCreate().
|
||||
@@ -160,14 +163,10 @@ object AuthManager {
|
||||
|
||||
// ── Connection Profiles ──────────────────────────────────────────────
|
||||
|
||||
fun getConnectionProfiles(): List<com.m57.hermescontrol.data.model.ConnectionProfile> {
|
||||
fun getConnectionProfiles(): List<ConnectionProfile> {
|
||||
val json = requirePrefs().getString(KEY_CONNECTION_PROFILES, null) ?: return emptyList()
|
||||
return try {
|
||||
val type =
|
||||
object : com.google.gson.reflect.TypeToken<
|
||||
List<com.m57.hermescontrol.data.model.ConnectionProfile>,
|
||||
>() {}.type
|
||||
gson.fromJson(json, type) ?: emptyList()
|
||||
OkHttpProvider.json.decodeFromString<List<ConnectionProfile>>(json)
|
||||
} catch (e: Exception) {
|
||||
if (com.m57.hermescontrol.BuildConfig.DEBUG) {
|
||||
android.util.Log.w("AuthManager", "Failed to parse connection profiles", e)
|
||||
@@ -176,21 +175,17 @@ object AuthManager {
|
||||
}
|
||||
}
|
||||
|
||||
fun saveConnectionProfiles(profiles: List<com.m57.hermescontrol.data.model.ConnectionProfile>) {
|
||||
val json = gson.toJson(profiles)
|
||||
fun saveConnectionProfiles(profiles: List<ConnectionProfile>) {
|
||||
val json = OkHttpProvider.json.encodeToString(profiles)
|
||||
requirePrefs().edit().putString(KEY_CONNECTION_PROFILES, json).apply()
|
||||
}
|
||||
|
||||
// ── Pinned Models ────────────────────────────────────────────────────
|
||||
|
||||
fun getPinnedModels(): List<com.m57.hermescontrol.data.model.PinnedModel> {
|
||||
fun getPinnedModels(): List<PinnedModel> {
|
||||
val json = requirePrefs().getString(KEY_PINNED_MODELS, null) ?: return emptyList()
|
||||
return try {
|
||||
val type =
|
||||
object : com.google.gson.reflect.TypeToken<
|
||||
List<com.m57.hermescontrol.data.model.PinnedModel>,
|
||||
>() {}.type
|
||||
gson.fromJson(json, type) ?: emptyList()
|
||||
OkHttpProvider.json.decodeFromString<List<PinnedModel>>(json)
|
||||
} catch (e: Exception) {
|
||||
if (com.m57.hermescontrol.BuildConfig.DEBUG) {
|
||||
android.util.Log.w("AuthManager", "Failed to parse pinned models", e)
|
||||
@@ -200,7 +195,9 @@ object AuthManager {
|
||||
}
|
||||
|
||||
fun savePinnedModels(pinned: List<com.m57.hermescontrol.data.model.PinnedModel>) {
|
||||
val json = gson.toJson(pinned)
|
||||
val json =
|
||||
com.m57.hermescontrol.data.remote.OkHttpProvider.json
|
||||
.encodeToString(pinned)
|
||||
requirePrefs().edit().putString(KEY_PINNED_MODELS, json).apply()
|
||||
}
|
||||
|
||||
|
||||
@@ -1,68 +1,73 @@
|
||||
package com.m57.hermescontrol.data.model
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
@Serializable
|
||||
data class AchievementsResponse(
|
||||
val achievements: List<Achievement>? = emptyList(),
|
||||
@SerializedName("unlocked_count") val unlockedCount: Int = 0,
|
||||
@SerializedName("discovered_count") val discoveredCount: Int = 0,
|
||||
@SerializedName("secret_count") val secretCount: Int = 0,
|
||||
@SerializedName("total_count") val totalCount: Int = 0,
|
||||
@SerializedName("is_stale") val isStale: Boolean = false,
|
||||
@SerializedName("generated_at") val generatedAt: Long? = null,
|
||||
@SerializedName("scan_meta") val scanMeta: ScanMeta? = null,
|
||||
@SerialName("unlocked_count") val unlockedCount: Int = 0,
|
||||
@SerialName("discovered_count") val discoveredCount: Int = 0,
|
||||
@SerialName("secret_count") val secretCount: Int = 0,
|
||||
@SerialName("total_count") val totalCount: Int = 0,
|
||||
@SerialName("is_stale") val isStale: Boolean = false,
|
||||
@SerialName("generated_at") val generatedAt: Long? = null,
|
||||
@SerialName("scan_meta") val scanMeta: ScanMeta? = null,
|
||||
val error: String? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class ScanMeta(
|
||||
val status: ScanStatus? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class ScanStatus(
|
||||
val state: String = "idle",
|
||||
@SerializedName("started_at") val startedAt: Long? = null,
|
||||
@SerializedName("finished_at") val finishedAt: Long? = null,
|
||||
@SerializedName("last_error") val lastError: String? = null,
|
||||
@SerializedName("last_duration_ms") val lastDurationMs: Long? = null,
|
||||
@SerializedName("run_count") val runCount: Int = 0,
|
||||
@SerialName("started_at") val startedAt: Long? = null,
|
||||
@SerialName("finished_at") val finishedAt: Long? = null,
|
||||
@SerialName("last_error") val lastError: String? = null,
|
||||
@SerialName("last_duration_ms") val lastDurationMs: Long? = null,
|
||||
@SerialName("run_count") val runCount: Int = 0,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class RecentUnlock(
|
||||
val id: String,
|
||||
val name: String,
|
||||
val description: String?,
|
||||
val category: String?,
|
||||
val kind: String?,
|
||||
val icon: String?,
|
||||
val description: String? = null,
|
||||
val category: String? = null,
|
||||
val kind: String? = null,
|
||||
val icon: String? = null,
|
||||
val unlocked: Boolean,
|
||||
val discovered: Boolean,
|
||||
val state: String?,
|
||||
val tier: String?,
|
||||
val progress: Double?,
|
||||
@SerializedName("next_tier") val nextTier: String?,
|
||||
@SerializedName("next_threshold") val nextThreshold: Double?,
|
||||
@SerializedName("progress_pct") val progressPct: Double?,
|
||||
@SerializedName("unlocked_at") val unlockedAt: Long?,
|
||||
val secret: Boolean?,
|
||||
val criteria: String?,
|
||||
val state: String? = null,
|
||||
val tier: String? = null,
|
||||
val progress: Double? = null,
|
||||
@SerialName("next_tier") val nextTier: String? = null,
|
||||
@SerialName("next_threshold") val nextThreshold: Double? = null,
|
||||
@SerialName("progress_pct") val progressPct: Double? = null,
|
||||
@SerialName("unlocked_at") val unlockedAt: Long? = null,
|
||||
val secret: Boolean? = null,
|
||||
val criteria: String? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class Achievement(
|
||||
val id: String,
|
||||
val name: String,
|
||||
val description: String?,
|
||||
val category: String?,
|
||||
val kind: String?,
|
||||
val icon: String?,
|
||||
val description: String? = null,
|
||||
val category: String? = null,
|
||||
val kind: String? = null,
|
||||
val icon: String? = null,
|
||||
val unlocked: Boolean,
|
||||
val discovered: Boolean,
|
||||
val state: String?,
|
||||
val tier: String?,
|
||||
val progress: Double?,
|
||||
@SerializedName("next_tier") val nextTier: String?,
|
||||
@SerializedName("next_threshold") val nextThreshold: Double?,
|
||||
@SerializedName("progress_pct") val progressPct: Double?,
|
||||
@SerializedName("unlocked_at") val unlockedAt: Long?,
|
||||
val secret: Boolean?,
|
||||
val criteria: String?,
|
||||
val state: String? = null,
|
||||
val tier: String? = null,
|
||||
val progress: Double? = null,
|
||||
@SerialName("next_tier") val nextTier: String? = null,
|
||||
@SerialName("next_threshold") val nextThreshold: Double? = null,
|
||||
@SerialName("progress_pct") val progressPct: Double? = null,
|
||||
@SerialName("unlocked_at") val unlockedAt: Long? = null,
|
||||
val secret: Boolean? = null,
|
||||
val criteria: String? = null,
|
||||
)
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.m57.hermescontrol.data.model
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class ActionResponse(
|
||||
val archive: String? = null,
|
||||
val name: String? = null,
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.m57.hermescontrol.data.model
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class ActionStatusResponse(
|
||||
val exit_code: Int? = null,
|
||||
val lines: List<String>? = null,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
package com.m57.hermescontrol.data.model
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* Represents a file attached to a chat message.
|
||||
@@ -9,6 +10,7 @@ package com.m57.hermescontrol.data.model
|
||||
* @param size File size in bytes
|
||||
* @param localPath Optional local file system path for direct access
|
||||
*/
|
||||
@Serializable
|
||||
data class Attachment(
|
||||
val uri: String,
|
||||
val name: String,
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
package com.m57.hermescontrol.data.model
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class CheckpointsResponse(
|
||||
val sessions: List<CheckpointSession>? = null,
|
||||
val total_bytes: Long? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class CheckpointSession(
|
||||
val session: String? = null,
|
||||
val files: Int? = null,
|
||||
|
||||
@@ -1,22 +1,26 @@
|
||||
package com.m57.hermescontrol.data.model
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.json.JsonElement
|
||||
|
||||
import com.google.gson.JsonElement
|
||||
|
||||
@Serializable
|
||||
data class RawConfigResponse(
|
||||
val path: String?,
|
||||
val yaml: String?,
|
||||
val path: String? = null,
|
||||
val yaml: String? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class UpdateRawConfigRequest(
|
||||
val yaml_text: String,
|
||||
val profile: String? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class ConfigSchemaResponse(
|
||||
val fields: Map<String, SchemaField>,
|
||||
val category_order: List<String>,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class SchemaField(
|
||||
val type: String,
|
||||
val description: String? = null,
|
||||
@@ -24,6 +28,7 @@ data class SchemaField(
|
||||
val options: List<String>? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class ConfigUpdateRequest(
|
||||
val config: Map<String, JsonElement>,
|
||||
val profile: String? = null,
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
package com.m57.hermescontrol.data.model
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import java.util.UUID
|
||||
|
||||
@Serializable
|
||||
data class ConnectionProfile(
|
||||
val id: String = UUID.randomUUID().toString(),
|
||||
val name: String,
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
package com.m57.hermescontrol.data.model
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class CredentialPoolResponse(
|
||||
val providers: List<CredentialPoolProvider>? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class CredentialPoolProvider(
|
||||
val provider: String? = null,
|
||||
val entries: List<CredentialPoolEntry>? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class CredentialPoolEntry(
|
||||
val index: Int? = null,
|
||||
val label: String? = null,
|
||||
|
||||
@@ -1,17 +1,23 @@
|
||||
package com.m57.hermescontrol.data.model
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.json.JsonElement
|
||||
import kotlinx.serialization.json.JsonObject
|
||||
import kotlinx.serialization.json.JsonPrimitive
|
||||
|
||||
@Serializable
|
||||
data class CronJobRepeat(
|
||||
val times: Int? = null,
|
||||
val completed: Int = 0,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class CronJob(
|
||||
val id: String,
|
||||
val name: String,
|
||||
val schedule: Any?,
|
||||
val state: String?,
|
||||
val last_run_status: String?,
|
||||
val next_run: String?,
|
||||
val schedule: JsonElement? = null,
|
||||
val state: String? = null,
|
||||
val last_run_status: String? = null,
|
||||
val next_run: String? = null,
|
||||
val schedule_display: String? = null,
|
||||
val last_status: String? = null,
|
||||
val next_run_at: String? = null,
|
||||
@@ -36,8 +42,8 @@ data class CronJob(
|
||||
val scheduleText: String
|
||||
get() =
|
||||
when (schedule) {
|
||||
is String -> schedule
|
||||
is Map<*, *> -> (schedule["display"] as? String) ?: schedule_display ?: ""
|
||||
is JsonPrimitive -> schedule.content
|
||||
is JsonObject -> (schedule["display"] as? JsonPrimitive)?.content ?: schedule_display ?: ""
|
||||
else -> schedule_display ?: ""
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
package com.m57.hermescontrol.data.model
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.json.JsonElement
|
||||
|
||||
@Serializable
|
||||
data class CreateCronJobRequest(
|
||||
val name: String = "",
|
||||
val schedule: String,
|
||||
@@ -17,6 +20,7 @@ data class CreateCronJobRequest(
|
||||
val repeat: Int? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class UpdateCronJobRequest(
|
||||
val updates: Map<String, Any?>,
|
||||
val updates: Map<String, JsonElement>,
|
||||
)
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.m57.hermescontrol.data.model
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class CuratorResponse(
|
||||
val enabled: Boolean? = null,
|
||||
val paused: Boolean? = null,
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.m57.hermescontrol.data.model
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class DebugShareResponse(
|
||||
val ok: Boolean? = null,
|
||||
val urls: Map<String, String>? = null,
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package com.m57.hermescontrol.data.model
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class DoctorResponse(
|
||||
val ok: Boolean,
|
||||
val pid: Int?,
|
||||
val name: String?,
|
||||
val pid: Int? = null,
|
||||
val name: String? = null,
|
||||
)
|
||||
@@ -1,32 +1,37 @@
|
||||
package com.m57.hermescontrol.data.model
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
@Serializable
|
||||
data class EnvVarConfig(
|
||||
@SerializedName("is_set") val isSet: Boolean,
|
||||
@SerializedName("redacted_value") val redactedValue: String?,
|
||||
val description: String?,
|
||||
val url: String?,
|
||||
val category: String?,
|
||||
@SerializedName("is_password") val isPassword: Boolean,
|
||||
@SerialName("is_set") val isSet: Boolean,
|
||||
@SerialName("redacted_value") val redactedValue: String? = null,
|
||||
val description: String? = null,
|
||||
val url: String? = null,
|
||||
val category: String? = null,
|
||||
@SerialName("is_password") val isPassword: Boolean,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class EnvVarRevealRequest(
|
||||
val key: String,
|
||||
val profile: String? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class EnvVarRevealResponse(
|
||||
val key: String,
|
||||
val value: String,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class EnvVarUpdate(
|
||||
val key: String,
|
||||
val value: String,
|
||||
val profile: String? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class EnvVarDeleteRequest(
|
||||
val key: String,
|
||||
val profile: String? = null,
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
package com.m57.hermescontrol.data.model
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class HookResponse(
|
||||
val hooks: List<HookEntry>? = null,
|
||||
val valid_events: List<String>? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class HookEntry(
|
||||
val event: String? = null,
|
||||
val matcher: String? = null,
|
||||
|
||||
@@ -1,37 +1,43 @@
|
||||
package com.m57.hermescontrol.data.model
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
@Serializable
|
||||
data class KanbanBoard(
|
||||
@SerializedName("slug") val id: String,
|
||||
@SerialName("slug") val id: String,
|
||||
val name: String,
|
||||
val description: String?,
|
||||
val description: String? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class KanbanTask(
|
||||
val id: String,
|
||||
val title: String,
|
||||
@SerializedName("body") val description: String?,
|
||||
@SerialName("body") val description: String? = null,
|
||||
val status: String,
|
||||
@SerializedName("assignee") val assignedTo: String?,
|
||||
@SerialName("assignee") val assignedTo: String? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class CreateTaskBody(
|
||||
val title: String,
|
||||
val body: String?,
|
||||
val body: String? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class KanbanColumn(
|
||||
val name: String,
|
||||
val tasks: List<KanbanTask>,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class KanbanBoardResponse(
|
||||
val columns: List<KanbanColumn>,
|
||||
val assignees: List<String>?,
|
||||
val tenants: List<String>?,
|
||||
val assignees: List<String>? = null,
|
||||
val tenants: List<String>? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class KanbanBoardsResponse(
|
||||
val boards: List<KanbanBoard>,
|
||||
val current: String? = null,
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.m57.hermescontrol.data.model
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class LogResponse(
|
||||
val lines: List<String>? = null,
|
||||
val logs: List<String>? = null,
|
||||
|
||||
@@ -1,25 +1,30 @@
|
||||
package com.m57.hermescontrol.data.model
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class McpServersResponse(
|
||||
val servers: List<McpServer>,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class McpServer(
|
||||
val name: String,
|
||||
val transport: String?,
|
||||
val url: String?,
|
||||
val command: String?,
|
||||
val args: List<String>?,
|
||||
val env: Map<String, String>?,
|
||||
val transport: String? = null,
|
||||
val url: String? = null,
|
||||
val command: String? = null,
|
||||
val args: List<String>? = null,
|
||||
val env: Map<String, String>? = null,
|
||||
val enabled: Boolean,
|
||||
val status: String? = null,
|
||||
val error: String? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class McpServerToggleRequest(
|
||||
val enabled: Boolean,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class AddMcpServerRequest(
|
||||
val name: String,
|
||||
val url: String? = null,
|
||||
@@ -28,10 +33,12 @@ data class AddMcpServerRequest(
|
||||
val env: Map<String, String>? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class McpCatalogResponse(
|
||||
val entries: List<McpCatalogEntry>,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class McpCatalogEntry(
|
||||
val name: String,
|
||||
val description: String? = null,
|
||||
@@ -42,6 +49,7 @@ data class McpCatalogEntry(
|
||||
val env: List<McpCatalogEnvVar>? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class McpCatalogEnvVar(
|
||||
val key: String,
|
||||
val label: String? = null,
|
||||
@@ -49,6 +57,7 @@ data class McpCatalogEnvVar(
|
||||
val required: Boolean = false,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class McpCatalogInstallRequest(
|
||||
val name: String,
|
||||
val env: Map<String, String>? = null,
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
package com.m57.hermescontrol.data.model
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class MemoryResponse(
|
||||
val active: String? = null,
|
||||
val builtin_files: BuiltinFileSizes? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class BuiltinFileSizes(
|
||||
val memory: Long? = null,
|
||||
val user: Long? = null,
|
||||
|
||||
@@ -1,57 +1,63 @@
|
||||
package com.m57.hermescontrol.data.model
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
@Serializable
|
||||
data class MessagingPlatform(
|
||||
val id: String,
|
||||
val name: String,
|
||||
val description: String? = null,
|
||||
@SerializedName("docs_url") val docsUrl: String? = null,
|
||||
@SerialName("docs_url") val docsUrl: String? = null,
|
||||
val enabled: Boolean,
|
||||
val configured: Boolean,
|
||||
@SerializedName("gateway_running") val gatewayRunning: Boolean = false,
|
||||
@SerialName("gateway_running") val gatewayRunning: Boolean = false,
|
||||
val state: String = "disabled",
|
||||
@SerializedName("error_code") val errorCode: String? = null,
|
||||
@SerializedName("error_message") val errorMessage: String? = null,
|
||||
@SerializedName("env_vars") val envVars: List<EnvVarField>? = null,
|
||||
@SerializedName("updated_at") val updatedAt: String? = null,
|
||||
@SerializedName("home_channel") val homeChannel: HomeChannelInfo? = null,
|
||||
@SerialName("error_code") val errorCode: String? = null,
|
||||
@SerialName("error_message") val errorMessage: String? = null,
|
||||
@SerialName("env_vars") val envVars: List<EnvVarField>? = null,
|
||||
@SerialName("updated_at") val updatedAt: String? = null,
|
||||
@SerialName("home_channel") val homeChannel: HomeChannelInfo? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class HomeChannelInfo(
|
||||
val platform: String,
|
||||
@SerializedName("chat_id") val chatId: String,
|
||||
@SerialName("chat_id") val chatId: String,
|
||||
val name: String,
|
||||
@SerializedName("thread_id") val threadId: String? = null,
|
||||
@SerialName("thread_id") val threadId: String? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class EnvVarField(
|
||||
val key: String,
|
||||
val required: Boolean,
|
||||
@SerializedName("is_set") val isSet: Boolean,
|
||||
@SerializedName("redacted_value") val redactedValue: String? = null,
|
||||
@SerialName("is_set") val isSet: Boolean,
|
||||
@SerialName("redacted_value") val redactedValue: String? = null,
|
||||
val description: String? = null,
|
||||
val prompt: String? = null,
|
||||
val help: String? = null,
|
||||
@SerializedName("is_password") val isPassword: Boolean,
|
||||
@SerialName("is_password") val isPassword: Boolean,
|
||||
val advanced: Boolean = false,
|
||||
val url: String? = null,
|
||||
@SerializedName("expires_at") val expiresAt: String? = null,
|
||||
@SerialName("expires_at") val expiresAt: String? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class MessagingPlatformResponse(
|
||||
@SerializedName("env_path") val envPath: String,
|
||||
@SerializedName("gateway_start_command") val gatewayStartCommand: String,
|
||||
@SerialName("env_path") val envPath: String,
|
||||
@SerialName("gateway_start_command") val gatewayStartCommand: String,
|
||||
val platforms: List<MessagingPlatform>,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class MessagingPlatformUpdate(
|
||||
val enabled: Boolean? = null,
|
||||
val env: Map<String, String> = emptyMap(),
|
||||
@SerializedName("clear_env") val clearEnv: List<String> = emptyList(),
|
||||
@SerialName("clear_env") val clearEnv: List<String> = emptyList(),
|
||||
val profile: String? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class MessagingPlatformTestResult(
|
||||
val ok: Boolean,
|
||||
val state: String,
|
||||
@@ -60,37 +66,42 @@ data class MessagingPlatformTestResult(
|
||||
|
||||
// ── Telegram onboarding ─────────────────────────────────────────────────
|
||||
|
||||
@Serializable
|
||||
data class TelegramOnboardingStartRequest(
|
||||
@SerializedName("bot_name") val botName: String = "Hermes Agent",
|
||||
@SerialName("bot_name") val botName: String = "Hermes Agent",
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class TelegramOnboardingStartResponse(
|
||||
@SerializedName("pairing_id") val pairingId: String,
|
||||
@SerializedName("suggested_username") val suggestedUsername: String,
|
||||
@SerializedName("deep_link") val deepLink: String,
|
||||
@SerializedName("qr_payload") val qrPayload: String,
|
||||
@SerializedName("expires_at") val expiresAt: String,
|
||||
@SerialName("pairing_id") val pairingId: String,
|
||||
@SerialName("suggested_username") val suggestedUsername: String,
|
||||
@SerialName("deep_link") val deepLink: String,
|
||||
@SerialName("qr_payload") val qrPayload: String,
|
||||
@SerialName("expires_at") val expiresAt: String,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class TelegramOnboardingStatusResponse(
|
||||
val status: String,
|
||||
@SerializedName("bot_username") val botUsername: String? = null,
|
||||
@SerializedName("owner_user_id") val ownerUserId: String? = null,
|
||||
@SerializedName("expires_at") val expiresAt: String? = null,
|
||||
@SerialName("bot_username") val botUsername: String? = null,
|
||||
@SerialName("owner_user_id") val ownerUserId: String? = null,
|
||||
@SerialName("expires_at") val expiresAt: String? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class TelegramOnboardingApplyRequest(
|
||||
@SerializedName("allowed_user_ids") val allowedUserIds: List<String>,
|
||||
@SerialName("allowed_user_ids") val allowedUserIds: List<String>,
|
||||
val profile: String? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class TelegramOnboardingApplyResponse(
|
||||
val ok: Boolean,
|
||||
val platform: String,
|
||||
@SerializedName("bot_username") val botUsername: String? = null,
|
||||
@SerializedName("needs_restart") val needsRestart: Boolean,
|
||||
@SerializedName("restart_started") val restartStarted: Boolean? = null,
|
||||
@SerializedName("restart_action") val restartAction: String? = null,
|
||||
@SerializedName("restart_pid") val restartPid: Int? = null,
|
||||
@SerializedName("restart_error") val restartError: String? = null,
|
||||
@SerialName("bot_username") val botUsername: String? = null,
|
||||
@SerialName("needs_restart") val needsRestart: Boolean,
|
||||
@SerialName("restart_started") val restartStarted: Boolean? = null,
|
||||
@SerialName("restart_action") val restartAction: String? = null,
|
||||
@SerialName("restart_pid") val restartPid: Int? = null,
|
||||
@SerialName("restart_error") val restartError: String? = null,
|
||||
)
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.m57.hermescontrol.data.model
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class AuxiliaryTaskAssignment(
|
||||
val task: String,
|
||||
val provider: String,
|
||||
@@ -7,21 +9,25 @@ data class AuxiliaryTaskAssignment(
|
||||
val base_url: String? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class AuxiliaryModelsResponse(
|
||||
val tasks: List<AuxiliaryTaskAssignment>,
|
||||
val main: MainModelAssignment,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class MainModelAssignment(
|
||||
val provider: String,
|
||||
val model: String,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class MoaModelSlot(
|
||||
val provider: String,
|
||||
val model: String,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class MoaConfigPreset(
|
||||
val reference_models: List<MoaModelSlot>,
|
||||
val aggregator: MoaModelSlot,
|
||||
@@ -31,6 +37,7 @@ data class MoaConfigPreset(
|
||||
val enabled: Boolean = true,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class MoaConfigResponse(
|
||||
val default_preset: String = "",
|
||||
val active_preset: String = "",
|
||||
@@ -43,6 +50,7 @@ data class MoaConfigResponse(
|
||||
val enabled: Boolean = true,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class ModelAssignmentRequest(
|
||||
val confirm_expensive_model: Boolean = false,
|
||||
val scope: String,
|
||||
@@ -52,6 +60,7 @@ data class ModelAssignmentRequest(
|
||||
val task: String? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class ModelAssignmentResponse(
|
||||
val confirm_message: String? = null,
|
||||
val confirm_required: Boolean? = null,
|
||||
|
||||
@@ -1,22 +1,26 @@
|
||||
package com.m57.hermescontrol.data.model
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class ModelOptionsResponse(
|
||||
val providers: List<ModelProvider>,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class ModelProvider(
|
||||
val slug: String,
|
||||
val name: String,
|
||||
val is_current: Boolean?,
|
||||
val is_user_defined: Boolean?,
|
||||
val models: List<String>?,
|
||||
val total_models: Int?,
|
||||
val source: String?,
|
||||
val authenticated: Boolean?,
|
||||
val auth_type: String?,
|
||||
val warning: String?,
|
||||
val is_current: Boolean? = null,
|
||||
val is_user_defined: Boolean? = null,
|
||||
val models: List<String>? = null,
|
||||
val total_models: Int? = null,
|
||||
val source: String? = null,
|
||||
val authenticated: Boolean? = null,
|
||||
val auth_type: String? = null,
|
||||
val warning: String? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class PinnedModel(
|
||||
val providerSlug: String,
|
||||
val modelName: String,
|
||||
|
||||
@@ -1,24 +1,29 @@
|
||||
package com.m57.hermescontrol.data.model
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class PairingResponse(
|
||||
val pending: List<PairingItem>,
|
||||
val approved: List<PairingItem>,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class PairingItem(
|
||||
val platform: String,
|
||||
val user_id: String?,
|
||||
val username: String?,
|
||||
val display_name: String?,
|
||||
val code: String?,
|
||||
val created_at: String?,
|
||||
val user_id: String? = null,
|
||||
val username: String? = null,
|
||||
val display_name: String? = null,
|
||||
val code: String? = null,
|
||||
val created_at: String? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class PairingApproveRequest(
|
||||
val platform: String,
|
||||
val code: String,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class PairingRevokeRequest(
|
||||
val platform: String,
|
||||
val user_id: String,
|
||||
|
||||
@@ -1,20 +1,21 @@
|
||||
package com.m57.hermescontrol.data.model
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
@Serializable
|
||||
data class PluginInfo(
|
||||
val name: String,
|
||||
val description: String?,
|
||||
val version: String?,
|
||||
val source: String?,
|
||||
@SerializedName("runtime_status") val runtimeStatus: String?,
|
||||
@SerializedName("has_dashboard_manifest") val hasDashboardManifest: Boolean = false,
|
||||
@SerializedName("dashboard_manifest") val dashboardManifest: PluginManifestData? = null,
|
||||
@SerializedName("can_remove") val canRemove: Boolean = false,
|
||||
@SerializedName("can_update_git") val canUpdateGit: Boolean = false,
|
||||
@SerializedName("auth_required") val authRequired: Boolean = false,
|
||||
@SerializedName("auth_command") val authCommand: String? = null,
|
||||
@SerializedName("user_hidden") val userHidden: Boolean = false,
|
||||
val description: String? = null,
|
||||
val version: String? = null,
|
||||
val source: String? = null,
|
||||
@SerialName("runtime_status") val runtimeStatus: String? = null,
|
||||
@SerialName("has_dashboard_manifest") val hasDashboardManifest: Boolean = false,
|
||||
@SerialName("dashboard_manifest") val dashboardManifest: PluginManifestData? = null,
|
||||
@SerialName("can_remove") val canRemove: Boolean = false,
|
||||
@SerialName("can_update_git") val canUpdateGit: Boolean = false,
|
||||
@SerialName("auth_required") val authRequired: Boolean = false,
|
||||
@SerialName("auth_command") val authCommand: String? = null,
|
||||
@SerialName("user_hidden") val userHidden: Boolean = false,
|
||||
) {
|
||||
val enabled: Boolean
|
||||
get() = runtimeStatus.equals("enabled", ignoreCase = true)
|
||||
@@ -25,6 +26,7 @@ data class PluginInfo(
|
||||
runtimeStatus.equals("disabled", ignoreCase = true)
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class PluginManifestData(
|
||||
val name: String? = null,
|
||||
val label: String? = null,
|
||||
@@ -35,10 +37,11 @@ data class PluginManifestData(
|
||||
val slots: List<String>? = null,
|
||||
val entry: String? = null,
|
||||
val css: String? = null,
|
||||
@SerializedName("has_api") val hasApi: Boolean = false,
|
||||
@SerialName("has_api") val hasApi: Boolean = false,
|
||||
val source: String? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class PluginTabInfo(
|
||||
val path: String? = null,
|
||||
val position: String? = null,
|
||||
@@ -46,6 +49,7 @@ data class PluginTabInfo(
|
||||
val hidden: Boolean = false,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class DashboardPluginMeta(
|
||||
val name: String? = null,
|
||||
val label: String? = null,
|
||||
@@ -53,31 +57,36 @@ data class DashboardPluginMeta(
|
||||
val tab: PluginTabInfo? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class PluginsHubProviders(
|
||||
@SerializedName("memory_provider") val memoryProvider: String? = null,
|
||||
@SerializedName("memory_options") val memoryOptions: List<ProviderOption> = emptyList(),
|
||||
@SerializedName("context_engine") val contextEngine: String? = null,
|
||||
@SerializedName("context_options") val contextOptions: List<ProviderOption> = emptyList(),
|
||||
@SerialName("memory_provider") val memoryProvider: String? = null,
|
||||
@SerialName("memory_options") val memoryOptions: List<ProviderOption> = emptyList(),
|
||||
@SerialName("context_engine") val contextEngine: String? = null,
|
||||
@SerialName("context_options") val contextOptions: List<ProviderOption> = emptyList(),
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class ProviderOption(
|
||||
val name: String,
|
||||
val description: String? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class PluginsHubResponse(
|
||||
val plugins: List<PluginInfo>,
|
||||
@SerializedName("orphan_dashboard_plugins") val orphanDashboardPlugins: List<DashboardPluginMeta> = emptyList(),
|
||||
@SerialName("orphan_dashboard_plugins") val orphanDashboardPlugins: List<DashboardPluginMeta> = emptyList(),
|
||||
val providers: PluginsHubProviders? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class AgentPluginInstallBody(
|
||||
val identifier: String,
|
||||
val force: Boolean = false,
|
||||
val enable: Boolean = true,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class PluginProvidersPutRequest(
|
||||
@SerializedName("memory_provider") val memoryProvider: String? = null,
|
||||
@SerializedName("context_engine") val contextEngine: String? = null,
|
||||
@SerialName("memory_provider") val memoryProvider: String? = null,
|
||||
@SerialName("context_engine") val contextEngine: String? = null,
|
||||
)
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.m57.hermescontrol.data.model
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class PortalResponse(
|
||||
val logged_in: Boolean? = null,
|
||||
val portal_url: String? = null,
|
||||
@@ -9,6 +11,7 @@ data class PortalResponse(
|
||||
val features: List<PortalFeature>? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class PortalFeature(
|
||||
val label: String? = null,
|
||||
val state: String? = null,
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
package com.m57.hermescontrol.data.model
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class ProfilesResponse(
|
||||
val profiles: List<ProfileInfo>,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class ProfileInfo(
|
||||
val name: String,
|
||||
val path: String? = null,
|
||||
@@ -17,46 +20,55 @@ data class ProfileInfo(
|
||||
val description_auto: Boolean? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class ActiveProfileResponse(
|
||||
val active: String,
|
||||
val current: String?,
|
||||
val current: String? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class SetActiveProfileRequest(
|
||||
val name: String,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class ProfileSoulResponse(
|
||||
val content: String,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class UpdateProfileSoulRequest(
|
||||
val content: String,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class UpdateProfileModelRequest(
|
||||
val provider: String,
|
||||
val model: String,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class CloneProfileRequest(
|
||||
val name: String,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class UpdateProfileDescriptionRequest(
|
||||
val description: String,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class CreateProfileRequest(
|
||||
val name: String,
|
||||
val description: String?,
|
||||
val provider: String?,
|
||||
val model: String?,
|
||||
val mcp_servers: List<McpServerConfigInput>?,
|
||||
val keep_skills: Boolean?,
|
||||
val hub_skills: List<String>?,
|
||||
val description: String? = null,
|
||||
val provider: String? = null,
|
||||
val model: String? = null,
|
||||
val mcp_servers: List<McpServerConfigInput>? = null,
|
||||
val keep_skills: Boolean? = null,
|
||||
val hub_skills: List<String>? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class McpServerConfigInput(
|
||||
val name: String,
|
||||
val transport: String,
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.m57.hermescontrol.data.model
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class SaveSkillContentRequest(
|
||||
val name: String,
|
||||
val content: String,
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.m57.hermescontrol.data.model
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class SessionListResponse(
|
||||
val sessions: List<SessionInfo>,
|
||||
val total: Int = 0,
|
||||
@@ -7,35 +9,41 @@ data class SessionListResponse(
|
||||
val offset: Int = 0,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class SessionInfo(
|
||||
val id: String,
|
||||
val title: String?,
|
||||
val created_at: String?,
|
||||
val message_count: Int?,
|
||||
val status: String?,
|
||||
val title: String? = null,
|
||||
val created_at: String? = null,
|
||||
val message_count: Int? = null,
|
||||
val status: String? = null,
|
||||
val preview: String? = null,
|
||||
val started_at: Double? = null,
|
||||
val source: String? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class SessionStatsResponse(
|
||||
val total: Int = 0,
|
||||
val active: Int = 0,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class SessionRenameRequest(
|
||||
val title: String,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class BulkDeleteRequest(
|
||||
val ids: List<String>,
|
||||
val delete_all: Boolean = false,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class PruneRequest(
|
||||
val days: Int,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class SessionPromptResponse(
|
||||
val prompt: String? = null,
|
||||
val id: String? = null,
|
||||
|
||||
@@ -1,12 +1,20 @@
|
||||
package com.m57.hermescontrol.data.model
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.json.JsonElement
|
||||
import kotlinx.serialization.json.JsonPrimitive
|
||||
|
||||
@Serializable
|
||||
data class SessionMessagesResponse(
|
||||
val messages: List<SessionMessage>,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class SessionMessage(
|
||||
val role: String?,
|
||||
val content: String?,
|
||||
val timestamp: String?,
|
||||
val type: String?,
|
||||
)
|
||||
val role: String? = null,
|
||||
val content: String? = null,
|
||||
val timestamp: JsonElement? = null,
|
||||
val type: String? = null,
|
||||
) {
|
||||
val timestampText: String?
|
||||
get() = (timestamp as? JsonPrimitive)?.content
|
||||
}
|
||||
@@ -1,11 +1,12 @@
|
||||
package com.m57.hermescontrol.data.model
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
@Serializable
|
||||
data class Skill(
|
||||
val name: String,
|
||||
val description: String?,
|
||||
val category: String?,
|
||||
val description: String? = null,
|
||||
val category: String? = null,
|
||||
val enabled: Boolean,
|
||||
val content: String? = null,
|
||||
val source: String? = null, // built-in, hub, optional
|
||||
@@ -13,40 +14,46 @@ data class Skill(
|
||||
val linked_files: List<String>? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class HubSkill(
|
||||
val name: String,
|
||||
val description: String?,
|
||||
val source: String?,
|
||||
val description: String? = null,
|
||||
val source: String? = null,
|
||||
val category: String? = null,
|
||||
val identifier: String? = null,
|
||||
@SerializedName("trust_level") val trustLevel: String? = null,
|
||||
@SerialName("trust_level") val trustLevel: String? = null,
|
||||
val repo: String? = null,
|
||||
val tags: List<String>? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class SkillHubSearchResponse(
|
||||
val results: List<HubSkill>,
|
||||
@SerializedName("source_counts") val sourceCounts: Map<String, Int>? = null,
|
||||
@SerializedName("timed_out") val timedOut: List<String>? = null,
|
||||
@SerialName("source_counts") val sourceCounts: Map<String, Int>? = null,
|
||||
@SerialName("timed_out") val timedOut: List<String>? = null,
|
||||
val installed: Map<String, SkillHubInstalledEntry>? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class SkillHubInstalledEntry(
|
||||
val name: String? = null,
|
||||
@SerializedName("trust_level") val trustLevel: String? = null,
|
||||
@SerializedName("scan_verdict") val scanVerdict: String? = null,
|
||||
@SerialName("trust_level") val trustLevel: String? = null,
|
||||
@SerialName("scan_verdict") val scanVerdict: String? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class SkillHubInstallRequest(
|
||||
val identifier: String,
|
||||
val profile: String? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class SkillHubUninstallRequest(
|
||||
val name: String,
|
||||
val profile: String? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class SkillScanResponse(
|
||||
val identifier: String,
|
||||
val passed: Boolean? = null,
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.m57.hermescontrol.data.model
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class SkillContentResponse(
|
||||
val name: String,
|
||||
val content: String,
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
package com.m57.hermescontrol.data.model
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class StatusResponse(
|
||||
val version: String?,
|
||||
val gateway_running: Boolean?,
|
||||
val active_sessions: Int?,
|
||||
val auth_required: Boolean?,
|
||||
val gateway_platforms: Map<String, PlatformStatus>?,
|
||||
val version: String? = null,
|
||||
val gateway_running: Boolean? = null,
|
||||
val active_sessions: Int? = null,
|
||||
val auth_required: Boolean? = null,
|
||||
val gateway_platforms: Map<String, PlatformStatus?>? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class PlatformStatus(
|
||||
val state: String?,
|
||||
val state: String? = null,
|
||||
val error_code: String? = null,
|
||||
)
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.m57.hermescontrol.data.model
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class SystemStatsResponse(
|
||||
val os: String? = null,
|
||||
val os_release: String? = null,
|
||||
@@ -20,6 +22,7 @@ data class SystemStatsResponse(
|
||||
val process: ProcessStats? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class MemoryStats(
|
||||
val total: Long? = null,
|
||||
val available: Long? = null,
|
||||
@@ -27,6 +30,7 @@ data class MemoryStats(
|
||||
val percent: Double? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class DiskStats(
|
||||
val total: Long? = null,
|
||||
val used: Long? = null,
|
||||
@@ -34,6 +38,7 @@ data class DiskStats(
|
||||
val percent: Double? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class ProcessStats(
|
||||
val pid: Int? = null,
|
||||
val rss: Long? = null,
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.m57.hermescontrol.data.model
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class ToggleSkillRequest(
|
||||
val name: String,
|
||||
val enabled: Boolean,
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
package com.m57.hermescontrol.data.model
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class Toolset(
|
||||
val name: String,
|
||||
val label: String?,
|
||||
val description: String?,
|
||||
val label: String? = null,
|
||||
val description: String? = null,
|
||||
val enabled: Boolean,
|
||||
val available: Boolean?,
|
||||
val configured: Boolean?,
|
||||
val tools: List<String>?,
|
||||
val available: Boolean? = null,
|
||||
val configured: Boolean? = null,
|
||||
val tools: List<String>? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class ToolsetToggleRequest(
|
||||
val enabled: Boolean,
|
||||
val profile: String? = null,
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.m57.hermescontrol.data.model
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class UpdateCheckResponse(
|
||||
val install_method: String? = null,
|
||||
val current_version: String? = null,
|
||||
|
||||
@@ -1,25 +1,29 @@
|
||||
package com.m57.hermescontrol.data.model
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class WebhooksResponse(
|
||||
val enabled: Boolean,
|
||||
val base_url: String?,
|
||||
val subscriptions: List<WebhookSubscription>?,
|
||||
val base_url: String? = null,
|
||||
val subscriptions: List<WebhookSubscription>? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class WebhookSubscription(
|
||||
val name: String,
|
||||
val description: String?,
|
||||
val events: List<String>?,
|
||||
val deliver: String?,
|
||||
val deliver_only: Boolean?,
|
||||
val prompt: String?,
|
||||
val skills: List<String>?,
|
||||
val created_at: String?,
|
||||
val description: String? = null,
|
||||
val events: List<String>? = null,
|
||||
val deliver: String? = null,
|
||||
val deliver_only: Boolean? = null,
|
||||
val prompt: String? = null,
|
||||
val skills: List<String>? = null,
|
||||
val created_at: String? = null,
|
||||
val url: String,
|
||||
val secret_set: Boolean?,
|
||||
val enabled: Boolean?,
|
||||
val secret_set: Boolean? = null,
|
||||
val enabled: Boolean? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class CreateWebhookRequest(
|
||||
val name: String,
|
||||
val description: String? = null,
|
||||
@@ -32,14 +36,17 @@ data class CreateWebhookRequest(
|
||||
val secret: String? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class WebhooksToggleRequest(
|
||||
val enabled: Boolean,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class WebhookToggleSubscriptionRequest(
|
||||
val enabled: Boolean,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class DeleteWebhookResponse(
|
||||
val ok: Boolean,
|
||||
)
|
||||
@@ -4,9 +4,10 @@ import com.m57.hermescontrol.BuildConfig
|
||||
import com.m57.hermescontrol.data.local.AuthManager
|
||||
import okhttp3.CertificatePinner
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.MediaType.Companion.toMediaType
|
||||
import okhttp3.logging.HttpLoggingInterceptor
|
||||
import retrofit2.Retrofit
|
||||
import retrofit2.converter.gson.GsonConverterFactory
|
||||
import retrofit2.converter.kotlinx.serialization.asConverterFactory
|
||||
|
||||
/**
|
||||
* Provides a Retrofit-backed [HermesApiService].
|
||||
@@ -71,7 +72,7 @@ object ApiClient {
|
||||
.Builder()
|
||||
.baseUrl("http://$host:$port/")
|
||||
.client(tempOkHttp)
|
||||
.addConverterFactory(GsonConverterFactory.create(OkHttpProvider.gson))
|
||||
.addConverterFactory(OkHttpProvider.json.asConverterFactory("application/json".toMediaType()))
|
||||
.build()
|
||||
|
||||
return tempRetrofit.create(HermesApiService::class.java)
|
||||
@@ -135,7 +136,7 @@ object ApiClient {
|
||||
.Builder()
|
||||
.baseUrl(AuthManager.baseUrl())
|
||||
.client(okHttp)
|
||||
.addConverterFactory(GsonConverterFactory.create(OkHttpProvider.gson))
|
||||
.addConverterFactory(OkHttpProvider.json.asConverterFactory("application/json".toMediaType()))
|
||||
.build()
|
||||
.also { retrofit = it }
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package com.m57.hermescontrol.data.remote
|
||||
|
||||
import com.google.gson.JsonElement
|
||||
import com.m57.hermescontrol.data.model.AchievementsResponse
|
||||
import com.m57.hermescontrol.data.model.ActionResponse
|
||||
import com.m57.hermescontrol.data.model.ActionStatusResponse
|
||||
@@ -91,6 +90,7 @@ import com.m57.hermescontrol.data.model.WebhookSubscription
|
||||
import com.m57.hermescontrol.data.model.WebhookToggleSubscriptionRequest
|
||||
import com.m57.hermescontrol.data.model.WebhooksResponse
|
||||
import com.m57.hermescontrol.data.model.WebhooksToggleRequest
|
||||
import kotlinx.serialization.json.JsonElement
|
||||
import okhttp3.ResponseBody
|
||||
import retrofit2.Response
|
||||
import retrofit2.http.Body
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
package com.m57.hermescontrol.data.remote
|
||||
|
||||
import com.google.gson.JsonParser
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.serialization.json.jsonObject
|
||||
import kotlinx.serialization.json.jsonPrimitive
|
||||
import retrofit2.Response
|
||||
import java.io.IOException
|
||||
|
||||
@@ -49,11 +50,8 @@ fun mapHttpError(
|
||||
val detail =
|
||||
if (errorBody != null) {
|
||||
try {
|
||||
val parsed =
|
||||
com.google.gson.JsonParser
|
||||
.parseString(errorBody)
|
||||
.asJsonObject
|
||||
parsed.get("detail")?.asString
|
||||
val parsed = OkHttpProvider.json.parseToJsonElement(errorBody).jsonObject
|
||||
parsed["detail"]?.jsonPrimitive?.content
|
||||
} catch (_: Exception) {
|
||||
null
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
package com.m57.hermescontrol.data.remote
|
||||
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.GsonBuilder
|
||||
import kotlinx.serialization.ExperimentalSerializationApi
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.JsonNamingStrategy
|
||||
import okhttp3.ConnectionPool
|
||||
import okhttp3.OkHttpClient
|
||||
import java.util.concurrent.TimeUnit
|
||||
@@ -42,6 +43,10 @@ object OkHttpProvider {
|
||||
.followRedirects(false)
|
||||
.build()
|
||||
|
||||
// Shared Gson — thread-safe, zero-config
|
||||
val gson: Gson = GsonBuilder().create()
|
||||
@OptIn(ExperimentalSerializationApi::class)
|
||||
val json: Json =
|
||||
Json {
|
||||
ignoreUnknownKeys = true
|
||||
namingStrategy = JsonNamingStrategy.SnakeCase
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,12 @@
|
||||
package com.m57.hermescontrol.data.ws
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* Response from `commands.catalog` RPC — full catalog of available
|
||||
* slash commands, subcommands, and alias mappings.
|
||||
*/
|
||||
@Serializable
|
||||
data class CommandCatalog(
|
||||
val pairs: List<List<String>> = emptyList(),
|
||||
val sub: Map<String, List<String>> = emptyMap(),
|
||||
@@ -16,6 +19,7 @@ data class CommandCatalog(
|
||||
/**
|
||||
* A named category within the command catalog.
|
||||
*/
|
||||
@Serializable
|
||||
data class CommandCategory(
|
||||
val name: String,
|
||||
val pairs: List<List<String>>,
|
||||
|
||||
@@ -24,12 +24,13 @@ object EventParser {
|
||||
return if (response.error != null) {
|
||||
WsEvent.RpcError(id, response.error)
|
||||
} else {
|
||||
WsEvent.RpcResult(id, response.result)
|
||||
WsEvent.RpcResult(id, response.result?.toAny())
|
||||
}
|
||||
}
|
||||
|
||||
// ── Notification / event (no id, has method) ─────────────────────
|
||||
val params = response.params ?: return WsEvent.Unknown(rawJson)
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val params = response.params?.toAny() as? Map<String, Any?> ?: return WsEvent.Unknown(rawJson)
|
||||
val eventType = params["type"] as? String ?: return WsEvent.Unknown(rawJson)
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
|
||||
@@ -24,6 +24,8 @@ import kotlinx.coroutines.flow.flowOn
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.shareIn
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.serialization.decodeFromString
|
||||
import kotlinx.serialization.encodeToString
|
||||
import okhttp3.Request
|
||||
import okhttp3.RequestBody.Companion.toRequestBody
|
||||
import okhttp3.Response
|
||||
@@ -123,7 +125,7 @@ object HermesWsClient {
|
||||
.buffer(Channel.BUFFERED)
|
||||
.map { text ->
|
||||
try {
|
||||
val rpc = OkHttpProvider.gson.fromJson(text, JsonRpcResponse::class.java)
|
||||
val rpc = OkHttpProvider.json.decodeFromString<JsonRpcResponse>(text)
|
||||
EventParser.parse(rpc, text)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Failed to parse message", e)
|
||||
@@ -231,8 +233,8 @@ object HermesWsClient {
|
||||
): String {
|
||||
val id = requestId.incrementAndGet().toString()
|
||||
onSent?.invoke(id)
|
||||
val request = JsonRpcRequest(id = id, method = method, params = params)
|
||||
val json = OkHttpProvider.gson.toJson(request)
|
||||
val request = JsonRpcRequest(id = id, method = method, params = params.mapValues { it.value.toJsonElement() })
|
||||
val json = OkHttpProvider.json.encodeToString(request)
|
||||
if (BuildConfig.DEBUG) Log.d(TAG, "→ $json")
|
||||
val ws = webSocket
|
||||
if (ws != null && connected.get()) {
|
||||
|
||||
@@ -1,32 +1,77 @@
|
||||
package com.m57.hermescontrol.data.ws
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.json.JsonArray
|
||||
import kotlinx.serialization.json.JsonElement
|
||||
import kotlinx.serialization.json.JsonNull
|
||||
import kotlinx.serialization.json.JsonObject
|
||||
import kotlinx.serialization.json.JsonPrimitive
|
||||
import kotlinx.serialization.json.booleanOrNull
|
||||
import kotlinx.serialization.json.doubleOrNull
|
||||
import kotlinx.serialization.json.longOrNull
|
||||
|
||||
/**
|
||||
* Outgoing JSON-RPC 2.0 request sent over the WebSocket.
|
||||
*/
|
||||
@Serializable
|
||||
data class JsonRpcRequest(
|
||||
val jsonrpc: String = "2.0",
|
||||
val id: String,
|
||||
val method: String,
|
||||
val params: Map<String, Any> = emptyMap(),
|
||||
val params: Map<String, JsonElement> = emptyMap(),
|
||||
)
|
||||
|
||||
/**
|
||||
* Incoming JSON-RPC 2.0 response **or** server-pushed notification.
|
||||
*
|
||||
* - RPC result/error → [id] is non-null, [result] or [error] is populated.
|
||||
* - Event notification → [id] is null, [method] is `"event"`, event data in [params].
|
||||
*/
|
||||
@Serializable
|
||||
data class JsonRpcResponse(
|
||||
val jsonrpc: String?,
|
||||
val id: String?,
|
||||
val result: Any?,
|
||||
val error: JsonRpcError?,
|
||||
val method: String?,
|
||||
val params: Map<String, Any?>?,
|
||||
val jsonrpc: String? = null,
|
||||
val id: String? = null,
|
||||
val result: JsonElement? = null,
|
||||
val error: JsonRpcError? = null,
|
||||
val method: String? = null,
|
||||
val params: JsonObject? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class JsonRpcError(
|
||||
val code: Int,
|
||||
val message: String,
|
||||
val data: Any? = null,
|
||||
val data: JsonElement? = null,
|
||||
)
|
||||
|
||||
fun Any?.toJsonElement(): JsonElement =
|
||||
when (this) {
|
||||
null -> JsonNull
|
||||
is JsonElement -> this
|
||||
is Boolean -> JsonPrimitive(this)
|
||||
is Number -> JsonPrimitive(this)
|
||||
is String -> JsonPrimitive(this)
|
||||
is Map<*, *> -> JsonObject(this.map { it.key.toString() to it.value.toJsonElement() }.toMap())
|
||||
is List<*> -> JsonArray(this.map { it.toJsonElement() })
|
||||
else -> JsonPrimitive(this.toString())
|
||||
}
|
||||
|
||||
fun JsonElement.toAny(): Any? =
|
||||
when (this) {
|
||||
is JsonNull -> {
|
||||
null
|
||||
}
|
||||
|
||||
is JsonPrimitive -> {
|
||||
if (isString) {
|
||||
content
|
||||
} else {
|
||||
booleanOrNull ?: doubleOrNull ?: longOrNull ?: content
|
||||
}
|
||||
}
|
||||
|
||||
is JsonObject -> {
|
||||
mapValues { it.value.toAny() }
|
||||
}
|
||||
|
||||
is JsonArray -> {
|
||||
map { it.toAny() }
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user