Files
agentik/NATIVE-COMPATIBILITY.md
T

148 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Совместимость с native (linuxX64) — чеклист
Цель: `:standalone` собирается и запускается как `linuxX64` executable (и потенциально
остальные нативные таргеты KMP). Текущее состояние — JVM-only executable на
`runJvm`-таске.
Формат: `- N. [ ]` — задача; `- N. [x]` — выполнена; `- N. [-]` — отменена/не нужна.
Решения (что выбрали и почему) — курсивом внизу пункта.
---
## Блок 1. Подготовка (уже сделана или тривиальная)
- 1. [x] **Замена `java.util.UUID` на KMP-stdlib (`kotlin.uuid.Uuid`).** Helper `Ids.new(prefix)` в `standalone/src/commonMain/.../persistence/Ids.kt`, три call-site (`ChatAgent`, `ChatConversation`, `SqliteWorkingMemoryStore`). Коммит `e816d8d`.
- 2. [x] **HTTP-движок: `ktor-server-netty` → `ktor-server-cio`.** Netty — JVM-only; CIO — KMP (jvm + linuxX64 + ios/...). Каталог-эntry Netty оставлен в `libs.versions.toml` на случай отката. Коммит `e816d8d`.
## Блок 2. Библиотеки litert-kmp (наши)
- 3. [ ] **`litert-api`: добавить `linuxX64()` в `kotlin{}`.**
Сейчас только `androidTarget() + jvm()`. commonMain уже KMP-ready (только
`kotlinx-coroutines-core`). Объём: одна строка в `litert-api/build.gradle.kts`,
один `litert-api-linuxx64`-артефакт публикуется в Nexus.
- 4. [ ] **`litert-openai`: добавить `linuxX64()` в `kotlin{}`.**
Та же история. commonMain deps (`ktor-client-core`, `ktor-client-cio`,
`kotlinx-serialization-json`) — все KMP с готовыми linuxX64-артефактами.
Объём: одна строка + проверить, что Android-only фичи (если есть) изолированы
в `androidMain` (сейчас модуль не имеет `androidMain`-специфичного кода).
*Каталог-запись `litert-openai-jvm` → переименовать в `litert-openai`*
(чтобы Linux-таргет подтягивал `-linuxx64`-вариант автоматически, не нужно
ручного `when target`).
- 5. [-] **`litert-google`: linuxX64 — невозможно.** Upstream-блокер: Google
публикует `litertlm-android` (AAR с `.so`) и `litertlm-jvm` (fat-jar с `.so`
для linux/mac/win JVM), но НЕ нативного `litertlm-linuxx64`. Без правки
upstream LiteRT-LM KMP-враппер для linuxX64 не напишется. Варианты:
(a) выкинуть `litert-google` на linuxX64 (`backend=google` → отказ); (b)
альтернативный движок (llama.cpp через JNI/Kotlin/Native);
(c) upstream PR в `litertlm`. *Решение: (a) для v1 linuxX64 — упасть с
понятной ошибкой `LlmBackend.GOOGLE is not supported on this platform`
вместо reflection-fallback на JVM .so, который на linuxX64 не сработает бы.*
## Блок 3. Наш `:server` модуль
- 6. [ ] **Переписать `:server` с `kotlin("jvm")` на `kotlin("multiplatform")` с
`jvm() + linuxX64()`.** Текущий контракт `Route.agentikAgent(agent, path)` —
чистый Ktor Route, не зависит от engine. commonMain: Ktor core + SSE +
ContentNegotiation + kotlinx-serialization-json. jvmMain: текущее
содержимое. linuxX64Main: тот же commonMain + ktor-server-cio engine для
embedded-сервера, если `Main.kt` на linuxX64 будет поднимать сервер сам
(иначе — оставить сервер только в jvmMain/:standalone, а `:server`-маршруты
вызывать из native-обёртки). Объём: ~30 строк gradle + перенос imports.
*Решение: на linuxX64 `:server`-роуты вызываются тем же `Main.kt`-ом через
`embeddedServer(CIO, ...)` — engine-нейтральность уже заложена.*
## Блок 4. Наш `:proto` модуль
- 7. [ ] **`:proto`: заменить `kotlinx.datetime.Instant` на `kotlin.time.Instant`.**
Per Memory #3708 — typealias `kotlinx.datetime.Instant` deprecated в
kotlinx-datetime 0.8.0, нужно перейти на `kotlin.time.Instant`
(он же в commonMain). После: дропнуть `api(libs.kotlinx.datetime)` из
commonMain, и из `libs.versions.toml`. Объём: замена импорта + типов +
возможно `InstantSerializer` в `:server/src/main/.../Serialization.kt`.
## Блок 5. SQLDelight — нативный драйвер
- 8. [ ] **`sqlite-driver` (JVM/JDBC) → `native-driver` для linuxX64.**
В jvmMain: оставить `sqlite-driver` (JDBC over `java.sql.DriverManager`).
В linuxX64Main: добавить `native-driver` — KMP driver поверх cinterop
с `libsqlite3` (нужен `-lsqlite3` linker-флаг или `SQLiteBundle` из
`co.touchlab:sqliter` для бандлинга). Создать `SqliteStores`-
нейтральный интерфейс хранилищ (он уже есть в `commonMain`), разделить
`jvmMain/SqliteStores.kt` и `linuxX64Main/SqliteStoresNative.kt` —
оба реализуют один `commonMain`-интерфейс. Объём: ~150 строк
(новый `SqliteStoresNative.kt` + gradle wiring).
*Решение по бандлу: использовать `SQLiteBundle` из
`co.touchlab:sqliter` (KMP-обёртка, тянет свой libsqlite) — без
зависимости от системного libsqlite3, бинарь работает на любом linux.*
- 9. [ ] **Тесты SQLite переписать на commonTest + `expect`/`actual` обёртку.**
`inMemory()`-открытие в `native-driver` использует другой API (нет
JDBC `:memory:`). Объём: один `expect fun openInMemory()` в commonMain +
два `actual` (JDBC `jdbc:sqlite::memory:` / native-driver нативный API).
## Блок 6. Внешние либы (`a2a-server`, `agui-server`) — наши
- 10. [ ] **`agui-server`: проверить KMP-готовность.** Per Memory #3561 — KMP
jvm+native. *Если по факту уже KMP — только подключить `linuxX64`-артефакт в `:standalone`; если JVM-only — пункт 11.*
- 11. [ ] **`a2a-server`: JVM-only → KMP.** Per Memory #3561 — JVM-only
(client/server; shared уже KMP). Без native-таргета весь стек A2A
недоступен на linuxX64. Объём: переписать на `multiplatform`,
выделить engine-нейтральную маршрутизацию. *Альтернатива: на linuxX64
не подключать a2a-server вообще (только `:server`-фасад). Решение
отложено — зависит от того, нужен ли нативный MCP-юзер-кейс вообще
подключаться к A2A.*
## Блок 7. `:standalone` — добавить native target
- 12. [ ] **`standalone/build.gradle.kts`: добавить `linuxX64()` и
`linuxX64Main`/`linuxX64Test` source-sets.** В `linuxX64Main`:
`:server` (после п.6), `litert-openai` (после п.4), `litert-api`
(после п.3), `ktor-server-cio` (engine), `ktor-client-cio`,
`kotlin-sdk-client` (MCP — KMP, готов), `kotlinx-io` (KMP, готов).
Исключить `litert-google` (п.5) и `a2a-server` (п.11) из linuxX64Main.
- 13. [ ] **`Main.kt`: разделить на `commonMain` (бизнес-логика: создание
ChatAgent, MCP, LlmConfig) + `jvmMain`/`linuxX64Main` (выбор engine,
embeddedServer).** embeddedServer(CIO, …) — один и тот же код на обеих
платформах, KMP-нейтрален. Главное отличие — `SqliteStores.open()` и
обработка GOOGLE-бэкенда на linuxX64 (fail-fast).
## Блок 8. Тесты и CI
- 14. [ ] **`linuxX64Test` source-set.** Покрыть те же 44 теста на
native: должен зелёный прогон через `./gradlew :standalone:linuxX64Test`.
SQLDelight-тесты на native требуют `libsqlite3`/`SQLiteBundle`
на машине (CI-linux предоставляет).
- 15. [ ] **Smoke e2e на linuxX64.** Запустить
`./gradlew :standalone:runLinuxX64` локально, проверить
`POST /agentik/conversations` → 201, цикл с реальным LLM через OpenAI
backend; убедиться что LiteRT-LM GOOGLE не подключается (отказ с
понятной ошибкой).
## Блок 9. Дистрибуция native-бинаря
- 16. [ ] **`runLinuxX64`-таска через KMP binaries DSL.** После добавления
`linuxX64()` KMP генерирует её автоматически — нужно только убедиться
что `binaries { executable { mainClass.set("pw.binom.agentik.standalone.MainKt") } }`
работает для linuxX64 (по аналогии с уже настроенным `runJvm`).
- 17. [ ] **Дистрибутив `.tar`/`.zip`/`AppImage/Docker`.** KMP
`assembleDist` для linuxX64 делает tar.gz/zip. Для прод-дистрибуции —
Docker-образ (`FROM gcr.io/distroless/cc`) или AppImage. Объём:
~50 строк Dockerfile + GitHub Action.
---
## Что НЕ блокирует linuxX64 (закрыто или тривиально)
- `kotlinx-coroutines-core` — KMP linuxX64 ✓
- `kotlinx-serialization-json` — KMP linuxX64 ✓
- `kotlinx-io` — KMP linuxX64 (используется в MCP stdio-transport) ✓
- `kotlin-sdk-client` (MCP) — KMP full targets ✓
- `ktor-server-core`, `ktor-server-sse`, `ktor-server-cio` — KMP linuxX64 ✓
- `ktor-client-core`, `ktor-client-cio` — KMP linuxX64 ✓
- `pw.binom.uuid` — **отсутствует** в проекте (используем `kotlin.uuid`) ✓
## Жёсткие блокеры (не обойти без upstream-работы)
- `litertlm-jvm` → нужен `litertlm-native` от Google (см. п.5)
- `a2a-server` JVM-only (см. п.11) — нужно переписать или явно не подключать
- `agui-server` KMP-готовность под вопросом (см. п.10)