Skip to content

🔌 refactor: Drive useSSE Through the ChatTransport - #16336

Merged
berry-13 merged 6 commits into
canaryfrom
berry-13/chat-transport
Sep 25, 2026
Merged

berry-13 merged 6 commits into
canaryfrom
berry-13/chat-transport

Conversation

@berry-13

@berry-13 berry-13 commented Sep 24, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

useSSE parsed frames, retried a 401 and synthesized cancel in the same closure that ran the chat handlers, so the wire could not be tested apart from React state. This moves it into client/src/hooks/SSE/transport/, stacked on #16335.

normalizeFrame tags each frame by the keys the hooks already checked, in the same order, and createSSETransport emits ChatEvents, including attachments, errors, the 401 retry and abort. useSSE switches on event.type and calls the same handlers with the same arguments; its signature is unchanged and the existing specs pass unmodified. One difference: a malformed frame is logged and skipped, where before its parse error dropped every frame behind it in the chunk.

createdHandler, which never read its argument, is retyped from TResData to the created frame. useResumableSSE is next.

Type of change

  • Refactor

Testing

Tested environments/configuration:

  • lc dev servers on this branch (shared librechat.yaml started with CONFIG_BYPASS_VALIDATION=true, since canary's schema rejects a key in it). No provider key in the dev environment is valid (OpenAI and Anthropic return 401), so no live streamed turn could run. An agents turn reached the provider and rendered the provider's 401 as the error card, which exercises the untouched resumable path and the retyped handlers. The Assistants endpoint, the only one useSSE serves, is user_provided with no key on the dev account, so useSSE itself is covered by specs only.

Automated tests:

  • Added __tests__/frames.spec.ts (key order, side channels, unknown events as steps, legacy content and text frames, empty frames) and __tests__/sse.spec.ts (real sse.js over a fake XHR: created, sync and final in order; named and in-band attachments; malformed JSON; server error event; non-JSON failure body; 401 refresh and retry; abort mid-stream; abort after close; already-aborted signal; abort during a 401 refresh; a second 401)
  • cd client && npx jest hooks/SSE data-provider/SSE: 16 suites, 466 passed, including the useResumableSSE.spec.ts cases that drive useSSE through final, cancel and error
  • cd client && npm run typecheck: clean
  • npx eslint, npx prettier --check, node scripts/sort-imports.mts --check on every touched file: clean

Screenshots / recordings

No user-facing change

Risk / compatibility

The transport also refreshes the token at most once per stream (a second 401 now reaches the error path instead of looping), and stopping a turn while its refresh is pending now cancels it, where before the retry reopened the stream and sent the turn anyway. Both behaviors predate this change.

useSSE only serves the Assistants endpoints. It now ignores steering and label side channels instead of passing them to the step handler, which only warned about them; only agent runs emit those, and agent runs stream through useResumableSSE. For the same reason, an in-band { event: 'attachment' } frame now reaches the attachment handler on this path too, though only resumable streams send that shape.

Checklist

  • I reviewed my own changes
  • Relevant tests have been added or updated
  • Existing relevant tests pass
  • The change does not introduce new warnings or errors
  • Required documentation PR: N/A

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 24, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-09-25T07:29:26.456319Z c3efa04 New commits
🔒 Security Review ✅ Completed 2026-09-24T22:26:39.413493Z 6180094 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@berry-13
berry-13 force-pushed the berry-13/chat-transport branch 2 times, most recently from a725610 to 19c7ba7 Compare September 24, 2026 23:06

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 19c7ba7c77

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +63 to +64
if (signal.aborted) {
return;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Settle the caller when aborting an auth refresh

When an Assistants request receives a 401 and the user presses Stop while refreshToken() is pending, the underlying SSE is already closed, so the abort listener does not emit abort; this early return then suppresses the refreshed/error path as well. The Stop path only clears submissions, while handleAbort/handleError are responsible for resetting isSubmitting and the stop button, leaving the chat permanently shown as generating until another reset or reload. Ensure aborting this refresh still settles the caller's UI state without reopening the stream.

AGENTS.md reference: AGENTS.md:L31-L34

Useful? React with 👍 / 👎.

@berry-13 berry-13 Sep 24, 2026 •

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 861e222. The transport now treats a pending 401 refresh as open, so an abort during it dispatches cancel and useSSE's abort path settles the submitting state; the retry still never re-streams. Covered by sse.spec.ts 'cancels, and stays closed, when aborted while a 401 refresh succeeds/fails'.

@berry-13
berry-13 added this pull request to stack #16346 September 24, 2026 23:16
Base automatically changed from berry-13/chat-transport-types to canary September 25, 2026 07:26
Move the sse.js connection, frame parsing, the named attachment and
error events, the 401 refresh-and-retry and the cancel-on-close signal
into client/src/hooks/SSE/transport. normalizeFrame tags each message
frame by the keys the hooks already checked, in the same order, and
createSSETransport emits the resulting ChatEvent union. useSSE now
switches on event.type and calls the same handlers with the same
arguments; its signature is unchanged.

A frame that is not JSON is now logged and skipped. Previously the
parse error escaped the listener and dropped every frame behind it in
the same chunk.

createdHandler never read its first argument, so it is typed as the
created frame it actually receives instead of TResData.
A refresh that resolved after the caller aborted called sse.stream()
again, reopening the request with the unmounted consumer's handlers; a
refresh that failed after abort dispatched the error path. Both now
return once the signal is aborted.
A retried connection that drew another 401 refreshed and reconnected
again with no bound, so a server rejecting the refreshed token looped.
The second 401 now reaches the error path.
The content handler never read userMessageId, which Assistants content
frames do not send, so it now takes ChatContentFrame.
The handlers accept narrower payload types than the wire frames now
admit. useSSE narrows at the call, where these frames already reached
the handlers unchanged before the transport existed.
sse.js marks the connection closed on the 401, so an abort during the
refresh skipped the cancel path and nothing reset the submitting state.
The stream now counts as open until the refresh settles.
@berry-13
berry-13 force-pushed the berry-13/chat-transport branch from 861e222 to c3efa04 Compare September 25, 2026 07:26
@berry-13
berry-13 merged commit a3541da into canary Sep 25, 2026
19 checks passed
@berry-13
berry-13 deleted the berry-13/chat-transport branch September 25, 2026 07:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant