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
3 changes: 3 additions & 0 deletions scraper/litellm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ export type LiteLLMModel = {
cache_read_input_token_cost?: number;
cache_creation_input_token_cost?: number;
input_cost_per_token_cache_hit?: number;
input_cost_per_image?: number;
input_cost_per_pixel?: number;
output_cost_per_image?: number;
litellm_provider?: string;
max_input_tokens?: number;
max_output_tokens?: number;
Expand Down
176 changes: 122 additions & 54 deletions scraper/scrapers/aws-image.ts
Original file line number Diff line number Diff line change
@@ -1,74 +1,142 @@
import type { DataFormat } from "@/src/dataFormat";
import type { DataFormat, ImageResolution } from "@/src/dataFormat";
import { addImageModelToFormat, type ImageModelDefinition } from "../shared";

// AWS Bedrock image generation model pricing
// Prices are per image, varying by resolution
// Source: https://aws.amazon.com/bedrock/pricing/
const AWS_IMAGE_MODELS: Record<string, ImageModelDefinition> = {
"stability.stable-diffusion-xl-v1": {
name: "Stable Diffusion XL 1.0",
provider: "Stability AI",
supportedResolutions: ["512x512", "1024x1024"],
supportsNegativePrompts: true,
pricing: [
{ resolution: "512x512", pricePerImage: 0.018 },
{ resolution: "1024x1024", pricePerImage: 0.036 },
],
},
"stability.sd3-large-v1:0": {
name: "Stable Diffusion 3 Large",
provider: "Stability AI",
supportedResolutions: ["1024x1024"],
supportsNegativePrompts: true,
pricing: [{ resolution: "1024x1024", pricePerImage: 0.08 }],
},
"stability.stable-image-ultra-v1:0": {
name: "Stable Image Ultra",
provider: "Stability AI",
supportedResolutions: ["1024x1024"],
supportsNegativePrompts: true,
pricing: [{ resolution: "1024x1024", pricePerImage: 0.14 }],
},
"stability.stable-image-core-v1:0": {
name: "Stable Image Core",
provider: "Stability AI",
supportedResolutions: ["1024x1024"],
supportsNegativePrompts: true,
pricing: [{ resolution: "1024x1024", pricePerImage: 0.04 }],
},
"amazon.titan-image-generator-v1": {
// Non-pricing metadata that can't be derived from the AWS pricing API.
// Keys are the attribute values from the pricing file (titanModel or model field).
const MODEL_META: Record<
string,
{ name: string; provider: string; supportsNegativePrompts: boolean }
> = {
"Titan Image Generator G1": {
name: "Amazon Titan Image Generator",
provider: "Amazon",
supportedResolutions: ["512x512", "1024x1024"],
supportsNegativePrompts: true,
pricing: [
{ resolution: "512x512", pricePerImage: 0.008 },
{ resolution: "1024x1024", pricePerImage: 0.01 },
],
},
"amazon.titan-image-generator-v2:0": {
"Titan Image Generator V2": {
name: "Amazon Titan Image Generator v2",
provider: "Amazon",
supportedResolutions: ["512x512", "1024x1024"],
supportsNegativePrompts: true,
pricing: [
{ resolution: "512x512", pricePerImage: 0.008 },
{ resolution: "1024x1024", pricePerImage: 0.01 },
],
},
"Nova Canvas": {
name: "Nova Canvas",
provider: "Amazon",
supportsNegativePrompts: true,
},
};

// Maps resolution number from inferenceType (e.g. "T2I 1024 Standard") to ImageResolution
const RESOLUTION_MAP: Record<string, ImageResolution> = {
"512": "512x512",
"1024": "1024x1024",
"2048": "2048x2048",
};

type PriceDimension = {
pricePerUnit?: { USD?: string };
description?: string;
unit?: string;
};

type Term = {
priceDimensions?: Record<string, PriceDimension>;
};

type PricingFile = {
products: Record<string, { attributes: Record<string, string> }>;
terms: Record<string, Record<string, Record<string, Term>>>;
};

async function getBedrockPricingFile(): Promise<PricingFile> {
const response = await fetch(
"https://pricing.us-east-1.amazonaws.com/offers/v1.0/aws/AmazonBedrock/current/us-east-1/index.json"
);
if (!response.ok) {
throw new Error(`Failed to fetch Bedrock pricing file: ${response.statusText}`);
}
return response.json();
}

// Parse inferenceType like "T2I 1024 Standard" → { mode: "T2I", resolution: "1024x1024", quality: "Standard" }
function parseInferenceType(
inferenceType: string
): { mode: string; resolution: ImageResolution; quality: string } | null {
// Strip optional "Custom " prefix
const normalized = inferenceType.replace(/^Custom\s+/, "");
const parts = normalized.split(" ");
if (parts.length !== 3) return null;
const [mode, resNum, quality] = parts;
const resolution = RESOLUTION_MAP[resNum];
if (!resolution) return null;
return { mode, resolution, quality };
}

export default async function scrapeAwsImageData(fmt: DataFormat) {
// Initialize imageModels if not present
if (!fmt.imageModels) {
fmt.imageModels = {};
}

for (const [_modelId, model] of Object.entries(AWS_IMAGE_MODELS)) {
await addImageModelToFormat(fmt, "aws", "us-east-1", model, "hardcoded", "2026-03-20");
const pricingFile = await getBedrockPricingFile();

// modelKey -> resolution -> lowest Standard T2I price
const pricingByModel: Record<string, Record<string, number>> = {};

for (const [sku, product] of Object.entries(pricingFile.products)) {
const attrs = product.attributes;

// Titan models use titanModel attr; others use model attr
const modelKey = attrs.titanModel ?? attrs.model;
if (!modelKey || !MODEL_META[modelKey]) continue;

const inferenceType = attrs.inferenceType;
if (!inferenceType) continue;

const parsed = parseInferenceType(inferenceType);
if (!parsed) continue;

// Only include text-to-image Standard pricing (the base on-demand price)
if (parsed.mode !== "T2I" || parsed.quality !== "Standard") continue;

const termHolder = pricingFile.terms.OnDemand[sku as any];
if (!termHolder) continue;
const termKeys = Object.keys(termHolder);
if (termKeys.length !== 1) continue;
const term = termHolder[termKeys[0]];
if (!term.priceDimensions) continue;

for (const dim of Object.values(term.priceDimensions)) {
if (dim.unit?.toLowerCase() !== "image") continue;
const usd = dim.pricePerUnit?.USD;
if (!usd) continue;

if (!pricingByModel[modelKey]) pricingByModel[modelKey] = {};
pricingByModel[modelKey][parsed.resolution] = parseFloat(usd);
}
}

let modelsAdded = 0;
for (const [modelKey, meta] of Object.entries(MODEL_META)) {
const pricing = pricingByModel[modelKey];
if (!pricing || Object.keys(pricing).length === 0) {
console.warn(`No pricing found in Bedrock pricing file for image model: ${modelKey}`);
continue;
}

const supportedResolutions = Object.keys(pricing) as ImageResolution[];

const modelDef: ImageModelDefinition = {
name: meta.name,
provider: meta.provider,
supportsNegativePrompts: meta.supportsNegativePrompts,
supportedResolutions,
pricing: supportedResolutions.map((resolution) => ({
resolution,
pricePerImage: pricing[resolution],
})),
};

await addImageModelToFormat(fmt, "aws", "us-east-1", modelDef, "scraped");
modelsAdded++;
}

console.log(
`Finished scraping AWS image generation data (${Object.keys(AWS_IMAGE_MODELS).length} models)`
);
console.log(`Finished scraping AWS image generation data (${modelsAdded} models)`);
}
80 changes: 58 additions & 22 deletions scraper/scrapers/gcp-image.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,41 @@
import type { DataFormat } from "@/src/dataFormat";
import type { DataFormat, ImageResolution } from "@/src/dataFormat";
import { addImageModelToFormat, type ImageModelDefinition } from "../shared";
import { fetchLiteLLMPricing } from "../litellm";

// GCP Vertex AI image generation model pricing
// Prices are per image
// Source: https://cloud.google.com/vertex-ai/generative-ai/pricing
const GCP_IMAGE_MODELS: Record<string, ImageModelDefinition> = {
"imagen-3.0-generate": {
name: "Imagen 3",
provider: "Google",
// Non-pricing metadata keyed by LiteLLM model ID (vertex_ai/* prefix stripped)
const MODEL_META: Record<
string,
{ name: string; supportsNegativePrompts: boolean; supportedResolutions: ImageResolution[] }
> = {
"imagegeneration@006": {
name: "Imagen 2",
supportsNegativePrompts: true,
supportedResolutions: ["1024x1024"],
},
"imagen-3.0-generate-001": {
name: "Imagen 3",
supportsNegativePrompts: false,
pricing: [{ resolution: "1024x1024", pricePerImage: 0.04 }],
supportedResolutions: ["1024x1024"],
},
"imagen-3.0-fast-generate": {
"imagen-3.0-fast-generate-001": {
name: "Imagen 3 Fast",
provider: "Google",
supportsNegativePrompts: false,
supportedResolutions: ["1024x1024"],
},
"imagen-4.0-generate-001": {
name: "Imagen 4",
supportsNegativePrompts: false,
pricing: [{ resolution: "1024x1024", pricePerImage: 0.02 }],
supportedResolutions: ["1024x1024"],
},
"imagegeneration@006": {
name: "Imagen 2",
provider: "Google",
"imagen-4.0-fast-generate-001": {
name: "Imagen 4 Fast",
supportsNegativePrompts: false,
supportedResolutions: ["1024x1024"],
},
"imagen-4.0-ultra-generate-001": {
name: "Imagen 4 Ultra",
supportsNegativePrompts: false,
supportedResolutions: ["1024x1024"],
supportsNegativePrompts: true,
pricing: [{ resolution: "1024x1024", pricePerImage: 0.02 }],
},
};

Expand All @@ -33,11 +44,36 @@ export default async function scrapeGcpImageData(fmt: DataFormat) {
fmt.imageModels = {};
}

for (const [_modelId, model] of Object.entries(GCP_IMAGE_MODELS)) {
await addImageModelToFormat(fmt, "gcp", "us-central1", model, "hardcoded", "2026-03-20");
const pricing = await fetchLiteLLMPricing();
let modelsAdded = 0;

for (const [modelId, model] of Object.entries(pricing)) {
if (model.litellm_provider !== "vertex_ai-image-models") continue;
if (model.mode !== "image_generation") continue;
if (!model.output_cost_per_image) continue;

// Skip deprecated models
if (model.deprecation_date && new Date(model.deprecation_date) < new Date()) continue;

// Strip "vertex_ai/" prefix to get the bare model ID
const bareId = modelId.replace(/^vertex_ai\//, "");
const meta = MODEL_META[bareId];
if (!meta) continue;

const modelDef: ImageModelDefinition = {
name: meta.name,
provider: "Google",
supportsNegativePrompts: meta.supportsNegativePrompts,
supportedResolutions: meta.supportedResolutions,
pricing: meta.supportedResolutions.map((resolution) => ({
resolution,
pricePerImage: model.output_cost_per_image!,
})),
};

await addImageModelToFormat(fmt, "gcp", "us-central1", modelDef, "scraped");
modelsAdded++;
}

console.log(
`Finished scraping GCP Vertex AI image generation data (${Object.keys(GCP_IMAGE_MODELS).length} models)`
);
console.log(`Finished scraping GCP Vertex AI image generation data (${modelsAdded} models)`);
}
Loading
Loading