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` модуль
- 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-нейтральность уже заложена.*
- 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` модуль
+34 -16
View File
@@ -1,23 +1,41 @@
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
plugins {
alias(libs.plugins.kotlin.jvm)
alias(libs.plugins.kotlin.multiplatform)
alias(libs.plugins.kotlin.serialization)
}
kotlin {
compilerOptions {
jvmTarget.set(JvmTarget.JVM_21)
jvmToolchain(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.request.receive
import io.ktor.server.response.respond
import io.ktor.server.response.respondBytesWriter
import io.ktor.server.response.respondText
import io.ktor.server.response.respondTextWriter
import io.ktor.server.routing.Route
import io.ktor.server.routing.delete
import io.ktor.server.routing.get
import io.ktor.server.routing.patch
import io.ktor.server.routing.post
import io.ktor.utils.io.writeStringUtf8
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.catch
import kotlinx.serialization.KSerializer
@@ -147,13 +148,13 @@ private suspend fun <T> ApplicationCall.streamJsonSse(
serializer: KSerializer<T>,
json: Json = agentikJson,
) {
respondTextWriter(contentType = ContentType.Text.EventStream) {
respondBytesWriter(contentType = ContentType.Text.EventStream) {
flow.catch { /* клиент отвалился — глушим */ }
.collect { value ->
val s = json.encodeToString(serializer, value)
write("data: ")
write(s)
write("\n\n")
writeStringUtf8("data: ")
writeStringUtf8(s)
writeStringUtf8("\n\n")
flush()
}
}