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:
+12
-10
@@ -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` модуль
|
||||||
|
|
||||||
|
|||||||
+26
-8
@@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
// "Все возможные цели сборки": 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"))
|
implementation(project(":proto"))
|
||||||
|
|
||||||
|
// Ktor (без engine — engine подключает потребитель, см. :standalone).
|
||||||
implementation(libs.ktor.server.core)
|
implementation(libs.ktor.server.core)
|
||||||
implementation(libs.ktor.server.content.negotiation)
|
implementation(libs.ktor.server.content.negotiation)
|
||||||
implementation(libs.ktor.serialization.kotlinx.json)
|
implementation(libs.ktor.serialization.kotlinx.json)
|
||||||
|
|
||||||
|
// Commons
|
||||||
implementation(libs.kotlinx.coroutines.core)
|
implementation(libs.kotlinx.coroutines.core)
|
||||||
implementation(libs.kotlinx.serialization.json)
|
implementation(libs.kotlinx.serialization.json)
|
||||||
}
|
}
|
||||||
|
commonTest.dependencies {
|
||||||
|
implementation(libs.kotlinx.coroutines.core)
|
||||||
|
implementation(kotlin("test"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+6
-5
@@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user