Изменения вводят live-канал OnlineOutbox и фоновую синхронизацию диалогов.

This commit is contained in:
2026-09-30 11:43:49 +03:00
parent 509763cf36
commit bd7e079780
25 changed files with 491 additions and 102 deletions
@@ -3,6 +3,7 @@ package pw.binom.agentik.cli.commands
import kotlinx.cli.ArgType
import kotlinx.cli.vararg
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.takeWhile
import kotlinx.coroutines.launch
@@ -10,6 +11,7 @@ import pw.binom.agentik.cli.AgentikSubcommand
import pw.binom.agentik.cli.defaultCliHttpClient
import pw.binom.agentik.client.AgentikAgent
import pw.binom.agentik.outbox.Event
import pw.binom.agentik.outbox.OnlineEvent
import pw.binom.agentik.proto.Content
import kotlin.time.Instant
@@ -24,8 +26,8 @@ class SendSubcommand : AgentikSubcommand("send", "Отправить user-ход
return@runBlocking
}
try {
// Подписываемся на поток событий ДО send: события, отправленные
// до подписки, не реплеятся (shared-flow без replay).
// Durable-поток (End/Interrupted/Error + Tool*) — ловит терминатор хода.
// Подписываемся ДО send: события, отправленные до подписки, не реплеятся.
val eventsJob = launch {
agent.outbox.conversationEvents(Instant.DISTANT_PAST, conv.id)
// onEach печатает и терминальный event, takeWhile лишь
@@ -35,10 +37,19 @@ class SendSubcommand : AgentikSubcommand("send", "Отправить user-ход
.takeWhile { ev -> !isTerminal(ev) }
.collect { }
}
// Даём SSE-подписке установиться, затем шлём ход.
// Онлайн-поток (дельты стриминга ответа) — live-only, без терминатора.
val onlineJob = launch {
agent.onlineOutbox.onlineEvents(conv.id)
.onEach { ev -> emitOnline(ev) }
.collect { }
}
// Даём SSE-подпискам установиться, затем шлём ход.
delay(200)
conv.send(listOf(Content.Text(text.joinToString(" "))))
eventsJob.join()
// Даём онлайн-потоку дослать хвостовые дельты, эмитнутые до End.
delay(100)
onlineJob.cancel()
} finally {
conv.close()
}
@@ -49,10 +60,6 @@ class SendSubcommand : AgentikSubcommand("send", "Отправить user-ход
private fun emit(ev: Event) {
when (ev) {
is Event.StartReasoning -> println("event StartReasoning")
is Event.StartResponse -> println("event StartResponse ${ev.responseType}")
is Event.AppendText -> println("event AppendText ${escape(ev.body)}")
is Event.AppendImage -> println("event AppendImage <${ev.body.size}B ${ev.mime}>")
is Event.ToolCall -> println("event ToolCall ${ev.id} ${ev.toolName} ${escape(ev.toolArgs)}")
is Event.ToolResult -> println("event ToolResult ${ev.toolCallId} ${escape(ev.result ?: "")}")
is Event.End -> println("event End")
@@ -61,5 +68,14 @@ class SendSubcommand : AgentikSubcommand("send", "Отправить user-ход
}
}
private fun emitOnline(ev: OnlineEvent) {
when (ev) {
is OnlineEvent.StartReasoning -> println("event StartReasoning")
is OnlineEvent.StartResponse -> println("event StartResponse ${ev.responseType}")
is OnlineEvent.AppendText -> println("event AppendText ${escape(ev.body)}")
is OnlineEvent.AppendImage -> println("event AppendImage <${ev.body.size}B ${ev.mime}>")
}
}
private fun escape(s: String): String = s.replace("\n", "\\n").replace("\r", "\\r")
}