Skip to content
Merged
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
5 changes: 5 additions & 0 deletions .changeset/gemini-3-7-flash.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@tanstack/ai-gemini': minor
---

Add Gemini 3.7 Flash (`gemini-3.7-flash`) with full multimodal input, thinking, structured output, caching, and the same built-in tools as Gemini 3.6 Flash.
10 changes: 10 additions & 0 deletions examples/ts-react-chat/src/lib/model-selection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,11 @@ export const MODEL_OPTIONS: Array<ModelOption> = [
},

// Gemini (stateless `geminiText`)
{
provider: 'gemini',
model: 'gemini-3.7-flash',
label: 'Gemini - 3.7 Flash',
},
{
provider: 'gemini',
model: 'gemini-3.6-flash',
Expand Down Expand Up @@ -102,6 +107,11 @@ export const MODEL_OPTIONS: Array<ModelOption> = [
},

// Gemini Interactions (stateful, experimental — `@tanstack/ai-gemini/experimental`)
{
provider: 'gemini-interactions',
model: 'gemini-3.7-flash',
label: 'Gemini Interactions - 3.7 Flash (experimental)',
},
{
provider: 'gemini-interactions',
model: 'gemini-3.6-flash',
Expand Down
4 changes: 2 additions & 2 deletions examples/ts-react-chat/src/routes/api.structured-output.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,10 +160,10 @@ function adapterFor(provider: Provider, model?: string): AnyTextAdapter {
// for the combination and falls back to the engine's legacy
// finalization path.
//
// Default is `gemini-3.6-flash`: the newest *stable* (non-preview)
// Default is `gemini-3.7-flash`: the newest *stable* (non-preview)
// 3.x id, matching the dropdown's first entry. The previous default
// (`gemini-3-pro-preview`) was retired by Google and now 404s.
return geminiText((baseModel || 'gemini-3.6-flash') as 'gemini-3.6-flash')
return geminiText((baseModel || 'gemini-3.7-flash') as 'gemini-3.7-flash')
case 'grok':
return grokText((model || 'grok-build-0.1') as 'grok-build-0.1')
case 'groq':
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ const PROVIDER_MODELS: Record<
// keys on the exact string, so any drift here silently breaks
// combined-mode routing.
gemini: [
{ value: 'gemini-3.7-flash', label: 'Gemini 3.7 Flash' },
{ value: 'gemini-3.6-flash', label: 'Gemini 3.6 Flash' },
{ value: 'gemini-3.5-flash', label: 'Gemini 3.5 Flash' },
{ value: 'gemini-3.5-flash-lite', label: 'Gemini 3.5 Flash Lite' },
Expand Down
54 changes: 54 additions & 0 deletions packages/ai-gemini/src/model-meta.ts
Original file line number Diff line number Diff line change
Expand Up @@ -744,6 +744,50 @@ const GEMINI_OMNI_FLASH_PREVIEW = {
GeminiCachedContentOptions
>

const GEMINI_3_7_FLASH = {
name: 'gemini-3.7-flash',
max_input_tokens: 1_048_576,
max_output_tokens: 65_536,
knowledge_cutoff: '2026-03-01',
supports: {
input: ['text', 'image', 'video', 'audio', 'document'],
output: ['text'],
capabilities: [
'batch_api',
'caching',
'function_calling',
'structured_output',
'thinking',
],
tools: [
'code_execution',
'file_search',
'google_search',
'google_maps',

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Filter Maps from the Interactions tool type

When geminiTextInteractions('gemini-3.7-flash') is used, its ResolveToolCapabilities reuses this tool list, so googleMapsTool() now passes compile-time gating even though convertToolsToInteractionsFormat explicitly throws for google_maps. This turns a supposedly type-safe call into a runtime RUN_ERROR; keep Maps enabled for geminiText() but filter it from the Interactions-specific capability resolution.

Useful? React with 👍 / 👎.

'url_context',
'computer_use',
],
},
// Currently 50% off through the end of 2026.
// https://ai.google.dev/gemini-api/docs/pricing#gemini-3.7-flash
pricing: {
input: {
normal: 0.75,
cached: 0.075,
},
output: {
normal: 3.75,
},
},
} as const satisfies ModelMeta<
GeminiToolConfigOptions &
GeminiSafetyOptions &
GeminiCommonConfigOptions &
GeminiCachedContentOptions &
GeminiStructuredOutputOptions &
GeminiThinkingOptions
>

const GEMINI_3_6_FLASH = {
name: 'gemini-3.6-flash',
max_input_tokens: 1_048_576,
Expand Down Expand Up @@ -869,6 +913,7 @@ const GEMINI_3_5_FLASH_LITE = {
>

export const GEMINI_MODELS = [
GEMINI_3_7_FLASH.name,
GEMINI_3_6_FLASH.name,
GEMINI_3_5_FLASH.name,
GEMINI_3_5_FLASH_LITE.name,
Expand All @@ -889,6 +934,7 @@ export const GEMINI_MODELS = [
* brittle and keeps the engine's legacy finalization fallback.
*/
export const GEMINI_COMBINED_TOOLS_AND_SCHEMA_MODELS = new Set<string>([
GEMINI_3_7_FLASH.name,
GEMINI_3_6_FLASH.name,
GEMINI_3_5_FLASH.name,
GEMINI_3_5_FLASH_LITE.name,
Expand Down Expand Up @@ -1017,6 +1063,12 @@ export type GeminiEmbeddingModelInputModalitiesByName = {
// Manual type map for per-model provider options
export type GeminiChatModelProviderOptionsByName = {
// Models with thinking and structured output support
[GEMINI_3_7_FLASH.name]: GeminiToolConfigOptions &
GeminiSafetyOptions &
GeminiCommonConfigOptions &
GeminiCachedContentOptions &
GeminiStructuredOutputOptions &
GeminiThinkingOptions
[GEMINI_3_6_FLASH.name]: GeminiToolConfigOptions &
GeminiSafetyOptions &
GeminiCommonConfigOptions &
Expand Down Expand Up @@ -1084,6 +1136,7 @@ export type GeminiChatModelProviderOptionsByName = {
* Based on the 'supports.tools' arrays defined for each model.
*/
export type GeminiChatModelToolCapabilitiesByName = {
[GEMINI_3_7_FLASH.name]: typeof GEMINI_3_7_FLASH.supports.tools
[GEMINI_3_6_FLASH.name]: typeof GEMINI_3_6_FLASH.supports.tools
[GEMINI_3_5_FLASH.name]: typeof GEMINI_3_5_FLASH.supports.tools
[GEMINI_3_5_FLASH_LITE.name]: typeof GEMINI_3_5_FLASH_LITE.supports.tools
Expand Down Expand Up @@ -1111,6 +1164,7 @@ export type GeminiChatModelToolCapabilitiesByName = {
*/
export type GeminiModelInputModalitiesByName = {
// Models with full multimodal support (text, image, audio, video, document)
[GEMINI_3_7_FLASH.name]: typeof GEMINI_3_7_FLASH.supports.input
[GEMINI_3_6_FLASH.name]: typeof GEMINI_3_6_FLASH.supports.input
[GEMINI_3_5_FLASH.name]: typeof GEMINI_3_5_FLASH.supports.input
[GEMINI_3_5_FLASH_LITE.name]: typeof GEMINI_3_5_FLASH_LITE.supports.input
Expand Down
39 changes: 39 additions & 0 deletions packages/ai-gemini/tests/model-meta.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,15 @@ describe('Gemini Model Provider Options Type Assertions', () => {
expectTypeOf<Options>().toExtend<BaseOptions>()
})

it('gemini-3.7-flash should support thinking options', () => {
type Model = 'gemini-3.7-flash'
type Options = GeminiChatModelProviderOptionsByName[Model]

expectTypeOf<Options>().toExtend<GeminiThinkingOptions>()
expectTypeOf<Options>().toExtend<GeminiStructuredOutputOptions>()
expectTypeOf<Options>().toExtend<BaseOptions>()
})

it('gemini-3.6-flash should support thinking options', () => {
type Model = 'gemini-3.6-flash'
type Options = GeminiChatModelProviderOptionsByName[Model]
Expand All @@ -220,6 +229,7 @@ describe('Gemini Model Provider Options Type Assertions', () => {
// Verify the type map has all expected model keys
type Keys = keyof GeminiChatModelProviderOptionsByName

expectTypeOf<'gemini-3.7-flash'>().toExtend<Keys>()
expectTypeOf<'gemini-3.6-flash'>().toExtend<Keys>()
expectTypeOf<'gemini-3.5-flash'>().toExtend<Keys>()
expectTypeOf<'gemini-3.5-flash-lite'>().toExtend<Keys>()
Expand Down Expand Up @@ -261,6 +271,9 @@ describe('Gemini Model Provider Options Type Assertions', () => {
})

it('all models should have safety settings', () => {
expectTypeOf<
GeminiChatModelProviderOptionsByName['gemini-3.7-flash']
>().toHaveProperty('safetySettings')
expectTypeOf<
GeminiChatModelProviderOptionsByName['gemini-3.6-flash']
>().toHaveProperty('safetySettings')
Expand Down Expand Up @@ -294,6 +307,9 @@ describe('Gemini Model Provider Options Type Assertions', () => {
})

it('all models should have tool config', () => {
expectTypeOf<
GeminiChatModelProviderOptionsByName['gemini-3.7-flash']
>().toHaveProperty('toolConfig')
expectTypeOf<
GeminiChatModelProviderOptionsByName['gemini-3.6-flash']
>().toHaveProperty('toolConfig')
Expand Down Expand Up @@ -327,6 +343,9 @@ describe('Gemini Model Provider Options Type Assertions', () => {
})

it('all models should have cached content option', () => {
expectTypeOf<
GeminiChatModelProviderOptionsByName['gemini-3.7-flash']
>().toHaveProperty('cachedContent')
expectTypeOf<
GeminiChatModelProviderOptionsByName['gemini-3.6-flash']
>().toHaveProperty('cachedContent')
Expand Down Expand Up @@ -362,6 +381,9 @@ describe('Gemini Model Provider Options Type Assertions', () => {

describe('Type discrimination between model categories', () => {
it('models with thinking should extend GeminiThinkingOptions', () => {
expectTypeOf<
GeminiChatModelProviderOptionsByName['gemini-3.7-flash']
>().toExtend<GeminiThinkingOptions>()
expectTypeOf<
GeminiChatModelProviderOptionsByName['gemini-3.6-flash']
>().toExtend<GeminiThinkingOptions>()
Expand Down Expand Up @@ -395,6 +417,9 @@ describe('Gemini Model Provider Options Type Assertions', () => {
})

it('all models should extend GeminiStructuredOutputOptions', () => {
expectTypeOf<
GeminiChatModelProviderOptionsByName['gemini-3.7-flash']
>().toExtend<GeminiStructuredOutputOptions>()
expectTypeOf<
GeminiChatModelProviderOptionsByName['gemini-3.6-flash']
>().toExtend<GeminiStructuredOutputOptions>()
Expand Down Expand Up @@ -436,6 +461,7 @@ describe('Gemini Model Provider Options Type Assertions', () => {
* content parts based on each Gemini model's supported input modalities.
*
* Models with full multimodal (text + image + audio + video + document):
* - gemini-3.7-flash
* - gemini-3.6-flash
* - gemini-3.5-flash
* - gemini-3.5-flash-lite
Expand Down Expand Up @@ -553,6 +579,19 @@ describe('Gemini Model Input Modality Type Assertions', () => {
})
})

describe('gemini-3.7-flash (full multimodal)', () => {
type Modalities = GeminiModelInputModalitiesByName['gemini-3.7-flash']
type Message = ConstrainedModelMessage<MakeInputModalitiesTypes<Modalities>>

it('should allow all content part types', () => {
expectTypeOf<MessageWithContent<GeminiTextPart>>().toExtend<Message>()
expectTypeOf<MessageWithContent<GeminiImagePart>>().toExtend<Message>()
expectTypeOf<MessageWithContent<GeminiAudioPart>>().toExtend<Message>()
expectTypeOf<MessageWithContent<GeminiVideoPart>>().toExtend<Message>()
expectTypeOf<MessageWithContent<GeminiDocumentPart>>().toExtend<Message>()
})
})

describe('gemini-3.6-flash (full multimodal)', () => {
type Modalities = GeminiModelInputModalitiesByName['gemini-3.6-flash']
type Message = ConstrainedModelMessage<MakeInputModalitiesTypes<Modalities>>
Expand Down
18 changes: 18 additions & 0 deletions packages/ai-gemini/tests/tools-per-model-type-safety.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,24 @@ describe('Gemini per-model tool gating', () => {
])
})

it('gemini-3.7-flash accepts code_execution, file_search, google_search, google_maps, url_context, computer_use', () => {
const adapter = geminiText('gemini-3.7-flash')
typedTools(adapter, [
userTool,
codeExecutionTool(),
fileSearchTool({ fileSearchStoreNames: [] }),
googleSearchTool(),
googleMapsTool(),
urlContextTool(),
computerUseTool({
environment: Environment.ENVIRONMENT_BROWSER,
excludedPredefinedFunctions: [],
}),
// @ts-expect-error - gemini-3.7-flash does not support google_search_retrieval
googleSearchRetrievalTool(),
])
})

it('gemini-3.6-flash accepts code_execution, file_search, google_search, google_maps, url_context, computer_use', () => {
const adapter = geminiText('gemini-3.6-flash')
typedTools(adapter, [
Expand Down
Loading