@file:OptIn(org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi::class) import org.gradle.api.artifacts.ConfigurationContainer import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar plugins { alias(libs.plugins.kotlin.multiplatform) alias(libs.plugins.kotlin.serialization) alias(libs.plugins.shadow) } // CI-флаг: при -PskipVectorMemory=true :memory-vector не подключается как // зависимость — нужно для CI runner'а (text-embedding-kmp ещё не опубликован // в caffeine). Подробнее см. memory-vector/build.gradle.kts. val skipVectorMemory: Boolean = (project.findProperty("skipVectorMemory") == "true") || System.getenv("SKIP_VECTOR_MEMORY") == "1" // :standalone — единственный entry-point проекта, JVM-only по факту (один // `jvm()` target). Чтобы не таскать лишние sourceSet'ы, ВСЁ живёт в // commonMain + commonTest — даже зависимости, которые формально JVM-only // (ktor-server-cio, litert-openai, storage-ksqlite, ...). KMP-плагин тут // только ради бесшовного потребления KMP-зависимостей (`:proto`, `:server`, // `:journal-api`, ...); компилируется всё ровно в одну JVM-таргет. kotlin { jvmToolchain(21) jvm { binaries { executable { mainClass.set("pw.binom.agentik.standalone.MainKt") } } } sourceSets { commonMain.dependencies { // Протокол + KMP storage API implementation(project(":proto")) implementation(project(":server")) implementation(project(":journal-api")) implementation(project(":outbox-api")) implementation(project(":context-api")) implementation(project(":reflection-api")) // Commons implementation(libs.kotlinx.coroutines.core) implementation(libs.kotlinx.serialization.json) // Парсер и загрузчик скилов (YAML frontmatter + markdown body). implementation(project(":skills")) // Долговременная память (Hermes-style MD-бэкенд) + хранилище истории. implementation(project(":memory-api")) implementation(project(":memory-md")) if (!skipVectorMemory) { implementation(project(":memory-vector")) // Гибрид: .md (single source of truth) + ksqlite vector-кэш // (sqlite-vec vec0). Подключается при AGENTIK_MEMORY_BACKEND=hybrid. implementation(project(":memory-md-vector")) } // SQLite-бэкенд (ksqlite JNI) — JVM-only по факту. implementation(project(":storage-ksqlite")) // Bounded-tail live event stream + per-event TTL. implementation(project(":outbox-inmemory")) // LLM-toolsets + generic LLM-side tools (LlmReflector, SkillMiner, // LlmMemoryReviewer, ContextCompactor, парсеры/промпты). implementation(project(":agent-toolsets")) implementation(project(":llm-tools")) // Generic MCP-bridge (McpConfig, McpRegistry, McpLiteToolAdapter). implementation(project(":mcp-bridge")) // litert-kmp: контракт (commonMain) api(libs.litert.api) // litert-openai: JVM-реализация implementation(libs.litert.openai) // litert-google: встроенный LiteRT-LM движок, нужен только на runtime runtimeOnly(libs.litert.google) // Ktor server (для :server facade + a2aServer) // Используем CIO вместо Netty — он KMP (jvm + linuxX64/ios/...), нам нужен // для будущей native-сборки; на JVM работает идентично для нашего сценария. implementation(libs.ktor.server.core) implementation(libs.ktor.server.sse) implementation(libs.ktor.server.cio) implementation(libs.ktor.server.content.negotiation) implementation(libs.ktor.serialization.kotlinx.json) // Транспортные фасады (A2A остаётся заготовкой; на v1 не подключается в Main.kt — // см. docs/ARCHITECTURE.md §3). implementation(libs.a2a.server) // MCP (Model Context Protocol) клиент — подключение внешних/внутренних MCP-серверов implementation(libs.mcp.sdk.client) implementation(libs.ktor.client.core) implementation(libs.ktor.client.cio) implementation(libs.ktor.client.content.negotiation) // Логирование: kotlin-logging (тонкая обёртка slf4j-api) + logback-classic // (binding для JVM; без него slf4j-api NOP-логирует и не падает). implementation(libs.kotlin.logging) implementation(libs.logback.classic) } commonTest.dependencies { implementation(libs.kotlinx.coroutines.core) implementation(libs.kotlinx.serialization.json) implementation(libs.kotlin.test) // Ktor test engine для smoke-тестов HTTP implementation(libs.ktor.server.test.host) } } } // --- Fatjar (uberjar) --- // // :standalone — KMP jvm-only бэкенд. Стандартный KMP `jvmJar` содержит только // наши классы и требует подтянуть все зависимости на runtime через classpath. // Для дистрибутива нужен self-contained `*-all.jar`, который содержит и наш // код, и runtime classpath. Делается это Shadow-плагином: // // 1. shadowJar расширяет `jvmJar` (через from(tasks.named("jvmJar"))) // 2. Добавляет все артефакты из jvmRuntimeClasspath как `zipTree` // 3. Корректно сливает META-INF/services (через ServiceFileTransformer) // 4. Прописывает Main-Class в манифесте // // Результат: `build/libs/standalone-*-all.jar` запускается как // java -jar standalone-0.1.0-all.jar // // KMP-сборка под ios/lin/macos не затрагивается — задача живёт только в jvm. // Shadow 8.x не авторегистрирует shadowJar в KMP-проектах (нет стандартного `jar`), // поэтому регистрируем явно через tasks.register. // // В Gradle 9 KGP подменяет `configurations` на сгенерированный dependency-accessor // (он возвращает List), поэтому используем прямой доступ к Project API. val shadowJarTask = tasks.register("shadowJar") { archiveBaseName.set("standalone") archiveClassifier.set("all") description = "Self-contained fatjar with all runtime dependencies bundled." group = "build" from(tasks.named("jvmJar")) // javaClass-каст через Any? чтобы вытащить реальный ConfigurationContainer // из любого типа, который сейчас прикидывается `configurations`. val cc = try { @Suppress("UNCHECKED_CAST") configurations as ConfigurationContainer } catch (_: ClassCastException) { // KGP-generated dependency accessor; обходим через raw project. @Suppress("UNCHECKED_CAST") (project as org.gradle.api.Project).configurations as ConfigurationContainer } from(cc.getByName("jvmRuntimeClasspath")) mergeServiceFiles() duplicatesStrategy = DuplicatesStrategy.EXCLUDE manifest { attributes["Main-Class"] = "pw.binom.agentik.standalone.MainKt" attributes["Implementation-Title"] = "agentik-standalone" attributes["Implementation-Version"] = project.version.toString() } includeEmptyDirs = false } // :standalone deploy // // `deployJar` собирает fatjar через shadowJar и копирует его на приватный // домашний хост (root@192.168.76.166). Репа приватная, IP захардкожен — // пользователь явно сказал "можно в наглую указать конкретно этот ip". // Если понадобится другой хост — переопредели через -PagentikDeployHost=... // // Конвенция имени файла: agentik-standalone-{version}-all.jar (как на хосте // уже лежат agentik-cli-0.1.0-all.jar / agentik-0.1.0-all.jar — // выровнено по стилю существующего деплоя). // // SSH — ключевой, пароль не спрашивает. Если упадёт — проверь // `ssh root@192.168.76.166 'echo OK'` вручную. val agentikDeployHost: String = (project.findProperty("agentikDeployHost") as? String) ?: "root@192.168.76.166" val agentikDeployPath: String = (project.findProperty("agentikDeployPath") as? String) ?: "~/agentik-standalone-${project.version}-all.jar" tasks.register("deployJar") { group = "deployment" description = "Builds shadowJar and copies it to $agentikDeployHost:$agentikDeployPath" dependsOn(shadowJarTask) doLast { val jar = shadowJarTask.get().outputs.files.singleFile val target = "$agentikDeployHost:$agentikDeployPath" println("→ scp $jar $target") val proc = ProcessBuilder("scp", jar.absolutePath, target) .redirectErrorStream(true) .start() proc.inputStream.bufferedReader().forEachLine { println("scp> $it") } val rc = proc.waitFor() if (rc != 0) { throw GradleException("scp failed (exit=$rc). Check ssh access to $agentikDeployHost.") } println("✓ deployed to $target") } }