AG-UI as in-process KMP API with Ktor client/server transports #1

Merged
subochev merged 2 commits from feat/agui-kmp into main 2026-09-09 20:44:11 +00:00
24 changed files with 203 additions and 171 deletions
Showing only changes of commit 4ca54a9b05 - Show all commits
+8
View File
@@ -6,6 +6,14 @@ plugins {
kotlin {
jvmToolchain(21)
jvm()
Review

Добавь цели сборки все возможные. mac цели тоже. android не надо, jvm цель и так его покроет

Добавь цели сборки все возможные. mac цели тоже. android не надо, jvm цель и так его покроет
macosX64()
macosArm64()
iosX64()
iosArm64()
iosSimulatorArm64()
linuxX64()
linuxArm64()
mingwX64()
sourceSets {
commonMain.dependencies {
@@ -1,14 +1,14 @@
package com.agui.api.agent
package pw.binom.agui.api.agent
import com.agui.api.event.BaseEvent
import com.agui.api.message.Message
import com.agui.api.run.RunAgentInput
import com.agui.api.run.RunAgentParameters
import com.agui.api.run.randomRunId
import com.agui.api.run.toParameters
import com.agui.api.run.toRunAgentInput
import com.agui.api.state.State
import com.agui.api.state.mutableState
import pw.binom.agui.api.event.BaseEvent
import pw.binom.agui.api.message.Message
import pw.binom.agui.api.run.RunAgentInput
import pw.binom.agui.api.run.RunAgentParameters
import pw.binom.agui.api.run.randomRunId
import pw.binom.agui.api.run.toParameters
import pw.binom.agui.api.run.toRunAgentInput
import pw.binom.agui.api.state.State
import pw.binom.agui.api.state.mutableState
import kotlinx.coroutines.flow.Flow
/**
@@ -1,7 +1,7 @@
package com.agui.api.agent
package pw.binom.agui.api.agent
import com.agui.api.event.BaseEvent
import com.agui.api.run.RunAgentInput
import pw.binom.agui.api.event.BaseEvent
import pw.binom.agui.api.run.RunAgentInput
import kotlinx.coroutines.flow.Flow
/**
@@ -10,7 +10,7 @@ import kotlinx.coroutines.flow.Flow
*
* This is the network-agnostic heart of the protocol. A concrete [Agent] can be:
*
* - remote — [com.agui.client.HttpAgent] streams events over SSE;
* - remote — [pw.binom.agui.client.HttpAgent] streams events over SSE;
* - local — a real agent implementation;
* - composed/decorated — logging, auth, retry, observability wrappers.
*/
@@ -1,23 +1,23 @@
package com.agui.api.agent
package pw.binom.agui.api.agent
import com.agui.api.event.BaseEvent
import com.agui.api.event.CustomEvent
import com.agui.api.event.MessagesSnapshotEvent
import com.agui.api.event.RawEvent
import com.agui.api.event.RunErrorEvent
import com.agui.api.event.RunFinishedEvent
import com.agui.api.event.RunStartedEvent
import com.agui.api.event.StateDeltaEvent
import com.agui.api.event.StateSnapshotEvent
import com.agui.api.event.StepFinishedEvent
import com.agui.api.event.StepStartedEvent
import com.agui.api.event.TextMessageContentEvent
import com.agui.api.event.TextMessageEndEvent
import com.agui.api.event.TextMessageStartEvent
import com.agui.api.event.ToolCallArgsEvent
import com.agui.api.event.ToolCallEndEvent
import com.agui.api.event.ToolCallResultEvent
import com.agui.api.event.ToolCallStartEvent
import pw.binom.agui.api.event.BaseEvent
import pw.binom.agui.api.event.CustomEvent
import pw.binom.agui.api.event.MessagesSnapshotEvent
import pw.binom.agui.api.event.RawEvent
import pw.binom.agui.api.event.RunErrorEvent
import pw.binom.agui.api.event.RunFinishedEvent
import pw.binom.agui.api.event.RunStartedEvent
import pw.binom.agui.api.event.StateDeltaEvent
import pw.binom.agui.api.event.StateSnapshotEvent
import pw.binom.agui.api.event.StepFinishedEvent
import pw.binom.agui.api.event.StepStartedEvent
import pw.binom.agui.api.event.TextMessageContentEvent
import pw.binom.agui.api.event.TextMessageEndEvent
import pw.binom.agui.api.event.TextMessageStartEvent
import pw.binom.agui.api.event.ToolCallArgsEvent
import pw.binom.agui.api.event.ToolCallEndEvent
import pw.binom.agui.api.event.ToolCallResultEvent
import pw.binom.agui.api.event.ToolCallStartEvent
/**
* Observer for a stream of [BaseEvent]s.
@@ -1,23 +1,23 @@
package com.agui.api.agent
package pw.binom.agui.api.agent
import com.agui.api.event.BaseEvent
import com.agui.api.event.CustomEvent
import com.agui.api.event.MessagesSnapshotEvent
import com.agui.api.event.RawEvent
import com.agui.api.event.RunErrorEvent
import com.agui.api.event.RunFinishedEvent
import com.agui.api.event.RunStartedEvent
import com.agui.api.event.StateDeltaEvent
import com.agui.api.event.StateSnapshotEvent
import com.agui.api.event.StepFinishedEvent
import com.agui.api.event.StepStartedEvent
import com.agui.api.event.TextMessageContentEvent
import com.agui.api.event.TextMessageEndEvent
import com.agui.api.event.TextMessageStartEvent
import com.agui.api.event.ToolCallArgsEvent
import com.agui.api.event.ToolCallEndEvent
import com.agui.api.event.ToolCallResultEvent
import com.agui.api.event.ToolCallStartEvent
import pw.binom.agui.api.event.BaseEvent
import pw.binom.agui.api.event.CustomEvent
import pw.binom.agui.api.event.MessagesSnapshotEvent
import pw.binom.agui.api.event.RawEvent
import pw.binom.agui.api.event.RunErrorEvent
import pw.binom.agui.api.event.RunFinishedEvent
import pw.binom.agui.api.event.RunStartedEvent
import pw.binom.agui.api.event.StateDeltaEvent
import pw.binom.agui.api.event.StateSnapshotEvent
import pw.binom.agui.api.event.StepFinishedEvent
import pw.binom.agui.api.event.StepStartedEvent
import pw.binom.agui.api.event.TextMessageContentEvent
import pw.binom.agui.api.event.TextMessageEndEvent
import pw.binom.agui.api.event.TextMessageStartEvent
import pw.binom.agui.api.event.ToolCallArgsEvent
import pw.binom.agui.api.event.ToolCallEndEvent
import pw.binom.agui.api.event.ToolCallResultEvent
import pw.binom.agui.api.event.ToolCallStartEvent
import kotlinx.coroutines.flow.Flow
/**
@@ -1,4 +1,4 @@
package com.agui.api.context
package pw.binom.agui.api.context
import kotlinx.serialization.Serializable
@@ -1,8 +1,8 @@
package com.agui.api.event
package pw.binom.agui.api.event
import com.agui.api.message.Message
import com.agui.api.message.MessageRole
import com.agui.api.state.State
import pw.binom.agui.api.message.Message
import pw.binom.agui.api.message.MessageRole
import pw.binom.agui.api.state.State
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonElement
@@ -14,6 +14,7 @@ import kotlinx.serialization.json.JsonElement
* subclasses mirror the AG-UI protocol event catalogue and are (de)serialized
* polymorphically by the `type` discriminator field.
*/
@Serializable
public sealed interface BaseEvent {
/** Event creation timestamp in epoch milliseconds, if provided. */
@@ -1,4 +1,4 @@
package com.agui.api.message
package pw.binom.agui.api.message
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@@ -1,9 +1,9 @@
package com.agui.api.run
package pw.binom.agui.api.run
import com.agui.api.context.Context
import com.agui.api.message.Message
import com.agui.api.state.State
import com.agui.api.tool.Tool
import pw.binom.agui.api.context.Context
import pw.binom.agui.api.message.Message
import pw.binom.agui.api.state.State
import pw.binom.agui.api.tool.Tool
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonElement
import kotlin.random.Random
@@ -1,23 +1,23 @@
package com.agui.api.serialization
package pw.binom.agui.api.serialization
import com.agui.api.event.BaseEvent
import com.agui.api.event.CustomEvent
import com.agui.api.event.MessagesSnapshotEvent
import com.agui.api.event.RawEvent
import com.agui.api.event.RunErrorEvent
import com.agui.api.event.RunFinishedEvent
import com.agui.api.event.RunStartedEvent
import com.agui.api.event.StateDeltaEvent
import com.agui.api.event.StateSnapshotEvent
import com.agui.api.event.StepFinishedEvent
import com.agui.api.event.StepStartedEvent
import com.agui.api.event.TextMessageContentEvent
import com.agui.api.event.TextMessageEndEvent
import com.agui.api.event.TextMessageStartEvent
import com.agui.api.event.ToolCallArgsEvent
import com.agui.api.event.ToolCallEndEvent
import com.agui.api.event.ToolCallResultEvent
import com.agui.api.event.ToolCallStartEvent
import pw.binom.agui.api.event.BaseEvent
import pw.binom.agui.api.event.CustomEvent
import pw.binom.agui.api.event.MessagesSnapshotEvent
import pw.binom.agui.api.event.RawEvent
import pw.binom.agui.api.event.RunErrorEvent
import pw.binom.agui.api.event.RunFinishedEvent
import pw.binom.agui.api.event.RunStartedEvent
import pw.binom.agui.api.event.StateDeltaEvent
import pw.binom.agui.api.event.StateSnapshotEvent
import pw.binom.agui.api.event.StepFinishedEvent
import pw.binom.agui.api.event.StepStartedEvent
import pw.binom.agui.api.event.TextMessageContentEvent
import pw.binom.agui.api.event.TextMessageEndEvent
import pw.binom.agui.api.event.TextMessageStartEvent
import pw.binom.agui.api.event.ToolCallArgsEvent
import pw.binom.agui.api.event.ToolCallEndEvent
import pw.binom.agui.api.event.ToolCallResultEvent
import pw.binom.agui.api.event.ToolCallStartEvent
import kotlinx.serialization.json.Json
import kotlinx.serialization.modules.SerializersModule
import kotlinx.serialization.modules.polymorphic
@@ -1,10 +1,10 @@
package com.agui.api.state
package pw.binom.agui.api.state
import kotlinx.serialization.json.JsonElement
/**
* A loosely-typed, platform-agnostic bag of agent state carried in AG-UI
* `STATE_SNAPSHOT`/`STATE_DELTA` events and in [com.agui.api.run.RunAgentInput.state].
* `STATE_SNAPSHOT`/`STATE_DELTA` events and in [pw.binom.agui.api.run.RunAgentInput.state].
*
* Because the value is a JSON element, it can be any JSON scalar, object or array — the
* protocol does not prescribe a concrete schema.
@@ -1,4 +1,4 @@
package com.agui.api.tool
package pw.binom.agui.api.tool
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonObject
@@ -1,19 +1,19 @@
package com.agui.api
package pw.binom.agui.api
import com.agui.api.agent.AbstractAgent
import com.agui.api.agent.AgentSubscriber
import com.agui.api.agent.EventDispatcher
import com.agui.api.agent.dispatchTo
import com.agui.api.context.Context
import com.agui.api.event.BaseEvent
import com.agui.api.event.CustomEvent
import com.agui.api.event.RunStartedEvent
import com.agui.api.run.RunAgentInput
import com.agui.api.run.RunAgentParameters
import com.agui.api.run.randomRunId
import com.agui.api.run.toParameters
import com.agui.api.run.toRunAgentInput
import com.agui.api.tool.Tool
import pw.binom.agui.api.agent.AbstractAgent
import pw.binom.agui.api.agent.AgentSubscriber
import pw.binom.agui.api.agent.EventDispatcher
import pw.binom.agui.api.agent.dispatchTo
import pw.binom.agui.api.context.Context
import pw.binom.agui.api.event.BaseEvent
import pw.binom.agui.api.event.CustomEvent
import pw.binom.agui.api.event.RunStartedEvent
import pw.binom.agui.api.run.RunAgentInput
import pw.binom.agui.api.run.RunAgentParameters
import pw.binom.agui.api.run.randomRunId
import pw.binom.agui.api.run.toParameters
import pw.binom.agui.api.run.toRunAgentInput
import pw.binom.agui.api.tool.Tool
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.toList
import kotlinx.coroutines.test.runTest
@@ -1,27 +1,27 @@
package com.agui.api
package pw.binom.agui.api
import com.agui.api.event.BaseEvent
import com.agui.api.event.CustomEvent
import com.agui.api.event.MessagesSnapshotEvent
import com.agui.api.event.RawEvent
import com.agui.api.event.RunErrorEvent
import com.agui.api.event.RunFinishedEvent
import com.agui.api.event.RunStartedEvent
import com.agui.api.event.StateDeltaEvent
import com.agui.api.event.StateSnapshotEvent
import com.agui.api.event.StepFinishedEvent
import com.agui.api.event.StepStartedEvent
import com.agui.api.event.TextMessageContentEvent
import com.agui.api.event.TextMessageEndEvent
import com.agui.api.event.TextMessageStartEvent
import com.agui.api.event.ToolCallArgsEvent
import com.agui.api.event.ToolCallEndEvent
import com.agui.api.event.ToolCallResultEvent
import com.agui.api.event.ToolCallStartEvent
import com.agui.api.message.Message
import com.agui.api.message.MessageRole
import com.agui.api.serialization.AguiJson
import com.agui.api.state.mutableState
import pw.binom.agui.api.event.BaseEvent
import pw.binom.agui.api.event.CustomEvent
import pw.binom.agui.api.event.MessagesSnapshotEvent
import pw.binom.agui.api.event.RawEvent
import pw.binom.agui.api.event.RunErrorEvent
import pw.binom.agui.api.event.RunFinishedEvent
import pw.binom.agui.api.event.RunStartedEvent
import pw.binom.agui.api.event.StateDeltaEvent
import pw.binom.agui.api.event.StateSnapshotEvent
import pw.binom.agui.api.event.StepFinishedEvent
import pw.binom.agui.api.event.StepStartedEvent
import pw.binom.agui.api.event.TextMessageContentEvent
import pw.binom.agui.api.event.TextMessageEndEvent
import pw.binom.agui.api.event.TextMessageStartEvent
import pw.binom.agui.api.event.ToolCallArgsEvent
import pw.binom.agui.api.event.ToolCallEndEvent
import pw.binom.agui.api.event.ToolCallResultEvent
import pw.binom.agui.api.event.ToolCallStartEvent
import pw.binom.agui.api.message.Message
import pw.binom.agui.api.message.MessageRole
import pw.binom.agui.api.serialization.AguiJson
import pw.binom.agui.api.state.mutableState
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.JsonPrimitive
+1 -1
View File
@@ -4,6 +4,6 @@ plugins {
}
allprojects {
group = "com.agui"
group = "pw.binom.agui"
version = "0.1.0-SNAPSHOT"
}
+11
View File
@@ -6,6 +6,14 @@ plugins {
kotlin {
jvmToolchain(21)
jvm()
Review

тут тоже надо добавить все возможные цели. android не надо т.к. jvm цель и так его покроет

тут тоже надо добавить все возможные цели. android не надо т.к. jvm цель и так его покроет
macosX64()
macosArm64()
iosX64()
iosArm64()
iosSimulatorArm64()
linuxX64()
linuxArm64()
mingwX64()
sourceSets {
commonMain.dependencies {
@@ -17,6 +25,9 @@ kotlin {
jvmMain.dependencies {
implementation(libs.ktor.client.cio)
}
nativeMain.dependencies {
implementation(libs.ktor.client.cio)
}
commonTest.dependencies {
implementation(kotlin("test"))
}
@@ -1,9 +1,9 @@
package com.agui.client
package pw.binom.agui.client
import com.agui.api.agent.Agent
import com.agui.api.event.BaseEvent
import com.agui.api.run.RunAgentInput
import com.agui.api.serialization.AguiJson
import pw.binom.agui.api.agent.Agent
import pw.binom.agui.api.event.BaseEvent
import pw.binom.agui.api.run.RunAgentInput
import pw.binom.agui.api.serialization.AguiJson
import io.ktor.client.HttpClient
import io.ktor.client.plugins.sse.sse
import io.ktor.client.request.setBody
@@ -1,4 +1,4 @@
package com.agui.client
package pw.binom.agui.client
import io.ktor.client.HttpClient
import io.ktor.client.HttpClientConfig
@@ -1,15 +1,15 @@
package com.agui.client
package pw.binom.agui.client
import com.agui.api.agent.Agent
import com.agui.api.event.RunFinishedEvent
import com.agui.api.event.RunStartedEvent
import com.agui.api.event.TextMessageContentEvent
import com.agui.api.event.TextMessageEndEvent
import com.agui.api.event.TextMessageStartEvent
import com.agui.api.message.Message
import com.agui.api.message.MessageRole
import com.agui.api.run.RunAgentInput
import com.agui.server.agui
import pw.binom.agui.api.agent.Agent
import pw.binom.agui.api.event.RunFinishedEvent
import pw.binom.agui.api.event.RunStartedEvent
import pw.binom.agui.api.event.TextMessageContentEvent
import pw.binom.agui.api.event.TextMessageEndEvent
import pw.binom.agui.api.event.TextMessageStartEvent
import pw.binom.agui.api.message.Message
import pw.binom.agui.api.message.MessageRole
import pw.binom.agui.api.run.RunAgentInput
import pw.binom.agui.server.agui
import io.ktor.client.plugins.sse.SSE
import io.ktor.server.application.application
import io.ktor.server.testing.testApplication
@@ -21,7 +21,7 @@ import kotlin.test.assertEquals
import kotlin.test.assertIs
/**
* Exercises [HttpAgent] against the real AG-UI endpoint exposed by [com.agui.server.agui],
* Exercises [HttpAgent] against the real AG-UI endpoint exposed by [pw.binom.agui.server.agui],
* using the Ktor test host so the SSE transport is exercised end-to-end over HTTP.
*/
class HttpAgentTest {
+1
View File
@@ -12,6 +12,7 @@ kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serializa
ktor-server-core = { module = "io.ktor:ktor-server-core", version.ref = "ktor" }
ktor-server-sse = { module = "io.ktor:ktor-server-sse", version.ref = "ktor" }
ktor-server-netty = { module = "io.ktor:ktor-server-netty", version.ref = "ktor" }
ktor-server-cio = { module = "io.ktor:ktor-server-cio", version.ref = "ktor" }
ktor-server-content-negotiation = { module = "io.ktor:ktor-server-content-negotiation", version.ref = "ktor" }
ktor-server-test-host = { module = "io.ktor:ktor-server-test-host", version.ref = "ktor" }
+11
View File
@@ -6,6 +6,14 @@ plugins {
kotlin {
jvmToolchain(21)
jvm()
macosX64()
macosArm64()
iosX64()
iosArm64()
iosSimulatorArm64()
linuxX64()
linuxArm64()
mingwX64()
sourceSets {
commonMain.dependencies {
@@ -18,6 +26,9 @@ kotlin {
jvmMain.dependencies {
implementation(libs.ktor.server.netty)
}
nativeMain.dependencies {
implementation(libs.ktor.server.cio)
}
jvmTest.dependencies {
implementation(kotlin("test"))
implementation(libs.kotlinx.coroutines.test)
@@ -1,10 +1,10 @@
package com.agui.server
package pw.binom.agui.server
import com.agui.api.agent.Agent
import com.agui.api.event.BaseEvent
import com.agui.api.event.RunErrorEvent
import com.agui.api.run.RunAgentInput
import com.agui.api.serialization.AguiJson
import pw.binom.agui.api.agent.Agent
import pw.binom.agui.api.event.BaseEvent
import pw.binom.agui.api.event.RunErrorEvent
import pw.binom.agui.api.run.RunAgentInput
import pw.binom.agui.api.serialization.AguiJson
import io.ktor.server.application.Application
import io.ktor.server.application.ApplicationCall
import io.ktor.server.application.call
@@ -1,13 +1,13 @@
package com.agui.server.demo
package pw.binom.agui.server.demo
import com.agui.api.agent.Agent
import com.agui.api.event.RunFinishedEvent
import com.agui.api.event.RunStartedEvent
import com.agui.api.event.StateSnapshotEvent
import com.agui.api.event.TextMessageContentEvent
import com.agui.api.event.TextMessageEndEvent
import com.agui.api.event.TextMessageStartEvent
import com.agui.server.agui
import pw.binom.agui.api.agent.Agent
import pw.binom.agui.api.event.RunFinishedEvent
import pw.binom.agui.api.event.RunStartedEvent
import pw.binom.agui.api.event.StateSnapshotEvent
import pw.binom.agui.api.event.TextMessageContentEvent
import pw.binom.agui.api.event.TextMessageEndEvent
import pw.binom.agui.api.event.TextMessageStartEvent
import pw.binom.agui.server.agui
import io.ktor.server.engine.embeddedServer
import io.ktor.server.netty.Netty
import kotlinx.coroutines.flow.flow
@@ -1,18 +1,18 @@
package com.agui.server
package pw.binom.agui.server
import com.agui.api.agent.Agent
import com.agui.api.event.RunErrorEvent
import com.agui.api.event.RunFinishedEvent
import com.agui.api.event.RunStartedEvent
import com.agui.api.event.StateSnapshotEvent
import com.agui.api.event.TextMessageContentEvent
import com.agui.api.event.TextMessageEndEvent
import com.agui.api.event.TextMessageStartEvent
import com.agui.api.message.Message
import com.agui.api.message.MessageRole
import com.agui.api.run.RunAgentInput
import com.agui.api.state.mutableState
import com.agui.client.HttpAgent
import pw.binom.agui.api.agent.Agent
import pw.binom.agui.api.event.RunErrorEvent
import pw.binom.agui.api.event.RunFinishedEvent
import pw.binom.agui.api.event.RunStartedEvent
import pw.binom.agui.api.event.StateSnapshotEvent
import pw.binom.agui.api.event.TextMessageContentEvent
import pw.binom.agui.api.event.TextMessageEndEvent
import pw.binom.agui.api.event.TextMessageStartEvent
import pw.binom.agui.api.message.Message
import pw.binom.agui.api.message.MessageRole
import pw.binom.agui.api.run.RunAgentInput
import pw.binom.agui.api.state.mutableState
import pw.binom.agui.client.HttpAgent
import io.ktor.client.plugins.sse.SSE
import io.ktor.server.application.application
import io.ktor.server.testing.testApplication