diff --git a/NATIVE-COMPATIBILITY.md b/NATIVE-COMPATIBILITY.md new file mode 100644 index 0000000..a3e661b --- /dev/null +++ b/NATIVE-COMPATIBILITY.md @@ -0,0 +1,147 @@ +# Совместимость с 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)