Files

227 lines
11 KiB
Kotlin
Raw Permalink 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.
@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:api/siglip-jvm
// опубликованы в caffeine начиная с v4, 2026-09-21). Подробнее см.
// 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, ...). 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(":journal-ksqlite"))
implementation(project(":context-ksqlite"))
implementation(project(":reflection-ksqlite"))
// ksqlite объявлен как `implementation` (не `api`) в каждом
// ksqlite-модуле, поэтому SqliteStores здесь использует
// SQLiteConnection напрямую — фиксируем зависимость явно.
implementation(libs.ksqlite)
// Bounded-tail live event stream + per-event TTL.
implementation(project(":outbox-inmemory"))
// :agent-api — MutableAgent + Component + ToolProvider/SystemPromptProvider.
// ChatAgent реализует MutableAgent; компоненты (McpBridgeComponent и т.п.)
// добавляются через agent.install(...).
implementation(project(":agent-api"))
// LLM-toolsets + generic LLM-side tools (LlmReflector, SkillMiner,
// LlmMemoryReviewer, ContextCompactor, парсеры/промпты).
implementation(project(":agent-toolsets"))
implementation(project(":llm-tools"))
// Skill-подсистема как Component: тулы + SkillMiningComponent.
implementation(project(":skill-mining"))
// Generic MCP-bridge (McpConfig, McpRegistry, McpLiteToolAdapter).
implementation(project(":mcp-bridge"))
// litert-kmp: контракт (commonMain)
api(libs.litert.api)
// liteTool DSL (типизированные LiteTool через @Serializable args)
implementation(libs.litert.tools.kotlinx.serialization)
// litert-openai: JVM-реализация
api(libs.litert.openai)
// litert-google: встроенный LiteRT-LM движок, нужен только на runtime
api(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>("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.156 'echo OK'` вручную.
val agentikDeployHost: String =
(project.findProperty("agentikDeployHost") as? String) ?: "root@192.168.76.156"
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")
}
}