Conversation
🦋 Changeset detectedLatest commit: dbd7d51 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
1 task
Large live sessions could not open in `kimi web`: the transcript baseline GET returned 200 but its body did not finish inside the web client's 30 s request budget, so the session rendered empty and retried forever. - Add an onSend hook that gzips JSON/text responses >= 1 KiB when the request accepts gzip (synchronous gzip level 1, because Fastify re-sends an empty body if an onSend hook is still pending when an async handler that called reply.send resolves). The Remote Control tunnel strips Accept-Encoding, so tunnel traffic is unchanged. - Cap the shell task outputTail in the live transcript projector (16 KiB bound, trimmed to 8 KiB via a task.upsert re-sync), matching the v1 projector; it was appended without limit and re-sent in full in every baseline response regardless of page_size.
REtoolsx
force-pushed
the
issue-3881-fix-da37a9
branch
from
September 19, 2026 17:02
4efefe6 to
dbd7d51
Compare
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.
Related Issue
Resolve #3881
Problem
See linked issue.
GET /sessions/{id}/transcripton a large live session can take longer than the web client's single 30 s request budget to deliver its body. Two server-side factors make the body larger than it needs to be:/api/v1JSON response uncompressed. Transcript JSON (tool outputs, thinking, prompts) compresses roughly 3× with gzip, and much more when tool output is repetitive. The Remote Control tunnel already gzips what it forwards, but direct LAN /--hostaccess and the local origin got no compression at all.outputTailgrew without bound: everyshell.outputchunk was appended to the in-memory task and re-sent in full inside the unpaginatedtaskscollection of every baseline response, regardless ofpage_size. The v1 projection (agentProjector) already capped this field at 8 KiB; the transcript projection did not.The remaining part of the issue — the web client's single 30 s budget for headers plus body and the endless identical retries — lives in the web UI source (code-app), which is not in this repository. This PR makes the server-side payload small enough that the budget is far less likely to be hit, and bounds the one collection that could grow indefinitely.
What changed
packages/kap-server/src/middleware/compression.ts(new): anonSendhook that gzips string/Buffer responses of JSON/text content types ≥ 1 KiB when the request'sAccept-Encodingallows gzip. It skips 204/206/304 and already-encoded responses, setsContent-Encoding: gzip+ the compressedContent-Length, and addsVary: Accept-Encoding(merged with an existingVary). Compression is synchronous (gzipSync, level 1) on purpose: this codebase's async route handlers callreply.send(...)without returning the reply, and Fastify'swrapThenablere-sends an empty body if anonSendhook is still pending (or returns a stream that has not ended) when the handler promise resolves — an async hook producedcontent-length: 0responses in a live test. Level 1 keeps a 30 MB body under ~0.2 s on the event loop while still giving ~2.7× on real transcript JSON.packages/kap-server/src/start.ts: register the hook after the request-logging and security-header hooks. The Remote Control tunnel stripsAccept-Encodingbefore forwarding to the local server, so tunnel traffic is unaffected (no double compression, and the tunnel's HTML/JS prefix rewrite still sees plain bodies). Node clients usinghttp.requestwithoutAccept-Encodingkeep receiving identity bodies;fetch/undici decompresses transparently.packages/kap-server/src/services/transcript/coreEventMap.ts:onShellOutputkeeps appending (appendops) while the tail is ≤ 16 KiB; once it would exceed that, the tail is trimmed to the last 8 KiB and re-synced with atask.upsertcarrying the trimmedoutputTail, so subsequentappendoffsets stay consistent for every subscriber. The hysteresis (16 KiB bound, 8 KiB keep) avoids sending an 8 KiB upsert for every chunk once the cap is reached.test/compression.test.ts(negotiation, type/size/status gating, Vary merging, round-trip viaapp.inject) and a new case intest/services/transcript.test.tscovering the tail bound and the re-sync upsert.Live before/after check on a
kimi webdev server, through a proxy that throttles/api/*responses (same synthetic session, one turn with 300 tool calls of non-repetitive output, same web bundle):transcript baseline retry failedloopSo the change roughly triples the transcript size a given link can deliver inside the client's 30 s budget; it does not remove the ceiling. The ceiling itself (one 30 s budget for headers + body, identical retries forever, and rendering multi-tens-of-MB transcripts) is client-side and needs a follow-up in the web UI repository.
gen-docs: no documented user-facing surface changed (no new flag or config), so no doc update.Checklist
/approve).gen-changesetsskill, or this PR needs no changeset.gen-docsskill, or this PR needs no doc update.