refactor(protocol): add toolName to ToolResult, remove proto typealiases, rename id→toolCallId, drop Conversation.events()
Three protocol-level changes from Android-client review (items 1-3, 5-6):
1) toolName denormalization in ToolResult (3 layers):
- :outbox-api/Event.ToolResult: +toolName: String? = null
- :journal-api/MessageRecord.ToolResult: +toolName: String? = null
- :proto/Message.ToolResult: +toolName: String? = null
- :storage-ksqlite, :journal-ksqlite ResultPayload codec: +toolName
- :standalone/ToolDispatcher, ConversationLoop: thread toolName = call.name
Nullable + default = backward-compat for already-persisted histories
and existing clients.
2) Drop proto/Event.kt, AgentEvent.kt, CommonEvent.kt typealiases.
is proto.Event.End failed with 'Unresolved reference End' (alias
loses nested-class access). Use pw.binom.agentik.outbox.{Event,
AgentEvent, CommonEvent} directly everywhere — :proto already has
api(:outbox-api), the package is visible to consumers, no shim
needed. 21 files rewired, 3 files deleted.
3) Rename Event.ToolResult.id → toolCallId (option B per user).
In :outbox-api Event.ToolResult.id == Event.ToolCall.id (one value,
one name); the persistent journal keeps MessageRecord.ToolResult.id
as its own PK + toolCallId as FK to the call — different semantics,
left untouched. Fixed ToolDispatcher bug: emitted id = resultId
while KDoc claimed id == ToolCall.id; now emits toolCallId = callId.
4) Remove Conversation.events() from :proto; OutboxStore is sole event source.
Conversation is a pure per-conversation abstraction (send/getMessages/
rename/close). Live events only via agent.outbox.conversationEvents/
agentEvents/events. HTTP route /conversations/{id}/events stays for
wire-compat but routes through outbox internally (map { it.event }).
jvmTest green (95 tasks).
This commit is contained in:
@@ -49,7 +49,8 @@ class A2aBridge(private val agent: Agent) : AgentHandler {
|
||||
val reply = StringBuilder()
|
||||
val turnDone = CompletableDeferred<Unit>()
|
||||
val subscription = async {
|
||||
conv.events(since).collect { e ->
|
||||
agent.outbox.conversationEvents(since, conv.id).collect { ce ->
|
||||
val e = ce.event
|
||||
when (e) {
|
||||
is Event.AppendText -> reply.append(e.body)
|
||||
is Event.End, is Event.Interrupted -> turnDone.complete(Unit)
|
||||
|
||||
@@ -18,11 +18,11 @@ import pw.binom.agentik.memory.MemorySystemGuidance
|
||||
import pw.binom.agentik.proto.Agent as ProtoAgent
|
||||
import pw.binom.agentik.outbox.AgentEvent
|
||||
import pw.binom.agentik.outbox.CommonEvent
|
||||
import pw.binom.agentik.outbox.Event as ProtoEvent
|
||||
import pw.binom.agentik.outbox.MutableOutboxStore
|
||||
import pw.binom.agentik.journal.JournalStore
|
||||
import pw.binom.agentik.outbox.OutboxStore
|
||||
import pw.binom.agentik.proto.Conversation as ProtoConversation
|
||||
import pw.binom.agentik.proto.Event as ProtoEvent
|
||||
import pw.binom.agentik.skills.SkillCatalog
|
||||
import pw.binom.agentik.skills.renderSystemPromptSection
|
||||
import pw.binom.agentik.standalone.agent.memory.MemoryToolsFactory
|
||||
|
||||
+4
-4
@@ -3,15 +3,15 @@ package pw.binom.agentik.standalone.agent
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import pw.binom.agentik.outbox.MutableOutboxStore
|
||||
import pw.binom.agentik.outbox.CommonEvent
|
||||
import pw.binom.agentik.proto.Event as ProtoEvent
|
||||
import pw.binom.agentik.outbox.Event
|
||||
import pw.binom.agentik.outbox.MutableOutboxStore
|
||||
|
||||
internal class ConversationEvents(
|
||||
private val globalEventStore: MutableOutboxStore,
|
||||
private val conversationId: String,
|
||||
) {
|
||||
fun tryEmit(event: ProtoEvent): Boolean {
|
||||
fun tryEmit(event: Event): Boolean {
|
||||
runBlocking {
|
||||
globalEventStore.append(
|
||||
CommonEvent.Conversation(
|
||||
@@ -24,7 +24,7 @@ internal class ConversationEvents(
|
||||
return true
|
||||
}
|
||||
|
||||
fun events(after: kotlin.time.Instant?): Flow<ProtoEvent> =
|
||||
fun events(after: kotlin.time.Instant?): Flow<Event> =
|
||||
globalEventStore.conversationEvents(after = after, conversationId = conversationId)
|
||||
.map { it.event }
|
||||
}
|
||||
|
||||
+1
-3
@@ -220,9 +220,6 @@ class ConversationLoop(
|
||||
toolDispatcher.currentToolJob?.cancel()
|
||||
}
|
||||
|
||||
override fun events(after: Instant): Flow<ProtoEvent> =
|
||||
events.events(after)
|
||||
|
||||
override suspend fun getMessages(after: Instant, offset: Int, limit: Int): List<ProtoMessage> =
|
||||
messageStore.list(conversationId = id, after = after, offset = offset, limit = limit)
|
||||
.map { it.toProto() }
|
||||
@@ -568,6 +565,7 @@ internal fun MessageRecord.toProto(): ProtoMessage = when (this) {
|
||||
is MessageRecord.ToolResult -> ProtoMessage.ToolResult(
|
||||
id = id,
|
||||
date = createdAt,
|
||||
toolName = toolName,
|
||||
result = result,
|
||||
)
|
||||
is MessageRecord.Error -> ProtoMessage.Error(
|
||||
|
||||
+2
-1
@@ -93,7 +93,7 @@ internal class ToolDispatcher(
|
||||
}
|
||||
|
||||
val resultAt = now()
|
||||
events.tryEmit(ProtoEvent.ToolResult(date = resultAt, id = resultId, result = resultText))
|
||||
events.tryEmit(ProtoEvent.ToolResult(date = resultAt, toolCallId = callId, toolName = call.name, result = resultText))
|
||||
|
||||
// Эмитим background event — другие компоненты (BackgroundScheduler)
|
||||
// решают, делать ли что-то. Cancellation = not a failure (не эмитим Failed).
|
||||
@@ -110,6 +110,7 @@ internal class ToolDispatcher(
|
||||
id = resultId,
|
||||
conversationId = state.id,
|
||||
toolCallId = callId,
|
||||
toolName = call.name,
|
||||
result = resultText,
|
||||
createdAt = resultAt,
|
||||
),
|
||||
|
||||
@@ -319,7 +319,7 @@ class ChatAgentTest {
|
||||
|
||||
val events = mutableListOf<ProtoEvent>()
|
||||
val job = launch(start = kotlinx.coroutines.CoroutineStart.UNDISPATCHED) {
|
||||
conv.events(Instant.DISTANT_PAST).collect { events.add(it) }
|
||||
agent.outbox.conversationEvents(Instant.DISTANT_PAST, conv.id).collect { events.add(it.event) }
|
||||
}
|
||||
conv.send(listOf(Content.Text("hi")))
|
||||
delay(50)
|
||||
@@ -356,7 +356,7 @@ class ChatAgentTest {
|
||||
// отправки событий подписка ничего не увидит.
|
||||
val events = mutableListOf<ProtoEvent>()
|
||||
val eventsJob = launch(start = kotlinx.coroutines.CoroutineStart.UNDISPATCHED) {
|
||||
conv.events(Instant.DISTANT_PAST).collect { events.add(it) }
|
||||
agent.outbox.conversationEvents(Instant.DISTANT_PAST, conv.id).collect { events.add(it.event) }
|
||||
}
|
||||
|
||||
val sendJob = launch {
|
||||
@@ -414,7 +414,7 @@ class ChatAgentTest {
|
||||
// Подписываемся ДО send — SharedFlow без replay
|
||||
val events = mutableListOf<ProtoEvent>()
|
||||
val eventsJob = launch(start = kotlinx.coroutines.CoroutineStart.UNDISPATCHED) {
|
||||
conv.events(Instant.DISTANT_PAST).collect { events.add(it) }
|
||||
agent.outbox.conversationEvents(Instant.DISTANT_PAST, conv.id).collect { events.add(it.event) }
|
||||
}
|
||||
|
||||
val sendJob = launch {
|
||||
|
||||
Reference in New Issue
Block a user