Repository navigation
Conversation
…ua#3488) createSSEStream recorded usage from flush() only. The Streams spec calls flush when the upstream ends and cancel when the readable is cancelled -- one or the other, never both -- so a client that went away mid-answer skipped recording entirely: no usage row, no request detail, nothing in Recent Requests, even though the provider had already generated (and been charged for) the partial answer. finalizeStream() already exists and is idempotent, and transform() already calls it on a terminal event for clients that close right after the answer. The remaining hole is the disconnect that arrives before any terminal event. Add cancel() and route it to the same function. Node path only: every disconnect signal in Next descends from the response's 'close', which Bun's node:http ServerResponse does not emit on a client hangup, so under start:bun nothing cancels this stream and finalizeStream() still only runs from flush().
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.
Closes the half of #3488 that is still open, rewritten against current
masterrather than replaying my earlier patch —masterhas since grownfinalizeStream(), which does most of what that patch'sfinishStream()did, so the old diff no longer applies and should not.What is left
finalizeStream()is called fromflush(), and fromtransform()on a terminal event — the comment above it says why: "a client that closes right after the terminal event cancels the reader, and flush() never runs."The transformer has no
cancel. The Streams spec callsflushwhen the upstream ends andcancelwhen the readable is cancelled, one or the other and never both, so a client that goes away before any terminal event still records nothing: no usage row, no request detail, nothing in Recent Requests — while the provider has already generated, and been charged for, the partial answer.The change is
cancel()routed to the samefinalizeStream(). Four lines of code; the rest is the comment explaining the runtime scope.Runtime scope. This is the Node path. Every disconnect signal in Next descends from the response's
'close'event, which Bun'snode:httpServerResponsedoes not emit on a client hangup, so understart:bunnothing cancels this stream andfinalizeStream()still only ever runs fromflush()— exactly as before. I would rather state that than let the fix look more complete than it is.Verification
(Both parts matter: without
--configthe@/alias does not resolve, and vitest 2.x cannot load anything that reachesopen-sse/translator/index.js.)Checked by reverting: removing
cancel()fails 2 of the 3 tests.Two things I measured that are worth writing down, because both cost me time and both would mislead the next reader:
TransformStreamis built without a readable queuing strategy, so its high-water mark is the default0and everywriter.write()blocks until something reads. My first two attempts wrote and only then read, and deadlocked five seconds at a time — the failure looks like a hang in the code under test, and is not.finalizedguard, and says so in a comment. I removed the guard to check, and it still passed. It cannot fail: oncetransform()has seen[DONE]the readable is already closed, socancel()is never invoked and there is no second call to suppress. The test still earns its place — it pins the user-visible property that a future cancel path firing unconditionally would break — but I am not claiming coverage I do not have.