feat: Add Gemini 3.6 Flash and Gemini 3.5 Flash-Lite Support (#14369)

*  feat: Add Gemini 3.6 Flash and Gemini 3.5 Flash-Lite Support

Adds first-class support for Google's Gemini 3.6 Flash (`gemini-3.6-flash`)
and Gemini 3.5 Flash-Lite (`gemini-3.5-flash-lite`) for both the Gemini API
(AI Studio) and Google Cloud/Vertex integrations.

- Context window (1M) in googleModels; API + cache pricing in tx.ts.
- Model dropdown (config.ts) and GOOGLE_MODELS examples for both integrations.
- Generalize the Gemini 3.5 Flash overrides into a flash-family handler that
  strips deprecated temperature/topP/topK and applies each model's default
  thinking level (3.6 Flash: medium, 3.5 Flash-Lite: minimal), with
  longest-prefix resolution so flash-lite does not collide with flash.

Ref: https://ai.google.dev/gemini-api/docs/latest-model#api-changes-and-parameter-updates

* 🩹 fix: Strip unsupported penalty params for Gemini Flash family

Gemini 3.6 Flash, 3.5 Flash-Lite, and 3.5 Flash reject presencePenalty/
frequencyPenalty with HTTP 400 ("Penalty is not enabled for this model",
verified live). These pass through llmConfig via knownGoogleParams, so add
them to the flash-family strip list alongside the deprecated sampling params.

* 🩹 fix: Strip Flash-blocked params on custom Google endpoint path

For custom OpenAI-compatible endpoints with defaultParamsEndpoint=google,
getOpenAIConfig strips Flash-blocked params via getGoogleConfig but then
transformToOpenAIConfig re-applies raw addParams, undoing the strip. Filter
addParams through stripGeminiFlashBlockedParams before the transform so the
deprecated sampling / rejected penalty params cannot reach the provider.

* 🔧 chore: Update sharp package to version 0.35.3 in package-lock.json, api/package.json, and packages/api/package.json

* 🔧 chore: Update dependencies in package-lock.json to latest versions for @google/genai (2.13.0), @hono/node-server (1.19.14), fast-uri (3.1.4), hono (4.12.31), and svgo (2.8.3)

* 🔧 chore: Update dependencies in package.json and package-lock.json for @librechat/agents (3.2.67), @opentelemetry/sdk-node (0.221.0), and add new dependencies for @opentelemetry/propagator-jaeger (2.10.0) and protobufjs (7.6.5). Update monaco-editor version in client package.json to 0.56.0.

* 🔧 chore: Upgrade turbo package to version 2.10.5 in package.json and package-lock.json, and update schema reference in turbo.json

* 🩹 fix: Resolve CI breakage from bundled dependency bumps

Not related to the Gemini models — both are fallout from the dep bumps on
this branch:
- monaco-editor 0.56 changed IEditorHoverOptions.enabled from boolean to
  'on' | 'off' | 'onKeyboardModifier'; update ArtifactCodeEditor to match
  (mirrors the sibling occurrencesHighlight/matchBrackets pattern).
- sharp 0.35.3 fails resize+encode on a degenerate 1x1 PNG (vipspng: libpng
  read error); the provider-file e2e fixture was 1x1, so use a 16x16 PNG.
  Normal images are unaffected (verified 64x64 resize/encode/jpeg all OK).

* 📝 docs: Correct e2e image-fixture comment (bad IDAT CRC, not a sharp bug)

Root cause was the old 1x1 fixture's corrupt IDAT CRC (verified: IHDR/IEND
CRC OK, IDAT CRC BAD), which sharp 0.35.3's stricter libpng correctly rejects.
Not a dimension/resize edge case and not a sharp bug; comment now reflects that.
This commit is contained in:
Danny Avila 2026-07-21 21:14:11 -04:00 committed by GitHub
parent 1dd7121d71
commit cbaa2fe2e3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 1273 additions and 245 deletions

View file

