mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-04-21 10:34:35 -07:00
fix(core): fix quota footer for non-auto models and improve display (#25121)
This commit is contained in:
@@ -3006,6 +3006,78 @@ describe('Config Quota & Preview Model Access', () => {
|
||||
// Never set => stays null (unknown); getter returns true so UI shows preview
|
||||
expect(config.getHasAccessToPreviewModel()).toBe(true);
|
||||
});
|
||||
it('should derive quota from remainingFraction when remainingAmount is missing', async () => {
|
||||
mockCodeAssistServer.retrieveUserQuota.mockResolvedValue({
|
||||
buckets: [
|
||||
{
|
||||
modelId: 'gemini-3-flash-preview',
|
||||
remainingFraction: 0.96,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
config.setModel('gemini-3-flash-preview');
|
||||
mockCoreEvents.emitQuotaChanged.mockClear();
|
||||
await config.refreshUserQuota();
|
||||
|
||||
// Normalized: limit=100, remaining=96
|
||||
expect(mockCoreEvents.emitQuotaChanged).toHaveBeenCalledWith(
|
||||
96,
|
||||
100,
|
||||
undefined,
|
||||
);
|
||||
expect(config.getQuotaRemaining()).toBe(96);
|
||||
expect(config.getQuotaLimit()).toBe(100);
|
||||
});
|
||||
|
||||
it('should store quota from remainingFraction when remainingFraction is 0', async () => {
|
||||
mockCodeAssistServer.retrieveUserQuota.mockResolvedValue({
|
||||
buckets: [
|
||||
{
|
||||
modelId: 'gemini-3-pro-preview',
|
||||
remainingFraction: 0,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
config.setModel('gemini-3-pro-preview');
|
||||
mockCoreEvents.emitQuotaChanged.mockClear();
|
||||
await config.refreshUserQuota();
|
||||
|
||||
// remaining=0, limit=100 but limit>0 check still passes
|
||||
// however remaining=0 means 0% remaining = 100% used
|
||||
expect(config.getQuotaRemaining()).toBe(0);
|
||||
expect(config.getQuotaLimit()).toBe(100);
|
||||
});
|
||||
|
||||
it('should emit QuotaChanged when model is switched via setModel', async () => {
|
||||
mockCodeAssistServer.retrieveUserQuota.mockResolvedValue({
|
||||
buckets: [
|
||||
{
|
||||
modelId: 'gemini-2.5-pro',
|
||||
remainingAmount: '10',
|
||||
remainingFraction: 0.2,
|
||||
},
|
||||
{
|
||||
modelId: 'gemini-2.5-flash',
|
||||
remainingAmount: '80',
|
||||
remainingFraction: 0.8,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
config.setModel('auto-gemini-2.5');
|
||||
await config.refreshUserQuota();
|
||||
mockCoreEvents.emitQuotaChanged.mockClear();
|
||||
|
||||
// Switch to a specific model — should re-emit quota for that model
|
||||
config.setModel('gemini-2.5-pro');
|
||||
expect(mockCoreEvents.emitQuotaChanged).toHaveBeenCalledWith(
|
||||
10,
|
||||
50,
|
||||
undefined,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('refreshUserQuotaIfStale', () => {
|
||||
|
||||
@@ -832,18 +832,16 @@ export class Config implements McpContext, AgentLoopContext {
|
||||
private lastEmittedQuotaLimit: number | undefined;
|
||||
|
||||
private emitQuotaChangedEvent(): void {
|
||||
const pooled = this.getPooledQuota();
|
||||
const remaining = this.getQuotaRemaining();
|
||||
const limit = this.getQuotaLimit();
|
||||
const resetTime = this.getQuotaResetTime();
|
||||
if (
|
||||
this.lastEmittedQuotaRemaining !== pooled.remaining ||
|
||||
this.lastEmittedQuotaLimit !== pooled.limit
|
||||
this.lastEmittedQuotaRemaining !== remaining ||
|
||||
this.lastEmittedQuotaLimit !== limit
|
||||
) {
|
||||
this.lastEmittedQuotaRemaining = pooled.remaining;
|
||||
this.lastEmittedQuotaLimit = pooled.limit;
|
||||
coreEvents.emitQuotaChanged(
|
||||
pooled.remaining,
|
||||
pooled.limit,
|
||||
pooled.resetTime,
|
||||
);
|
||||
this.lastEmittedQuotaRemaining = remaining;
|
||||
this.lastEmittedQuotaLimit = limit;
|
||||
coreEvents.emitQuotaChanged(remaining, limit, resetTime);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1819,6 +1817,9 @@ export class Config implements McpContext, AgentLoopContext {
|
||||
// When the user explicitly sets a model, that becomes the active model.
|
||||
this._activeModel = newModel;
|
||||
coreEvents.emitModelChanged(newModel);
|
||||
this.lastEmittedQuotaRemaining = undefined;
|
||||
this.lastEmittedQuotaLimit = undefined;
|
||||
this.emitQuotaChangedEvent();
|
||||
}
|
||||
if (this.onModelChange && !isTemporary) {
|
||||
this.onModelChange(newModel);
|
||||
@@ -2112,24 +2113,31 @@ export class Config implements McpContext, AgentLoopContext {
|
||||
this.lastQuotaFetchTime = Date.now();
|
||||
|
||||
for (const bucket of quota.buckets) {
|
||||
if (
|
||||
bucket.modelId &&
|
||||
bucket.remainingAmount &&
|
||||
bucket.remainingFraction != null
|
||||
) {
|
||||
const remaining = parseInt(bucket.remainingAmount, 10);
|
||||
const limit =
|
||||
if (!bucket.modelId || bucket.remainingFraction == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let remaining: number;
|
||||
let limit: number;
|
||||
|
||||
if (bucket.remainingAmount) {
|
||||
remaining = parseInt(bucket.remainingAmount, 10);
|
||||
limit =
|
||||
bucket.remainingFraction > 0
|
||||
? Math.round(remaining / bucket.remainingFraction)
|
||||
: (this.modelQuotas.get(bucket.modelId)?.limit ?? 0);
|
||||
} else {
|
||||
// Server only sent remainingFraction — use a normalized scale.
|
||||
limit = 100;
|
||||
remaining = Math.round(bucket.remainingFraction * limit);
|
||||
}
|
||||
|
||||
if (!isNaN(remaining) && Number.isFinite(limit) && limit > 0) {
|
||||
this.modelQuotas.set(bucket.modelId, {
|
||||
remaining,
|
||||
limit,
|
||||
resetTime: bucket.resetTime,
|
||||
});
|
||||
}
|
||||
if (!isNaN(remaining) && Number.isFinite(limit) && limit > 0) {
|
||||
this.modelQuotas.set(bucket.modelId, {
|
||||
remaining,
|
||||
limit,
|
||||
resetTime: bucket.resetTime,
|
||||
});
|
||||
}
|
||||
}
|
||||
this.emitQuotaChangedEvent();
|
||||
|
||||
Reference in New Issue
Block a user