1- import type { UIMessage } from "@ai-sdk/react" ;
2- import { useEffect , useState } from "react" ;
1+ import { useEffect , useRef , useState } from "react" ;
32import { useCurrentPlan } from "~/routes/_app.orgs.$organizationSlug/route" ;
4- import { countUserMessages , resolveMessageQuota , type MessageQuota } from "./message-quota" ;
3+ import { resolveMessageQuota , type MessageQuota } from "./message-quota" ;
54
65// Gated on billing PRESENCE, not the plan value: no subscription means billing isn't wired
76// up (self-hosted), so there is no cap and no upgrade UI. A wired-up, non-paying plan is free.
@@ -11,20 +10,30 @@ function useIsFreePlan(): boolean | undefined {
1110 return subscription . isPaying === false ;
1211}
1312
14- // `used` is the server's per-period count for the org. Re-read whenever the user sends, so
15- // the running total tracks the message just sent without counting the transcript twice.
13+ // `used` is the server's per-period count for the org. Re-read once a turn settles — the
14+ // server increment happens mid-turn in the `.in` proxy, so reading on optimistic append
15+ // would lag the count by one message and show the cap a message late.
1616export function useAgentMessageQuota ( {
1717 actionPath,
1818 chatId,
19- messages ,
19+ status ,
2020} : {
2121 actionPath : string ;
2222 chatId : string ;
23- messages : UIMessage [ ] ;
23+ status : string ;
2424} ) : MessageQuota {
2525 const isFreePlan = useIsFreePlan ( ) ;
2626 const [ used , setUsed ] = useState < number | undefined > ( undefined ) ;
27- const sentCount = countUserMessages ( messages ) ;
27+
28+ // Bumped each time the status leaves streaming/submitted, which drives the re-read.
29+ const [ settleTick , setSettleTick ] = useState ( 0 ) ;
30+ const prevStatus = useRef ( status ) ;
31+ useEffect ( ( ) => {
32+ const wasInFlight = prevStatus . current === "streaming" || prevStatus . current === "submitted" ;
33+ const nowSettled = status === "ready" || status === "error" ;
34+ prevStatus . current = status ;
35+ if ( wasInFlight && nowSettled ) setSettleTick ( ( tick ) => tick + 1 ) ;
36+ } , [ status ] ) ;
2837
2938 useEffect ( ( ) => {
3039 if ( isFreePlan !== true ) return ;
@@ -40,7 +49,7 @@ export function useAgentMessageQuota({
4049 }
4150 } ) ( ) ;
4251 return ( ) => controller . abort ( ) ;
43- } , [ isFreePlan , actionPath , chatId , sentCount ] ) ;
52+ } , [ isFreePlan , actionPath , chatId , settleTick ] ) ;
4453
4554 return resolveMessageQuota ( { isFreePlan, used } ) ;
4655}
0 commit comments