Agent message
A live thread: agent leads (left), user trails (right)
The Gateway routes every session through the credential store for credentials. Want me to wire the retry/backoff next?
Yes. Exponential backoff, cap at 30s. Show me the diff first.
On it. Reading SseClient.kt, then patching the reconnect loop:
Here’s the backoff helper:
fun nextDelay(attempt: Int): Long = minOf(30_000, 1_000 shl attempt) .plus(Random.nextLong(250))
- Backoff is 1s → 30s with jitter.
- Replays from
lastSeqon reconnect.
Applying the patch and re-running the SSE tests
System divider + a failed turn (status is warm + icon + text, never color alone)
Reconnected. The stream is healthy again.
Retry fired 0 times · tab to Copy / Thinking / Retry. The island is live.
// Web: React, consuming @dizyx/nockerl-tokens. (Web is the laggard platform;// this is the intended @dizyx/nockerl-react API (not yet exported; promotion tracked),// modeled on the shipped Compose MessageBubble.)
export function Thread() { return ( <> <AgentMessage role="assistant" model="Large 2.1" timestamp="10:21 AM"> <p>Reading the reconnect loop, then patching the backoff:</p> <ToolCallBlock family="shell-fs" name="Read SseClient.kt" status="done" elapsed="0.4s" /> </AgentMessage>
<AgentMessage role="user"> <p>Cap it at 30s.</p> </AgentMessage>
{/* Live SSE target. The typing dots render after the body */} <AgentMessage role="assistant" model="Large 2.1" showHeader={false} streaming> <p>Applying the patch…</p> </AgentMessage>
{/* Failed turn: a filled status-error disc + icon + text + a real Retry */} <AgentMessage role="assistant" model="Large 2.1" error onRetry={resend}> <Failure>Stream failed after 3 retries.</Failure> </AgentMessage> </> );}// Android: Jetpack Compose (canonical). com.nockerl.app.chat.ui.MessageBubbleimport com.nockerl.app.chat.ui.MessageBubbleimport com.nockerl.app.chat.domain.ChatMessageimport com.nockerl.app.chat.domain.MessageRole
// One bubble per ChatMessage. role drives alignment (ASSISTANT = leading/left,// USER = trailing/right); the body renders message.blocks in order: markdown// text, tool-call panels, thinking, images. The MessageList passes showHeader =// !isContinuation and the live-stream id as isStreaming.MessageBubble( message = message, // role, blocks, model, timestamp, tools showHeader = !isContinuation, // collapse header on a same-speaker run isStreaming = message.id == streamingId, // live SSE target → live markdown + dot bubbleMaxWidth = layout.bubbleMaxWidth, // 320.dp phone cap; wider on tablets modelLabel = uiState.modelLabel, // resolved name, e.g. "Large 2.1" onImageClick = onImageClick, // hands a Coil model up for the lightbox)// macOS SwiftUI. NOT YET IMPLEMENTED: Nockerl Voice is a dictation/transcription// surface and ships no conversational chat bubble. This is the INTENDED API,// derived from the laws + the Voice component vocabulary (NockerlTheme, the card// material) so the assistant turn matches Android when a chat surface lands.AgentMessage(role: .assistant, model: "Large 2.1", timestamp: "10:21 AM") { Text("Reading the reconnect loop, then patching the backoff:") ToolCallBlock(family: .shellFs, name: "Read SseClient.kt", status: .done, elapsed: "0.4s")}
AgentMessage(role: .user) { Text("Cap it at 30s.") }
AgentMessage(role: .assistant, model: "Large 2.1", showHeader: false, isStreaming: true) { Text("Applying the patch…")}Parameters
Section titled “Parameters”| Prop | Type | Default | Description |
|---|---|---|---|
role * | 'assistant' | 'user' | Speaker. assistant → leading/left (soft lifted plane); user → trailing/right (cyan jewel card). | |
model | string | Resolved model name in the identity header (assistant only), e.g. "Large 2.1". | |
timestamp | string | Short time string in the header (e.g. "10:23 AM"). | |
showHeader | boolean | true | Show the identity header. Pass false on a consecutive same-speaker turn to collapse it. |
streaming | boolean | false | Live SSE target. Renders the typing dots after the body (frozen under reduced-motion). |
error | boolean | false | Failed turn: a filled status-error disc + the error material. Always paired with an icon + text, never color-alone. |
onRetry | () => void | Retry handler for a failed turn; renders a real, focusable Retry button. | |
children | ReactNode | Ordered body: markdown text, ToolCallBlock, code, list, a reasoning block. |
| Parameter | Type | Default | Description |
|---|---|---|---|
message * | ChatMessage | The message model, carrying role (drives alignment), blocks, provider/model, timestamp, tools, toolProgress. | |
showHeader | Boolean | true | Render the model · timestamp header. The list passes !isContinuation to collapse it on a same-speaker run. |
accentColor | Color | Color.Unspecified | Optional override for the user bubble fill. Unspecified → the default cyan userCard gradient. |
isStreaming | Boolean | false | Live SSE target. Renders live, flash-free markdown and the pulsing dot while tokens arrive. |
bubbleMaxWidth | Dp | 320.dp | Upper bound on bubble width. Phone cap by default; the list passes a wider value on tablets. |
modelLabel | String? | null | Resolved display name; falls back to provider · model when not cached. |
onImageClick | OnImageClick | NoopOnImageClick | Invoked with a Coil-compatible model when an inline image is tapped (opens the lightbox). |
Not yet implemented: Nockerl Voice is a dictation/transcription surface with no
chat thread. The signature below is the intended SwiftUI API (a ViewBuilder
content closure), derived from the laws + the shipped Compose MessageBubble.
| Parameter | Type | Default | Description |
|---|---|---|---|
role * | MessageRole | .assistant → leading/left; .user → trailing/right. Drives alignment + bubble material. | |
model | String? | nil | Resolved model name in the identity header (assistant only). |
timestamp | String? | nil | Short time string in the header. |
showHeader | Bool | true | Show the identity header; false collapses it on a same-speaker run. |
isStreaming | Bool | false | Live target. Renders the typing dots (frozen when Reduce Motion is on). |
content * | () -> Content | A @ViewBuilder body: text + tool / code / reasoning blocks. |