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.
This commit is contained in:
2026-09-13 18:28:12 +03:00
parent adab112b5b
commit 5b4c8ceae8
6 changed files with 52 additions and 31 deletions
+12 -10
View File
@@ -40,16 +40,18 @@
## Блок 3. Наш `:server` модуль ## Блок 3. Наш `:server` модуль
- 6. [ ] **Переписать `:server` с `kotlin("jvm")` на `kotlin("multiplatform")` с - 6. [x] **Переписать `:server` с `kotlin("jvm")` на `kotlin("multiplatform")`.**
`jvm() + linuxX64()`.** Текущий контракт `Route.agentikAgent(agent, path)` — Все 9 целей сборки (jvm + macosX64 + macosArm64 + iosX64 + iosArm64 +
чистый Ktor Route, не зависит от engine. commonMain: Ktor core + SSE + iosSimulatorArm64 + linuxX64 + linuxArm64 + mingwX64) собираются
ContentNegotiation + kotlinx-serialization-json. jvmMain: текущее `./gradlew :server:assemble` без ошибок. Блокеров нет: Ktor
содержимое. linuxX64Main: тот же commonMain + ktor-server-cio engine для `ktor-server-{core,sse,cio,content-negotiation}` и `ktor-io` (для
embedded-сервера, если `Main.kt` на linuxX64 будет поднимать сервер сам `ByteWriteChannel.writeStringUtf8`) есть в KMP-вариантах под все
(иначе — оставить сервер только в jvmMain/:standalone, а `:server`-маршруты цели; `kotlin.time.Instant` уже использовался. Единственное
вызывать из native-обёртки). Объём: ~30 строк gradle + перенос imports. изменение по сравнению с JVM-версией: `respondTextWriter` (JVM-only)
*Решение: на linuxX64 `:server`-роуты вызываются тем же `Main.kt`-ом через → `respondBytesWriter` + `writeStringUtf8` (KMP, тот же
`embeddedServer(CIO, ...)` — engine-нейтральность уже заложена.* `ByteWriteChannel` API).
Источники переехали `src/main/kotlin/...` → `src/commonMain/kotlin/...`.
`:standalone:jvmTest` 44/44 зелёные после миграции.
## Блок 4. Наш `:proto` модуль ## Блок 4. Наш `:proto` модуль
+34 -16
View File
@@ -1,23 +1,41 @@
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
plugins { plugins {
alias(libs.plugins.kotlin.jvm) alias(libs.plugins.kotlin.multiplatform)
alias(libs.plugins.kotlin.serialization) alias(libs.plugins.kotlin.serialization)
} }
kotlin { kotlin {
compilerOptions { jvmToolchain(21)
jvmTarget.set(JvmTarget.JVM_21)
// "Все возможные цели сборки": jvm + весь натив. Зеркалит набор :proto.
// Никаких блокеров нет: ktor-server-{core,sse,cio,content-negotiation} имеет
// опубликованные linuxX64/macosX64/macosArm64/iosX64/iosArm64/iosSimulatorArm64/
// linuxArm64/mingwX64 варианты; :proto уже собирается под все эти таргеты.
jvm()
macosX64()
macosArm64()
iosX64()
iosArm64()
iosSimulatorArm64()
linuxX64()
linuxArm64()
mingwX64()
sourceSets {
commonMain.dependencies {
implementation(project(":proto"))
// Ktor (без engine — engine подключает потребитель, см. :standalone).
implementation(libs.ktor.server.core)
implementation(libs.ktor.server.content.negotiation)
implementation(libs.ktor.serialization.kotlinx.json)
// Commons
implementation(libs.kotlinx.coroutines.core)
implementation(libs.kotlinx.serialization.json)
}
commonTest.dependencies {
implementation(libs.kotlinx.coroutines.core)
implementation(kotlin("test"))
}
} }
} }
dependencies {
implementation(project(":proto"))
implementation(libs.ktor.server.core)
implementation(libs.ktor.server.content.negotiation)
implementation(libs.ktor.serialization.kotlinx.json)
implementation(libs.kotlinx.coroutines.core)
implementation(libs.kotlinx.serialization.json)
}
@@ -6,13 +6,14 @@ import io.ktor.server.application.ApplicationCall
import io.ktor.server.application.call import io.ktor.server.application.call
import io.ktor.server.request.receive import io.ktor.server.request.receive
import io.ktor.server.response.respond import io.ktor.server.response.respond
import io.ktor.server.response.respondBytesWriter
import io.ktor.server.response.respondText import io.ktor.server.response.respondText
import io.ktor.server.response.respondTextWriter
import io.ktor.server.routing.Route import io.ktor.server.routing.Route
import io.ktor.server.routing.delete import io.ktor.server.routing.delete
import io.ktor.server.routing.get import io.ktor.server.routing.get
import io.ktor.server.routing.patch import io.ktor.server.routing.patch
import io.ktor.server.routing.post import io.ktor.server.routing.post
import io.ktor.utils.io.writeStringUtf8
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.catch
import kotlinx.serialization.KSerializer import kotlinx.serialization.KSerializer
@@ -147,13 +148,13 @@ private suspend fun <T> ApplicationCall.streamJsonSse(
serializer: KSerializer<T>, serializer: KSerializer<T>,
json: Json = agentikJson, json: Json = agentikJson,
) { ) {
respondTextWriter(contentType = ContentType.Text.EventStream) { respondBytesWriter(contentType = ContentType.Text.EventStream) {
flow.catch { /* клиент отвалился — глушим */ } flow.catch { /* клиент отвалился — глушим */ }
.collect { value -> .collect { value ->
val s = json.encodeToString(serializer, value) val s = json.encodeToString(serializer, value)
write("data: ") writeStringUtf8("data: ")
write(s) writeStringUtf8(s)
write("\n\n") writeStringUtf8("\n\n")
flush() flush()
} }
} }