Files
agentik/NATIVE-COMPATIBILITY.md
T
subochev 5b4c8ceae8 server: convert :server from kotlin-jvm to KMP with all 9 targets
Punkt 6 of NATIVE-COMPATIBILITY.md. Mirrors :proto's target set:
jvm + macosX64 + macosArm64 + iosX64 + iosArm64 + iosSimulatorArm64
+ linuxX64 + linuxArm64 + mingwX64.

All 9 targets compile via './gradlew :server:assemble'.

No blockers: the only deps (ktor-server-core, ktor-server-content-
negotiation, ktor-serialization-kotlinx-json, kotlinx-coroutines-core,
kotlinx-serialization-json) ship native variants for every target.
kotlin.time.Instant was already in use (not the deprecated
kotlinx.datetime.Instant), so no proto-side work needed.

Only API change vs the JVM version: respondTextWriter (JVM-only) ->
respondBytesWriter + writeStringUtf8 (KMP, ByteWriteChannel API).
SSE behaviour is identical ('data: <json>\n\n' chunks flushed as
they arrive).

Verification: :standalone:jvmTest 44/44 green after the migration
(ChatAgent 15, LlmConfig 6, McpConfig 8, McpRegistry 4, Persistence 11).
No call-site changes needed; :standalone picks up the new :server
jvm artifact automatically.
2026-09-13 18:28:12 +03:00

150 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. [x] **Переписать `:server` с `kotlin("jvm")` на `kotlin("multiplatform")`.**
Все 9 целей сборки (jvm + macosX64 + macosArm64 + iosX64 + iosArm64 +
iosSimulatorArm64 + linuxX64 + linuxArm64 + mingwX64) собираются
`./gradlew :server:assemble` без ошибок. Блокеров нет: Ktor
`ktor-server-{core,sse,cio,content-negotiation}` и `ktor-io` (для
`ByteWriteChannel.writeStringUtf8`) есть в KMP-вариантах под все
цели; `kotlin.time.Instant` уже использовался. Единственное
изменение по сравнению с JVM-версией: `respondTextWriter` (JVM-only)
→ `respondBytesWriter` + `writeStringUtf8` (KMP, тот же
`ByteWriteChannel` API).
Источники переехали `src/main/kotlin/...` → `src/commonMain/kotlin/...`.
`:standalone:jvmTest` 44/44 зелёные после миграции.
## Блок 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)