refactor(chat): unify tool catalog and prewarm payloads (#1326, #1327) (#1356)

This commit is contained in:
m57 authored and GitHub committed 2026-09-28 13:39:17 +03:00
1 parent b199c0347a
commit cc010b451a
8 files changed
+339 -383

No files matched your search

@@ -0,0 +1,53 @@
package com.m57.hermescontrol.ui.chat.tool
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.AccountTree
import androidx.compose.material.icons.filled.Build
import androidx.compose.material.icons.filled.Edit
import androidx.compose.material.icons.filled.Keyboard
import androidx.compose.material.icons.filled.Terminal
import com.m57.hermescontrol.ui.chat.ToolSchemaRegistry
import com.m57.hermescontrol.ui.chat.tool.render.BrowserTypeRenderer
import com.m57.hermescontrol.ui.chat.tool.render.FileEditRenderer
import com.m57.hermescontrol.ui.chat.tool.render.TerminalRenderer
import org.junit.Assert.assertEquals
import org.junit.Assert.assertSame
import org.junit.Test
class ToolCatalogTest {
@Test
fun `icon and renderer lookups read the same catalog entry`() {
for ((name, def) in ToolCatalog.entries) {
assertSame(name, def.icon, ToolSchemaRegistry.getDisplayConfig(name).icon)
assertSame(name, def.renderer, ToolRendererRegistry.rendererFor(name))
assertEquals(name, ToolSchemaRegistry.getDisplayConfig(name).name)
}
}
@Test
fun `tools that drifted now have a real icon`() {
assertSame(Icons.Filled.Edit, ToolSchemaRegistry.getDisplayConfig("edit_file").icon)
assertSame(Icons.Filled.Keyboard, ToolSchemaRegistry.getDisplayConfig("browser_fill").icon)
for (name in listOf("project_list", "project_create", "project_switch")) {
assertSame(name, Icons.Filled.AccountTree, ToolSchemaRegistry.getDisplayConfig(name).icon)
}
}
@Test
fun `known tools keep their renderers and icons`() {
assertSame(TerminalRenderer, ToolRendererRegistry.rendererFor("terminal"))
assertSame(Icons.Filled.Terminal, ToolSchemaRegistry.getDisplayConfig("terminal").icon)
assertSame(FileEditRenderer, ToolRendererRegistry.rendererFor("edit_file"))
assertSame(BrowserTypeRenderer, ToolRendererRegistry.rendererFor("browser_fill"))
// Icon-only tools still render generically.
assertSame(GenericToolRenderer, ToolRendererRegistry.rendererFor("clarify"))
}
@Test
fun `unknown tools fall back to build icon and generic renderer`() {
assertSame(Icons.Filled.Build, ToolSchemaRegistry.getDisplayConfig("mystery").icon)
assertEquals("mystery", ToolSchemaRegistry.getDisplayConfig("mystery").name)
assertEquals("tool", ToolSchemaRegistry.getDisplayConfig(null).name)
assertSame(GenericToolRenderer, ToolRendererRegistry.rendererFor("mystery"))
}
}
@@ -0,0 +1,51 @@
package com.m57.hermescontrol.ui.chat.tool
import com.m57.hermescontrol.ui.chat.parseToolOutput
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotSame
import org.junit.Assert.assertNull
import org.junit.Assert.assertSame
import org.junit.Before
import org.junit.Test
class ToolViewCacheTest {
private val payload = """{"name":"terminal","args":{"command":"ls"},"result":{"output":"a","exit_code":0}}"""
@Before
fun reset() = ToolViewCache.clear()
@Test
fun `cached view equals a fresh parse`() {
val cached = ToolViewCache.getOrParse(payload, "terminal", false)
assertEquals(parseToolOutput(payload, "terminal", false), cached)
}
@Test
fun `prewarm makes composition read a hit`() {
ToolViewCache.prewarm(payload, "terminal", false)
val first = ToolViewCache.getOrParse(payload, "terminal", false)
assertSame(first, ToolViewCache.getOrParse(payload, "terminal", false))
assertEquals(1, ToolViewCache.size())
}
@Test
fun `running state and content changes are separate entries`() {
val running = ToolViewCache.getOrParse(payload, "terminal", true)
val done = ToolViewCache.getOrParse(payload, "terminal", false)
assertNotSame(running, done)
assertEquals(2, ToolViewCache.size())
}
@Test
fun `non json content is cached as null`() {
assertNull(ToolViewCache.getOrParse("plain text", "terminal", false))
assertNull(ToolViewCache.getOrParse("plain text", "terminal", false))
assertEquals(1, ToolViewCache.size())
}
@Test
fun `cache is bounded`() {
repeat(600) { i -> ToolViewCache.getOrParse("""{"i":$i}""", "terminal", false) }
assertEquals(512, ToolViewCache.size())
}
}