Conversation
Handle the cascade_step_start / cascade_escalation SSE events on the agent stream: keep the thinking indicator alive during buffered steps and show a generic "Thinking harder…" indicator on escalation. No model names, confidence, or cost are exposed to end users; unknown SSE event types remain gracefully ignored.
A mid-stream error after a cascade_escalation left isEscalating stuck true, so the generic "Thinking harder…" indicator persisted at rest and bled into the next turn (it outranks the thinking/typing indicators). Reset it in the send error handler and at the start of each send so every turn begins clean.
|
Warning Review limit reached
Next review available in: 51 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (6)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Pull request overview
Adds support in the React chat widget for backend “model cascade” SSE events by tracking an escalation state and showing a generic “Thinking harder…” indicator during model escalation, while keeping the stream handler forward-compatible with unknown events.
Changes:
- Extend SSE event types with
cascade_step_start/cascade_escalation. - Add
isEscalatingstate + reducer action, and render anEscalatingIndicatorin the widget. - Add unit tests covering cascade event parsing behavior and reducer escalation resets.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| src/types.ts | Adds cascade SSE event literals (and needs a small type adjustment to keep unknown events type-safe). |
| src/store/chat-store.tsx | Introduces isEscalating in state, reducer handling, and reset behavior. |
| src/store/chat-store-cascade.test.tsx | Adds reducer tests for the new escalation flag and reset behavior. |
| src/components/Indicators.tsx | Adds EscalatingIndicator UI component for “Thinking harder…” display. |
| src/components/ChatWidget.tsx | Handles cascade SSE events and updates indicator rendering logic. |
| src/api/chat-api-cascade.test.ts | Adds a streaming parser test for cascade + unknown event passthrough behavior. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| | "thinking" | ||
| | "cascade_step_start" | ||
| | "cascade_escalation" | ||
| | "done" | ||
| | "error"; |
| vi.fn(async () => ({ ok: true, statusText: "OK", body })), | ||
| ); | ||
|
|
||
| const events = []; |
|
Superseded by The cascade events were already parsed there ( Two parts were deliberately not taken, both verified against the EDDI Java:
The copy changed on review: Thanks — the idea was right, and the escalation wait really is silent (a buffered cascade emits its whole answer as one token). |
Compared the two other open PRs against this branch. #27 (model-cascade hint) and #28 (multimodal attachments) are now superseded and can be closed; what was genuinely missing is here, and what they got wrong is deliberately not. Attachments — three real gaps, all verified against the EDDI Java: - forwardableInline. The upload cap and the forward cap are different limits (20 MiB vs 10 MiB). A file in between is stored, returns 201, and is then dropped at forward time with a ForwardSkipException. That skip lands in attachments:errors, which every writer marks setPublic(false) — so the turn can never reveal it and the agent simply answers as if no file existed. The upload response is the only signal we get; the chip and the sent bubble now say so. - deleteAttachment had zero callers. Removing a chip dropped it client-side only, leaving the blob in the store against a 50-file / 100 MB per-conversation quota until nothing further could be attached. - Error copy named the wrong cause. ATTACHMENT_REJECTED is a catch-all over MIME rejection, both quotas and empty files; we printed "that file type is not accepted" for all of them. errorPayload() now parses the {error, code} envelope and prefers the server's own text, which is the actionable half. Also: multi-file select with a batch-aware cap (the picker was single-file while the cap is 5), placeholder chips while bytes are in flight, an empty fileName is omitted so the extractor's own backfill can fire, and the chip strip is a live region — an upload failure posts into the transcript, which is not announced. Cascade — ported the "Thinking harder…" hint, not the commit. A buffered cascade emits its whole answer as one token, so the wait after an escalation is silent. Two things in #27 stay out: its unguarded SET_THINKING on cascade_step_start defeats this branch's tokenCount guard (a guaranteed-accept step can stream live, time out, and be followed by the next step's start), and it is built on master's phantom `thinking` event, which this branch removed as non-existent. The reset lives in the FINISH_STREAMING reducer so every exit path is covered at once. Also fixes an unguarded continuation: a stream abandoned by New Conversation dispatched FINISH_STREAMING into the conversation that replaced it — the guard sat one statement too low. Currently benign, so it has no dedicated test. Untracks grep.exe.stackdump and git-log-output.txt, committed by accident. Deliberately not taken, with reasons in the handoff doc: #28's object-URL lifecycle (this branch creates none), its positional undo/redo preservation (silently fails on redo), its reducer-side revocation, its hardcoded 20 MiB client pre-check (the limit is deployment config), and its list/download/ delete-all endpoints (dead exports in #28 too, and the download URL cannot carry this branch's bearer token). 254 tests (was 232), tsc --noEmit clean, production build succeeds. Each new fix was checked by reverting it and confirming the matching test fails. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Compared the two other open PRs against this branch. #27 (model-cascade hint) and #28 (multimodal attachments) are now superseded and can be closed; what was genuinely missing is here, and what they got wrong is deliberately not. Attachments — three real gaps, all verified against the EDDI Java: - forwardableInline. The upload cap and the forward cap are different limits (20 MiB vs 10 MiB). A file in between is stored, returns 201, and is then dropped at forward time with a ForwardSkipException. That skip lands in attachments:errors, which every writer marks setPublic(false) — so the turn can never reveal it and the agent simply answers as if no file existed. The upload response is the only signal we get; the chip and the sent bubble now say so. - deleteAttachment had zero callers. Removing a chip dropped it client-side only, leaving the blob in the store against a 50-file / 100 MB per-conversation quota until nothing further could be attached. - Error copy named the wrong cause. ATTACHMENT_REJECTED is a catch-all over MIME rejection, both quotas and empty files; we printed "that file type is not accepted" for all of them. errorPayload() now parses the {error, code} envelope and prefers the server's own text, which is the actionable half. Also: multi-file select with a batch-aware cap (the picker was single-file while the cap is 5), placeholder chips while bytes are in flight, an empty fileName is omitted so the extractor's own backfill can fire, and the chip strip is a live region — an upload failure posts into the transcript, which is not announced. Cascade — ported the "Thinking harder…" hint, not the commit. A buffered cascade emits its whole answer as one token, so the wait after an escalation is silent. Two things in #27 stay out: its unguarded SET_THINKING on cascade_step_start defeats this branch's tokenCount guard (a guaranteed-accept step can stream live, time out, and be followed by the next step's start), and it is built on master's phantom `thinking` event, which this branch removed as non-existent. The reset lives in the FINISH_STREAMING reducer so every exit path is covered at once. Also fixes an unguarded continuation: a stream abandoned by New Conversation dispatched FINISH_STREAMING into the conversation that replaced it — the guard sat one statement too low. Currently benign, so it has no dedicated test. Untracks grep.exe.stackdump and git-log-output.txt, committed by accident. Deliberately not taken, with reasons in the handoff doc: #28's object-URL lifecycle (this branch creates none), its positional undo/redo preservation (silently fails on redo), its reducer-side revocation, its hardcoded 20 MiB client pre-check (the limit is deployment config), and its list/download/ delete-all endpoints (dead exports in #28 too, and the download URL cannot carry this branch's bearer token). 254 tests (was 232), tsc --noEmit clean, production build succeeds. Each new fix was checked by reverting it and confirming the matching test fails.
Tasteful model-cascade signal for end users
The backend model cascade emits
cascade_step_start/cascade_escalationevents on the same/agents/{conversationId}/streamendpoint the widget already consumes. This PR handles them:Branched off the v6 line (
feature/version-6.0.0), which has since merged intomaster(#25) — so this targetsmaster.Verification
typecheck✓ ·vitest50 tests ✓