Add GET /conversations/{id}/count endpoint for total/filtered message counts, update JournalStore API, and implement client/server support with tests.
release / Publish KMP libraries → caffeine Nexus (release) Successful in 1m4s
release / Publish KMP libraries → caffeine Nexus (release) Successful in 1m4s
This commit is contained in:
@@ -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<List<MessageRecord>>()
|
||||
}
|
||||
|
||||
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<CountResponse>().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<CountResponse>().count
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
// HttpClient закрывает владелец (AgentClient / AgentikAgent).
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
private data class CountResponse(val count: Long)
|
||||
|
||||
@@ -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"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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": <Long>}`.
|
||||
*
|
||||
* **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)
|
||||
|
||||
@@ -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<MessageRecord>()
|
||||
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<MessageRecord>()
|
||||
override fun close() {}
|
||||
}
|
||||
|
||||
@@ -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 | Создать диалог |
|
||||
|
||||
@@ -84,7 +84,7 @@ fun main(args: Array<String>) {
|
||||
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())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user