feat(client): refactor AgentikAgent to manage its own HttpClient
- `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:
+36
-33
@@ -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)`
|
||||
напрямую.
|
||||
|
||||
## Тесты
|
||||
|
||||
|
||||
Reference in New Issue
Block a user