Изменения вводят live-канал OnlineOutbox и фоновую синхронизацию диалогов.
This commit is contained in:
@@ -25,6 +25,7 @@ import pw.binom.agentik.proto.Content
|
||||
import pw.binom.agentik.outbox.AgentEvent
|
||||
import pw.binom.agentik.outbox.CommonEvent
|
||||
import pw.binom.agentik.outbox.Event
|
||||
import pw.binom.agentik.outbox.OnlineEvent
|
||||
import kotlin.time.Instant
|
||||
|
||||
internal fun Route.agentikRoutes(agent: Agent) {
|
||||
@@ -167,6 +168,27 @@ internal fun Route.agentikRoutes(agent: Agent) {
|
||||
call.streamJsonSse(agent.outbox.conversationEvents(after, id).map { it.event }, Event.serializer())
|
||||
}
|
||||
|
||||
/**
|
||||
* `GET /conversations/{id}/online` — **live-only** SSE поток
|
||||
* онлайн-событий ([pw.binom.agentik.outbox.OnlineEvent]): дельты
|
||||
* текста/картинок стриминга ответа.
|
||||
*
|
||||
* Отличия от `/conversations/{id}/events`:
|
||||
* - нет `after` — catchup невозможен, события не хранятся;
|
||||
* - подписка получает только то, что эмитится после подключения;
|
||||
* - потерянные дельты восстанавливаются из durable-истории
|
||||
* (`/events` + журнал), а не реплеятся здесь.
|
||||
*/
|
||||
get("/conversations/{id}/online") {
|
||||
val id = call.parameters["id"]!!
|
||||
val c = agent.getConversation(id)
|
||||
if (c == null) {
|
||||
call.respond(HttpStatusCode.NotFound)
|
||||
return@get
|
||||
}
|
||||
call.streamJsonSse(agent.onlineOutbox.onlineEvents(id), OnlineEvent.serializer())
|
||||
}
|
||||
|
||||
get("/events") {
|
||||
val after = call.parseAfter() ?: return@get
|
||||
// agent.outbox.agentEvents(after) возвращает Flow<CommonEvent.Agent>;
|
||||
|
||||
@@ -21,6 +21,7 @@ import pw.binom.agentik.journal.ConversationStore
|
||||
import pw.binom.agentik.journal.JournalStore
|
||||
import pw.binom.agentik.journal.MessageRecord
|
||||
import pw.binom.agentik.outbox.CommonEvent
|
||||
import pw.binom.agentik.outbox.OnlineOutbox
|
||||
import pw.binom.agentik.outbox.OutboxStore
|
||||
import pw.binom.agentik.proto.Agent
|
||||
import pw.binom.agentik.proto.AgentInfo
|
||||
@@ -74,6 +75,7 @@ class AgentInfoRouteTest {
|
||||
override suspend fun earliestEventDate(): Instant = Instant.DISTANT_PAST
|
||||
override fun close() {}
|
||||
}
|
||||
override val onlineOutbox: OnlineOutbox = emptyOnlineOutbox()
|
||||
override val conversationStore: ConversationStore = object : ConversationStore {
|
||||
override suspend fun get(id: String) = null
|
||||
override suspend fun list(offset: Int, limit: Int) = emptyList<pw.binom.agentik.journal.ConversationRecord>()
|
||||
@@ -143,6 +145,7 @@ class AgentInfoRouteTest {
|
||||
override suspend fun earliestEventDate(): Instant = Instant.DISTANT_PAST
|
||||
override fun close() {}
|
||||
}
|
||||
override val onlineOutbox: OnlineOutbox = emptyOnlineOutbox()
|
||||
override val conversationStore: ConversationStore = object : ConversationStore {
|
||||
override suspend fun get(id: String) = null
|
||||
override suspend fun list(offset: Int, limit: Int) = emptyList<pw.binom.agentik.journal.ConversationRecord>()
|
||||
|
||||
@@ -17,6 +17,7 @@ import pw.binom.agentik.journal.ConversationStore
|
||||
import pw.binom.agentik.journal.JournalStore
|
||||
import pw.binom.agentik.journal.MessageRecord
|
||||
import pw.binom.agentik.outbox.CommonEvent
|
||||
import pw.binom.agentik.outbox.OnlineOutbox
|
||||
import pw.binom.agentik.outbox.OutboxStore
|
||||
import pw.binom.agentik.proto.Agent
|
||||
import pw.binom.agentik.proto.AgentInfo
|
||||
@@ -58,6 +59,7 @@ class BearerTokenTest {
|
||||
override suspend fun earliestEventDate(): Instant = Instant.DISTANT_PAST
|
||||
override fun close() {}
|
||||
}
|
||||
override val onlineOutbox: OnlineOutbox = emptyOnlineOutbox()
|
||||
override val conversationStore: ConversationStore = object : ConversationStore {
|
||||
override suspend fun get(id: String) = null
|
||||
override suspend fun list(offset: Int, limit: Int) = emptyList<pw.binom.agentik.journal.ConversationRecord>()
|
||||
|
||||
@@ -20,6 +20,7 @@ import pw.binom.agentik.journal.JournalStore
|
||||
import pw.binom.agentik.journal.inmemory.InMemoryJournalStore
|
||||
import pw.binom.agentik.journal.inmemory.InMemoryMutableConversationStore
|
||||
import pw.binom.agentik.outbox.CommonEvent
|
||||
import pw.binom.agentik.outbox.OnlineOutbox
|
||||
import pw.binom.agentik.outbox.OutboxStore
|
||||
import pw.binom.agentik.proto.Agent
|
||||
import pw.binom.agentik.proto.AgentInfo
|
||||
@@ -168,6 +169,7 @@ class ConversationRoutesTest {
|
||||
override suspend fun earliestEventDate(): Instant = Instant.DISTANT_PAST
|
||||
override fun close() {}
|
||||
}
|
||||
override val onlineOutbox: OnlineOutbox = emptyOnlineOutbox()
|
||||
override val conversationStore: ConversationStore = cs
|
||||
|
||||
override fun createConversation(temp: Boolean): pw.binom.agentik.proto.Conversation =
|
||||
|
||||
@@ -24,6 +24,7 @@ import pw.binom.agentik.journal.JournalStore
|
||||
import pw.binom.agentik.journal.MessageRecord
|
||||
import pw.binom.agentik.journal.inmemory.InMemoryJournalStore
|
||||
import pw.binom.agentik.outbox.CommonEvent
|
||||
import pw.binom.agentik.outbox.OnlineOutbox
|
||||
import pw.binom.agentik.outbox.OutboxStore
|
||||
import pw.binom.agentik.proto.Agent
|
||||
import pw.binom.agentik.proto.AgentInfo
|
||||
@@ -187,6 +188,7 @@ class JournalRoutesCountTest {
|
||||
override suspend fun earliestEventDate(): Instant = Instant.DISTANT_PAST
|
||||
override fun close() {}
|
||||
}
|
||||
override val onlineOutbox: OnlineOutbox = emptyOnlineOutbox()
|
||||
override val conversationStore: ConversationStore = object : ConversationStore {
|
||||
override suspend fun get(id: String) = null
|
||||
override suspend fun list(offset: Int, limit: Int) = emptyList<pw.binom.agentik.journal.ConversationRecord>()
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
package pw.binom.agentik.server
|
||||
|
||||
import kotlinx.coroutines.flow.emptyFlow
|
||||
import pw.binom.agentik.outbox.OnlineEvent
|
||||
import pw.binom.agentik.outbox.OnlineOutbox
|
||||
|
||||
/**
|
||||
* Пустой [OnlineOutbox] для тестовых [pw.binom.agentik.proto.Agent]-заглушек:
|
||||
* онлайн-канал тестам не нужен, но интерфейс обязывает его отдать.
|
||||
*/
|
||||
internal fun emptyOnlineOutbox(): OnlineOutbox = object : OnlineOutbox {
|
||||
override fun onlineEvents(conversationId: String) = emptyFlow<OnlineEvent>()
|
||||
override fun close() {}
|
||||
}
|
||||
Reference in New Issue
Block a user