Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions packages/types/src/providers/gemini.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,13 @@ export const geminiModels = {
cacheReadsPrice: 0.625,
cacheWritesPrice: 4.5,
tiers: [
{
// Free tier: 125k input tokens per minute quota
contextWindow: 125_000,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider extracting this magic number into a constant like GEMINI_25_PRO_FREE_TIER_LIMIT. The value 125_000 appears 8 times across the codebase, and having it as a constant would make future quota adjustments easier to manage.

Suggested change
contextWindow: 125_000,
// Free tier: 125k input tokens per minute quota
contextWindow: GEMINI_25_PRO_FREE_TIER_LIMIT,

inputPrice: 0,
outputPrice: 0,
cacheReadsPrice: 0,
},
{
contextWindow: 200_000,
inputPrice: 1.25,
Expand All @@ -102,6 +109,13 @@ export const geminiModels = {
cacheReadsPrice: 0.625,
cacheWritesPrice: 4.5,
tiers: [
{
// Free tier: 125k input tokens per minute quota
contextWindow: 125_000,
inputPrice: 0,
outputPrice: 0,
cacheReadsPrice: 0,
},
{
contextWindow: 200_000,
inputPrice: 1.25,
Expand All @@ -128,6 +142,13 @@ export const geminiModels = {
maxThinkingTokens: 32_768,
supportsReasoningBudget: true,
tiers: [
{
// Free tier: 125k input tokens per minute quota
contextWindow: 125_000,
inputPrice: 0,
outputPrice: 0,
cacheReadsPrice: 0,
},
{
contextWindow: 200_000,
inputPrice: 1.25,
Expand Down Expand Up @@ -155,6 +176,13 @@ export const geminiModels = {
supportsReasoningBudget: true,
requiredReasoningBudget: true,
tiers: [
{
// Free tier: 125k input tokens per minute quota
contextWindow: 125_000,
inputPrice: 0,
outputPrice: 0,
cacheReadsPrice: 0,
},
{
contextWindow: 200_000,
inputPrice: 1.25,
Expand Down
90 changes: 90 additions & 0 deletions src/api/providers/__tests__/gemini-tier-config.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
// npx vitest run src/api/providers/__tests__/gemini-tier-config.spec.ts

import { geminiModels } from "@roo-code/types"

describe("Gemini 2.5 Pro Tier Configuration", () => {
const gemini25ProModels = [
"gemini-2.5-pro",
"gemini-2.5-pro-preview-03-25",
"gemini-2.5-pro-preview-05-06",
"gemini-2.5-pro-preview-06-05",
] as const

gemini25ProModels.forEach((modelId) => {
describe(`${modelId}`, () => {
it("should have a free tier with 125k context window", () => {
const model = geminiModels[modelId]
expect(model).toBeDefined()
expect(model.tiers).toBeDefined()
expect(model.tiers!.length).toBeGreaterThanOrEqual(3)

// Check the first tier is the free tier
const freeTier = model.tiers![0]
expect(freeTier.contextWindow).toBe(125_000)
expect(freeTier.inputPrice).toBe(0)
expect(freeTier.outputPrice).toBe(0)
expect(freeTier.cacheReadsPrice).toBe(0)
})

it("should have correct tier ordering", () => {
const model = geminiModels[modelId]
const tiers = model.tiers!

// Verify tier ordering: 125k (free) -> 200k -> Infinity
expect(tiers[0].contextWindow).toBe(125_000)
expect(tiers[1].contextWindow).toBe(200_000)
expect(tiers[2].contextWindow).toBe(Infinity)
})

it("should have correct pricing for paid tiers", () => {
const model = geminiModels[modelId]
const tiers = model.tiers!

// 200k tier pricing
expect(tiers[1].inputPrice).toBe(1.25)
expect(tiers[1].outputPrice).toBe(10)
expect(tiers[1].cacheReadsPrice).toBe(0.31)

// Infinity tier pricing
expect(tiers[2].inputPrice).toBe(2.5)
expect(tiers[2].outputPrice).toBe(15)
expect(tiers[2].cacheReadsPrice).toBe(0.625)
})

it("should trigger context condensing before hitting 125k limit", () => {
const model = geminiModels[modelId]
const freeTierLimit = model.tiers![0].contextWindow

// With a typical context condensing threshold of 50-80%,
// the condensing should trigger well before reaching 125k tokens
const typicalCondenseThreshold = 0.7 // 70%
const expectedTriggerPoint = freeTierLimit * typicalCondenseThreshold

// Verify that the free tier limit is correctly set to prevent 429 errors
expect(freeTierLimit).toBe(125_000)
expect(expectedTriggerPoint).toBeLessThan(freeTierLimit)
expect(expectedTriggerPoint).toBe(87_500) // 70% of 125k
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For better test maintainability, could we use the calculation directly instead of hardcoding the result?

Suggested change
expect(expectedTriggerPoint).toBe(87_500) // 70% of 125k
expect(expectedTriggerPoint).toBe(125_000 * 0.7) // 70% of 125k

This makes it clearer that we're testing the 70% threshold and easier to update if the percentage changes.

})
})
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be valuable to add a test that verifies the tier selection logic in the actual calculateCost method? While we're testing the tier configuration here, we're not directly testing that the correct tier gets selected when processing requests with <125k tokens.

Something like:

it("should select free tier for requests under 125k tokens", () => {
  // Test that calculateCost selects the free tier
  // when input tokens are below 125k
})

})

describe("Other Gemini models", () => {
it("should not have free tier for non-2.5-pro models", () => {
// Check a few other models to ensure we didn't accidentally add free tier to them
const otherModels = [
"gemini-2.0-flash-001",
"gemini-1.5-flash-002",
"gemini-2.0-flash-thinking-exp-01-21",
] as const

otherModels.forEach((modelId) => {
const model = geminiModels[modelId]
if ("tiers" in model && model.tiers) {
// If tiers exist, verify none have 125k context window
const has125kTier = model.tiers.some((tier: any) => tier.contextWindow === 125_000)
expect(has125kTier).toBe(false)
}
})
})
})
})
Loading