@@ -267,6 +267,23 @@ function collectUsageCandidates(root: Record<string, unknown>): Record<string, u
267267 return candidates . map ( ( candidate ) => candidate . record ) ;
268268}
269269
270+ function deriveWindowLabelFromTimestamps ( record : Record < string , unknown > ) : string | undefined {
271+ const startTime = parseEpoch ( record . start_time ?? record . startTime ) ;
272+ const endTime = parseEpoch ( record . end_time ?? record . endTime ) ;
273+ if ( startTime && endTime && endTime > startTime ) {
274+ const durationHours = ( endTime - startTime ) / 3_600_000 ;
275+ if ( durationHours >= 1 && Number . isFinite ( durationHours ) ) {
276+ const rounded = Math . round ( durationHours ) ;
277+ return `${ rounded } h` ;
278+ }
279+ const durationMinutes = Math . round ( ( endTime - startTime ) / 60_000 ) ;
280+ if ( durationMinutes > 0 ) {
281+ return `${ durationMinutes } m` ;
282+ }
283+ }
284+ return undefined ;
285+ }
286+
270287function deriveWindowLabel ( payload : Record < string , unknown > ) : string {
271288 const hours = pickNumber ( payload , WINDOW_HOUR_KEYS ) ;
272289 if ( hours && Number . isFinite ( hours ) ) {
@@ -276,6 +293,10 @@ function deriveWindowLabel(payload: Record<string, unknown>): string {
276293 if ( minutes && Number . isFinite ( minutes ) ) {
277294 return `${ minutes } m` ;
278295 }
296+ const fromTimestamps = deriveWindowLabelFromTimestamps ( payload ) ;
297+ if ( fromTimestamps ) {
298+ return fromTimestamps ;
299+ }
279300 return "5h" ;
280301}
281302
@@ -315,6 +336,40 @@ function deriveUsedPercent(payload: Record<string, unknown>): number | null {
315336 return null ;
316337}
317338
339+ /**
340+ * When the API returns a `model_remains` array, prefer the entry whose
341+ * `model_name` matches a chat/text model (e.g. "MiniMax-M*") and that has
342+ * a non-zero `current_interval_total_count`. Models with total_count === 0
343+ * (speech, video, image) are not relevant to the coding-plan budget.
344+ */
345+ function pickChatModelRemains (
346+ modelRemains : unknown [ ] ,
347+ ) : Record < string , unknown > | undefined {
348+ const records = modelRemains . filter ( isRecord ) ;
349+ if ( records . length === 0 ) {
350+ return undefined ;
351+ }
352+
353+ const chatRecord = records . find ( ( r ) => {
354+ const name = typeof r . model_name === "string" ? r . model_name : "" ;
355+ const total = parseFiniteNumber ( r . current_interval_total_count ) ;
356+ return (
357+ ( name . toLowerCase ( ) . startsWith ( "minimax-m" ) || name === "MiniMax-M*" ) &&
358+ total !== undefined &&
359+ total > 0
360+ ) ;
361+ } ) ;
362+
363+ if ( chatRecord ) {
364+ return chatRecord ;
365+ }
366+
367+ return records . find ( ( r ) => {
368+ const total = parseFiniteNumber ( r . current_interval_total_count ) ;
369+ return total !== undefined && total > 0 ;
370+ } ) ;
371+ }
372+
318373export async function fetchMinimaxUsage (
319374 apiKey : string ,
320375 timeoutMs : number ,
@@ -362,8 +417,15 @@ export async function fetchMinimaxUsage(
362417 }
363418
364419 const payload = isRecord ( data . data ) ? data . data : data ;
365- const candidates = collectUsageCandidates ( payload ) ;
366- let usageRecord : Record < string , unknown > = payload ;
420+
421+ // Handle the model_remains array structure returned by the coding-plan
422+ // endpoint. Pick the chat-model entry so that speech/video/image quotas
423+ // (which often have total_count === 0) don't shadow the relevant budget.
424+ const modelRemains = Array . isArray ( payload . model_remains ) ? payload . model_remains : null ;
425+ const chatRemains = modelRemains ? pickChatModelRemains ( modelRemains ) : undefined ;
426+
427+ const candidates = collectUsageCandidates ( chatRemains ?? payload ) ;
428+ let usageRecord : Record < string , unknown > = chatRemains ?? payload ;
367429 let usedPercent : number | null = null ;
368430 for ( const candidate of candidates ) {
369431 const candidatePercent = deriveUsedPercent ( candidate ) ;
@@ -374,7 +436,7 @@ export async function fetchMinimaxUsage(
374436 }
375437 }
376438 if ( usedPercent === null ) {
377- usedPercent = deriveUsedPercent ( payload ) ;
439+ usedPercent = deriveUsedPercent ( chatRemains ?? payload ) ;
378440 }
379441 if ( usedPercent === null ) {
380442 return {
@@ -398,10 +460,19 @@ export async function fetchMinimaxUsage(
398460 } ,
399461 ] ;
400462
463+ const modelName =
464+ chatRemains && typeof chatRemains . model_name === "string"
465+ ? chatRemains . model_name
466+ : undefined ;
467+ const plan =
468+ pickString ( usageRecord , PLAN_KEYS ) ??
469+ pickString ( payload , PLAN_KEYS ) ??
470+ ( modelName ? `Coding Plan · ${ modelName } ` : undefined ) ;
471+
401472 return {
402473 provider : "minimax" ,
403474 displayName : PROVIDER_LABELS . minimax ,
404475 windows,
405- plan : pickString ( usageRecord , PLAN_KEYS ) ?? pickString ( payload , PLAN_KEYS ) ,
476+ plan,
406477 } ;
407478}
0 commit comments