Skip to content

Commit 7a4cc40

Browse files
feat(Gemini Node): Add support for Nano Banana Pro model (#22254)
1 parent 52b93ed commit 7a4cc40

File tree

4 files changed

+99
-15
lines changed

4 files changed

+99
-15
lines changed

packages/@n8n/nodes-langchain/nodes/vendors/GoogleGemini/actions/image/edit.operation.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { updateDisplayOptions } from 'n8n-workflow';
44
import type { GenerateContentResponse } from '../../helpers/interfaces';
55
import { uploadFile } from '../../helpers/utils';
66
import { apiRequest } from '../../transport';
7+
import { modelRLC } from '../descriptions';
78

89
interface ImagesParameter {
910
values?: Array<{ binaryPropertyName?: string }>;
@@ -74,6 +75,7 @@ function isGenerateContentResponse(response: unknown): response is GenerateConte
7475
}
7576

7677
const properties: INodeProperties[] = [
78+
modelRLC('imageEditModelSearch'),
7779
{
7880
displayName: 'Prompt',
7981
name: 'prompt',
@@ -142,6 +144,11 @@ export const description = updateDisplayOptions(displayOptions, properties);
142144

143145
export async function execute(this: IExecuteFunctions, i: number): Promise<INodeExecutionData[]> {
144146
const prompt = this.getNodeParameter('prompt', i, '');
147+
let model = this.getNodeParameter('modelId', i, '', { extractValue: true }) as string;
148+
if (!model) {
149+
model = 'models/gemini-2.5-flash-image-preview';
150+
}
151+
145152
const binaryPropertyOutput = this.getNodeParameter('options.binaryPropertyOutput', i, 'edited');
146153
const outputKey = typeof binaryPropertyOutput === 'string' ? binaryPropertyOutput : 'data';
147154

@@ -168,7 +175,6 @@ export async function execute(this: IExecuteFunctions, i: number): Promise<INode
168175
fileParts.push({ fileData: { fileUri: uploaded.fileUri, mimeType: uploaded.mimeType } });
169176
}
170177

171-
const model = 'models/gemini-2.5-flash-image-preview';
172178
const generationConfig = {
173179
responseModalities: ['IMAGE'],
174180
};

packages/@n8n/nodes-langchain/nodes/vendors/GoogleGemini/actions/image/generate.operation.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ export async function execute(this: IExecuteFunctions, i: number): Promise<INode
108108
});
109109

110110
return await Promise.all(promises);
111-
} else if (model.includes('imagen') || model.includes('flash-image')) {
111+
} else if (model.includes('imagen')) {
112112
// Imagen models use a different endpoint and request/response structure
113113
const sampleCount = this.getNodeParameter('options.sampleCount', i, 1) as number;
114114
const body = {

packages/@n8n/nodes-langchain/nodes/vendors/GoogleGemini/methods/listSearch.test.ts

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import type { ILoadOptionsFunctions } from 'n8n-workflow';
33

44
import {
55
audioModelSearch,
6+
imageEditModelSearch,
67
imageGenerationModelSearch,
78
modelSearch,
89
videoGenerationModelSearch,
@@ -41,6 +42,9 @@ const mockResponse = {
4142
{
4243
name: 'models/gemini-2.5-flash-image',
4344
},
45+
{
46+
name: 'models/gemini-3-pro-image',
47+
},
4448
],
4549
};
4650

@@ -134,6 +138,65 @@ describe('GoogleGemini -> listSearch', () => {
134138
name: 'models/gemini-2.5-flash-image (Nano Banana)',
135139
value: 'models/gemini-2.5-flash-image',
136140
},
141+
{
142+
// eslint-disable-next-line n8n-nodes-base/node-param-display-name-miscased
143+
name: 'models/gemini-3-pro-image (Nano Banana Pro)',
144+
value: 'models/gemini-3-pro-image',
145+
},
146+
],
147+
});
148+
});
149+
150+
it('should filter out image models', async () => {
151+
apiRequestMock.mockResolvedValue(mockResponse);
152+
153+
const result = await imageGenerationModelSearch.call(mockExecuteFunctions, 'Exp');
154+
155+
expect(result).toEqual({
156+
results: [
157+
{
158+
name: 'models/gemini-2.0-flash-exp-image-generation',
159+
value: 'models/gemini-2.0-flash-exp-image-generation',
160+
},
161+
],
162+
});
163+
});
164+
});
165+
166+
describe('imageEditModelSearch', () => {
167+
it('should return image edit models', async () => {
168+
apiRequestMock.mockResolvedValue(mockResponse);
169+
170+
const result = await imageEditModelSearch.call(mockExecuteFunctions);
171+
172+
expect(result).toEqual({
173+
results: [
174+
{
175+
// eslint-disable-next-line n8n-nodes-base/node-param-display-name-miscased
176+
name: 'models/gemini-2.5-flash-image (Nano Banana)',
177+
value: 'models/gemini-2.5-flash-image',
178+
},
179+
{
180+
// eslint-disable-next-line n8n-nodes-base/node-param-display-name-miscased
181+
name: 'models/gemini-3-pro-image (Nano Banana Pro)',
182+
value: 'models/gemini-3-pro-image',
183+
},
184+
],
185+
});
186+
});
187+
188+
it('should filter out image edit models', async () => {
189+
apiRequestMock.mockResolvedValue(mockResponse);
190+
191+
const result = await imageEditModelSearch.call(mockExecuteFunctions, 'banana pro');
192+
193+
expect(result).toEqual({
194+
results: [
195+
{
196+
// eslint-disable-next-line n8n-nodes-base/node-param-display-name-miscased
197+
name: 'models/gemini-3-pro-image (Nano Banana Pro)',
198+
value: 'models/gemini-3-pro-image',
199+
},
137200
],
138201
});
139202
});

packages/@n8n/nodes-langchain/nodes/vendors/GoogleGemini/methods/listSearch.ts

Lines changed: 28 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -64,21 +64,36 @@ export async function imageGenerationModelSearch(
6464
this: ILoadOptionsFunctions,
6565
filter?: string,
6666
): Promise<INodeListSearchResult> {
67-
const results = await baseModelSearch.call(
68-
this,
69-
(model) =>
70-
model.includes('imagen') ||
71-
model.includes('image-generation') ||
72-
model.includes('flash-image'),
73-
filter,
74-
);
67+
const rawResult = await baseModelSearch.call(this, (model) => model.includes('image'));
68+
let results = rawResult.results.map((r) => {
69+
if (r.name.includes('gemini-2.5-flash-image')) {
70+
return { name: `${r.name} (Nano Banana)`, value: r.value };
71+
}
72+
73+
if (r.name.includes('gemini-3-pro-image')) {
74+
return { name: `${r.name} (Nano Banana Pro)`, value: r.value };
75+
}
76+
77+
return r;
78+
});
79+
80+
if (filter) {
81+
const filterLowerCase = filter.toLowerCase();
82+
results = results.filter((r) => r.name.toLowerCase().includes(filterLowerCase));
83+
}
84+
85+
return {
86+
results,
87+
};
88+
}
7589

90+
export async function imageEditModelSearch(
91+
this: ILoadOptionsFunctions,
92+
filter?: string,
93+
): Promise<INodeListSearchResult> {
94+
const result = await imageGenerationModelSearch.call(this, filter);
7695
return {
77-
results: results.results.map((r) =>
78-
r.name.includes('gemini-2.5-flash-image')
79-
? { name: `${r.name} (Nano Banana)`, value: r.value }
80-
: r,
81-
),
96+
results: result.results.filter((r) => r.name.toLowerCase().includes('nano banana')),
8297
};
8398
}
8499

0 commit comments

Comments
 (0)