feat(client): refactor AgentikAgent to manage its own HttpClient
ci / JVM build + tests (push) Failing after 54s
release / Publish KMP libraries → caffeine Nexus (release) Failing after 5s

- `AgentikAgent` now accepts `engineFactory` and an optional `token` to create an internal `HttpClient`, handling all configuration (JSON, Bearer).
- Removed `applyAgentikDefaults` and replaced it with `agentikHttpClient` for `HttpClient` creation with consistent settings.
- Updated `Agent` to implement `AutoCloseable`, ensuring proper resource closure with `agent.close()`.
- Adjusted tests, docs, and examples to align with the new `AgentikAgent` API.
This commit is contained in:
2026-09-21 03:53:35 +03:00
parent fb963bfb6b
commit f946186ef5
7 changed files with 95 additions and 102 deletions
+36 -33
View File
@@ -6,64 +6,63 @@
## Что есть
- `AgentikAgent(id, baseUrl, httpClient)` — entry-point. Возвращает `Agent`
(тот же интерфейс, что в `:proto`).
- `AgentikAgent(id, baseUrl, engineFactory, token?)` — entry-point. Возвращает
`Agent` (тот же интерфейс, что в `:proto`). HttpClient создаётся внутри
из переданной `engineFactory` (`CIO`, `OkHttp`, `Darwin`).
- `Agent`: `createConversation` / `getConversation` / `getConversations` /
`deleteConversation` / `journal` / `outbox`.
`deleteConversation` / `journal` / `outbox` / `close`.
- `Conversation`: `send(content, context?)` / `events(after)` (SSE `Flow<Event>`)
/ `getMessages(after, offset, limit)` / `rename` / `interrupt` / `close`.
- `HttpJournalStore` — `list(convId, after, offset, limit)` → `List<MessageRecord>`
со всеми типами записей (User/Assistant/ToolCall/ToolResult/Error + tokens).
- `HttpEventStore` — `events` / `agentEvents` / `conversationEvents` (SSE).
`HttpClient` создаётся снаружи (выбор движка — на тебе: CIO, OkHttp,
Darwin). Конфигурация (JSON + Bearer-токен) — через `applyAgentikDefaults`.
`Agent` — `AutoCloseable`; `agent.close()` закрывает HttpClient. Не нужно
вручную создавать `HttpClient` и накатывать на него JSON/Bearer-плагины.
## Подключение
```kotlin
// build.gradle.kts
kotlin {
sourceSets.commonMain.dependencies {
api("pw.binom.agentik:client:0.1.0")
// Опционально — только если будешь использовать `InMemoryJournalStore`
// как клиентский кэш. Свой `MutableJournalStore` — не нужен.
api("pw.binom.agentik:journal-inmemory:0.1.0")
}
dependencies {
api("pw.binom.agentik:client:0.1.0")
// Движок — на твой выбор (один из):
implementation("io.ktor:ktor-client-cio:3.x") // JVM/Native
implementation("io.ktor:ktor-client-okhttp:3.x") // JVM
implementation("io.ktor:ktor-client-darwin:3.x") // iOS/macOS
// Опционально — только если будешь использовать `InMemoryJournalStore`
// как клиентский кэш. Свой `MutableJournalStore` — не нужен.
api("pw.binom.agentik:journal-inmemory:0.1.0")
}
```
## Быстрый старт: свой клиент за 5 минут
Один self-contained пример: создаём HTTP-клиент, открываем диалог,
Один self-contained пример: создаём агента, открываем диалог,
отправляем сообщение, печатаем streaming-ответ.
```kotlin
import pw.binom.agentik.client.AgentikAgent
import pw.binom.agentik.client.applyAgentikDefaults
import pw.binom.agentik.proto.Content
import pw.binom.agentik.proto.Event
import io.ktor.client.HttpClient
import io.ktor.client.engine.cio.CIO
import kotlinx.coroutines.runBlocking
import kotlin.time.Clock
fun main() = runBlocking {
// 1. HTTP-клиент. Движок выбираешь сам (CIO/OkHttp/Darwin).
val http = HttpClient(CIO) { applyAgentikDefaults(token = "s3cret") }
// 2. Agent — обёртка над :server фасадом.
// 1. Agent — обёртка над :server фасадом. HttpClient создаётся внутри.
val agent = AgentikAgent(
id = "my-client",
baseUrl = "http://localhost:8080/agentik",
httpClient = http,
engineFactory = CIO,
token = "s3cret", // или null, если не нужен
)
// 3. Открыть диалог, отправить сообщение.
// 2. Открыть диалог, отправить сообщение.
val conv = agent.createConversation(temp = false)
conv.send(listOf(Content.Text("Привет")))
// 4. Собирать streaming-ответ.
// 3. Собирать streaming-ответ.
conv.events(after = Clock.System.now()).collect { ev ->
when (ev) {
is Event.StartResponse -> println("[start]")
@@ -74,15 +73,18 @@ fun main() = runBlocking {
}
}
// 5. Чистый shutdown.
// 4. Чистый shutdown.
conv.close()
http.close()
agent.close()
}
```
**Это весь клиент.** `:server` сам хранит историю, контекст, события.
Ты только получаешь типизированный `Flow<Event>` и рендеришь как хочешь.
`HttpClient`, `applyAgentikDefaults`, выбор engine'а — всё скрыто
внутри `AgentikAgent`. Один вызов — один готовый `Agent`.
### Добавить локальный кэш истории (ещё 4 строки)
```kotlin
@@ -117,14 +119,17 @@ history.forEach { rec ->
### Что вообще не нужно писать самому
- HTTP-сериализация `Event`/`Message` — `applyAgentikDefaults` регистрирует
- HTTP-сериализация `Event`/`Message` — `agentikHttpClient` регистрирует
`agentikJson` и `InstantSerializer`.
- SSE-парсер — `readSse()` внутри `:client`.
- Cursor-менеджмент для `listFlow` — дефолтная имплементация в
`JournalStore.listFlow` сама пагинирует.
- Lifecycle подписок на `events()` — `Conversation.close()` отменяет SSE-job.
- Bearer-токен в каждом запросе — `applyAgentikDefaults(token = ...)` инжектит
один раз на весь `HttpClient`.
- HTTP-клиент и Bearer — `AgentikAgent` создаёт `HttpClient(engineFactory)`
с Bearer'ом из `token=` под капотом; `agent.close()` его закрывает.
- Движковые настройки (requestTimeout и пр.) — `HttpClient(engineFactory) { ... }`
создаётся здесь; для нестандартных движковых настроек используй
`agentikHttpClient(engineFactory, token)` напрямую (он экспортирован).
### Что нужно написать самому
@@ -138,17 +143,14 @@ history.forEach { rec ->
```kotlin
import pw.binom.agentik.client.AgentikAgent
import pw.binom.agentik.client.applyAgentikDefaults
import pw.binom.agentik.proto.Content
import pw.binom.agentik.proto.Event
import io.ktor.client.HttpClient
import io.ktor.client.engine.cio.CIO
val http = HttpClient(CIO) { applyAgentikDefaults(token = "s3cret") }
val agent = AgentikAgent(
id = "agentik",
baseUrl = "http://localhost:8080/agentik",
httpClient = http,
engineFactory = CIO,
)
val conv = agent.createConversation(temp = false)
@@ -309,8 +311,9 @@ UI-обновление списка — отдельная задача, реш
- **Персистентность кэша** — `InMemoryJournalStore` хранит в RAM. Для
диска пиши свой `MutableJournalStore` (см. `KsqliteJournalStore` в
`:journal-ksqlite` как образец).
- **Авторизация** — `applyAgentikDefaults(token = "...")` для Bearer;
для OAuth/что-то ещё — конфигурируй `HttpClient` сам.
- **Нестандартные движковые настройки** — для `requestTimeout`,
прокси и т.п. используй `agentikHttpClient(engineFactory, token)`
напрямую.
## Тесты