From 8a8e9989f413c5db6be4074d4374b6958a0040d1 Mon Sep 17 00:00:00 2001 From: Kai Date: Mon, 9 Feb 2026 15:16:05 -0800 Subject: [PATCH] Add agent economics hub, comparison page, and pricing API --- src/app/api/economics/route.ts | 9 + src/app/economics/compare/page.tsx | 132 +++++++++++ src/app/economics/page.tsx | 350 +++++++++++++++++++++++++++++ src/data/model-economics.json | 110 +++++++++ src/lib/economics.ts | 55 +++++ 5 files changed, 656 insertions(+) create mode 100644 src/app/api/economics/route.ts create mode 100644 src/app/economics/compare/page.tsx create mode 100644 src/app/economics/page.tsx create mode 100644 src/data/model-economics.json create mode 100644 src/lib/economics.ts diff --git a/src/app/api/economics/route.ts b/src/app/api/economics/route.ts new file mode 100644 index 00000000..b3273a67 --- /dev/null +++ b/src/app/api/economics/route.ts @@ -0,0 +1,9 @@ +import { modelEconomics } from "@/lib/economics"; + +export function GET() { + return Response.json({ + generatedAt: new Date().toISOString(), + count: modelEconomics.length, + models: modelEconomics, + }); +} diff --git a/src/app/economics/compare/page.tsx b/src/app/economics/compare/page.tsx new file mode 100644 index 00000000..ac46ea20 --- /dev/null +++ b/src/app/economics/compare/page.tsx @@ -0,0 +1,132 @@ +/* eslint-disable react/no-unescaped-entities */ +import { Badge } from "@/components/ui/badge"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { Separator } from "@/components/ui/separator"; +import { costEfficiencyScore, modelEconomics, type ModelEconomics } from "@/lib/economics"; + +const formatCurrency = (value: number) => + new Intl.NumberFormat("en-US", { + style: "currency", + currency: "USD", + minimumFractionDigits: value < 1 ? 3 : 2, + maximumFractionDigits: value < 1 ? 3 : 2, + }).format(value); + +const scoreByUseCase = (model: ModelEconomics, useCase: string) => { + const qualityWeight = useCase === "feature-build" ? 0.6 : 0.45; + const speedWeight = useCase === "testing" ? 0.35 : 0.2; + const efficiencyWeight = 0.2; + const affinityWeight = 0.15; + + const affinity = model.recommendedFor.includes(useCase) ? 10 : 5; + + return ( + model.qualityRating * qualityWeight + + model.speedRating * speedWeight + + Math.min(costEfficiencyScore(model), 10) * efficiencyWeight + + affinity * affinityWeight + ); +}; + +const useCases = [ + { id: "feature-build", label: "Feature builds" }, + { id: "bug-fix", label: "Bug fixing" }, + { id: "code-review", label: "Code review" }, + { id: "testing", label: "Testing pipelines" }, + { id: "docs", label: "Documentation" }, +]; + +export default function EconomicsComparePage() { + const ranked = [...modelEconomics].sort((a, b) => costEfficiencyScore(b) - costEfficiencyScore(a)); + + return ( +
+
+ + Model Cost Comparison + +

Compare model economics side-by-side

+

+ Compare price, context window, speed, and quality. Cost-efficiency score highlights how much quality you get per dollar. +

+
+ + + +
+
+ {ranked.map((model, index) => { + const score = costEfficiencyScore(model); + + return ( + + +
+
+ {model.name} +

{model.provider}

+
+ {index === 0 && Best value} +
+
+ +
+ Input / 1M + {formatCurrency(model.inputPricePer1M)} +
+
+ Output / 1M + {formatCurrency(model.outputPricePer1M)} +
+
+ Context window + {model.contextWindow.toLocaleString()} +
+
+ Speed rating + {model.speedRating.toFixed(1)} / 10 +
+
+ Quality rating + {model.qualityRating.toFixed(1)} / 10 +
+
+ Cost-efficiency score + {score.toFixed(2)} +
+
+
+ ); + })} +
+
+ + + +
+

Best model recommendations by use case

+
+ {useCases.map((useCase) => { + const best = [...modelEconomics].sort( + (a, b) => scoreByUseCase(b, useCase.id) - scoreByUseCase(a, useCase.id) + )[0]; + + return ( + + + {useCase.label} + + +

{best.name}

+

+ Quality {best.qualityRating.toFixed(1)} · Speed {best.speedRating.toFixed(1)} · Efficiency {costEfficiencyScore(best).toFixed(2)} +

+
+
+ ); + })} +
+
+
+ ); +} diff --git a/src/app/economics/page.tsx b/src/app/economics/page.tsx new file mode 100644 index 00000000..3c55943d --- /dev/null +++ b/src/app/economics/page.tsx @@ -0,0 +1,350 @@ +/* eslint-disable react/no-unescaped-entities */ +"use client"; + +import Link from "next/link"; +import { useMemo, useState } from "react"; +import { Badge } from "@/components/ui/badge"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { Separator } from "@/components/ui/separator"; +import { + calculateTokenCostUSD, + complexityMultipliers, + modelEconomics, + taskProfiles, + type ComplexityKey, + type TaskType, +} from "@/lib/economics"; + +const featuredModelIds = [ + "gpt-5.3", + "gpt-5.3-codex", + "claude-opus-4.1", + "claude-sonnet-4.5", + "gemini-2.5-pro", + "llama-4-maverick", +]; + +const complexityLabels: Record = { + low: "Low", + medium: "Medium", + high: "High", + extreme: "Extreme", +}; + +const formatCurrency = (value: number) => + new Intl.NumberFormat("en-US", { + style: "currency", + currency: "USD", + minimumFractionDigits: value < 1 ? 4 : 2, + maximumFractionDigits: value < 1 ? 4 : 2, + }).format(value); + +export default function EconomicsHubPage() { + const [taskType, setTaskType] = useState("bug-fix"); + const [complexity, setComplexity] = useState("medium"); + const [selectedModelId, setSelectedModelId] = useState("gpt-5.3-codex"); + + const [tasksPerDay, setTasksPerDay] = useState(120); + const [avgInputTokens, setAvgInputTokens] = useState(3200); + const [avgOutputTokens, setAvgOutputTokens] = useState(1400); + const [mixPrimary, setMixPrimary] = useState("gpt-5.3-codex"); + const [mixSecondary, setMixSecondary] = useState("claude-sonnet-4.5"); + const [mixTertiary, setMixTertiary] = useState("gemini-2.5-flash"); + const [mixPrimaryShare, setMixPrimaryShare] = useState(40); + const [mixSecondaryShare, setMixSecondaryShare] = useState(35); + + const tertiaryShare = Math.max(0, 100 - mixPrimaryShare - mixSecondaryShare); + + const featuredModels = useMemo( + () => modelEconomics.filter((model) => featuredModelIds.includes(model.id)), + [] + ); + + const selectedModel = + modelEconomics.find((model) => model.id === selectedModelId) ?? modelEconomics[0]; + + const taskEstimate = useMemo(() => { + const profile = taskProfiles[taskType]; + const multiplier = complexityMultipliers[complexity]; + const input = Math.round(profile.inputTokens * multiplier); + const output = Math.round(profile.outputTokens * multiplier); + const cost = calculateTokenCostUSD(selectedModel, input, output); + + return { + input, + output, + cost, + }; + }, [complexity, selectedModel, taskType]); + + const monthlyProjection = useMemo(() => { + const primaryModel = modelEconomics.find((model) => model.id === mixPrimary) ?? modelEconomics[0]; + const secondaryModel = + modelEconomics.find((model) => model.id === mixSecondary) ?? modelEconomics[1] ?? modelEconomics[0]; + const tertiaryModel = + modelEconomics.find((model) => model.id === mixTertiary) ?? modelEconomics[2] ?? modelEconomics[0]; + + const costFor = (modelId: string) => { + const model = modelEconomics.find((candidate) => candidate.id === modelId) ?? modelEconomics[0]; + return calculateTokenCostUSD(model, avgInputTokens, avgOutputTokens); + }; + + const weightedTaskCost = + costFor(primaryModel.id) * (mixPrimaryShare / 100) + + costFor(secondaryModel.id) * (mixSecondaryShare / 100) + + costFor(tertiaryModel.id) * (tertiaryShare / 100); + + const monthlyTasks = tasksPerDay * 30; + + return { + weightedTaskCost, + monthlyTasks, + monthlyCost: weightedTaskCost * monthlyTasks, + annualCost: weightedTaskCost * monthlyTasks * 12, + models: [primaryModel, secondaryModel, tertiaryModel], + }; + }, [ + avgInputTokens, + avgOutputTokens, + mixPrimary, + mixPrimaryShare, + mixSecondary, + mixSecondaryShare, + mixTertiary, + tasksPerDay, + tertiaryShare, + ]); + + return ( +
+
+ + Agent Economics Hub + +

Agent Economics & Cost Calculator

+

+ Understand true operating cost per task, compare model pricing, and project monthly agent spend before your workload scales. +

+
+ + + +
+
+

Model cost comparison

+ + Open deep comparison → + +
+ +
+ + + + + + + + + + + {featuredModels.map((model, index) => ( + + + + + + + ))} + +
ModelProviderInput / 1MOutput / 1M
{model.name}{model.provider}{formatCurrency(model.inputPricePer1M)}{formatCurrency(model.outputPricePer1M)}
+
+
+ + + +
+ + + Cost per task estimator + + +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+

Projected cost for this task

+

{formatCurrency(taskEstimate.cost)}

+

+ {taskEstimate.input.toLocaleString()} input + {taskEstimate.output.toLocaleString()} output tokens +

+
+
+
+ + + + Monthly cost projection + + +
+ + setTasksPerDay(Number(event.target.value))} + className="w-full mt-1" + /> +
+ +
+ + setAvgInputTokens(Number(event.target.value))} + className="w-full mt-1" + /> +
+ +
+ + setAvgOutputTokens(Number(event.target.value))} + className="w-full mt-1" + /> +
+ +
+
+ +
+ + setMixPrimaryShare(Number(event.target.value))} className="w-full" /> +
+
+ +
+ +
+ + setMixSecondaryShare(Number(event.target.value))} className="w-full" /> +
+
+ +
+ +

