Merge pull request #826 from kaitranntt/kai/fix/gemini-3-1-presets

fix(cliproxy): resolve gemini presets from live models
This commit is contained in:
Kai (Tam Nhu) Tran authored and GitHub committed 2026-03-28 15:39:56 -04:00
commit d3f0acc59c
19 files changed
+488 -54

No files matched your search

@@ -640,6 +640,8 @@ auth-dir: "${cliproxyDir.replace(/\\/g, '/')}/auth"
const gemini31AliasLines = [
'alias: gemini-3.1-pro-preview',
'alias: gemini-3.1-pro-preview-customtools',
'alias: gemini-3.1-flash-preview',
'alias: gemini-3.1-flash-preview-customtools',
];
for (const aliasLine of gemini31AliasLines) {
+39 -9
View File
@@ -91,11 +91,11 @@ describe('Model Catalog', () => {
assert.strictEqual(ids.includes('claude-sonnet-4-5'), false);
});
it('includes Gemini 3 Pro (free via Antigravity)', () => {
it('includes Gemini 3.1 Pro (free via Antigravity)', () => {
const { MODEL_CATALOG } = modelCatalog;
const gem3 = MODEL_CATALOG.agy.models.find((m) => m.id === 'gemini-3-pro-preview');
assert(gem3, 'Should include Gemini 3 Pro');
assert.strictEqual(gem3.name, 'Gemini 3 Pro');
const gem3 = MODEL_CATALOG.agy.models.find((m) => m.id === 'gemini-3.1-pro-preview');
assert(gem3, 'Should include Gemini 3.1 Pro');
assert.strictEqual(gem3.name, 'Gemini 3.1 Pro');
// AGY models are all free - no paid tier
assert.strictEqual(gem3.tier, undefined, 'AGY models should not have paid tier');
});
@@ -139,11 +139,11 @@ describe('Model Catalog', () => {
assert.strictEqual(MODEL_CATALOG.gemini.defaultModel, 'gemini-2.5-pro');
});
it('includes Gemini 3 Pro with pro tier', () => {
it('includes Gemini 3.1 Pro with pro tier', () => {
const { MODEL_CATALOG } = modelCatalog;
const gem3 = MODEL_CATALOG.gemini.models.find((m) => m.id === 'gemini-3-pro-preview');
assert(gem3, 'Should include Gemini 3 Pro');
assert.strictEqual(gem3.name, 'Gemini 3 Pro');
const gem3 = MODEL_CATALOG.gemini.models.find((m) => m.id === 'gemini-3.1-pro-preview');
assert(gem3, 'Should include Gemini 3.1 Pro');
assert.strictEqual(gem3.name, 'Gemini 3.1 Pro');
assert.strictEqual(gem3.tier, 'pro');
});
@@ -246,6 +246,36 @@ describe('Model Catalog', () => {
assert.strictEqual(legacySonnet?.id, 'claude-sonnet-4-6');
});
it('treats Gemini 3 and 3.1 preview IDs as the same catalog family', () => {
const { findModel, getSuggestedReplacementModel } = modelCatalog;
const legacyAgyGemini = findModel('agy', 'gemini-3-pro-preview');
const legacyGemini = findModel('gemini', 'gemini-3-pro-preview');
const currentGemini = findModel('gemini', 'gemini-3.1-pro-preview');
assert.strictEqual(legacyAgyGemini?.id, 'gemini-3.1-pro-preview');
assert.strictEqual(legacyGemini?.id, 'gemini-3.1-pro-preview');
assert.strictEqual(currentGemini?.id, 'gemini-3.1-pro-preview');
assert.strictEqual(
getSuggestedReplacementModel('gemini', 'gemini-3.1-pro-preview'),
'gemini-2.5-pro'
);
});
it('falls back to the next supported model when the default is excluded', () => {
const { getSuggestedReplacementModel } = modelCatalog;
expect(getSuggestedReplacementModel('agy', 'claude-opus-4-6-thinking')).toBe(
'claude-sonnet-4-6'
);
expect(getSuggestedReplacementModel('agy')).toBe('claude-opus-4-6-thinking');
});
it('returns undefined when no provider catalog exists', () => {
const { getSuggestedReplacementModel } = modelCatalog;
expect(getSuggestedReplacementModel('qwen')).toBeUndefined();
});
it('returns undefined for unknown model', () => {
const { findModel } = modelCatalog;
const model = findModel('agy', 'unknown-model');
@@ -325,7 +355,7 @@ describe('Model Catalog', () => {
const sonnetThinkingIdx = models.findIndex((m) => m.id === 'claude-sonnet-4-6');
// Find indices of the remaining non-Claude model
const geminiIdx = models.findIndex((m) => m.id === 'gemini-3-pro-preview');
const geminiIdx = models.findIndex((m) => m.id === 'gemini-3.1-pro-preview');
// Primary Claude choices should appear ahead of Gemini fallback.
assert(opusIdx < geminiIdx, 'Opus should be above Gemini');
@@ -168,6 +168,23 @@ describe('Gemini CLI Quota Fetcher', () => {
expect(proBucket!.remainingFraction).toBe(0.9);
});
it('should recognize Gemini 3.1 preview IDs during the rollout', () => {
const rawBuckets = [
{ model_id: 'gemini-3.1-flash-preview', remaining_fraction: 0.7 },
{ model_id: 'gemini-3.1-pro-preview', remaining_fraction: 0.4 },
];
const buckets = buildGeminiCliBuckets(rawBuckets);
const flashBucket = buckets.find((b) => b.label === 'Gemini Flash Series');
const proBucket = buckets.find((b) => b.label === 'Gemini Pro Series');
expect(flashBucket).toBeDefined();
expect(flashBucket!.modelIds).toContain('gemini-3.1-flash-preview');
expect(proBucket).toBeDefined();
expect(proBucket!.modelIds).toContain('gemini-3.1-pro-preview');
});
it('should handle camelCase API response', () => {
const rawBuckets = [{ modelId: 'gemini-3-flash-preview', remainingFraction: 0.75 }];
+16
View File
@@ -76,6 +76,22 @@ describe('model-pricing', () => {
expect(pricing).not.toEqual(getModelPricing('unknown-model-xyz'));
});
it('should map Gemini 3 and 3.1 Flash preview variants to flash pricing', () => {
const canonical = getModelPricing('gemini-2.5-flash');
const aliases = [
'gemini-3-flash-preview',
'gemini-3-flash-preview-customtools',
'gemini-3.1-flash-preview',
'gemini-3.1-flash-preview-customtools',
'gemini-3-1-flash-preview',
'gemini-3-1-flash-preview-customtools',
];
for (const model of aliases) {
expect(getModelPricing(model)).toEqual(canonical);
}
});
it('should return different pricing for different model tiers', () => {
const sonnet = getModelPricing('claude-sonnet-4-5');
const opus = getModelPricing('claude-opus-4-5-20251101');