refactor(storage): drop StorageBundle + legacy EventStore, add HttpEventStore
ci / JVM build + tests (push) Successful in 6m13s
ci / JVM build + tests (push) Successful in 6m13s
- Remove legacy pw.binom.agentik.messageStore.events.EventStore (EventRecord,
EventType) and all three impls (in-memory, sqlite, ksqlite) + tests + .sq
- Drop :storage-bundle module entirely; ChatAgent / ChatConversation /
ConversationLoop / DebugRoutes now take stores individually
(conversationStore, messageStore, workingMemoryStore, reflectionStore,
eventStore) instead of StorageBundle
- Delete server endpoints /events/replay and /conversations/{id}/events/replay;
Route.agentikAgent no longer takes eventStore param
- Add :client/HttpEventStore implementing :event-store/EventStore over HTTP:
events() -> GET /events/all, agentEvents() -> GET /events,
conversationEvents(convId) -> GET /conversations/{id}/events;
exposed via AgentClient.eventStore
- :event-store: add macosX64/macosArm64/linuxArm64 targets to match :client KMP
- :working-memory-api: drop api dep on :message-store-api (no longer needed)
- :storage-{inmemory,sqlite,ksqlite}: drop deps on :storage-bundle
This commit is contained in:
@@ -6,7 +6,6 @@ import io.ktor.server.plugins.contentnegotiation.ContentNegotiation
|
||||
import io.ktor.server.routing.Route
|
||||
import io.ktor.server.routing.route
|
||||
import pw.binom.agentik.proto.Agent
|
||||
import pw.binom.agentik.messageStore.events.EventStore
|
||||
|
||||
/**
|
||||
* Встраивает HTTP/SSE-фасад протокола agentik в твой Ktor-роутинг.
|
||||
@@ -30,18 +29,19 @@ import pw.binom.agentik.messageStore.events.EventStore
|
||||
* - `POST /conversations/{id}/messages` — `send` (202 Accepted)
|
||||
* - `POST /conversations/{id}/interrupt` — `interrupt`
|
||||
* - `GET /conversations/{id}/messages` — история
|
||||
* - `GET /conversations/{id}/events` — SSE: события хода
|
||||
* - `GET /conversations/{id}/events/replay` — replay-after-disconnect (events after ?after_id=X)
|
||||
* - `GET /events` — SSE: события агента
|
||||
* - `GET /events/replay` — replay-after-disconnect (global)
|
||||
* - `GET /conversations/{id}/events` — SSE: события хода (catchup + live)
|
||||
* - `GET /events` — SSE: события агента (catchup + live)
|
||||
* - `GET /events/all` — SSE: всё в одном потоке
|
||||
* - `GET /health` — `"ok"`
|
||||
*
|
||||
* @param eventStore если null — `/events/replay` endpoints возвращают 503 (event
|
||||
* persistence не настроен). Live-streaming работает как обычно.
|
||||
* Event-эндпоинты сами по себе дают catchup + live в одном Flow: клиент
|
||||
* передаёт `?after=<ISO8601>`, сервер сначала отдаёт буферизованные события
|
||||
* с `date > after`, потом переключается на live. Отдельный replay-endpoint
|
||||
* не нужен — для событий старше буфера клиент должен идти в
|
||||
* `:message-log-api` (полный audit log).
|
||||
*/
|
||||
fun Route.agentikAgent(
|
||||
agent: Agent,
|
||||
eventStore: EventStore? = null,
|
||||
path: String = "/agentik",
|
||||
token: String? = null,
|
||||
) {
|
||||
@@ -54,6 +54,6 @@ fun Route.agentikAgent(
|
||||
this.token = token
|
||||
}
|
||||
}
|
||||
agentikRoutes(agent, eventStore)
|
||||
agentikRoutes(agent)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,10 +24,9 @@ import pw.binom.agentik.proto.CommonEvent
|
||||
import pw.binom.agentik.proto.Conversation
|
||||
import pw.binom.agentik.proto.Content
|
||||
import pw.binom.agentik.proto.Event
|
||||
import pw.binom.agentik.messageStore.events.EventStore
|
||||
import kotlin.time.Instant
|
||||
|
||||
internal fun Route.agentikRoutes(agent: Agent, eventStore: EventStore? = null) {
|
||||
internal fun Route.agentikRoutes(agent: Agent) {
|
||||
|
||||
get("/health") {
|
||||
call.respondText("ok")
|
||||
@@ -145,38 +144,6 @@ internal fun Route.agentikRoutes(agent: Agent, eventStore: EventStore? = null) {
|
||||
val after = call.parseAfter() ?: return@get
|
||||
call.streamJsonSse(agent.allEvents(after), CommonEvent.serializer())
|
||||
}
|
||||
|
||||
// ---- Replay-after-disconnect (event log) ----
|
||||
//
|
||||
// Stream `/events` и `/conversations/{id}/events` — cold (no replay).
|
||||
// Клиент, отвалившийся от SSE, при reconnect делает GET на replay-endpoint
|
||||
// с `?after_id=X` чтобы получить events, которые произошли во время разрыва.
|
||||
// paginated: делает несколько запросов пока `result.size < limit`.
|
||||
//
|
||||
// Если [eventStore] == null (не сконфигурирован), эти endpoints возвращают 503.
|
||||
|
||||
get("/events/replay") {
|
||||
if (eventStore == null) {
|
||||
call.respond(HttpStatusCode.ServiceUnavailable, "EventStore not configured on this agent")
|
||||
return@get
|
||||
}
|
||||
val afterId = call.request.queryParameters["after_id"]?.takeIf { it.isNotBlank() }
|
||||
val limit = (call.request.queryParameters["limit"]?.toIntOrNull() ?: 100).coerceIn(1, 1000)
|
||||
val records = eventStore.query(conversationId = null, afterId = afterId, limit = limit)
|
||||
call.respond(records)
|
||||
}
|
||||
|
||||
get("/conversations/{id}/events/replay") {
|
||||
if (eventStore == null) {
|
||||
call.respond(HttpStatusCode.ServiceUnavailable, "EventStore not configured on this agent")
|
||||
return@get
|
||||
}
|
||||
val id = call.parameters["id"]!!
|
||||
val afterId = call.request.queryParameters["after_id"]?.takeIf { it.isNotBlank() }
|
||||
val limit = (call.request.queryParameters["limit"]?.toIntOrNull() ?: 100).coerceIn(1, 1000)
|
||||
val records = eventStore.query(conversationId = id, afterId = afterId, limit = limit)
|
||||
call.respond(records)
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- helpers ----------
|
||||
|
||||
Reference in New Issue
Block a user