@ -375,10 +375,10 @@ GOOGLE_KEY=user_provided
# GOOGLE_AUTH_HEADER=true
# Gemini API (AI Studio)
# GOOGLE_MODELS=gemini-3.1-pro-preview,gemini-3.1-pro-preview-customtools,gemini-3.1-flash-lite-preview,gemini-2.5-pro,gemini-2.5-flash,gemini-2.5-flash-lite,gemini-2.0-flash,gemini-2.0-flash-lite
# GOOGLE_MODELS=gemini-3.6-flash,gemini-3.5-flash,gemini-3.5-flash-lite,gemini-3.1-pro-preview,gemini-3.1-pro-preview-customtools,gemini-3.1-flash-lite-preview,gemini-2.5-pro,gemini-2.5-flash,gemini-2.5-flash-lite,gemini-2.0-flash,gemini-2.0-flash-lite
# Vertex AI
# GOOGLE_MODELS=gemini-3.1-pro-preview,gemini-3.1-pro-preview-customtools,gemini-3.1-flash-lite-preview,gemini-2.5-pro,gemini-2.5-flash,gemini-2.5-flash-lite,gemini-2.0-flash-001,gemini-2.0-flash-lite-001
# GOOGLE_MODELS=gemini-3.6-flash,gemini-3.5-flash,gemini-3.5-flash-lite,gemini-3.1-pro-preview,gemini-3.1-pro-preview-customtools,gemini-3.1-flash-lite-preview,gemini-2.5-pro,gemini-2.5-flash,gemini-2.5-flash-lite,gemini-2.0-flash-001,gemini-2.0-flash-lite-001
# GOOGLE_TITLE_MODEL=gemini-2.0-flash-lite-001

View file

@ -46,7 +46,7 @@
"@azure/storage-blob": "^12.30.0",
"@google/genai": "^2.8.0",
"@keyv/redis": "^4.3.3",
"@librechat/agents": "^3.2.66",
"@librechat/agents": "^3.2.67",
"@librechat/api": "*",
"@librechat/data-schemas": "*",
"@microsoft/microsoft-graph-client": "^3.0.7",
@ -60,7 +60,7 @@
"@opentelemetry/instrumentation-mongoose": "^0.54.0",
"@opentelemetry/instrumentation-undici": "^0.18.0",
"@opentelemetry/resources": "^2.6.1",
"@opentelemetry/sdk-node": "^0.218.0",
"@opentelemetry/sdk-node": "^0.221.0",
"@opentelemetry/semantic-conventions": "^1.39.0",
"@smithy/node-http-handler": "^4.4.5",
"ai-tokenizer": "^1.0.6",
@ -123,7 +123,7 @@
"prom-client": "^15.1.3",
"rate-limit-redis": "^4.2.0",
"sanitize-html": "^2.13.0",
"sharp": "^0.33.5",
"sharp": "^0.35.3",
"ua-parser-js": "^1.0.36",
"undici": "^7.24.1",
"winston": "^3.11.0",

View file

