Skip to content
This repository was archived by the owner on Sep 17, 2026. It is now read-only.

feat(cascade): tasteful 'thinking harder' hint on model escalation - #27

Closed
ginccc wants to merge 2 commits into
masterfrom
feat/model-cascade-ui
Closed

ginccc wants to merge 2 commits into
masterfrom
feat/model-cascade-ui

Conversation

@ginccc

@ginccc ginccc commented Jul 3, 2026

Copy link
Copy Markdown
Member

Tasteful model-cascade signal for end users

The backend model cascade emits cascade_step_start / cascade_escalation events on the same /agents/{conversationId}/stream endpoint the widget already consumes. This PR handles them:

  • Robustness — the SSE parser and event handler now recognise the cascade events; unknown/forward-compatible events remain gracefully ignored (verified with a new test).
  • UX — a generic "Thinking harder…" indicator appears while the cascade escalates to a more capable model. No model names, confidence, or cost are shown to end users — that detail is admin-only in EDDI-Manager.
  • Correctness — the escalation indicator resets on stream error and at the start of each turn, so it never gets stuck on screen or bleeds into the next message.

Branched off the v6 line (feature/version-6.0.0), which has since merged into master (#25) — so this targets master.

Verification

typecheck ✓ · vitest 50 tests ✓

ginccc added 2 commits July 3, 2026 13:12
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.
@coderabbitai

coderabbitai Bot commented Jul 3, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@ginccc, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 51 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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 configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 33c2f3bf-e01c-44b2-9a48-8a714ca8f9c9

📥 Commits

Reviewing files that changed from the base of the PR and between b2c1bd0 and e22ace4.

📒 Files selected for processing (6)
  • src/api/chat-api-cascade.test.ts
  • src/components/ChatWidget.tsx
  • src/components/Indicators.tsx
  • src/store/chat-store-cascade.test.tsx
  • src/store/chat-store.tsx
  • src/types.ts
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/model-cascade-ui

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Copilot AI 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.

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 isEscalating state + reducer action, and render an EscalatingIndicator in 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.

Comment thread src/types.ts
Comment on lines 44 to 48
| "thinking"
| "cascade_step_start"
| "cascade_escalation"
| "done"
| "error";
vi.fn(async () => ({ ok: true, statusText: "OK", body })),
);

const events = [];
@ginccc

ginccc commented Jul 22, 2026 •

Copy link
Copy Markdown
Member Author

Superseded by feat/chat-ui-eddi-feature-parity (#29) — closing.

The cascade events were already parsed there (types.ts, chat-api.ts); what this PR uniquely added was the user-visible hint, and that has now been ported in commits 2ad34f0 / c322ead.

Two parts were deliberately not taken, both verified against the EDDI Java:

  • The unguarded SET_THINKING on cascade_step_start. feat(chat-ui): EDDI feature parity since the HITL framework #29 raises the thinking indicator from task_start behind a tokenCount === 0 guard, so it can never reappear once text has started. CascadingModelExecutor lets a guaranteed-accept step stream live, time out mid-stream, and be followed by the next step's start — so this would have covered a bubble that already had content. Only cascade_escalation now sets state.
  • The phantom thinking SSE event this branch is built on. RestAgentEngineStreaming emits eight events and none is thinking; feat(chat-ui): EDDI feature parity since the HITL framework #29 removed it because the indicator was only ever cleared by the first token, leaving it spinning forever on a turn that failed or paused before producing one.

The copy changed on review: ⚡ Thinking harder… → 🧠 Taking a closer look…. ⚡ conventionally reads as fast, and this state exists precisely because the answer is taking longer; "harder" implied the first attempt was half-hearted. It is also now a prop on ThinkingIndicator rather than a separate component, so React reconciles the same element instead of remounting and replaying the entrance animation mid-wait.

Thanks — the idea was right, and the escalation wait really is silent (a buffered cascade emits its whole answer as one token).

@ginccc ginccc closed this Jul 22, 2026
ginccc added a commit that referenced this pull request Jul 22, 2026
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>
ginccc added a commit that referenced this pull request Jul 22, 2026
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.
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants