Package com.axelor.ai.chat.stream
Class ChatStreamRegistry
java.lang.Object
com.axelor.ai.chat.stream.ChatStreamRegistry
Registry of in-flight chat runs, backed by the unified cache API: in-memory under caffeine,
distributed under Redisson/JCache — chosen by the app's cache provider config, nothing
AI-specific.
Layout in the streams cache (single writer per run, all values JSON):
chat:<chatId>— claim{"streamId","deadline","done","initiatorUser","rebuildsTurn"}; staleness is decided bydeadline, cache TTL is only garbage collectionrun:<streamId>#<seq>— chunk of concatenated SSE frames, seq from 1run:<streamId>#head—{"seq","done"}run:<streamId>#turn— the assistant message id from the run's opening frame; with the claim'srebuildsTurn, which turn a resuming client must let the replay rebuildtool:<chatId>#<callId>— pending client-tool call{"toolName","ownerUser", "claimedBy","state"}; written before the pending part is persisted. Execution eligibility comes from a claimed record only — never from the frame stream or from record absence (clients read absence as "not mine to run", the server as expired). The cache TTL is the only expiry.
A single shared topic carries {"kind","streamId"} envelopes as pure wake-up signals;
subscribers always read data from the cache and dedupe by seq, so a lost publish never loses
data. All topic access stays in this class — DistributedFactory.getTopic(java.lang.String) scopes topic
names by caller class.
-
Nested Class Summary
Nested ClassesModifier and TypeClassDescriptionstatic final recordA client-tool call the run paused on. -
Constructor Summary
Constructors -
Method Summary
Modifier and TypeMethodDescriptionabsenceMark(String chatId, String callId) When the initiator was first seen absent for this pending call, or null.Claims the chat for a new run.Claims the chat for a run initiated byinitiatorUser.Claims the chat, recording whether a replay rebuilds the turn (seeChatStreamRegistry.Claim).booleanRequests cooperative cancellation.booleanCancels only whenuserinitiated the active run.booleanClaims the call for one of the owner's sessions.voidclearAbsenceMark(String chatId, String callId) booleanTrue when a live run holds the chat's claim.voidmarkAbsence(String chatId, String callId, long epochMillis) voidputPendingCall(String chatId, String callId, String toolName, String ownerUser) Registers a pending client-tool call.readPendingCall(String chatId, String callId) The pending-call record, or null when absent (expired, cancelled, or never registered).rebuildingMessageId(String chatId) The message a resuming client must drop and let the replay produce again, or null when there is nothing to hand over: no live run, a continuation, or no opening frame written yet.voidremovePendingCall(String chatId, String callId) Drops the record (the call was cancelled/superseded) — absence means "not mine to run".booleanresolveCall(String chatId, String callId, String user, String sessionId) Resolves the call — one-time CAS: only the claiming session of the owning user wins, exactly once.Returns a subscription replaying the chat's latest run (live or recently finished and still cached), or null when there is nothing to replay.
-
Constructor Details
-
ChatStreamRegistry
@Inject public ChatStreamRegistry(com.fasterxml.jackson.databind.ObjectMapper mapper)
-
-
Method Details
-
begin
Claims the chat for a new run. ThrowsAlreadyStreamingExceptionif a live run holds the claim (not done, deadline not passed). A stale deadline means the owning node died — the claim is overwritten. -
begin
Claims the chat for a run initiated byinitiatorUser. -
begin
public ActiveStream begin(String chatId, Duration timeout, String initiatorUser, boolean rebuildsTurn) Claims the chat, recording whether a replay rebuilds the turn (seeChatStreamRegistry.Claim). -
subscribe
Returns a subscription replaying the chat's latest run (live or recently finished and still cached), or null when there is nothing to replay. -
rebuildingMessageId
The message a resuming client must drop and let the replay produce again, or null when there is nothing to hand over: no live run, a continuation, or no opening frame written yet. -
isActive
True when a live run holds the chat's claim. -
cancel
Requests cooperative cancellation. Returns true if a live run was signalled. -
cancel
Cancels only whenuserinitiated the active run. This lets a shared-chat prompter stop their own work without allowing one driver to interrupt another driver's run. -
putPendingCall
Registers a pending client-tool call. Must precede persisting the pending part. -
readPendingCall
The pending-call record, or null when absent (expired, cancelled, or never registered). -
claimCall
Claims the call for one of the owner's sessions. CAS under the per-key lock: only apendingrecord owned byuseris claimable; a repeat claim by the same session is idempotent (absorbs a lost response + retry). Everything else — absent record, other user, other session — loses. -
resolveCall
Resolves the call — one-time CAS: only the claiming session of the owning user wins, exactly once. Late duplicates and wrong-session submissions return false. -
removePendingCall
Drops the record (the call was cancelled/superseded) — absence means "not mine to run". -
absenceMark
When the initiator was first seen absent for this pending call, or null. -
markAbsence
-
clearAbsenceMark
-