Package com.axelor.ai.chat
Class ChatTurnPersistence
java.lang.Object
com.axelor.ai.chat.ChatTurnPersistence
Owns AI chat persistence: loads history, saves the user row, progressively upserts the assistant
row as the agent loop runs, and finalizes on stream end. The caller (the streamer) holds the
Turn on the stack and passes it to each method.-
Nested Class Summary
Nested ClassesModifier and TypeClassDescriptionstatic final recordResult of preparing a stream: aTurnto thread through the frames plus a possibly modified request (with history preloaded, the user message saved, or — for sub-agent resume — an isolated child context). -
Method Summary
Modifier and TypeMethodDescriptionvoidappend(Turn turn, ChatRequest request, UIMessage message) Called for each ASSISTANT / TOOL UIMessage produced by the streamer.begin(ChatRequest request) Called at stream() start.voidend(Turn turn, ChatRequest request) Called at stream end (onComplete or onError).voidexpireStalePendingCalls(String chatUuid) Public expiry touchpoint for read paths (the/messagesendpoint — which also covers client attach/replay, since attaching always refetches messages first).static StringfindConsultCallId(List<UIPart> parts, String subAgentCode) Walkspartsfor the most recentconsult_agenttool whoseinput.agentmatchessubAgentCodeand returns its toolCallId — used to re-attach updates from a sub-agent resume or cascade to the same tool without a DB lookup.isolateAgentView(List<UIPart> parts, String subAgentCode) Builds the sub-agent's isolated view from a single row's parts: a synthetic USER (the consult question pulled fromconsult_agent's input) plus an ASSISTANT carrying the sub-agent's own parts with theaxelormarker stripped.voidkeep(Turn turn, ChatRequest request, UIPart part) Keeps a part the stream itself adds to the turn, such as a refusal's: it marks the current step, not a step of its own.voidrecordFinish(Turn turn, FinishReason reason) Records the terminalFinishReasonobserved on the stream.toUIMessage(AiChatMessage msg) The UIMessage view of a persisted row, with server-derived author/agentCode metadata.
-
Method Details
-
begin
Called at stream() start. Loads or creates the chat, saves the user row when applicable, preloads the turn from the latest ASSISTANT row on continuation, and returns a fresh or preloadedTurn. Returnsnew Prepared(request, new Turn())in the few edge cases where persistence is not relevant (programmatic / null id / empty messages / no user). -
append
Called for each ASSISTANT / TOOL UIMessage produced by the streamer. Appends or merges into the turn's parts and progressively upserts the row. Parts produced under a sub-agent (request.isSubAgent()) get stamped withproviderMetadata.axelor.agentCodeso the provider adapter can filter them out of LLM replay while the wire shape stays AI-SDK valid. -
keep
Keeps a part the stream itself adds to the turn, such as a refusal's: it marks the current step, not a step of its own. -
recordFinish
Records the terminalFinishReasonobserved on the stream. -
end
Called at stream end (onComplete or onError). Sanitizes any still-pending tool parts unless the stream paused legitimately withTOOL_CALLS— even on a client disconnect, a persisted approval/client-tool pause must survive so the next request can resume it. -
expireStalePendingCalls
Public expiry touchpoint for read paths (the/messagesendpoint — which also covers client attach/replay, since attaching always refetches messages first). Persists the injected errors so a freshly attached client sees the call as resolved-with-error and nobody re-runs it. -
toUIMessage
The UIMessage view of a persisted row, with server-derived author/agentCode metadata. -
findConsultCallId
Walkspartsfor the most recentconsult_agenttool whoseinput.agentmatchessubAgentCodeand returns its toolCallId — used to re-attach updates from a sub-agent resume or cascade to the same tool without a DB lookup. Returns null when no such tool is found. -
isolateAgentView
Builds the sub-agent's isolated view from a single row's parts: a synthetic USER (the consult question pulled fromconsult_agent's input) plus an ASSISTANT carrying the sub-agent's own parts with theaxelormarker stripped. Empty when the row has no matchingconsult_agenttool.
-