refactor: migrate EventStore and MessageStore to :outbox-api and :journal-api
ci / JVM build + tests (push) Failing after 1m9s
ci / JVM build + tests (push) Failing after 1m9s
- Replaced usages of `:message-store-api` and `:working-memory-api` with `:journal-api`, `:outbox-api`, and `:context-api`. - Deprecated legacy `EventStore` and `MessageStore` interfaces, added `typealias` for backward compatibility. - Updated imports across all modules with references to `:journal-api` and `:outbox-api`. - Introduced `journalRoutes` and `outboxRoutes` in `:server` for audit log and live event stream endpoints. - Adjusted `Agent` to expose read-only `journal` and `outbox` stores for improved modularity and clarity. - Removed legacy Event and AgentEvent definitions from `:proto`, migrated to `:outbox-api`. - Storage-related modules have been updated to support the new APIs consistently.
This commit is contained in:
@@ -4,23 +4,17 @@ import io.ktor.client.HttpClient
|
||||
import io.ktor.client.call.body
|
||||
import io.ktor.client.request.delete
|
||||
import io.ktor.client.request.get
|
||||
import io.ktor.client.request.prepareGet
|
||||
import io.ktor.client.request.parameter
|
||||
import io.ktor.client.request.post
|
||||
import io.ktor.client.request.setBody
|
||||
import io.ktor.client.statement.bodyAsChannel
|
||||
import io.ktor.http.ContentType
|
||||
import io.ktor.http.HttpStatusCode
|
||||
import io.ktor.http.contentType
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import pw.binom.agentik.eventStore.EventStore
|
||||
import pw.binom.agentik.journal.JournalStore
|
||||
import pw.binom.agentik.outbox.OutboxStore
|
||||
import pw.binom.agentik.proto.Agent
|
||||
import pw.binom.agentik.proto.AgentEvent
|
||||
import pw.binom.agentik.proto.CommonEvent
|
||||
import pw.binom.agentik.proto.Conversation
|
||||
import kotlin.time.Instant
|
||||
|
||||
/**
|
||||
* HTTP-реализация [Agent]. Ходит в `:server`-фасад, см. `agentikAgent(...)`.
|
||||
@@ -30,6 +24,12 @@ import kotlin.time.Instant
|
||||
* POST `/conversations`. Используем `runBlocking` — это одноразовая
|
||||
* операция (открытие чата), не горячий путь. В UI-контексте вызывающий сам
|
||||
* решает, что делать.
|
||||
*
|
||||
* **Storage handles** ([journal], [outbox]) — read-only views на серверные
|
||||
* хранилища. [outbox] уже реализован ([HttpEventStore]); [journal] —
|
||||
* заглушка, потому что соответствующий HTTP endpoint'ы (`/journal/...`)
|
||||
* ещё не выставлены на стороне `:server`. После их добавления подменить
|
||||
* `error(...)` на `HttpJournalStore(...)`.
|
||||
*/
|
||||
internal class AgentClient(
|
||||
private val httpClient: HttpClient,
|
||||
@@ -43,7 +43,16 @@ internal class AgentClient(
|
||||
* Единый канал событий (lifecycle + per-conversation). Под капотом —
|
||||
* [HttpEventStore]: каждый метод бьёт свой URL (см. KDoc).
|
||||
*/
|
||||
val eventStore: EventStore = HttpEventStore(httpClient = httpClient, baseUrl = agentUrl)
|
||||
override val outbox: OutboxStore = HttpEventStore(httpClient = httpClient, baseUrl = agentUrl)
|
||||
|
||||
/**
|
||||
* HTTP-фасад для journal пока не реализован: на стороне `:server` ещё
|
||||
* не выставлены endpoint'ы `/journal/conversations/{id}/messages`.
|
||||
* Как только появятся — заменить на `HttpJournalStore(httpClient, agentUrl)`.
|
||||
*/
|
||||
override val journal: JournalStore = error(
|
||||
"HttpJournalStore ещё не реализован — дождаться :server endpoint'а /journal/...",
|
||||
)
|
||||
|
||||
override fun createConversation(temp: Boolean): Conversation =
|
||||
runBlocking {
|
||||
@@ -73,35 +82,4 @@ internal class AgentClient(
|
||||
}.body<List<ConversationSnapshot>>()
|
||||
return snapshots.map { ConversationClient(httpClient, agentUrl, it) }
|
||||
}
|
||||
|
||||
override fun events(after: Instant): Flow<AgentEvent> = flow {
|
||||
httpClient.prepareGet("$agentUrl/events?after=$after") { noSseReadTimeout() }
|
||||
.execute { response ->
|
||||
check(response.status == HttpStatusCode.OK) {
|
||||
"events: server returned ${response.status}"
|
||||
}
|
||||
readSse(response.bodyAsChannel())
|
||||
.collect { payload ->
|
||||
emit(agentikJson.decodeFromString(AgentEvent.serializer(), payload))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Подписка на ВСЕ события: agent lifecycle + все conversation events.
|
||||
* Использует SSE endpoint /events/all.
|
||||
*/
|
||||
override fun allEvents(after: Instant): Flow<CommonEvent> = flow {
|
||||
httpClient.prepareGet("$agentUrl/events/all?after=$after") { noSseReadTimeout() }
|
||||
.execute { response ->
|
||||
check(response.status == HttpStatusCode.OK) {
|
||||
"allEvents: server returned ${response.status}"
|
||||
}
|
||||
readSse(response.bodyAsChannel())
|
||||
.collect { payload ->
|
||||
emit(agentikJson.decodeFromString(CommonEvent.serializer(), payload))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,14 +7,15 @@ import io.ktor.http.HttpStatusCode
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import pw.binom.agentik.eventStore.EventStore
|
||||
import pw.binom.agentik.proto.AgentEvent
|
||||
import pw.binom.agentik.proto.CommonEvent
|
||||
import pw.binom.agentik.proto.Event
|
||||
import pw.binom.agentik.outbox.AgentEvent
|
||||
import pw.binom.agentik.outbox.CommonEvent
|
||||
import pw.binom.agentik.outbox.Event
|
||||
import kotlin.time.Clock
|
||||
import kotlin.time.Instant
|
||||
|
||||
/**
|
||||
* HTTP-реализация [EventStore], ходящая в `:server`-фасад.
|
||||
* HTTP-реализация [EventStore] (= [pw.binom.agentik.outbox.OutboxStore]),
|
||||
* ходящая в `:server`-фасад.
|
||||
*
|
||||
* **Хитрый план**: вместо того, чтобы все методы шли в один общий endpoint и
|
||||
* фильтровали client-side ([EventStore.events]/[filterIsInstance]), эта
|
||||
@@ -36,6 +37,12 @@ import kotlin.time.Instant
|
||||
* (см. KDoc [EventStore.earliestEventDate] — для пустого буфера это и есть
|
||||
* контрактное значение). Клиент, который полагался на gap detection через
|
||||
* message store, продолжит работать — просто fallback никогда не сработает.
|
||||
*
|
||||
* **Импорты [CommonEvent]/[AgentEvent]/[Event] идут напрямую из
|
||||
* `pw.binom.agentik.outbox`** — typealias'ы в `:proto.CommonEvent` и т.п.
|
||||
* НЕ поддерживают nested-class access (`CommonEvent.Agent` через alias
|
||||
* даёт "Unresolved qualified name"), поэтому приходится использовать
|
||||
* конкретный пакет. Типы идентичны, alias только для удобства внешнего API.
|
||||
*/
|
||||
internal class HttpEventStore(
|
||||
private val httpClient: HttpClient,
|
||||
|
||||
Reference in New Issue
Block a user