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:
@@ -1,10 +0,0 @@
|
||||
package pw.binom.agentik.proto
|
||||
|
||||
/**
|
||||
* Backward-compat typealias: `AgentEvent` теперь живёт в `:outbox-api`
|
||||
* (логически принадлежит сущности outbox, не wire-протоколу `:proto`).
|
||||
*
|
||||
* Существующие импорты `pw.binom.agentik.proto.AgentEvent` продолжают
|
||||
* работать транспарентно. Использовать typealias в новом коде.
|
||||
*/
|
||||
typealias AgentEvent = pw.binom.agentik.outbox.AgentEvent
|
||||
@@ -1,9 +0,0 @@
|
||||
package pw.binom.agentik.proto
|
||||
|
||||
/**
|
||||
* Backward-compat typealias: `CommonEvent` теперь живёт в `:outbox-api`.
|
||||
*
|
||||
* Существующие импорты `pw.binom.agentik.proto.CommonEvent` продолжают
|
||||
* работать транспарентно.
|
||||
*/
|
||||
typealias CommonEvent = pw.binom.agentik.outbox.CommonEvent
|
||||
@@ -8,6 +8,19 @@ import kotlin.time.Instant
|
||||
* Stateful-диалог клиента и [Agent]. Хранит собственную историю: на каждый
|
||||
* [send] агенту не нужно пересылать транскрипт — он уже живёт внутри
|
||||
* [Conversation].
|
||||
*
|
||||
* **Live-события** диалога (turn stream: StartReasoning / AppendText / End /
|
||||
* ToolCall / ToolResult / ...) НЕ часть этого интерфейса — единственный
|
||||
* источник live-событий это [pw.binom.agentik.outbox.OutboxStore].
|
||||
* Подписаться на события конкретного диалога:
|
||||
* ```
|
||||
* agent.outbox.conversationEvents(after = lastSeen, conversationId = id)
|
||||
* .map { it.event }
|
||||
* .collect { e -> ... }
|
||||
* ```
|
||||
* Для cross-conversation view (admin / parent-agent / debug):
|
||||
* `agent.outbox.events(after)`. Для lifecycle агента (created/deleted/renamed):
|
||||
* `agent.outbox.agentEvents(after)`.
|
||||
*/
|
||||
interface Conversation : AutoCloseable {
|
||||
val id: String
|
||||
@@ -33,7 +46,7 @@ interface Conversation : AutoCloseable {
|
||||
|
||||
/**
|
||||
* Ставит новый user-ход в очередь. Возвращает управление сразу — поток
|
||||
* событий ответа приходит через [events].
|
||||
* событий ответа приходит через `agent.outbox.conversationEvents(...)`.
|
||||
*
|
||||
* Если в момент вызова выполняется другой ход, новый встаёт в очередь
|
||||
* за ним. Чтобы отменить текущий — вызови [interrupt] перед [send].
|
||||
@@ -48,25 +61,13 @@ interface Conversation : AutoCloseable {
|
||||
|
||||
/**
|
||||
* Прерывает текущий исполняемый ход (best-effort: LLM-stream прибивается,
|
||||
* in-flight tool может доехать или отвалиться). В [events] эмитится
|
||||
* [Event.Interrupted], затем может начаться следующий ход из очереди.
|
||||
* in-flight tool может доехать или отвалиться). В `agent.outbox.conversationEvents`
|
||||
* эмитится `Event.Interrupted`, затем может начаться следующий ход из очереди.
|
||||
*
|
||||
* Если хода нет — no-op.
|
||||
*/
|
||||
suspend fun interrupt()
|
||||
|
||||
/**
|
||||
* Live-подписка на всё, что происходит в диалоге, начиная с [after].
|
||||
*
|
||||
* **Не реплеит** события, произошедшие до [after] — для бэкфилла
|
||||
* используй [getMessages]. Если [after] — момент последнего виденного
|
||||
* клиентом события, поток продолжается «с того места».
|
||||
*
|
||||
* Подписки независимы: каждый вызов возвращает свой [Flow], отмена одного
|
||||
* не влияет на других подписчиков и на сам диалог.
|
||||
*/
|
||||
fun events(after: Instant): Flow<Event>
|
||||
|
||||
/** Страница истории: не более [limit] сообщений после [after], начиная с [offset]-го. */
|
||||
suspend fun getMessages(after: Instant, offset: Int, limit: Int): List<Message>
|
||||
|
||||
@@ -83,7 +84,7 @@ interface Conversation : AutoCloseable {
|
||||
|
||||
/**
|
||||
* Освобождает ресурсы диалога (подписки, сетевые хэндлы). Идемпотентно.
|
||||
* После [close] дальнейшие вызовы [send]/[interrupt]/[events]/[getMessages]/[rename] не определены.
|
||||
* После [close] дальнейшие вызовы [send]/[interrupt]/[getMessages]/[rename] не определены.
|
||||
*/
|
||||
override fun close()
|
||||
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
package pw.binom.agentik.proto
|
||||
|
||||
/**
|
||||
* Backward-compat typealias: `Event` теперь живёт в `:outbox-api`.
|
||||
*
|
||||
* Существующие импорты `pw.binom.agentik.proto.Event` продолжают
|
||||
* работать транспарентно. `Conversation.events(after): Flow<Event>` в
|
||||
* `:proto.Conversation` теперь фактически возвращает
|
||||
* `pw.binom.agentik.outbox.Event` — тот же тип, другое имя.
|
||||
*/
|
||||
typealias Event = pw.binom.agentik.outbox.Event
|
||||
@@ -45,9 +45,22 @@ sealed interface Message {
|
||||
override val date: Instant
|
||||
) : Message
|
||||
|
||||
/**
|
||||
* Результат вызова тула. Приходит в историю `getMessages` после завершения хода.
|
||||
* [id] совпадает с [ToolCall.id], к которому относится результат, и
|
||||
* с id соответствующего `MessageRecord.ToolResult` в journal.
|
||||
*
|
||||
* [toolName] денормализован из [ToolCall.toolName] — UI рендерит
|
||||
* имя тула в строке результата без отдельной `Map<id, name>`.
|
||||
*/
|
||||
@Serializable
|
||||
@SerialName("tool_result")
|
||||
class ToolResult(override val id: String, val result: String?, override val date: Instant) : Message
|
||||
class ToolResult(
|
||||
override val id: String,
|
||||
val toolName: String? = null,
|
||||
val result: String?,
|
||||
override val date: Instant,
|
||||
) : Message
|
||||
|
||||
/**
|
||||
* Ход завершился ошибкой (LLM, инициализация движка или иная отказоустойчивая
|
||||
|
||||
Reference in New Issue
Block a user