feat(models): support Gemini 3.1 custom tool model (#131)

* feat(models): add support for Gemini 3.1 and custom tool models

* test(routing): fix classifier and numerical classifier strategy tests

* test(routing): add Gemini 3.1 tests for classifier strategy

* fix(models): correctly filter active Gemini 3.1 models

* fix(routing): ensure useCustomToolModel is only true when Gemini 3.1 is enabled

* fix(test-utils): prevent double newline in lastFrame() on Windows

* fix(test-utils): surgically fix double newline in lastFrame() on Windows

* use custom_tools_model string for api key only

* fix(ui): correct useCustomToolModel logic and update tests

* fix(ui): correct useCustomToolModel logic in StatsDisplay

* fix(routing): ensure test models are active and sync useCustomToolModel logic
This commit is contained in:
Sehoon Shon
2026-02-19 11:18:44 -05:00
parent f669adbffd
commit ed8f7f6766
12 changed files with 501 additions and 35 deletions
@@ -26,6 +26,7 @@ import {
isActiveModel,
getDisplayString,
isAutoModel,
AuthType,
} from '@google/gemini-cli-core';
import { useSettings } from '../contexts/SettingsContext.js';
import { useConfig } from '../contexts/ConfigContext.js';
@@ -84,9 +85,12 @@ const buildModelRows = (
models: Record<string, ModelMetrics>,
quotas?: RetrieveUserQuotaResponse,
useGemini3_1 = false,
useCustomToolModel = false,
) => {
const getBaseModelName = (name: string) => name.replace('-001', '');
const usedModelNames = new Set(Object.keys(models).map(getBaseModelName));
const usedModelNames = new Set(
Object.keys(models).map(getBaseModelName).map(getDisplayString),
);
// 1. Models with active usage
const activeRows = Object.entries(models).map(([name, metrics]) => {
@@ -95,7 +99,7 @@ const buildModelRows = (
const inputTokens = metrics.tokens.input;
return {
key: name,
modelName,
modelName: getDisplayString(modelName),
requests: metrics.api.totalRequests,
cachedTokens: cachedTokens.toLocaleString(),
inputTokens: inputTokens.toLocaleString(),
@@ -111,12 +115,12 @@ const buildModelRows = (
?.filter(
(b) =>
b.modelId &&
isActiveModel(b.modelId, useGemini3_1) &&
!usedModelNames.has(b.modelId),
isActiveModel(b.modelId, useGemini3_1, useCustomToolModel) &&
!usedModelNames.has(getDisplayString(b.modelId)),
)
.map((bucket) => ({
key: bucket.modelId!,
modelName: bucket.modelId!,
modelName: getDisplayString(bucket.modelId!),
requests: '-',
cachedTokens: '-',
inputTokens: '-',
@@ -138,6 +142,7 @@ const ModelUsageTable: React.FC<{
pooledLimit?: number;
pooledResetTime?: string;
useGemini3_1?: boolean;
useCustomToolModel?: boolean;
}> = ({
models,
quotas,
@@ -147,9 +152,10 @@ const ModelUsageTable: React.FC<{
pooledRemaining,
pooledLimit,
pooledResetTime,
useGemini3_1 = false,
useGemini3_1,
useCustomToolModel,
}) => {
const rows = buildModelRows(models, quotas, useGemini3_1);
const rows = buildModelRows(models, quotas, useGemini3_1, useCustomToolModel);
if (rows.length === 0) {
return null;
@@ -407,7 +413,9 @@ export const StatsDisplay: React.FC<StatsDisplayProps> = ({
const settings = useSettings();
const config = useConfig();
const useGemini3_1 = config.getGemini31LaunchedSync?.() ?? false;
const useCustomToolModel =
useGemini3_1 &&
config.getContentGeneratorConfig().authType !== AuthType.USE_VERTEX_AI;
const pooledRemaining = quotaStats?.remaining;
const pooledLimit = quotaStats?.limit;
const pooledResetTime = quotaStats?.resetTime;
@@ -542,6 +550,7 @@ export const StatsDisplay: React.FC<StatsDisplayProps> = ({
pooledLimit={pooledLimit}
pooledResetTime={pooledResetTime}
useGemini3_1={useGemini3_1}
useCustomToolModel={useCustomToolModel}
/>
</Box>
);