diff --git a/settings.gradle.kts b/settings.gradle.kts index 5de7ea9..87f3e10 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -44,3 +44,7 @@ include(":memory-md") // для метаданных. JVM-only (JVector не имеет KMP-таргетов). На Android ART // работает через Java 11 base classes (scalar fallback). include(":memory-vector") +// Интерфейсы хранилища и разговорной истории: MessageStore / WorkingMemoryStore / +// ConversationStore / ReflectionStore + StorageBundle агрегатор. Реализации — +// в :storage-inmemory / :storage-sqlite / :storage-android (последний deferred). +include(":storage-core") diff --git a/standalone/build.gradle.kts b/standalone/build.gradle.kts index 8d18a79..3260029 100644 --- a/standalone/build.gradle.kts +++ b/standalone/build.gradle.kts @@ -49,6 +49,7 @@ kotlin { implementation(project(":memory-api")) implementation(project(":memory-md")) implementation(project(":memory-vector")) + implementation(project(":storage-core")) // litert-openai: JVM-реализация implementation(libs.litert.openai) diff --git a/standalone/src/jvmMain/kotlin/pw/binom/agentik/standalone/DebugRoutes.kt b/standalone/src/jvmMain/kotlin/pw/binom/agentik/standalone/DebugRoutes.kt index f821bb5..08d80b4 100644 --- a/standalone/src/jvmMain/kotlin/pw/binom/agentik/standalone/DebugRoutes.kt +++ b/standalone/src/jvmMain/kotlin/pw/binom/agentik/standalone/DebugRoutes.kt @@ -133,8 +133,8 @@ internal suspend fun recentTurns(stores: SqliteStores, conversationId: String, m var pendingUser: String? = null for (row in rows) { when (val e = row.entry) { - is pw.binom.agentik.standalone.persistence.WorkingMemoryEntry.User -> pendingUser = e.content.text() - is pw.binom.agentik.standalone.persistence.WorkingMemoryEntry.Assistant -> { + is pw.binom.agentik.storage.WorkingMemoryEntry.User -> pendingUser = e.content.text() + is pw.binom.agentik.storage.WorkingMemoryEntry.Assistant -> { val user = pendingUser ?: "" pendingUser = null pairs += ConversationTurn(userMessage = user, assistantMessage = e.content.text()) @@ -146,5 +146,5 @@ internal suspend fun recentTurns(stores: SqliteStores, conversationId: String, m } /** Текстовое содержимое записей working memory (Text-контент, без картинок). */ -internal fun List.text(): String = - filterIsInstance().joinToString("\n") { it.body } +internal fun List.text(): String = + filterIsInstance().joinToString("\n") { it.body } diff --git a/standalone/src/jvmMain/kotlin/pw/binom/agentik/standalone/Main.kt b/standalone/src/jvmMain/kotlin/pw/binom/agentik/standalone/Main.kt index 1e501a9..edc5937 100644 --- a/standalone/src/jvmMain/kotlin/pw/binom/agentik/standalone/Main.kt +++ b/standalone/src/jvmMain/kotlin/pw/binom/agentik/standalone/Main.kt @@ -177,7 +177,7 @@ fun main() { // и interval > 0. Загружаем top-K последних рефлексий из SQLite в system prompt. val reflector: pw.binom.agentik.standalone.agent.LlmReflector? = if (config.reflectionInterval > 0) LlmReflector(llm = llm) else null - val recentReflections: List = + val recentReflections: List = if (config.reflectionTopK > 0) kotlinx.coroutines.runBlocking { stores.reflections.listRecent(config.reflectionTopK) } else emptyList() diff --git a/standalone/src/jvmMain/kotlin/pw/binom/agentik/standalone/agent/ChatAgent.kt b/standalone/src/jvmMain/kotlin/pw/binom/agentik/standalone/agent/ChatAgent.kt index 95d9e85..f3a4c1d 100644 --- a/standalone/src/jvmMain/kotlin/pw/binom/agentik/standalone/agent/ChatAgent.kt +++ b/standalone/src/jvmMain/kotlin/pw/binom/agentik/standalone/agent/ChatAgent.kt @@ -16,9 +16,9 @@ import pw.binom.agentik.skills.SkillCatalog import pw.binom.agentik.skills.renderSystemPromptSection import pw.binom.agentik.standalone.agent.memory.MemoryToolsFactory import pw.binom.agentik.standalone.llm.LlmConfig -import pw.binom.agentik.standalone.persistence.ConversationRecord -import pw.binom.agentik.standalone.persistence.Reflection -import pw.binom.agentik.standalone.persistence.WorkingMemoryEntry +import pw.binom.agentik.storage.ConversationRecord +import pw.binom.agentik.storage.Reflection +import pw.binom.agentik.storage.WorkingMemoryEntry import pw.binom.agentik.standalone.persistence.sqlite.SqliteStores import pw.binom.litert.LiteLlm import kotlin.time.Instant @@ -148,7 +148,7 @@ class ChatAgent( override fun createConversation(temp: Boolean): ProtoConversation { val now = now() - val id = pw.binom.agentik.standalone.persistence.Ids.new("conv") + val id = pw.binom.agentik.storage.Ids.new("conv") val rec = ConversationRecord( id = id, title = null, diff --git a/standalone/src/jvmMain/kotlin/pw/binom/agentik/standalone/agent/ChatConversation.kt b/standalone/src/jvmMain/kotlin/pw/binom/agentik/standalone/agent/ChatConversation.kt index 822d1d8..16aa46b 100644 --- a/standalone/src/jvmMain/kotlin/pw/binom/agentik/standalone/agent/ChatConversation.kt +++ b/standalone/src/jvmMain/kotlin/pw/binom/agentik/standalone/agent/ChatConversation.kt @@ -27,17 +27,17 @@ import pw.binom.agentik.proto.Conversation as ProtoConversation import pw.binom.agentik.proto.Event as ProtoEvent import pw.binom.agentik.proto.Message as ProtoMessage import pw.binom.agentik.proto.MessageContext as ProtoMessageContext -import pw.binom.agentik.standalone.persistence.Content -import pw.binom.agentik.standalone.persistence.ConversationRecord -import pw.binom.agentik.standalone.persistence.ConversationStore -import pw.binom.agentik.standalone.persistence.MessageContext -import pw.binom.agentik.standalone.persistence.MessageOrigin -import pw.binom.agentik.standalone.persistence.MessageRecord -import pw.binom.agentik.standalone.persistence.TurnTokens -import pw.binom.agentik.standalone.persistence.MessageStore -import pw.binom.agentik.standalone.persistence.WorkingMemoryEntry -import pw.binom.agentik.standalone.persistence.WorkingMemoryRow -import pw.binom.agentik.standalone.persistence.WorkingMemoryStore +import pw.binom.agentik.storage.Content +import pw.binom.agentik.storage.ConversationRecord +import pw.binom.agentik.storage.ConversationStore +import pw.binom.agentik.storage.MessageContext +import pw.binom.agentik.storage.MessageOrigin +import pw.binom.agentik.storage.MessageRecord +import pw.binom.agentik.storage.TurnTokens +import pw.binom.agentik.storage.MessageStore +import pw.binom.agentik.storage.WorkingMemoryEntry +import pw.binom.agentik.storage.WorkingMemoryRow +import pw.binom.agentik.storage.WorkingMemoryStore import pw.binom.agentik.standalone.persistence.sqlite.SqliteStores import pw.binom.litert.LiteContentPart import pw.binom.litert.LiteConversation @@ -113,7 +113,7 @@ class ChatConversation( /** * Хранилище self-reflection. `null` = reflection отключён. */ - private val reflectionStore: pw.binom.agentik.standalone.persistence.ReflectionStore? = null, + private val reflectionStore: pw.binom.agentik.storage.ReflectionStore? = null, /** * Исполнитель рефлексии (one-shot LiteLlm). `null` = reflection отключён. */ @@ -896,7 +896,7 @@ class ChatConversation( private fun now(): Instant = Instant.fromEpochMilliseconds(System.currentTimeMillis()) - private fun newId(prefix: String): String = pw.binom.agentik.standalone.persistence.Ids.new(prefix) + private fun newId(prefix: String): String = pw.binom.agentik.storage.Ids.new(prefix) private fun encodeArgsJson(arguments: Map): String { val el = kotlinx.serialization.json.JsonElement.serializer() diff --git a/standalone/src/jvmMain/kotlin/pw/binom/agentik/standalone/agent/LlmReflector.kt b/standalone/src/jvmMain/kotlin/pw/binom/agentik/standalone/agent/LlmReflector.kt index 5bedff1..b9b3381 100644 --- a/standalone/src/jvmMain/kotlin/pw/binom/agentik/standalone/agent/LlmReflector.kt +++ b/standalone/src/jvmMain/kotlin/pw/binom/agentik/standalone/agent/LlmReflector.kt @@ -5,8 +5,8 @@ import kotlinx.coroutines.withContext import pw.binom.agentik.memory.ConversationTurn import pw.binom.litert.LiteConversationConfig import pw.binom.litert.LiteLlm -import pw.binom.agentik.standalone.persistence.Ids -import pw.binom.agentik.standalone.persistence.Reflection +import pw.binom.agentik.storage.Ids +import pw.binom.agentik.storage.Reflection import kotlin.time.Clock /** diff --git a/standalone/src/jvmMain/kotlin/pw/binom/agentik/standalone/agent/memory/LlmMemoryReviewer.kt b/standalone/src/jvmMain/kotlin/pw/binom/agentik/standalone/agent/memory/LlmMemoryReviewer.kt index 526dadc..3bd7fbd 100644 --- a/standalone/src/jvmMain/kotlin/pw/binom/agentik/standalone/agent/memory/LlmMemoryReviewer.kt +++ b/standalone/src/jvmMain/kotlin/pw/binom/agentik/standalone/agent/memory/LlmMemoryReviewer.kt @@ -10,7 +10,7 @@ import pw.binom.agentik.memory.MemoryStore import pw.binom.agentik.memory.MemoryStoreEvent import pw.binom.agentik.memory.NewMemoryNote import pw.binom.agentik.memory.ReviewedTurn -import pw.binom.agentik.standalone.persistence.Ids +import pw.binom.agentik.storage.Ids import pw.binom.litert.LiteLlm import kotlin.time.Clock import kotlin.time.Instant diff --git a/standalone/src/jvmMain/kotlin/pw/binom/agentik/standalone/persistence/ReflectionStore.kt b/standalone/src/jvmMain/kotlin/pw/binom/agentik/standalone/persistence/ReflectionStore.kt index 5293e9d..4fa4138 100644 --- a/standalone/src/jvmMain/kotlin/pw/binom/agentik/standalone/persistence/ReflectionStore.kt +++ b/standalone/src/jvmMain/kotlin/pw/binom/agentik/standalone/persistence/ReflectionStore.kt @@ -6,58 +6,15 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.asSharedFlow import mu.KotlinLogging +import pw.binom.agentik.storage.Reflection +import pw.binom.agentik.storage.ReflectionEvent +import pw.binom.agentik.storage.ReflectionStore import pw.binom.agentik.standalone.persistence.sqlite.AgentikDatabase import pw.binom.agentik.standalone.persistence.sqlite.Reflection as DbReflection import pw.binom.agentik.standalone.persistence.sqlite.ReflectionQueries private val log = KotlinLogging.logger {} -/** - * Self-reflection запись — что агент "думает" о качестве своих последних ходов. - * - * - [score]: 1..5 (самооценка качества) - * - [weakSpots]: конкретные слабые места ("медленно ищу в Y", "путаю A и B") - * - [summary]: свободный комментарий в markdown (что заметил, что улучшить) - * - * Источник: one-shot LLM-размышление после каждых N ходов (см. AGENTIK_REFLECTION_INTERVAL). - * Используется в `ChatAgent.buildSystemPrompt` как "слабые места за последнее время". - */ -data class Reflection( - val id: String, - val conversationId: String?, - val createdAt: Instant, - val turnsAnalyzed: Int, - val score: Int, - val summary: String, - val weakSpots: List, -) - -/** - * Хранилище рефлексий. Backed by SQLDelight `reflection` таблицу. - * - * Рефлексии — append-only: старые записи удаляются [deleteOlderThan] (cleanup) - * или архивируются через [Curator]-подобный процесс, но не редактируются. - */ -interface ReflectionStore : AutoCloseable { - suspend fun insert(reflection: Reflection) - suspend fun get(id: String): Reflection? - /** Самые свежие рефлексии (по всему агенту). */ - suspend fun listRecent(limit: Int = 10): List - /** Рефлексии для конкретного диалога. */ - suspend fun listForConversation(conversationId: String, limit: Int = 10): List - suspend fun deleteOlderThan(cutoff: Instant) - suspend fun count(): Int - - /** Стрим новых рефлексий для подписчиков (для UI в будущем). */ - fun events(): Flow = kotlinx.coroutines.flow.emptyFlow() - - override fun close() -} - -sealed interface ReflectionEvent { - data class Created(val reflection: Reflection) : ReflectionEvent -} - /** * SQLDelight-реализация [ReflectionStore]. * @@ -65,6 +22,11 @@ sealed interface ReflectionEvent { * парсим минимальным ручным сканером, чтобы не тащить kotlinx-serialization * в этот модуль (он уже есть в commonMain через standalone — но не хочется * парсить JSON ради одного массива строк). + * + * В commit 3 переедет в `:storage-sqlite`. Сейчас остаётся в `:standalone`, + * потому что зависит от SQLDelight'ной `AgentikDatabase`, которую мы ещё + * не отвязали от `:standalone` (`generateMainDatabase` нужно подключить к + * новому модулю). */ class SqliteReflectionStore( private val db: AgentikDatabase, diff --git a/standalone/src/jvmMain/kotlin/pw/binom/agentik/standalone/persistence/sqlite/SqliteConversationStore.kt b/standalone/src/jvmMain/kotlin/pw/binom/agentik/standalone/persistence/sqlite/SqliteConversationStore.kt index c1b0950..a3b015a 100644 --- a/standalone/src/jvmMain/kotlin/pw/binom/agentik/standalone/persistence/sqlite/SqliteConversationStore.kt +++ b/standalone/src/jvmMain/kotlin/pw/binom/agentik/standalone/persistence/sqlite/SqliteConversationStore.kt @@ -1,8 +1,8 @@ package pw.binom.agentik.standalone.persistence.sqlite import kotlin.time.Instant -import pw.binom.agentik.standalone.persistence.ConversationRecord -import pw.binom.agentik.standalone.persistence.ConversationStore +import pw.binom.agentik.storage.ConversationRecord +import pw.binom.agentik.storage.ConversationStore /** * SQLite-реализация [ConversationStore]. diff --git a/standalone/src/jvmMain/kotlin/pw/binom/agentik/standalone/persistence/sqlite/SqliteMessageStore.kt b/standalone/src/jvmMain/kotlin/pw/binom/agentik/standalone/persistence/sqlite/SqliteMessageStore.kt index d7a1540..5fe5c24 100644 --- a/standalone/src/jvmMain/kotlin/pw/binom/agentik/standalone/persistence/sqlite/SqliteMessageStore.kt +++ b/standalone/src/jvmMain/kotlin/pw/binom/agentik/standalone/persistence/sqlite/SqliteMessageStore.kt @@ -1,11 +1,11 @@ package pw.binom.agentik.standalone.persistence.sqlite import kotlinx.serialization.json.Json -import pw.binom.agentik.standalone.persistence.MessageRecord -import pw.binom.agentik.standalone.persistence.MessageStore -import pw.binom.agentik.standalone.persistence.TokenStats -import pw.binom.agentik.standalone.persistence.decodeBodyPayload -import pw.binom.agentik.standalone.persistence.encodeBodyPayload +import pw.binom.agentik.storage.MessageRecord +import pw.binom.agentik.storage.MessageStore +import pw.binom.agentik.storage.TokenStats +import pw.binom.agentik.storage.decodeBodyPayload +import pw.binom.agentik.storage.encodeBodyPayload import kotlin.time.Instant /** diff --git a/standalone/src/jvmMain/kotlin/pw/binom/agentik/standalone/persistence/sqlite/SqliteStores.kt b/standalone/src/jvmMain/kotlin/pw/binom/agentik/standalone/persistence/sqlite/SqliteStores.kt index ef7b3fc..6b7a4f7 100644 --- a/standalone/src/jvmMain/kotlin/pw/binom/agentik/standalone/persistence/sqlite/SqliteStores.kt +++ b/standalone/src/jvmMain/kotlin/pw/binom/agentik/standalone/persistence/sqlite/SqliteStores.kt @@ -3,11 +3,11 @@ package pw.binom.agentik.standalone.persistence.sqlite import app.cash.sqldelight.db.QueryResult import app.cash.sqldelight.db.SqlDriver import app.cash.sqldelight.driver.jdbc.sqlite.JdbcSqliteDriver -import pw.binom.agentik.standalone.persistence.ConversationStore -import pw.binom.agentik.standalone.persistence.MessageStore -import pw.binom.agentik.standalone.persistence.ReflectionStore +import pw.binom.agentik.storage.ConversationStore +import pw.binom.agentik.storage.MessageStore +import pw.binom.agentik.storage.ReflectionStore import pw.binom.agentik.standalone.persistence.SqliteReflectionStore -import pw.binom.agentik.standalone.persistence.WorkingMemoryStore +import pw.binom.agentik.storage.WorkingMemoryStore /** * Корневой объект SQLite-слоя: держит [SqlDriver] и три [WorkingMemoryStore]/[MessageStore]/[ConversationStore]. diff --git a/standalone/src/jvmMain/kotlin/pw/binom/agentik/standalone/persistence/sqlite/SqliteWorkingMemoryStore.kt b/standalone/src/jvmMain/kotlin/pw/binom/agentik/standalone/persistence/sqlite/SqliteWorkingMemoryStore.kt index 64eee10..429a18a 100644 --- a/standalone/src/jvmMain/kotlin/pw/binom/agentik/standalone/persistence/sqlite/SqliteWorkingMemoryStore.kt +++ b/standalone/src/jvmMain/kotlin/pw/binom/agentik/standalone/persistence/sqlite/SqliteWorkingMemoryStore.kt @@ -1,9 +1,9 @@ package pw.binom.agentik.standalone.persistence.sqlite import kotlinx.serialization.json.Json -import pw.binom.agentik.standalone.persistence.WorkingMemoryEntry -import pw.binom.agentik.standalone.persistence.WorkingMemoryRow -import pw.binom.agentik.standalone.persistence.WorkingMemoryStore +import pw.binom.agentik.storage.WorkingMemoryEntry +import pw.binom.agentik.storage.WorkingMemoryRow +import pw.binom.agentik.storage.WorkingMemoryStore import kotlin.time.Instant /** @@ -90,4 +90,4 @@ private fun Working_memory.toRow(): WorkingMemoryRow { ) } -private fun newId(): String = pw.binom.agentik.standalone.persistence.Ids.new("wm") +private fun newId(): String = pw.binom.agentik.storage.Ids.new("wm") diff --git a/standalone/src/jvmTest/kotlin/pw/binom/agentik/standalone/agent/ChatAgentReflectionTest.kt b/standalone/src/jvmTest/kotlin/pw/binom/agentik/standalone/agent/ChatAgentReflectionTest.kt index 9ef53b2..bbd2f4e 100644 --- a/standalone/src/jvmTest/kotlin/pw/binom/agentik/standalone/agent/ChatAgentReflectionTest.kt +++ b/standalone/src/jvmTest/kotlin/pw/binom/agentik/standalone/agent/ChatAgentReflectionTest.kt @@ -5,7 +5,7 @@ import kotlin.test.assertFalse import kotlin.test.assertTrue import kotlin.time.Instant import pw.binom.agentik.skills.SkillCatalog -import pw.binom.agentik.standalone.persistence.Reflection +import pw.binom.agentik.storage.Reflection class ChatAgentReflectionTest { diff --git a/standalone/src/jvmTest/kotlin/pw/binom/agentik/standalone/agent/ChatAgentTest.kt b/standalone/src/jvmTest/kotlin/pw/binom/agentik/standalone/agent/ChatAgentTest.kt index b4787d7..de3d061 100644 --- a/standalone/src/jvmTest/kotlin/pw/binom/agentik/standalone/agent/ChatAgentTest.kt +++ b/standalone/src/jvmTest/kotlin/pw/binom/agentik/standalone/agent/ChatAgentTest.kt @@ -78,8 +78,8 @@ class ChatAgentTest { val wm = stores.workingMemory.list(conv.id) assertEquals(1, wm.size) val first = wm[0] - assertIs(first.entry) - assertEquals("be brief", first.entry.text) + val sysEntry = first.entry as pw.binom.agentik.storage.WorkingMemoryEntry.System + assertEquals("be brief", sysEntry.text) } @Test @@ -91,7 +91,7 @@ class ChatAgentTest { val conv = agent.createConversation(temp = false) as ChatConversation val system = stores.workingMemory.list(conv.id).first().entry - as pw.binom.agentik.standalone.persistence.WorkingMemoryEntry.System + as pw.binom.agentik.storage.WorkingMemoryEntry.System assertTrue("be brief" in system.text) assertTrue("## Навыки" in system.text) assertTrue("lint" in system.text) @@ -148,10 +148,10 @@ class ChatAgentTest { // добавим сообщение, чтобы потом убедиться, что каскад сработал stores.messages.append( - pw.binom.agentik.standalone.persistence.MessageRecord.UserMessage( + pw.binom.agentik.storage.MessageRecord.UserMessage( id = "m1", conversationId = id, - content = listOf(pw.binom.agentik.standalone.persistence.Content.Text("hi")), + content = listOf(pw.binom.agentik.storage.Content.Text("hi")), createdAt = Instant.fromEpochMilliseconds(1_700_000_000_000), ), ) @@ -178,11 +178,11 @@ class ChatAgentTest { // user message записан в audit + working memory val msgs = stores.messages.listAll(conv.id) assertEquals(2, msgs.size) - assertEquals("hi", (msgs[0] as pw.binom.agentik.standalone.persistence.MessageRecord.UserMessage).content.let { - (it[0] as pw.binom.agentik.standalone.persistence.Content.Text).body + assertEquals("hi", (msgs[0] as pw.binom.agentik.storage.MessageRecord.UserMessage).content.let { + (it[0] as pw.binom.agentik.storage.Content.Text).body }) - assertEquals("hello back", (msgs[1] as pw.binom.agentik.standalone.persistence.MessageRecord.AssistantMessage).content.let { - (it[0] as pw.binom.agentik.standalone.persistence.Content.Text).body + assertEquals("hello back", (msgs[1] as pw.binom.agentik.storage.MessageRecord.AssistantMessage).content.let { + (it[0] as pw.binom.agentik.storage.Content.Text).body }) } @@ -305,8 +305,8 @@ class ChatAgentTest { val msgs = stores.messages.listAll(conv.id) assertEquals(2, msgs.size) - assertIs(msgs[0]) - val err = assertIs(msgs[1]) + assertIs(msgs[0]) + val err = assertIs(msgs[1]) assertEquals("boom from llm", err.message) // backfill через getMessages (polling/reconnect) тоже видит ошибку @@ -338,7 +338,7 @@ class ChatAgentTest { // user сообщение в audit должно быть, assistant — нет (был отменён) val msgs = stores.messages.listAll(conv.id) assertEquals(1, msgs.size) - assertIs(msgs[0]) + assertIs(msgs[0]) } @Test diff --git a/standalone/src/jvmTest/kotlin/pw/binom/agentik/standalone/agent/CompactionTest.kt b/standalone/src/jvmTest/kotlin/pw/binom/agentik/standalone/agent/CompactionTest.kt index 1ede88a..640d8ee 100644 --- a/standalone/src/jvmTest/kotlin/pw/binom/agentik/standalone/agent/CompactionTest.kt +++ b/standalone/src/jvmTest/kotlin/pw/binom/agentik/standalone/agent/CompactionTest.kt @@ -88,7 +88,7 @@ class CompactionTest { } val wm = stores.workingMemory.list(conv.id) // Без compaction все ходы остаются в памяти (System + 10 user/assistant = 21 строк). - val summaries = wm.filter { it.entry is pw.binom.agentik.standalone.persistence.WorkingMemoryEntry.Summary } + val summaries = wm.filter { it.entry is pw.binom.agentik.storage.WorkingMemoryEntry.Summary } assertEquals(0, summaries.size, "compaction must not run without contextWindow") } @@ -100,7 +100,7 @@ class CompactionTest { conv.send(listOf(ProtoContent.Text("first"))) val wm = stores.workingMemory.list(conv.id) // System + User + Assistant = 3. Без compactor — никаких Summary. - val summaries = wm.filter { it.entry is pw.binom.agentik.standalone.persistence.WorkingMemoryEntry.Summary } + val summaries = wm.filter { it.entry is pw.binom.agentik.storage.WorkingMemoryEntry.Summary } assertEquals(0, summaries.size, "no compaction runs without compactor") } @@ -118,10 +118,10 @@ class CompactionTest { assertTrue(compactor.calls > 0, "compactor must be called at least once when above threshold") // В working memory должна появиться Summary. val wm = stores.workingMemory.list(conv.id) - val summaries = wm.filter { it.entry is pw.binom.agentik.standalone.persistence.WorkingMemoryEntry.Summary } + val summaries = wm.filter { it.entry is pw.binom.agentik.storage.WorkingMemoryEntry.Summary } assertTrue(summaries.isNotEmpty(), "at least one Summary entry should be present after compaction") // Summary-текст — то, что вернул наш compactor. - val summaryText = (summaries.first().entry as pw.binom.agentik.standalone.persistence.WorkingMemoryEntry.Summary).text + val summaryText = (summaries.first().entry as pw.binom.agentik.storage.WorkingMemoryEntry.Summary).text assertTrue(summaryText.startsWith("**Goal**"), "summary text should come from compactor: $summaryText") } @@ -161,8 +161,8 @@ class CompactionTest { // Должны быть: System + хотя бы один Summary + последние KEEP_RECENT_TURNS ходов. // KEEP_RECENT_TURNS = 4 → user/assistant последних двух ходов (third + second) могут быть не тронуты. val userAssistantCount = wm.count { - it.entry is pw.binom.agentik.standalone.persistence.WorkingMemoryEntry.User || - it.entry is pw.binom.agentik.standalone.persistence.WorkingMemoryEntry.Assistant + it.entry is pw.binom.agentik.storage.WorkingMemoryEntry.User || + it.entry is pw.binom.agentik.storage.WorkingMemoryEntry.Assistant } // Минимум 1 ход остаётся (KEEP_RECENT_TURNS). assertTrue(userAssistantCount >= 1, "at least one recent turn must be preserved") diff --git a/standalone/src/jvmTest/kotlin/pw/binom/agentik/standalone/agent/ContextPrefixTest.kt b/standalone/src/jvmTest/kotlin/pw/binom/agentik/standalone/agent/ContextPrefixTest.kt index c5afa63..43fad98 100644 --- a/standalone/src/jvmTest/kotlin/pw/binom/agentik/standalone/agent/ContextPrefixTest.kt +++ b/standalone/src/jvmTest/kotlin/pw/binom/agentik/standalone/agent/ContextPrefixTest.kt @@ -1,9 +1,9 @@ package pw.binom.agentik.standalone.agent -import pw.binom.agentik.standalone.persistence.MessageContext -import pw.binom.agentik.standalone.persistence.MessageOrigin.EVENT -import pw.binom.agentik.standalone.persistence.MessageOrigin.SYSTEM -import pw.binom.agentik.standalone.persistence.MessageOrigin.USER +import pw.binom.agentik.storage.MessageContext +import pw.binom.agentik.storage.MessageOrigin.EVENT +import pw.binom.agentik.storage.MessageOrigin.SYSTEM +import pw.binom.agentik.storage.MessageOrigin.USER import pw.binom.litert.LiteContentPart import kotlin.test.Test import kotlin.test.assertEquals diff --git a/standalone/src/jvmTest/kotlin/pw/binom/agentik/standalone/agent/MemoryWiringTest.kt b/standalone/src/jvmTest/kotlin/pw/binom/agentik/standalone/agent/MemoryWiringTest.kt index a6b5c3e..f5f6e6e 100644 --- a/standalone/src/jvmTest/kotlin/pw/binom/agentik/standalone/agent/MemoryWiringTest.kt +++ b/standalone/src/jvmTest/kotlin/pw/binom/agentik/standalone/agent/MemoryWiringTest.kt @@ -29,7 +29,7 @@ import pw.binom.agentik.standalone.agent.memory.MemoryToolsFactory import pw.binom.agentik.standalone.llm.LlmBackend import pw.binom.agentik.standalone.llm.LlmConfig import pw.binom.agentik.standalone.llm.OpenAiConfig -import pw.binom.agentik.standalone.persistence.WorkingMemoryEntry +import pw.binom.agentik.storage.WorkingMemoryEntry import pw.binom.agentik.standalone.persistence.sqlite.SqliteStores import kotlin.test.AfterTest import kotlin.test.BeforeTest diff --git a/standalone/src/jvmTest/kotlin/pw/binom/agentik/standalone/persistence/TokenStatsTest.kt b/standalone/src/jvmTest/kotlin/pw/binom/agentik/standalone/persistence/TokenStatsTest.kt index b57a802..83f1966 100644 --- a/standalone/src/jvmTest/kotlin/pw/binom/agentik/standalone/persistence/TokenStatsTest.kt +++ b/standalone/src/jvmTest/kotlin/pw/binom/agentik/standalone/persistence/TokenStatsTest.kt @@ -1,4 +1,4 @@ -package pw.binom.agentik.standalone.persistence +package pw.binom.agentik.storage import kotlin.test.Test import kotlin.test.assertEquals diff --git a/standalone/src/jvmTest/kotlin/pw/binom/agentik/standalone/persistence/PersistenceTest.kt b/standalone/src/jvmTest/kotlin/pw/binom/agentik/storage/PersistenceTest.kt similarity index 99% rename from standalone/src/jvmTest/kotlin/pw/binom/agentik/standalone/persistence/PersistenceTest.kt rename to standalone/src/jvmTest/kotlin/pw/binom/agentik/storage/PersistenceTest.kt index 956e24a..56942e1 100644 --- a/standalone/src/jvmTest/kotlin/pw/binom/agentik/standalone/persistence/PersistenceTest.kt +++ b/standalone/src/jvmTest/kotlin/pw/binom/agentik/storage/PersistenceTest.kt @@ -1,4 +1,4 @@ -package pw.binom.agentik.standalone.persistence +package pw.binom.agentik.storage import kotlinx.coroutines.test.runTest import pw.binom.agentik.standalone.persistence.sqlite.SqliteStores diff --git a/standalone/src/jvmTest/kotlin/pw/binom/agentik/standalone/persistence/ReflectionStoreTest.kt b/standalone/src/jvmTest/kotlin/pw/binom/agentik/storage/ReflectionStoreTest.kt similarity index 96% rename from standalone/src/jvmTest/kotlin/pw/binom/agentik/standalone/persistence/ReflectionStoreTest.kt rename to standalone/src/jvmTest/kotlin/pw/binom/agentik/storage/ReflectionStoreTest.kt index 6db760a..79a7a26 100644 --- a/standalone/src/jvmTest/kotlin/pw/binom/agentik/standalone/persistence/ReflectionStoreTest.kt +++ b/standalone/src/jvmTest/kotlin/pw/binom/agentik/storage/ReflectionStoreTest.kt @@ -1,10 +1,12 @@ -package pw.binom.agentik.standalone.persistence +package pw.binom.agentik.storage import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertNull import kotlin.test.assertNotNull import kotlin.time.Instant +import pw.binom.agentik.standalone.persistence.decodeStringArray +import pw.binom.agentik.standalone.persistence.encodeStringArray import pw.binom.agentik.standalone.persistence.sqlite.SqliteStores class ReflectionStoreTest { diff --git a/standalone/src/jvmTest/kotlin/pw/binom/agentik/standalone/persistence/SqliteStoresMigrationTest.kt b/standalone/src/jvmTest/kotlin/pw/binom/agentik/storage/SqliteStoresMigrationTest.kt similarity index 96% rename from standalone/src/jvmTest/kotlin/pw/binom/agentik/standalone/persistence/SqliteStoresMigrationTest.kt rename to standalone/src/jvmTest/kotlin/pw/binom/agentik/storage/SqliteStoresMigrationTest.kt index 9d2455a..964ff1c 100644 --- a/standalone/src/jvmTest/kotlin/pw/binom/agentik/standalone/persistence/SqliteStoresMigrationTest.kt +++ b/standalone/src/jvmTest/kotlin/pw/binom/agentik/storage/SqliteStoresMigrationTest.kt @@ -1,4 +1,4 @@ -package pw.binom.agentik.standalone.persistence +package pw.binom.agentik.storage import kotlin.test.Test import kotlin.test.assertEquals diff --git a/storage-core/build.gradle.kts b/storage-core/build.gradle.kts new file mode 100644 index 0000000..0168351 --- /dev/null +++ b/storage-core/build.gradle.kts @@ -0,0 +1,32 @@ +plugins { + alias(libs.plugins.kotlin.multiplatform) + alias(libs.plugins.kotlin.serialization) +} + +kotlin { + jvmToolchain(21) + + // Зеркалит набор :proto / :server / :memory-api — KMP-модуль с интерфейсами + // хранилища и разговорной истории, без платформенного IO. Конкретные + // реализации (sqlite, in-memory, android) живут в отдельных модулях. + jvm() + macosX64() + macosArm64() + iosX64() + iosArm64() + iosSimulatorArm64() + linuxX64() + linuxArm64() + mingwX64() + + sourceSets { + commonMain.dependencies { + api(libs.kotlinx.coroutines.core) + api(libs.kotlinx.serialization.json) + } + commonTest.dependencies { + implementation(kotlin("test")) + implementation(libs.kotlinx.coroutines.test) + } + } +} diff --git a/standalone/src/commonMain/kotlin/pw/binom/agentik/standalone/persistence/Content.kt b/storage-core/src/commonMain/kotlin/pw/binom/agentik/storage/Content.kt similarity index 95% rename from standalone/src/commonMain/kotlin/pw/binom/agentik/standalone/persistence/Content.kt rename to storage-core/src/commonMain/kotlin/pw/binom/agentik/storage/Content.kt index 5eca4f0..d10125d 100644 --- a/standalone/src/commonMain/kotlin/pw/binom/agentik/standalone/persistence/Content.kt +++ b/storage-core/src/commonMain/kotlin/pw/binom/agentik/storage/Content.kt @@ -1,4 +1,4 @@ -package pw.binom.agentik.standalone.persistence +package pw.binom.agentik.storage import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable diff --git a/standalone/src/commonMain/kotlin/pw/binom/agentik/standalone/persistence/ConversationRecord.kt b/storage-core/src/commonMain/kotlin/pw/binom/agentik/storage/ConversationRecord.kt similarity index 85% rename from standalone/src/commonMain/kotlin/pw/binom/agentik/standalone/persistence/ConversationRecord.kt rename to storage-core/src/commonMain/kotlin/pw/binom/agentik/storage/ConversationRecord.kt index a9658dd..946683e 100644 --- a/standalone/src/commonMain/kotlin/pw/binom/agentik/standalone/persistence/ConversationRecord.kt +++ b/storage-core/src/commonMain/kotlin/pw/binom/agentik/storage/ConversationRecord.kt @@ -1,4 +1,4 @@ -package pw.binom.agentik.standalone.persistence +package pw.binom.agentik.storage import kotlin.time.Instant diff --git a/standalone/src/commonMain/kotlin/pw/binom/agentik/standalone/persistence/ConversationStore.kt b/storage-core/src/commonMain/kotlin/pw/binom/agentik/storage/ConversationStore.kt similarity index 95% rename from standalone/src/commonMain/kotlin/pw/binom/agentik/standalone/persistence/ConversationStore.kt rename to storage-core/src/commonMain/kotlin/pw/binom/agentik/storage/ConversationStore.kt index b95d526..57b82db 100644 --- a/standalone/src/commonMain/kotlin/pw/binom/agentik/standalone/persistence/ConversationStore.kt +++ b/storage-core/src/commonMain/kotlin/pw/binom/agentik/storage/ConversationStore.kt @@ -1,4 +1,4 @@ -package pw.binom.agentik.standalone.persistence +package pw.binom.agentik.storage import kotlin.time.Instant diff --git a/standalone/src/commonMain/kotlin/pw/binom/agentik/standalone/persistence/Ids.kt b/storage-core/src/commonMain/kotlin/pw/binom/agentik/storage/Ids.kt similarity index 92% rename from standalone/src/commonMain/kotlin/pw/binom/agentik/standalone/persistence/Ids.kt rename to storage-core/src/commonMain/kotlin/pw/binom/agentik/storage/Ids.kt index b9431bc..be49ca6 100644 --- a/standalone/src/commonMain/kotlin/pw/binom/agentik/standalone/persistence/Ids.kt +++ b/storage-core/src/commonMain/kotlin/pw/binom/agentik/storage/Ids.kt @@ -1,4 +1,4 @@ -package pw.binom.agentik.standalone.persistence +package pw.binom.agentik.storage import kotlin.uuid.Uuid diff --git a/standalone/src/commonMain/kotlin/pw/binom/agentik/standalone/persistence/MessageContext.kt b/storage-core/src/commonMain/kotlin/pw/binom/agentik/storage/MessageContext.kt similarity index 79% rename from standalone/src/commonMain/kotlin/pw/binom/agentik/standalone/persistence/MessageContext.kt rename to storage-core/src/commonMain/kotlin/pw/binom/agentik/storage/MessageContext.kt index a09eeef..86e6bf7 100644 --- a/standalone/src/commonMain/kotlin/pw/binom/agentik/standalone/persistence/MessageContext.kt +++ b/storage-core/src/commonMain/kotlin/pw/binom/agentik/storage/MessageContext.kt @@ -1,4 +1,4 @@ -package pw.binom.agentik.standalone.persistence +package pw.binom.agentik.storage import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @@ -8,9 +8,8 @@ import kotlinx.serialization.json.JsonElement * Контекст инициации хода (кто/что и почему). * * Дубликат типа из `:proto` (`pw.binom.agentik.proto.MessageContext`): - * живёт в `:standalone/persistence` чтобы не тащить `:proto` в слой - * хранения данных. Маппинг между ними — в `MessageRecord.toProto()` / - * `ProtoMessage.toStorage()`. + * живёт в `:storage-core` чтобы не тащить `:proto` в слой хранения данных. + * Маппинг между ними — в `MessageRecord.toProto()` / `ProtoMessage.toStorage()`. * * Используется: * - в `MessageRecord.UserMessage.context` — фиксируется в audit log; diff --git a/standalone/src/commonMain/kotlin/pw/binom/agentik/standalone/persistence/MessageRecord.kt b/storage-core/src/commonMain/kotlin/pw/binom/agentik/storage/MessageRecord.kt similarity index 97% rename from standalone/src/commonMain/kotlin/pw/binom/agentik/standalone/persistence/MessageRecord.kt rename to storage-core/src/commonMain/kotlin/pw/binom/agentik/storage/MessageRecord.kt index 82373f1..de66330 100644 --- a/standalone/src/commonMain/kotlin/pw/binom/agentik/standalone/persistence/MessageRecord.kt +++ b/storage-core/src/commonMain/kotlin/pw/binom/agentik/storage/MessageRecord.kt @@ -1,4 +1,4 @@ -package pw.binom.agentik.standalone.persistence +package pw.binom.agentik.storage import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @@ -33,7 +33,7 @@ data class TurnTokens( /** * Запись в таблице `message` (append-only audit) и `working_memory` (mutable view). * - * Используется sealed-иерархию: подтипы `User`/`Assistant`/`ToolCall`/`ToolResult` + * Использует sealed-иерархию: подтипы `User`/`Assistant`/`ToolCall`/`ToolResult` * живут и там, и там. `Summary`/`System` — только в `working_memory` * (синтетические строки, созданные при суммаризации или как system-prompt). * diff --git a/standalone/src/commonMain/kotlin/pw/binom/agentik/standalone/persistence/MessageStore.kt b/storage-core/src/commonMain/kotlin/pw/binom/agentik/storage/MessageStore.kt similarity index 82% rename from standalone/src/commonMain/kotlin/pw/binom/agentik/standalone/persistence/MessageStore.kt rename to storage-core/src/commonMain/kotlin/pw/binom/agentik/storage/MessageStore.kt index 44f9c10..0b70300 100644 --- a/standalone/src/commonMain/kotlin/pw/binom/agentik/standalone/persistence/MessageStore.kt +++ b/storage-core/src/commonMain/kotlin/pw/binom/agentik/storage/MessageStore.kt @@ -1,5 +1,6 @@ -package pw.binom.agentik.standalone.persistence +package pw.binom.agentik.storage +import kotlinx.coroutines.flow.Flow import kotlin.time.Instant /** @@ -39,9 +40,17 @@ interface MessageStore : AutoCloseable { /** Все сообщения диалога, отсортированные по `createdAt ASC` (для rebuild working memory). */ suspend fun listAll(conversationId: String): List + /** Лайв-стрим новых сообщений (для SSE-подписчиков). По умолчанию — пустой. */ + fun events(): Flow = kotlinx.coroutines.flow.emptyFlow() + /** * Суммарная token-статистика по диалогу: input/output/turns. Один проход * по всем assistant-сообщениям. Дёшево (на практике < 1мс на SQLite). */ suspend fun tokenStats(conversationId: String): TokenStats } + +sealed interface MessageEvent { + val conversationId: String + data class Appended(override val conversationId: String, val record: MessageRecord) : MessageEvent +} diff --git a/standalone/src/commonMain/kotlin/pw/binom/agentik/standalone/persistence/Payload.kt b/storage-core/src/commonMain/kotlin/pw/binom/agentik/storage/Payload.kt similarity index 97% rename from standalone/src/commonMain/kotlin/pw/binom/agentik/standalone/persistence/Payload.kt rename to storage-core/src/commonMain/kotlin/pw/binom/agentik/storage/Payload.kt index a1a66a8..2531ad4 100644 --- a/standalone/src/commonMain/kotlin/pw/binom/agentik/standalone/persistence/Payload.kt +++ b/storage-core/src/commonMain/kotlin/pw/binom/agentik/storage/Payload.kt @@ -1,4 +1,4 @@ -package pw.binom.agentik.standalone.persistence +package pw.binom.agentik.storage import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @@ -27,7 +27,7 @@ private val bodyJson = Json { } @Serializable -internal data class MessageBodyPayload( +data class MessageBodyPayload( val content: List, @SerialName("context") val context: MessageContext? = null, diff --git a/storage-core/src/commonMain/kotlin/pw/binom/agentik/storage/ReflectionStore.kt b/storage-core/src/commonMain/kotlin/pw/binom/agentik/storage/ReflectionStore.kt new file mode 100644 index 0000000..da2c6a1 --- /dev/null +++ b/storage-core/src/commonMain/kotlin/pw/binom/agentik/storage/ReflectionStore.kt @@ -0,0 +1,51 @@ +package pw.binom.agentik.storage + +import kotlinx.coroutines.flow.Flow +import kotlin.time.Instant + +/** + * Self-reflection запись — что агент "думает" о качестве своих последних ходов. + * + * - [score]: 1..5 (самооценка качества) + * - [weakSpots]: конкретные слабые места ("медленно ищу в Y", "путаю A и B") + * - [summary]: свободный комментарий в markdown (что заметил, что улучшить) + * + * Источник: one-shot LLM-размышление после каждых N ходов (см. AGENTIK_REFLECTION_INTERVAL). + * Используется в `ChatAgent.buildSystemPrompt` как "слабые места за последнее время". + */ +data class Reflection( + val id: String, + val conversationId: String?, + val createdAt: Instant, + val turnsAnalyzed: Int, + val score: Int, + val summary: String, + val weakSpots: List, +) + +/** + * Хранилище рефлексий. Backed by SQLDelight `reflection` таблицу (impl в + * `:storage-sqlite`) или in-memory (impl в `:storage-inmemory`). + * + * Рефлексии — append-only: старые записи удаляются [deleteOlderThan] (cleanup) + * или архивируются через [Curator]-подобный процесс, но не редактируются. + */ +interface ReflectionStore : AutoCloseable { + suspend fun insert(reflection: Reflection) + suspend fun get(id: String): Reflection? + /** Самые свежие рефлексии (по всему агенту). */ + suspend fun listRecent(limit: Int = 10): List + /** Рефлексии для конкретного диалога. */ + suspend fun listForConversation(conversationId: String, limit: Int = 10): List + suspend fun deleteOlderThan(cutoff: Instant) + suspend fun count(): Int + + /** Стрим новых рефлексий для подписчиков (для UI в будущем). */ + fun events(): Flow = kotlinx.coroutines.flow.emptyFlow() + + override fun close() +} + +sealed interface ReflectionEvent { + data class Created(val reflection: Reflection) : ReflectionEvent +} diff --git a/storage-core/src/commonMain/kotlin/pw/binom/agentik/storage/StorageBundle.kt b/storage-core/src/commonMain/kotlin/pw/binom/agentik/storage/StorageBundle.kt new file mode 100644 index 0000000..5dd9352 --- /dev/null +++ b/storage-core/src/commonMain/kotlin/pw/binom/agentik/storage/StorageBundle.kt @@ -0,0 +1,29 @@ +package pw.binom.agentik.storage + +/** + * Агрегатор всех storage-интерфейсов, нужных агенту для работы с историей диалога. + * + * Реализации: + * - [SqliteStorageBundle] — JVM-only, основная. SQLDelight + SQLite. + * - [InMemoryStorageBundle] — Map-based, для тестов. + * - `:storage-android` — Android-специфичная (deferred, Android-сборка в планах). + * + * `SkillStore` НЕ входит сюда — он живёт в модуле `:skills` (другая ответственность: + * не сообщения/рефлексии, а контент-файлы навыков) и принимается отдельно в `ChatAgent`. + * + * AutoCloseable: один `close()` закрывает все четыре store'а. В реализациях, + * которые не владеют ресурсами (in-memory), close — no-op. + */ +data class StorageBundle( + val conversationStore: ConversationStore, + val messageStore: MessageStore, + val workingMemoryStore: WorkingMemoryStore, + val reflectionStore: ReflectionStore, +) : AutoCloseable { + override fun close() { + conversationStore.close() + messageStore.close() + workingMemoryStore.close() + reflectionStore.close() + } +} diff --git a/standalone/src/commonMain/kotlin/pw/binom/agentik/standalone/persistence/WorkingMemoryEntry.kt b/storage-core/src/commonMain/kotlin/pw/binom/agentik/storage/WorkingMemoryEntry.kt similarity index 98% rename from standalone/src/commonMain/kotlin/pw/binom/agentik/standalone/persistence/WorkingMemoryEntry.kt rename to storage-core/src/commonMain/kotlin/pw/binom/agentik/storage/WorkingMemoryEntry.kt index 1b3d8d4..e51f7e8 100644 --- a/standalone/src/commonMain/kotlin/pw/binom/agentik/standalone/persistence/WorkingMemoryEntry.kt +++ b/storage-core/src/commonMain/kotlin/pw/binom/agentik/storage/WorkingMemoryEntry.kt @@ -1,4 +1,4 @@ -package pw.binom.agentik.standalone.persistence +package pw.binom.agentik.storage import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable diff --git a/standalone/src/commonMain/kotlin/pw/binom/agentik/standalone/persistence/WorkingMemoryStore.kt b/storage-core/src/commonMain/kotlin/pw/binom/agentik/storage/WorkingMemoryStore.kt similarity index 98% rename from standalone/src/commonMain/kotlin/pw/binom/agentik/standalone/persistence/WorkingMemoryStore.kt rename to storage-core/src/commonMain/kotlin/pw/binom/agentik/storage/WorkingMemoryStore.kt index 81b03c2..a055df1 100644 --- a/standalone/src/commonMain/kotlin/pw/binom/agentik/standalone/persistence/WorkingMemoryStore.kt +++ b/storage-core/src/commonMain/kotlin/pw/binom/agentik/storage/WorkingMemoryStore.kt @@ -1,4 +1,4 @@ -package pw.binom.agentik.standalone.persistence +package pw.binom.agentik.storage import kotlin.time.Instant diff --git a/standalone/src/jvmTest/kotlin/pw/binom/agentik/standalone/persistence/PayloadTest.kt b/storage-core/src/commonTest/kotlin/pw/binom/agentik/storage/PayloadTest.kt similarity index 86% rename from standalone/src/jvmTest/kotlin/pw/binom/agentik/standalone/persistence/PayloadTest.kt rename to storage-core/src/commonTest/kotlin/pw/binom/agentik/storage/PayloadTest.kt index fda612e..f0dc8b0 100644 --- a/standalone/src/jvmTest/kotlin/pw/binom/agentik/standalone/persistence/PayloadTest.kt +++ b/storage-core/src/commonTest/kotlin/pw/binom/agentik/storage/PayloadTest.kt @@ -1,10 +1,7 @@ -package pw.binom.agentik.standalone.persistence +package pw.binom.agentik.storage import kotlinx.serialization.json.JsonPrimitive import kotlinx.serialization.json.buildJsonObject -import pw.binom.agentik.standalone.persistence.MessageOrigin.EVENT -import pw.binom.agentik.standalone.persistence.MessageOrigin.SYSTEM -import pw.binom.agentik.standalone.persistence.MessageOrigin.USER import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertNull @@ -19,7 +16,7 @@ class PayloadTest { @Test fun `user context roundtrips through MessageBodyPayload`() { val ctx = MessageContext( - origin = USER, + origin = MessageOrigin.USER, description = "irc PRIVMSG", sourceId = "irc:agentik", ) @@ -36,7 +33,7 @@ class PayloadTest { @Test fun `event context roundtrips with metadata`() { val ctx = MessageContext( - origin = EVENT, + origin = MessageOrigin.EVENT, description = "scheduled cron morning-briefing", sourceId = "cron-42", metadata = buildJsonObject { @@ -46,7 +43,7 @@ class PayloadTest { ) val encoded = encodeBodyPayload(listOf(Content.Text("wake up")), ctx) val decoded = decodeBodyPayload(encoded) - assertEquals(EVENT, decoded.context?.origin) + assertEquals(MessageOrigin.EVENT, decoded.context?.origin) assertEquals("scheduled cron morning-briefing", decoded.context?.description) assertEquals("cron-42", decoded.context?.sourceId) assertEquals("0 8 * * *", (decoded.context?.metadata?.let { (it as kotlinx.serialization.json.JsonObject)["rule"] } as? JsonPrimitive)?.content) @@ -73,10 +70,10 @@ class PayloadTest { @Test fun `system context with description only roundtrips`() { - val ctx = MessageContext(origin = SYSTEM, description = "agent startup greeting") + val ctx = MessageContext(origin = MessageOrigin.SYSTEM, description = "agent startup greeting") val encoded = encodeBodyPayload(listOf(Content.Text("boot")), ctx) val decoded = decodeBodyPayload(encoded) - assertEquals(SYSTEM, decoded.context?.origin) + assertEquals(MessageOrigin.SYSTEM, decoded.context?.origin) assertEquals("agent startup greeting", decoded.context?.description) assertNull(decoded.context?.sourceId) assertNull(decoded.context?.metadata)