@ -350,6 +350,12 @@ describe('getModelMaxTokens', () => {
expect(getModelMaxTokens('gemini-3.5-flash', EModelEndpoint.google)).toBe(
maxTokensMap[EModelEndpoint.google]['gemini-3.5-flash'],
);
expect(getModelMaxTokens('gemini-3.5-flash-lite', EModelEndpoint.google)).toBe(
maxTokensMap[EModelEndpoint.google]['gemini-3.5-flash-lite'],
);
expect(getModelMaxTokens('gemini-3.6-flash', EModelEndpoint.google)).toBe(
maxTokensMap[EModelEndpoint.google]['gemini-3.6-flash'],
);
expect(getModelMaxTokens('gemini-2.5-pro', EModelEndpoint.google)).toBe(
maxTokensMap[EModelEndpoint.google]['gemini-2.5-pro'],
);

View file

@ -91,6 +91,7 @@
"micromark-extension-gfm": "^3.0.0",
"micromark-extension-llm-math": "^3.1.0",
"micromark-extension-math": "^3.1.0",
"monaco-editor": "^0.56.0",
"qrcode.react": "^4.2.0",
"rc-input-number": "^7.4.2",
"react": "^18.2.0",
@ -158,7 +159,6 @@
"jest-environment-jsdom": "^30.2.0",
"jest-file-loader": "^1.0.3",
"jest-junit": "^17.0.0",
"monaco-editor": "^0.55.1",
"postcss": "^8.4.31",
"postcss-preset-env": "^11.2.0",
"tailwindcss": "^3.4.1",

View file

@ -422,7 +422,7 @@ export const ArtifactCodeEditor = function ArtifactCodeEditor({
quickSuggestions: !readOnly,
suggestOnTriggerCharacters: !readOnly,
parameterHints: { enabled: !readOnly },
hover: { enabled: !readOnly },
hover: { enabled: readOnly ? 'off' : 'on' },
matchBrackets: readOnly ? 'never' : 'always',
}),
[readOnly],

View file

@ -42,11 +42,15 @@ const textFixture: UploadFixture = {
buffer: Buffer.from('This text attachment should be available to the mock model.\n'),
};
// Valid 16x16 PNG. The previous 1x1 fixture had a corrupt IDAT CRC that older
// libpng silently accepted but sharp 0.35.3's newer libpng rejects during
// server-side image processing ("vipspng: libpng read error"). Keep this a
// spec-conformant PNG (correct chunk CRCs).
const imageFixture: UploadFixture = {
name: 'provider-context.png',
mimeType: 'image/png',
buffer: Buffer.from(
'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+/p9sAAAAASUVORK5CYII=',
'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAACXBIWXMAAAPoAAAD6AG1e1JrAAAAHUlEQVQ4jWNwaDjwnxLMMGrA/9EwODAaBg3DIgwACY9/HwbtciYAAAAASUVORK5CYII=',
'base64',
),
};

1188
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -148,7 +148,7 @@
"lint-staged": "^15.4.3",
"prettier": "^3.5.0",
"prettier-plugin-tailwindcss": "^0.6.11",
"turbo": "^2.9.17",
"turbo": "^2.10.5",
"typescript-eslint": "^8.60.1"
},
"overrides": {
@ -181,14 +181,19 @@
},
"underscore": "1.13.8",
"hono": "^4.12.25",
"@hono/node-server": "^1.19.10",
"@hono/node-server": "^2.0.11",
"monaco-editor": {
"dompurify": "3.4.0"
"dompurify": "3.4.12"
},
"svgo": "^2.8.2",
"js-yaml": "^4.2.0",
"ws": "^8.21.0",
"@opentelemetry/core": "^2.9.0"
"@opentelemetry/core": "^2.9.0",
"@opentelemetry/propagator-jaeger": "^2.10.0",
"protobufjs": "^7.6.5",
"gaxios": {
"uuid": "^11.1.1"
}
},
"nodemonConfig": {
"ignore": [

View file

@ -116,7 +116,7 @@
"@azure/storage-blob": "^12.30.0",
"@google/genai": "^2.8.0",
"@keyv/redis": "^4.3.3",
"@librechat/agents": "^3.2.66",
"@librechat/agents": "^3.2.67",
"@librechat/data-schemas": "*",
"@modelcontextprotocol/sdk": "^1.29.0",
"@opentelemetry/api": "^1.9.0",
@ -162,7 +162,7 @@
"prom-client": "^15.1.3",
"rate-limit-redis": "^4.2.0",
"sanitize-html": "^2.13.0",
"sharp": "^0.33.5",
"sharp": "^0.35.3",
"undici": "^7.24.1",
"yauzl": "^3.2.1",
"zod": "^3.22.4"

View file

@ -918,6 +918,127 @@ describe('getGoogleConfig', () => {
expect(result.llmConfig).not.toHaveProperty('thinkingConfig');
});
it('should default Gemini 3.6 Flash to medium thinkingLevel', () => {
const credentials = {
[AuthKeys.GOOGLE_API_KEY]: 'test-api-key',
};
const result = getGoogleConfig(credentials, {
modelOptions: {
model: 'gemini-3.6-flash',
},
});
expect((result.llmConfig as Record<string, unknown>).thinkingConfig).toMatchObject({
includeThoughts: true,
thinkingLevel: 'MEDIUM',
});
});
it('should remove legacy sampling params for Gemini 3.6 Flash', () => {
const credentials = {
[AuthKeys.GOOGLE_API_KEY]: 'test-api-key',
};
const modelOptions = {
model: 'gemini-3.6-flash',
temperature: 0.7,
topP: 0.9,
topK: 40,
top_p: 0.9,
top_k: 40,
presencePenalty: 0.5,
frequencyPenalty: 0.5,
thinking_budget: 5000,
} as unknown as t.GoogleParameters;
const result = getGoogleConfig(credentials, { modelOptions });
expect(result.llmConfig).not.toHaveProperty('temperature');
expect(result.llmConfig).not.toHaveProperty('topP');
expect(result.llmConfig).not.toHaveProperty('topK');
expect(result.llmConfig).not.toHaveProperty('top_p');
expect(result.llmConfig).not.toHaveProperty('top_k');
expect(result.llmConfig).not.toHaveProperty('presencePenalty');
expect(result.llmConfig).not.toHaveProperty('frequencyPenalty');
expect(result.llmConfig).not.toHaveProperty('thinking_budget');
});
it('should remove unsupported penalty params for Gemini 3.5 Flash-Lite', () => {
const credentials = {
[AuthKeys.GOOGLE_API_KEY]: 'test-api-key',
};
const result = getGoogleConfig(credentials, {
modelOptions: {
model: 'gemini-3.5-flash-lite',
presencePenalty: 0.5,
frequencyPenalty: 0.5,
} as unknown as t.GoogleParameters,
addParams: {
presencePenalty: 0.3,
frequencyPenalty: 0.3,
},
});
expect(result.llmConfig).not.toHaveProperty('presencePenalty');
expect(result.llmConfig).not.toHaveProperty('frequencyPenalty');
});
it('should default Gemini 3.5 Flash-Lite to minimal thinkingLevel', () => {
const credentials = {
[AuthKeys.GOOGLE_API_KEY]: 'test-api-key',
};
const result = getGoogleConfig(credentials, {
modelOptions: {
model: 'gemini-3.5-flash-lite',
},
});
expect((result.llmConfig as Record<string, unknown>).thinkingConfig).toMatchObject({
includeThoughts: true,
thinkingLevel: 'MINIMAL',
});
});
it('should resolve Flash-Lite default over the Flash prefix for versioned aliases', () => {
const credentials = {
[AuthKeys.GOOGLE_API_KEY]: 'test-api-key',
};
const result = getGoogleConfig(credentials, {
modelOptions: {
model: 'google/gemini-3.5-flash-lite-latest',
temperature: 0.7,
},
});
expect(result.llmConfig).not.toHaveProperty('temperature');
expect((result.llmConfig as Record<string, unknown>).thinkingConfig).toMatchObject({
includeThoughts: true,
thinkingLevel: 'MINIMAL',
});
});
it('should preserve explicit Gemini 3.5 Flash-Lite thinkingLevel', () => {
const credentials = {
[AuthKeys.GOOGLE_API_KEY]: 'test-api-key',
};
const result = getGoogleConfig(credentials, {
modelOptions: {
model: 'gemini-3.5-flash-lite',
thinkingLevel: ThinkingLevel.high,
},
});
expect((result.llmConfig as Record<string, unknown>).thinkingConfig).toMatchObject({
includeThoughts: true,
thinkingLevel: 'HIGH',
});
});
it('should omit thinkingLevel when unset (empty string) for Gemini 3', () => {
const credentials = {
[AuthKeys.GOOGLE_API_KEY]: 'test-api-key',

View file

@ -13,14 +13,33 @@ type GoogleThinkingConfig = {
thinkingLevel?: GoogleThinkingLevel;
};
const GEMINI_3_5_FLASH = 'gemini-3.5-flash';
const GEMINI_3_5_FLASH_DEFAULT_THINKING_LEVEL: GoogleThinkingLevel = 'MEDIUM';
const gemini35FlashLegacyParams = [
/**
* Gemini Flash models (3.5+) that drop the deprecated sampling parameters
* (`temperature`/`topP`/`topK`) and `thinkingBudget` in favor of the qualitative
* `thinkingLevel`, and that reject the penalty parameters
* (`presencePenalty`/`frequencyPenalty`) with HTTP 400 ("Penalty is not enabled
* for this model"). We strip all of these and apply each model's documented
* default thinking level when the request doesn't set one. Ordered
* most-specific-first so `gemini-3.5-flash-lite` resolves before the
* `gemini-3.5-flash` prefix.
* @see https://ai.google.dev/gemini-api/docs/latest-model#api-changes-and-parameter-updates
*/
const geminiFlashThinkingDefaults: ReadonlyArray<readonly [string, GoogleThinkingLevel]> = [
['gemini-3.6-flash', 'MEDIUM'],
['gemini-3.5-flash-lite', 'MINIMAL'],
['gemini-3.5-flash', 'MEDIUM'],
];
const geminiFlashLegacyParams = [
'temperature',
'topP',
'topK',
'top_p',
'top_k',
'presencePenalty',
'presence_penalty',
'frequencyPenalty',
'frequency_penalty',
'thinkingBudget',
'thinking_budget',
] as const;
@ -130,10 +149,36 @@ function normalizeGoogleThinkingLevel(value: unknown): GoogleThinkingLevel | und
return normalized;
}
function isGemini35Flash(model: string) {
function getGeminiFlashDefaultThinkingLevel(model: string): GoogleThinkingLevel | undefined {
const normalized = model.toLowerCase();
const modelId = normalized.split('/').pop() ?? normalized;
return modelId === GEMINI_3_5_FLASH || modelId.startsWith(`${GEMINI_3_5_FLASH}-`);
for (const [id, level] of geminiFlashThinkingDefaults) {
if (modelId === id || modelId.startsWith(`${id}-`)) {
return level;
}
}
return undefined;
}
/**
* Removes the parameters a Gemini Flash model rejects (see
* {@link geminiFlashLegacyParams}) from a params object. Used by the
* Google-compatible custom-endpoint path (`getOpenAIConfig`), where `addParams`
* is re-applied by `transformToOpenAIConfig` after `getGoogleConfig` has already
* stripped `llmConfig` without this the strip is undone and the deprecated
* sampling / rejected penalty params reach the provider again. No-op for
* non-Flash models and when there is nothing to strip.
*/
export function stripGeminiFlashBlockedParams<T extends Record<string, unknown> | undefined>(
params: T,
model: string | undefined,
): T {
if (params == null || getGeminiFlashDefaultThinkingLevel(model ?? '') == null) {
return params;
}
const sanitized = { ...params };
geminiFlashLegacyParams.forEach((key) => delete sanitized[key]);
return sanitized as T;
}
const urlContextModelRegex = /gemini-(\d+)(?:\.(\d+))?/i;
@ -172,7 +217,7 @@ function sanitizeModelOptions(modelOptions: Partial<t.GoogleParameters> | undefi
return sanitizedOptions;
}
function applyGemini35FlashOverrides({
function applyGeminiFlashOverrides({
config,
provider,
thinking,
@ -185,11 +230,15 @@ function applyGemini35FlashOverrides({
}) {
const mutableConfig = config as Record<string, unknown>;
const model = mutableConfig.model;
if (typeof model !== 'string' || !isGemini35Flash(model)) {
if (typeof model !== 'string') {
return;
}
const defaultThinkingLevel = getGeminiFlashDefaultThinkingLevel(model);
if (!defaultThinkingLevel) {
return;
}
gemini35FlashLegacyParams.forEach((param) => {
geminiFlashLegacyParams.forEach((param) => {
delete mutableConfig[param];
});
@ -220,7 +269,7 @@ function applyGemini35FlashOverrides({
}
if (!shouldDropThinkingLevel && !thinkingConfig.thinkingLevel) {
thinkingConfig.thinkingLevel = GEMINI_3_5_FLASH_DEFAULT_THINKING_LEVEL;
thinkingConfig.thinkingLevel = defaultThinkingLevel;
}
if (Object.keys(thinkingConfig).length > 0) {
@ -634,7 +683,7 @@ export function getGoogleConfig(
googleSettings.maxOutputTokens.reset(resolvedModel);
}
applyGemini35FlashOverrides({
applyGeminiFlashOverrides({
config: llmConfig,
provider,
thinking,

View file

@ -153,6 +153,38 @@ describe('getOpenAIConfig - Google Compatibility', () => {
expect(result.tools).toEqual([]);
});
it('should strip Flash-blocked addParams so the transform cannot re-add them', () => {
const apiKey = JSON.stringify({ GOOGLE_API_KEY: 'test-google-key' });
const endpoint = 'Gemini (Custom)';
const options = {
modelOptions: {
model: 'gemini-3.6-flash',
},
customParams: {
defaultParamsEndpoint: 'google',
},
addParams: {
temperature: 0.8,
topP: 0.95,
topK: 40,
presencePenalty: 0.5,
frequencyPenalty: 0.5,
maxOutputTokens: 8192, // Supported Google param, should survive
},
reverseProxyUrl: 'https://generativelanguage.googleapis.com/v1beta/openai',
};
const result = getOpenAIConfig(apiKey, options, endpoint);
expect(result.llmConfig).not.toHaveProperty('temperature');
expect(result.llmConfig).not.toHaveProperty('topP');
expect(result.llmConfig).not.toHaveProperty('presencePenalty');
expect(result.llmConfig).not.toHaveProperty('frequencyPenalty');
expect(result.llmConfig.modelKwargs ?? {}).not.toHaveProperty('topK');
expect(result.llmConfig.modelKwargs ?? {}).not.toHaveProperty('presencePenalty');
expect(result.llmConfig.modelKwargs).toMatchObject({ maxOutputTokens: 8192 });
});
it('should drop Google native params with dropParams', () => {
const apiKey = JSON.stringify({ GOOGLE_API_KEY: 'test-google-key' });
const endpoint = 'Gemini (Custom)';

View file

@ -3,10 +3,10 @@ import { Providers } from '@librechat/agents';
import { KnownEndpoints, EModelEndpoint, ReasoningParameterFormat } from 'librechat-data-provider';
import type { Dispatcher } from 'undici';
import type * as t from '~/types';
import { getGoogleConfig, stripGeminiFlashBlockedParams } from '~/endpoints/google/llm';
import { getLLMConfig as getAnthropicLLMConfig } from '~/endpoints/anthropic/llm';
import { createSSRFSafeAgents, createSSRFSafeUndiciConnect } from '~/auth';
import { getOpenAILLMConfig, extractDefaultParams } from './llm';
import { getGoogleConfig } from '~/endpoints/google/llm';
import { transformToOpenAIConfig } from './transform';
import { getProxyDispatcher } from '~/utils/proxy';
import { constructAzureURL } from '~/utils/azure';
@ -169,9 +169,19 @@ export function getOpenAIConfig(
},
true,
);
/** Transform handles addParams/dropParams - it knows about OpenAI params */
/**
* Transform handles addParams/dropParams - it knows about OpenAI params.
* `getGoogleConfig` already stripped Flash-blocked params from `llmConfig`,
* but the transform re-applies `addParams` raw, which would undo that; strip
* them from the forwarded `addParams` too so the model does not receive
* params it rejects. `defaultParams` is applied inside `getGoogleConfig`
* (and only read here for tool detection), so it needs no sanitizing.
*/
const transformed = transformToOpenAIConfig({
addParams,
addParams: stripGeminiFlashBlockedParams(
addParams,
(googleResult.llmConfig as { model?: string }).model,
),
dropParams,
defaultParams,
tools: googleResult.tools,

View file

@ -133,6 +133,8 @@ const googleModels = {
'gemini-3.1': 1000000,
'gemini-3.1-flash-lite': 1000000,
'gemini-3.5-flash': 1048576,
'gemini-3.5-flash-lite': 1048576,
'gemini-3.6-flash': 1048576,
};
const anthropicModels = {

View file

@ -2111,8 +2111,11 @@ export const defaultModels = {
[EModelEndpoint.assistants]: [...sharedOpenAIModels, 'chatgpt-4o-latest'],
[EModelEndpoint.agents]: sharedOpenAIModels, // TODO: Add agent models (agentsModels)
[EModelEndpoint.google]: [
// Gemini 3.6 Models
'gemini-3.6-flash',
// Gemini 3.5 Models
'gemini-3.5-flash',
'gemini-3.5-flash-lite',
// Gemini 3.1 Models
'gemini-3.1-pro-preview',
'gemini-3.1-pro-preview-customtools',

View file

@ -1526,7 +1526,9 @@ describe('Google Model Tests', () => {
'gemini-3.1-pro-preview',
'gemini-3.1-pro-preview-customtools',
'gemini-3.1-flash-lite-preview',
'gemini-3.6-flash',
'gemini-3.5-flash',
'gemini-3.5-flash-lite',
'gemini-2.5-pro',
'gemini-2.5-flash',
'gemini-2.5-flash-lite',
@ -1574,7 +1576,9 @@ describe('Google Model Tests', () => {
'gemini-3.1-pro-preview': 'gemini-3.1',
'gemini-3.1-pro-preview-customtools': 'gemini-3.1',
'gemini-3.1-flash-lite-preview': 'gemini-3.1-flash-lite',
'gemini-3.6-flash': 'gemini-3.6-flash',
'gemini-3.5-flash': 'gemini-3.5-flash',
'gemini-3.5-flash-lite': 'gemini-3.5-flash-lite',
'gemini-2.5-pro': 'gemini-2.5-pro',
'gemini-2.5-flash': 'gemini-2.5-flash',
'gemini-2.5-flash-lite': 'gemini-2.5-flash-lite',
@ -1692,6 +1696,38 @@ describe('Google Model Tests', () => {
cacheTokenValues['gemini-3.5-flash'].read,
);
});
it('should return correct rates for Gemini 3.6 Flash', () => {
const model = 'gemini-3.6-flash';
expect(getMultiplier({ model, tokenType: 'prompt', endpoint: EModelEndpoint.google })).toBe(
tokenValues['gemini-3.6-flash'].prompt,
);
expect(getMultiplier({ model, tokenType: 'completion', endpoint: EModelEndpoint.google })).toBe(
tokenValues['gemini-3.6-flash'].completion,
);
expect(getCacheMultiplier({ model, cacheType: 'write' })).toBe(
cacheTokenValues['gemini-3.6-flash'].write,
);
expect(getCacheMultiplier({ model, cacheType: 'read' })).toBe(
cacheTokenValues['gemini-3.6-flash'].read,
);
});
it('should return correct rates for Gemini 3.5 Flash-Lite', () => {
const model = 'gemini-3.5-flash-lite';
expect(getMultiplier({ model, tokenType: 'prompt', endpoint: EModelEndpoint.google })).toBe(
tokenValues['gemini-3.5-flash-lite'].prompt,
);
expect(getMultiplier({ model, tokenType: 'completion', endpoint: EModelEndpoint.google })).toBe(
tokenValues['gemini-3.5-flash-lite'].completion,
);
expect(getCacheMultiplier({ model, cacheType: 'write' })).toBe(
cacheTokenValues['gemini-3.5-flash-lite'].write,
);
expect(getCacheMultiplier({ model, cacheType: 'read' })).toBe(
cacheTokenValues['gemini-3.5-flash-lite'].read,
);
});
});
describe('Gemini 3.1 Premium Token Pricing', () => {

View file

@ -203,6 +203,8 @@ export const tokenValues: Record<string, { prompt: number; completion: number }>
'gemini-3.1': { prompt: 2, completion: 12 },
'gemini-3.1-flash-lite': { prompt: 0.25, completion: 1.5 },
'gemini-3.5-flash': { prompt: 1.5, completion: 9 },
'gemini-3.5-flash-lite': { prompt: 0.3, completion: 2.5 },
'gemini-3.6-flash': { prompt: 1.5, completion: 7.5 },
'gemini-pro-vision': { prompt: 0.5, completion: 1.5 },
grok: { prompt: 2.0, completion: 10.0 },
'grok-beta': { prompt: 5.0, completion: 15.0 },
@ -368,6 +370,10 @@ export const cacheTokenValues: Record<string, { write: number; read: number }> =
'gemini-3.1-flash-lite': { write: 0.25, read: 0.025 },
// Gemini 3.5 Flash - cache write: $1.50/1M, cache read: $0.15/1M
'gemini-3.5-flash': { write: 1.5, read: 0.15 },
// Gemini 3.5 Flash-Lite - cache write: $0.30/1M, cache read: $0.03/1M
'gemini-3.5-flash-lite': { write: 0.3, read: 0.03 },
// Gemini 3.6 Flash - cache write: $1.50/1M, cache read: $0.15/1M
'gemini-3.6-flash': { write: 1.5, read: 0.15 },
};
/**

View file

@ -1,5 +1,5 @@
{
"$schema": "https://v2-9-17.turborepo.dev/schema.json",
"$schema": "https://v2-10-5.turborepo.dev/schema.json",
"globalDependencies": ["package-lock.json"],
"tasks": {
"build": {