Tertiary mix auto-balances: {tertiaryShare}%

+
+
+ +
+

Weighted cost per task

+

{formatCurrency(monthlyProjection.weightedTaskCost)}

+

Monthly: {formatCurrency(monthlyProjection.monthlyCost)}

+

Annual run-rate: {formatCurrency(monthlyProjection.annualCost)}

+
+
+
+
+ + + +
+

How to reduce agent costs

+
+ + + Caching strategy + + + Cache stable system prompts, docs retrieval blocks, and repeated context so agents spend fewer input tokens each run. + + + + + + Model routing + + + Route simple tests and docs work to lower-cost models. Escalate only complex feature and architecture tasks to premium models. + + + + + + Prompt optimization + + + Keep prompts short, structured, and explicit. Better instructions reduce retries, output bloat, and wasted token cycles. + + +
+
+
+ ); +} diff --git a/src/data/model-economics.json b/src/data/model-economics.json new file mode 100644 index 00000000..0953b94b --- /dev/null +++ b/src/data/model-economics.json @@ -0,0 +1,110 @@ +[ + { + "id": "gpt-5.3", + "name": "GPT-5.3", + "provider": "OpenAI", + "family": "GPT", + "inputPricePer1M": 10, + "outputPricePer1M": 30, + "contextWindow": 256000, + "speedRating": 7.2, + "qualityRating": 9.8, + "recommendedFor": ["feature-build", "research", "architecture"] + }, + { + "id": "gpt-5.3-codex", + "name": "GPT-5.3-Codex", + "provider": "OpenAI", + "family": "GPT", + "inputPricePer1M": 8, + "outputPricePer1M": 24, + "contextWindow": 256000, + "speedRating": 8, + "qualityRating": 9.6, + "recommendedFor": ["bug-fix", "feature-build", "testing", "code-review"] + }, + { + "id": "claude-opus-4.1", + "name": "Claude Opus 4.1", + "provider": "Anthropic", + "family": "Claude Opus", + "inputPricePer1M": 15, + "outputPricePer1M": 75, + "contextWindow": 200000, + "speedRating": 6.5, + "qualityRating": 9.9, + "recommendedFor": ["feature-build", "research", "docs"] + }, + { + "id": "claude-sonnet-4.5", + "name": "Claude Sonnet 4.5", + "provider": "Anthropic", + "family": "Claude Sonnet", + "inputPricePer1M": 3, + "outputPricePer1M": 15, + "contextWindow": 200000, + "speedRating": 8.2, + "qualityRating": 9, + "recommendedFor": ["code-review", "docs", "testing", "bug-fix"] + }, + { + "id": "gemini-2.5-pro", + "name": "Gemini 2.5 Pro", + "provider": "Google", + "family": "Gemini", + "inputPricePer1M": 2.5, + "outputPricePer1M": 10, + "contextWindow": 1000000, + "speedRating": 7.8, + "qualityRating": 8.9, + "recommendedFor": ["research", "feature-build", "docs"] + }, + { + "id": "gemini-2.5-flash", + "name": "Gemini 2.5 Flash", + "provider": "Google", + "family": "Gemini", + "inputPricePer1M": 0.3, + "outputPricePer1M": 1.5, + "contextWindow": 1000000, + "speedRating": 9.5, + "qualityRating": 7.8, + "recommendedFor": ["testing", "docs", "support"] + }, + { + "id": "llama-4-maverick", + "name": "Llama 4 Maverick", + "provider": "Meta", + "family": "Llama", + "inputPricePer1M": 0.6, + "outputPricePer1M": 2, + "contextWindow": 128000, + "speedRating": 8.6, + "qualityRating": 7.6, + "recommendedFor": ["testing", "classification", "docs"] + }, + { + "id": "llama-3.3-70b", + "name": "Llama 3.3 70B", + "provider": "Meta", + "family": "Llama", + "inputPricePer1M": 0.8, + "outputPricePer1M": 2.4, + "contextWindow": 128000, + "speedRating": 8.1, + "qualityRating": 7.9, + "recommendedFor": ["code-review", "testing", "docs"] + }, + { + "id": "mistral-large-2", + "name": "Mistral Large 2", + "provider": "Mistral", + "family": "Mistral", + "inputPricePer1M": 2, + "outputPricePer1M": 6, + "contextWindow": 128000, + "speedRating": 8.4, + "qualityRating": 8.3, + "recommendedFor": ["code-review", "agent-routing", "docs"] + } +] diff --git a/src/lib/economics.ts b/src/lib/economics.ts new file mode 100644 index 00000000..b3c9b566 --- /dev/null +++ b/src/lib/economics.ts @@ -0,0 +1,55 @@ +import modelEconomicsData from "@/data/model-economics.json"; + +export interface ModelEconomics { + id: string; + name: string; + provider: string; + family: string; + inputPricePer1M: number; + outputPricePer1M: number; + contextWindow: number; + speedRating: number; + qualityRating: number; + recommendedFor: string[]; +} + +export type TaskType = + | "code-review" + | "bug-fix" + | "feature-build" + | "docs" + | "testing"; + +export const modelEconomics = modelEconomicsData as ModelEconomics[]; + +export const taskProfiles: Record = { + "code-review": { label: "Code review", inputTokens: 4200, outputTokens: 1100 }, + "bug-fix": { label: "Bug fix", inputTokens: 6200, outputTokens: 1800 }, + "feature-build": { label: "Feature build", inputTokens: 12000, outputTokens: 4200 }, + docs: { label: "Documentation", inputTokens: 3500, outputTokens: 1700 }, + testing: { label: "Testing", inputTokens: 7000, outputTokens: 2300 }, +}; + +export const complexityMultipliers = { + low: 0.7, + medium: 1, + high: 1.5, + extreme: 2.2, +} as const; + +export type ComplexityKey = keyof typeof complexityMultipliers; + +export function calculateTokenCostUSD(model: ModelEconomics, inputTokens: number, outputTokens: number) { + const inputCost = (inputTokens / 1_000_000) * model.inputPricePer1M; + const outputCost = (outputTokens / 1_000_000) * model.outputPricePer1M; + return inputCost + outputCost; +} + +export function blendedPricePer1M(model: ModelEconomics) { + return (model.inputPricePer1M + model.outputPricePer1M) / 2; +} + +export function costEfficiencyScore(model: ModelEconomics) { + const efficiencyBase = model.qualityRating / Math.max(blendedPricePer1M(model), 0.1); + return efficiencyBase * 10; +}