mirror of
https://github.com/HeyPuter/puter.git
synced 2026-05-04 00:20:45 +00:00
fix nano banana pro pricing and resolutions (#2501)
Docker Image CI / build-and-push-image (push) Has been cancelled
Maintain Release Merge PR / update-release-pr (push) Has been cancelled
release-please / release-please (push) Has been cancelled
test / test-backend (24.x) (push) Has been cancelled
test / API tests (node env, api-test) (24.x) (push) Has been cancelled
test / puterjs (node env, vitest) (24.x) (push) Has been cancelled
Docker Image CI / build-and-push-image (push) Has been cancelled
Maintain Release Merge PR / update-release-pr (push) Has been cancelled
release-please / release-please (push) Has been cancelled
test / test-backend (24.x) (push) Has been cancelled
test / API tests (node env, api-test) (24.x) (push) Has been cancelled
test / puterjs (node env, vitest) (24.x) (push) Has been cancelled
* 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
This commit is contained in:
+33
-16
@@ -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);
|
||||
|
||||
+15
-2
@@ -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<string, Record<string, { w: number; h: number }>> = {
|
||||
'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 } },
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user