From 779321d3342e53b7a0f0db33f50e5c2f2db8ce01 Mon Sep 17 00:00:00 2001 From: P3il4 <42489293+P3il4@users.noreply.github.com> Date: Mon, 23 Feb 2026 11:39:48 +0300 Subject: [PATCH] fix nano banana pro pricing and resolutions (#2501) * fix nano banana pro pricing and resolutions * move gemini resolution map into models.ts * require gemini 3 model for specific resolution and price logic * fix usageamount for mp models --- .../TogetherImageGenerationProvider.ts | 49 +++++++++++++------ .../TogetherImageGenerationProvider/models.ts | 17 ++++++- 2 files changed, 48 insertions(+), 18 deletions(-) diff --git a/src/backend/src/services/ai/image/providers/TogetherImageGenerationProvider/TogetherImageGenerationProvider.ts b/src/backend/src/services/ai/image/providers/TogetherImageGenerationProvider/TogetherImageGenerationProvider.ts index 5227c4630..8cb9387ae 100644 --- a/src/backend/src/services/ai/image/providers/TogetherImageGenerationProvider/TogetherImageGenerationProvider.ts +++ b/src/backend/src/services/ai/image/providers/TogetherImageGenerationProvider/TogetherImageGenerationProvider.ts @@ -24,7 +24,7 @@ import { Context } from '../../../../../util/context.js'; import { EventService } from '../../../../EventService.js'; import { MeteringService } from '../../../../MeteringService/MeteringService.js'; import { IGenerateParams, IImageModel, IImageProvider } from '../types.js'; -import { TOGETHER_IMAGE_GENERATION_MODELS } from './models.js'; +import { TOGETHER_IMAGE_GENERATION_MODELS, GEMINI_3_IMAGE_RESOLUTION_MAP } from './models.js'; const TOGETHER_DEFAULT_RATIO = { w: 1024, h: 1024 }; type TogetherGenerateParams = IGenerateParams & { @@ -101,21 +101,28 @@ export class TogetherImageGenerationProvider implements IImageProvider { throw new Error('actor not found in context'); } - const priceKey = '1MP'; - const centsPerMP = selectedModel.costs[priceKey]; // hardcoded for now since all together ai models use this type of pricing - if ( centsPerMP === undefined ) { - throw new Error(`No pricing configured for model ${selectedModel.id}`); + const isGemini3 = selectedModel.id === 'togetherai:google/gemini-3-pro-image'; + + let costInMicroCents: number; + let usageAmount: number; + const qualityCostKey = isGemini3 && quality && selectedModel.costs[quality] !== undefined ? quality : undefined; + + if ( qualityCostKey ) { + const centsPerImage = selectedModel.costs[qualityCostKey]; + costInMicroCents = centsPerImage * 1_000_000; + usageAmount = 1; + } else { + const priceKey = '1MP'; + const centsPerMP = selectedModel.costs[priceKey]; + if ( centsPerMP === undefined ) { + throw new Error(`No pricing configured for model ${selectedModel.id}`); + } + const MP = (ratio.h * ratio.w) / 1_000_000; + costInMicroCents = centsPerMP * MP * 1_000_000; + usageAmount = MP; } - const usageType = `${selectedModel.id}:${priceKey}`; - - let MP = (ratio.h * ratio.w) / 1_000_000; - if ( quality ) { - // if quality its gemini 3 image, so price based on those K sizes as MP - MP = parseInt(quality[0]) ; // convert to microcents - } - - const costInMicroCents = centsPerMP * MP * 1_000_000; // cost in microcents + const usageType = `${selectedModel.id}:${quality || '1MP'}`; const usageAllowed = await this.#meteringService.hasEnoughCredits(actor, costInMicroCents); @@ -123,7 +130,17 @@ export class TogetherImageGenerationProvider implements IImageProvider { throw APIError.create('insufficient_funds'); } - const request = this.#buildRequest(prompt, { ...options, ratio, model: selectedModel.id.replace('togetherai:', '') }) as unknown as Together.Images.ImageGenerateParams; + // Resolve abstract aspect ratios to actual pixel dimensions for Gemini 3 Pro + let resolvedRatio = ratio; + if ( isGemini3 && quality ) { + const ratioKey = `${ratio.w}:${ratio.h}`; + const resolutionEntry = GEMINI_3_IMAGE_RESOLUTION_MAP[ratioKey]?.[quality]; + if ( resolutionEntry ) { + resolvedRatio = resolutionEntry; + } + } + + const request = this.#buildRequest(prompt, { ...options, ratio: resolvedRatio, model: selectedModel.id.replace('togetherai:', '') }) as unknown as Together.Images.ImageGenerateParams; try { const response = await this.#client.images.generate(request); @@ -131,7 +148,7 @@ export class TogetherImageGenerationProvider implements IImageProvider { throw new Error('Together AI response did not include image data'); } - this.#meteringService.incrementUsage(actor, usageType, MP, costInMicroCents); + this.#meteringService.incrementUsage(actor, usageType, usageAmount, costInMicroCents); const first = response.data[0] as { url?: string; b64_json?: string }; const url = first.url || (first.b64_json ? `data:image/png;base64,${ first.b64_json}` : undefined); diff --git a/src/backend/src/services/ai/image/providers/TogetherImageGenerationProvider/models.ts b/src/backend/src/services/ai/image/providers/TogetherImageGenerationProvider/models.ts index 9fbd1e2b8..5eb43ace3 100644 --- a/src/backend/src/services/ai/image/providers/TogetherImageGenerationProvider/models.ts +++ b/src/backend/src/services/ai/image/providers/TogetherImageGenerationProvider/models.ts @@ -237,7 +237,7 @@ export const TOGETHER_IMAGE_GENERATION_MODELS: IImageModel[] = [ aliases: ['gemini-3-pro-image', 'google/gemini-3-pro-image'], name: 'gemini-3-pro-image (Together AI)', costs_currency: 'usd-cents', - index_cost_key: '1MP', + index_cost_key: '1K', allowedQualityLevels: ['1K', '2K', '4K'], allowedRatios: [ { w: 1, h: 1 }, @@ -251,7 +251,7 @@ export const TOGETHER_IMAGE_GENERATION_MODELS: IImageModel[] = [ { w: 16, h: 9 }, { w: 21, h: 9 }, ], - costs: { '1MP': 13.51 }, + costs: { '1K': 13.4, '2K': 13.4, '4K': 24 }, }, { id: 'togetherai:google/imagen-4.0-fast', @@ -317,3 +317,16 @@ export const TOGETHER_IMAGE_GENERATION_MODELS: IImageModel[] = [ costs: { '1MP': 7 }, }, ]; + +export const GEMINI_3_IMAGE_RESOLUTION_MAP: Record> = { + '1:1': { '1K': { w: 1024, h: 1024 }, '2K': { w: 2048, h: 2048 }, '4K': { w: 4096, h: 4096 } }, + '2:3': { '1K': { w: 848, h: 1264 }, '2K': { w: 1696, h: 2528 }, '4K': { w: 3392, h: 5096 } }, + '3:2': { '1K': { w: 1264, h: 848 }, '2K': { w: 2528, h: 1696 }, '4K': { w: 5096, h: 3392 } }, + '3:4': { '1K': { w: 896, h: 1200 }, '2K': { w: 1792, h: 2400 }, '4K': { w: 3584, h: 4800 } }, + '4:3': { '1K': { w: 1200, h: 896 }, '2K': { w: 2400, h: 1792 }, '4K': { w: 4800, h: 3584 } }, + '4:5': { '1K': { w: 928, h: 1152 }, '2K': { w: 1856, h: 2304 }, '4K': { w: 3712, h: 4608 } }, + '5:4': { '1K': { w: 1152, h: 928 }, '2K': { w: 2304, h: 1856 }, '4K': { w: 4608, h: 3712 } }, + '9:16': { '1K': { w: 768, h: 1376 }, '2K': { w: 1536, h: 2752 }, '4K': { w: 3072, h: 5504 } }, + '16:9': { '1K': { w: 1376, h: 768 }, '2K': { w: 2752, h: 1536 }, '4K': { w: 5504, h: 3072 } }, + '21:9': { '1K': { w: 1584, h: 672 }, '2K': { w: 3168, h: 1344 }, '4K': { w: 6336, h: 2688 } }, +};