Summary
Adds client-side daily message limits to the tool-router playground only (/tool-router page — "Chat with over 1000+ Apps"), tiered by plan:
| Plan | Daily Limit |
|---|
| HOBBY / Free | 20 |
| STARTER | 50 |
| GROWTH | 100 |
How it works:
usePlaygroundRateLimit hook (28 lines) tracks daily message count in localStorage, keyed by date
- On each message send,
recordMessage() checks the count against the user's plan-specific limit and increments
- When the limit is hit, a generic orange upgrade banner appears: "You have reached your playground message limit. Upgrade for more." — no plan name or limit number is shown
- Plan detection uses
authSession.project.org.plan to look up the limit from an inline LIMITS map
- While auth session is loading, limit is
Infinity (no enforcement) to avoid briefly blocking paid users before plan data arrives
- Unknown plan strings fall back to default limit of 20
New files:
usePlaygroundRateLimit.ts — self-contained hook (~28 lines); limits, storage key, and localStorage logic all inlined
PlaygroundRateLimitBanner.tsx — generic upgrade prompt (no props, no plan/limit details exposed)
Modified files:
- Tool-router ChatBox component — integrated rate limit check before message send;
<form> wrapped in a <div> to position the banner above
Not modified (intentional):
- Regular playground ChatBox and floating playground ChatBox are untouched — rate limiting applies only to tool-router per product request
Revision history
- v1: Single 20/day limit for free users only, with
isFreePlan guard
- v2: Fixed auth loading race condition; simplified hook API
- v3: Tiered limits by plan (HOBBY=10, STARTER=50, GROWTH=500); all plans rate-limited
- v4: Simplified banner — removed plan name and limit number from UI per product request
- v5: Radically simplified implementation — inlined all config into the hook, removed
constants.ts changes entirely
- v6: Updated limits to HOBBY=20, STARTER=50, GROWTH=100 per product request
- v7 (current): Scoped rate limiting to tool-router playground only; reverted regular and floating playground ChatBox files to main
Test plan
Tested locally by temporarily overriding the plan in the hook and setting localStorage counts to limit - 1:
- HOBBY plan (limit 20): Set localStorage count to 19 → sent 20th message (allowed) → sent 21st message (blocked, banner appeared with generic text). ✅
- STARTER plan (limit 50): Set localStorage count to 49 → sent 50th message (allowed) → sent 51st message (blocked, same generic banner). ✅
- Upgrade Plan button: Clicked button → navigated to
/settings/billing page with pricing plans visible. ✅
- Banner text: Verified no plan name or limit number shown — only "Daily message limit reached" and "You have reached your playground message limit. Upgrade for more." ✅

View test recording (rec-3050429707b34678aeb871035f52f039-edited.mp4)
Review & Testing Checklist for Human
Suggested manual test:
- Navigate to the tool-router playground (
/<org>/<project>/tool-router)
- Send messages until the limit for your plan is hit → verify the upgrade banner appears
- Verify the "Upgrade Plan" button links to the billing page
- Verify the regular playground and floating playground are not rate-limited
Notes
- This is intentionally client-side only (localStorage). It's bypassable via clearing storage/incognito but raises the bar against casual abuse per the product requirement.
- Limits are defined inline in
usePlaygroundRateLimit.ts (line 4) rather than in a separate constants file, to minimize the number of files touched.
- No server-side rate limiting is added in this PR. The
/api/tool-router/chat API route remains unprotected server-side.
- The hook and banner live under
features/playground/ but are currently only consumed by features/tool-router-playground/. If rate limiting is later extended to other surfaces, these files can be reused as-is.
Link to Devin session: https://app.devin.ai/sessions/a156c5a7266245738941cf2d45fd65ab
Requested by: @palash-c