diff --git a/client/src/commonMain/kotlin/pw/binom/agentik/client/HttpJournalStore.kt b/client/src/commonMain/kotlin/pw/binom/agentik/client/HttpJournalStore.kt index ea5fb06..9190a6c 100644 --- a/client/src/commonMain/kotlin/pw/binom/agentik/client/HttpJournalStore.kt +++ b/client/src/commonMain/kotlin/pw/binom/agentik/client/HttpJournalStore.kt @@ -5,6 +5,7 @@ import io.ktor.client.call.body import io.ktor.client.request.get import io.ktor.client.request.parameter import io.ktor.http.HttpStatusCode +import kotlinx.serialization.Serializable import pw.binom.agentik.journal.JournalStore import pw.binom.agentik.journal.MessageRecord import kotlin.time.Instant @@ -13,8 +14,11 @@ import kotlin.time.Instant * HTTP-реализация [JournalStore] (append-only audit log сообщений диалога), * ходящая в `:server`-фасад. * - * **Endpoint**: `GET {baseUrl}/journal/conversations/{id}/messages?after=&offset=&limit=` - * (см. [pw.binom.agentik.server.journalRoutes]). + * **Endpoints** (см. [pw.binom.agentik.server.journalRoutes]): + * - `GET {baseUrl}/journal/conversations/{id}/messages?after=&offset=&limit=` + * → [list] + * - `GET {baseUrl}/journal/conversations/{id}/count` → [count] (total) + * - `GET {baseUrl}/journal/conversations/{id}/count?after=` → [count] (after cursor) * * Возвращает raw [MessageRecord] (все типы: UserMessage / AssistantMessage / * ToolCall / ToolResult / Error). В отличие от `GET /conversations/{id}/messages` @@ -54,7 +58,28 @@ internal class HttpJournalStore( return response.body>() } + override suspend fun count(conversationId: String): Long { + val response = httpClient.get("$agentUrl/journal/conversations/$conversationId/count") + check(response.status == HttpStatusCode.OK) { + "journal.count: server returned ${response.status}" + } + return response.body().count + } + + override suspend fun count(conversationId: String, after: Instant): Long { + val response = httpClient.get("$agentUrl/journal/conversations/$conversationId/count") { + parameter("after", after.toString()) + } + check(response.status == HttpStatusCode.OK) { + "journal.count(after): server returned ${response.status}" + } + return response.body().count + } + override fun close() { // HttpClient закрывает владелец (AgentClient / AgentikAgent). } } + +@Serializable +private data class CountResponse(val count: Long) diff --git a/server/build.gradle.kts b/server/build.gradle.kts index f4a9a00..2ea29e2 100644 --- a/server/build.gradle.kts +++ b/server/build.gradle.kts @@ -49,6 +49,9 @@ kotlin { implementation(kotlin("test")) implementation(libs.ktor.server.test.host) implementation(libs.ktor.server.cio) + implementation(libs.ktor.client.content.negotiation) + implementation(libs.ktor.serialization.kotlinx.json) + implementation(project(":journal-inmemory")) } } } diff --git a/server/src/commonMain/kotlin/pw/binom/agentik/server/JournalRoutes.kt b/server/src/commonMain/kotlin/pw/binom/agentik/server/JournalRoutes.kt index 8095830..3d40793 100644 --- a/server/src/commonMain/kotlin/pw/binom/agentik/server/JournalRoutes.kt +++ b/server/src/commonMain/kotlin/pw/binom/agentik/server/JournalRoutes.kt @@ -23,6 +23,10 @@ import pw.binom.agentik.journal.JournalStore * project'нутые proto-[pw.binom.agentik.proto.Message]), здесь клиент * получает полный transcript с tool-call/tool-result/error payload-ами, * turn-tokens и context-метаданными. + * - `GET /conversations/{id}/count?after=` — сколько сообщений в диалоге + * всего (без `after`) или строго позже `after` (с `after`). Лёгкий + * endpoint для UI-бейджей "N новых сообщений" и compaction-метрик; + * тело ответа — JSON `{"count": }`. * * **Read-only:** [JournalStore] не имеет `append` — запись только через * writer-референс, который ChatAgent держит внутри (тип `MutableJournalStore`, @@ -40,5 +44,19 @@ fun Route.journalRoutes( val limit = call.request.queryParameters["limit"]?.toIntOrNull() ?: JournalStore.PAGE_SIZE call.respond(journal.list(id, after, offset, limit)) } + get("/conversations/{id}/count") { + val id = call.parameters["id"]!! + val after = call.parseAfter() + val count = if (after == null) { + journal.count(id) + } else { + journal.count(id, after) + } + call.respond(CountResponse(count = count)) + } } } + +/** Тело ответа `GET /conversations/{id}/count`. */ +@kotlinx.serialization.Serializable +private data class CountResponse(val count: Long) diff --git a/server/src/commonTest/kotlin/pw/binom/agentik/server/BearerTokenTest.kt b/server/src/commonTest/kotlin/pw/binom/agentik/server/BearerTokenTest.kt index b058252..e3a059a 100644 --- a/server/src/commonTest/kotlin/pw/binom/agentik/server/BearerTokenTest.kt +++ b/server/src/commonTest/kotlin/pw/binom/agentik/server/BearerTokenTest.kt @@ -44,6 +44,8 @@ class BearerTokenTest { ) : Agent { override val journal: JournalStore = object : JournalStore { override suspend fun list(conversationId: String, after: Instant, offset: Int, limit: Int) = emptyList() + override suspend fun count(conversationId: String): Long = 0L + override suspend fun count(conversationId: String, after: Instant): Long = 0L override fun listFlow(conversationId: String, after: Instant, pageSize: Int) = emptyFlow() override fun close() {} } diff --git a/standalone/README.md b/standalone/README.md index eec2444..58325f6 100644 --- a/standalone/README.md +++ b/standalone/README.md @@ -4,7 +4,6 @@ Главный исполняемый модуль проекта — single-jar HTTP-сервер с: -- **AG-UI** transport на `POST /agui` (SSE) + `GET /health`. - **A2A** transport на `POST /` (JSON-RPC) + `GET /.well-known/agent-card.json`. - **`:proto`** transport на `POST /agentik/*` (HTTP+JSON+SSE) — наш stateful. - **Embedded LLM backend**: `GOOGLE` (LiteRT) или `OPENAI`-совместимый @@ -98,7 +97,6 @@ AGENTIK_GOOGLE_MODEL_PATH=/root/gemma-4-E2B-it.litertlm \ | Метод | Путь | Transport | Описание | |---|---|---|---| | `GET` | `/health` | любой | health-check (`{"ok":true}`) | -| `POST` | `/agui` | AG-UI | Стриминг run (SSE) | | `POST` | `/` | A2A | JSON-RPC `message/send`, `tasks/get`, `tasks/cancel` | | `GET` | `/.well-known/agent-card.json` | A2A | Discovery | | `POST` | `/agentik/conversations` | :proto | Создать диалог | diff --git a/standalone/src/commonMain/kotlin/pw/binom/agentik/standalone/Main.kt b/standalone/src/commonMain/kotlin/pw/binom/agentik/standalone/Main.kt index 8addd5e..7a67693 100644 --- a/standalone/src/commonMain/kotlin/pw/binom/agentik/standalone/Main.kt +++ b/standalone/src/commonMain/kotlin/pw/binom/agentik/standalone/Main.kt @@ -84,7 +84,7 @@ fun main(args: Array) { private fun printHelp() { println(""" agentik standalone — usage: - java -jar agentik.jar Start HTTP server (AGUI + A2A + :proto) + java -jar agentik.jar Start HTTP server (A2A + :proto) java -jar agentik.jar pull-model Download the LiteRT-LM model from static.binom.pw """.trimIndent()) }