Add opt-in message pagination to remote chat HTTP API#498
Merged
ericdallo merged 3 commits intoJun 13, 2026
Merged
Conversation
ericdallo
reviewed
Jun 12, 2026
|
|
||
| **Multi-session** — You can connect to multiple ECA instances simultaneously. Each connection appears as a tab in the top bar. | ||
|
|
||
| ## HTTP API: message pagination |
Member
There was a problem hiding this comment.
I don't think we need to add any of this, this doc is for ECA users, not a explanation how ECA clients remote connection work, more to teach how users can use ECA remote feature.
GET /api/v1/chats/:id accepts limit/before/after query params with opaque cursors and an after=lastCompaction sentinel, returning a messagesMeta object with before/after/compaction cursors.
Fetching a chat's history previously had no pull API on the editor
(JSON-RPC) side: chat/open always replayed the entire history as a
stream, and the only windowed access lived on the remote HTTP endpoint.
For long chats that means streaming tens of thousands of messages on
every open, and the optimization was reachable only by the web client.
Unify both transports around one transport-agnostic windowing core
(eca.features.chat.history) that operates on raw persisted messages:
opaque base64url "index.checksum" cursors that resolve by index when the
checksum still matches and rescan by checksum otherwise (tolerating
flag-insert/remove shifts), a lastCompaction sentinel, and a
compaction-cursor. Pages are newest-anchored so opening a chat shows the
latest context; an opaque after cursor pages forward instead. before/
after cursors are computed against full history so paging transparently
crosses the compaction boundary, and are nil at the ends.
On top of the core:
- New JSON-RPC chat/history request returns a window inline as
{contents, meta} where contents are the same ChatContent items
chat/contentReceived streams, so editors reuse their renderer. Inline
(not streamed) so the client can place an older page deterministically
while live updates keep arriving on the stream.
- chat/open gains optional limit/before/after to replay only a window and
return meta; no params replays the full history as before.
- Remote HTTP GET /api/v1/chats/:id keeps its opt-in query-param
pagination, now backed by the shared core.
To feed both the stream and the inline response from one place, the
message->wire-content transform (including subagent expansion) is lifted
out of send-chat-contents! into a pure messages->contents collector, so
replay behavior is unchanged.
b1570cc to
0689f7f
Compare
ericdallo
approved these changes
Jun 13, 2026
ericdallo
left a comment
Member
There was a problem hiding this comment.
looks good, also I checked with eca-emacs if it will fit good and looks good too.
I'll make the changes in editor clients starting with emacs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
GET /api/v1/chats/:id accepts limit/before/after query params with opaque cursors and an after=lastCompaction sentinel, returning a messagesMeta object with before/after/compaction cursors.
Add opt-in pagination via limit/before/after query params. When none are
present the response is unchanged, so existing clients keep working; the
new behavior is purely additive (a sibling messagesMeta object alongside
the existing messages array), which the object-shaped response already
accommodates without a breaking change.
Cursors are opaque base64url tokens encoding
<index>.<checksum>, wherethe checksum fingerprints the message. Resolution trusts the encoded
index when its checksum still matches, otherwise rescans by checksum, so
cursors survive mid-history shifts (e.g. flag insert/remove) and only a
genuinely removed message (rollback/clear) yields 409 cursor_expired,
prompting the client to refetch. Token pruning rewrites content in place
and never shifts positions, so the active window stays stable.
Pages are newest-anchored so opening a chat shows the latest context,
with before/after cursors computed against full history so paging
transparently crosses the compaction boundary. The after=lastCompaction
sentinel returns the active context since the last compaction in a single
call, fulfilling the original "messages since last compaction" goal
without a dedicated flag; before=lastCompaction returns the summarized
history. Cursors are nil at the ends (nil-punning) instead of separate
hasMore/hasBefore fields.
Request examples
Response shape
{ "id": "abc123", "title": "My chat", "status": "idle", "messages": [ /* ...the page... */ ], "messagesMeta": { "total": 412, // total messages in full history "returned": 50, // messages in this page "beforeCursor": "eyJpZHgiOjEyfQ", // pass as `before` to load older (null at start) "afterCursor": null, // pass as `after` to load newer (null at tail) "compactionCursor": "eyJpZHgiOjUwfQ" // boundary of last compaction (null if never compacted) } // ...model, agent, variant, pendingToolCalls, etc. }A stale cursor returns: