web: Roo Code Cloud Provider pricing page and changes elsewhere (#9195)

Co-authored-by: roomote[bot] <219738659+roomote[bot]@users.noreply.github.com>
Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>
Co-authored-by: Matt Rubens <mrubens@users.noreply.github.com>
This commit is contained in:
Bruno Bergher 2025-11-16 05:25:37 +00:00 committed by GitHub
parent 744f4bd4c8
commit 7a06902dd8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 574 additions and 42 deletions

View file

@ -1,4 +1,4 @@
import { Users, Building2, ArrowRight, Star, LucideIcon, Check, Cloud } from "lucide-react"
import { Users, Building2, ArrowRight, Star, LucideIcon, Check, Cloud, PlugZap } from "lucide-react"
import type { Metadata } from "next"
import Link from "next/link"
@ -64,7 +64,6 @@ interface PricingTier {
period?: string
creditPrice?: string
trial?: string
cancellation?: string
description: string
featuresIntro?: string
features: string[]
@ -80,13 +79,12 @@ const pricingTiers: PricingTier[] = [
name: "Cloud Free",
icon: Cloud,
price: "$0",
cancellation: "Cancel anytime",
description: "For folks just getting started",
features: [
"Token usage analytics",
"Access to the Roo Code Cloud Provider, including early access to free stealth models",
"Follow your tasks from anywhere",
"Share tasks with friends and co-workers",
"Early access to free AI Models",
"Community support",
],
cta: {
@ -95,18 +93,18 @@ const pricingTiers: PricingTier[] = [
},
},
{
name: "Pro",
name: "Cloud Pro",
icon: Star,
price: "$20",
period: "/mo",
trial: "Free 14-day trial · ",
trial: "Free for 14 days, then",
creditPrice: `$${PRICE_CREDITS}`,
cancellation: "Cancel anytime",
description: "For pro Roo coders",
featuresIntro: "Everything in Free +",
features: [
"Cloud Agents: PR Reviewer and more",
"Roomote Control: Start, stop and control tasks from anywhere",
"Cloud Agents: Coder, Explainer, Planner, Reviewer, Fixer and more",
"Start tasks from Slack",
"Roomote Control: Start, stop and control extension tasks from anywhere",
"Paid support",
],
cta: {
@ -115,13 +113,12 @@ const pricingTiers: PricingTier[] = [
},
},
{
name: "Team",
name: "Cloud Team",
icon: Users,
price: "$99",
period: "/mo",
creditPrice: `$${PRICE_CREDITS}`,
trial: "Free 14-day trial · ",
cancellation: "Cancel anytime",
trial: "Free for 14 days, then",
description: "For AI-forward teams",
featuresIntro: "Everything in Pro +",
features: ["Unlimited users (no per-seat cost)", "Shared configuration & policies", "Centralized billing"],
@ -151,12 +148,30 @@ export default function PricingPage() {
</div>
</section>
{/* Free Extension Notice */}
<div className="mx-auto max-w-6xl">
<div className="rounded-xl p-4 mb-8 text-center bg-gradient-to-r from-blue-500/10 via-cyan-500/10 to-purple-500/10 border border-blue-500/20 dark:border-white/20">
<div className="mx-6 md:mx-auto max-w-6xl">
<div className="rounded-xl p-4 mb-8 text-center bg-gradient-to-r from-blue-500/10 via-cyan-500/10 to-purple-500/10 border border-blue-500/20 dark:border-white/20 ">
<p className="text-center">
<strong className="font-semibold">The Roo Code extension is free! </strong>
Roo Code Cloud is an optional service which takes it to the next level.
<strong className="font-semibold">The Roo Code extension is totally free! </strong>
But Cloud takes you so much further.
</p>
</div>
</div>
<div className="mx-6 md:mx-auto max-w-6xl p-7 mb-4 relative flex flex-col justify-start bg-background border rounded-2xl transition-all shadow-none hover:shadow-lg">
<h3 className="text-xl font-semibold flex items-center gap-2 justify-between">
Roo Code Provider
<PlugZap className="size-6" />
</h3>
<div className="text-sm text-muted-foreground space-y-1 mt-2">
<p className="">
On any plan, you can bring your own provider key or use the built-in Roo Code Cloud provider.
</p>
<p className="text-sm text-muted-foreground">
We offer a select mix of tested state of the art closed and open weight LLMs for you to choose,
with no markup.
<Link href="/provider/pricing" className="underline hover:no-underline ml-1">
See detailed pricing
</Link>
</p>
</div>
</div>
@ -183,7 +198,7 @@ export default function PricingPage() {
<p className="text-sm text-muted-foreground font-light mb-2">
{tier.featuresIntro}&nbsp;
</p>
<ul className="space-y-3 my-0 h-[148px]">
<ul className="space-y-3 my-0 h-[168px]">
{tier.features.map((feature) => (
<li key={feature} className="flex items-start gap-2">
<Check className="mt-0.5 h-4 w-4 text-muted-foreground shrink-0" />
@ -193,20 +208,28 @@ export default function PricingPage() {
</ul>
</div>
<p className="text-2xl mt-0 mb-1 tracking-tight">
<strong>{tier.price}</strong>
{tier.period}
<p className="text-base font-light">{tier.trial}</p>
<p className="text-xl my-1 tracking-tight font-light">
<strong className="font-bold">{tier.price}</strong>
{tier.period} + prepaid credits
</p>
{tier.creditPrice && (
<p className="text-sm text-muted-foreground mb-1">
+ {tier.creditPrice}/hour for Cloud tasks
</p>
)}
<p className="text-xs text-muted-foreground mb-4">
{tier.trial}
{tier.cancellation}
<p className="text-sm text-muted-foreground mb-3">
{tier.creditPrice && (
<>
Cloud Agents: {tier.creditPrice}/hour if used
<br />
</>
)}
Inference:{" "}
<Link href="/provider/pricing" className="underline hover:no-underline">
Roo Provider pricing
</Link>{" "}
or{" "}
<abbr title="Bring Your Own Key" className="cursor-help">
BYOK
</abbr>
</p>
{tier.cta.isContactForm ? (
@ -249,7 +272,7 @@ export default function PricingPage() {
<h2 className="text-3xl font-bold tracking-tight sm:text-4xl">Frequently Asked Questions</h2>
</div>
<div className="mx-auto mt-12 grid max-w-5xl gap-8 md:grid-cols-2">
<div className="rounded-lg border border-border bg-card p-6">
<div className="rounded-xl border border-border bg-card p-6">
<h3 className="font-semibold">Wait, is Roo Code free or not?</h3>
<p className="mt-2 text-sm text-muted-foreground">
Yes! The Roo Code VS Code extension is open source and free forever. The extension acts
@ -257,7 +280,7 @@ export default function PricingPage() {
Code Cloud.
</p>
</div>
<div className="rounded-lg border border-border bg-card p-6">
<div className="rounded-xl border border-border bg-card p-6">
<h3 className="font-semibold">Is there a free trial?</h3>
<p className="mt-2 text-sm text-muted-foreground">
Yes, all paid plans come with a 14-day free trial to try out functionality.
@ -266,12 +289,25 @@ export default function PricingPage() {
To use Cloud Agents, you can buy credits.
</p>
</div>
<div className="rounded-lg border border-border bg-card p-6">
<h3 className="font-semibold">How do Cloud Agent credits work?</h3>
<div className="rounded-xl border border-border bg-card p-6">
<h3 className="font-semibold">How do credits work?</h3>
<p className="mt-2 text-sm text-muted-foreground">
Cloud Agents are a version of Roo running in the cloud without depending on your IDE.
You can run as many as you want, and bring your own inference provider key.
Roo Code Cloud credits can be used in two ways:
</p>
<ul className="mt-2 list-disc pl-5 text-sm text-muted-foreground">
<li>To pay for Cloud Agents running time (${PRICE_CREDITS}/hour)</li>
<li>
To pay for AI model inference costs (
<a
href="https://app.roocode.com/provider/pricing"
target="_blank"
rel="noopener noreferrer"
className="underline">
varies by model
</a>
)
</li>
</ul>
<p className="mt-2 text-sm text-muted-foreground">
To cover our infrastructure costs, we charge ${PRICE_CREDITS}/hour while the agent is
running (independent of inference costs).
@ -280,25 +316,25 @@ export default function PricingPage() {
There are no markups, no tiers, no dumbing-down of models to increase our profit.
</p>
</div>
<div className="rounded-lg border border-border bg-card p-6">
<div className="rounded-xl border border-border bg-card p-6">
<h3 className="font-semibold">Do I need a credit card for the free trial?</h3>
<p className="mt-2 text-sm text-muted-foreground">
Yes, but you won&apos;t be charged until your trial ends, except for credit purchases.
</p>
<p className="mt-2 text-sm text-muted-foreground">You can cancel anytime with one click.</p>
</div>
<div className="rounded-lg border border-border bg-card p-6">
<div className="rounded-xl border border-border bg-card p-6">
<h3 className="font-semibold">What payment methods do you accept?</h3>
<p className="mt-2 text-sm text-muted-foreground">
We accept all major credit cards, debit cards, and can arrange invoice billing for
Enterprise customers.
</p>
</div>
<div className="rounded-lg border border-border bg-card p-6">
<h3 className="font-semibold">Can I change plans anytime?</h3>
<div className="rounded-xl border border-border bg-card p-6">
<h3 className="font-semibold">Can I cancel or change plans?</h3>
<p className="mt-2 text-sm text-muted-foreground">
Yes, you can upgrade or downgrade your plan at any time. Changes will be reflected in
your next billing cycle.
Yes, you can upgrade, downgrade or cancel your plan at any time. Changes will be
reflected in your next billing cycle.
</p>
</div>
</div>

View file

@ -0,0 +1,190 @@
import { ModelWithTotalPrice } from "@/lib/types/models"
import { formatCurrency, formatTokens } from "@/lib/formatters"
import {
ArrowLeftToLine,
ArrowRightToLine,
Building2,
Check,
Expand,
Gift,
HardDriveDownload,
HardDriveUpload,
RulerDimensionLine,
ChevronDown,
ChevronUp,
} from "lucide-react"
import { useState } from "react"
interface ModelCardProps {
model: ModelWithTotalPrice
}
export function ModelCard({ model }: ModelCardProps) {
// Prices are per token, multiply by 1M to get price per million tokens
const inputPrice = parseFloat(model.pricing.input) * 1_000_000
const outputPrice = parseFloat(model.pricing.output) * 1_000_000
const cacheReadPrice = parseFloat(model.pricing.input_cache_read || "0") * 1_000_000
const cacheWritePrice = parseFloat(model.pricing.input_cache_write || "0") * 1_000_000
const free = model.tags.includes("free")
// Filter tags to only show vision and reasoning
const displayTags = model.tags.filter((tag) => tag === "vision" || tag === "reasoning")
// Mobile collapsed/expanded state
const [expanded, setExpanded] = useState(false)
return (
<div
className={[
"relative cursor-default px-8 pt-7 pb-5 flex flex-col justify-start bg-background border rounded-3xl transition-all hover:shadow-xl",
// On mobile, visually hint at expandability
"sm:cursor-default",
].join(" ")}>
{/* Header: always visible */}
<div className="mb-4">
<h3 className="text-xl font-semibold tracking-tight mb-2 flex items-center gap-2 justify-between">
{model.name}
{free && (
<span className="inline-flex items-center text-sm font-medium text-green-500">
<Gift className="size-4 mr-1" />
Free!
</span>
)}
</h3>
<p
className={[
"text-sm text-muted-foreground",
// On mobile + collapsed: clamp description
"sm:line-clamp-none",
!expanded ? "line-clamp-2" : "",
]
.join(" ")
.trim()}>
{model.description}
</p>
</div>
{/* Content - pinned to bottom */}
<div className="overflow-x-auto mt-auto">
<table className="w-full text-xs">
<tbody>
{/* Provider: always visible if present */}
{model.owned_by && (
<tr className="border-b border-border">
<td className="py-1.5 font-medium text-muted-foreground">
<Building2 className="size-4 inline-block mr-1.5" />
Provider
</td>
<td className="py-1.5 text-right">{model.owned_by}</td>
</tr>
)}
{/* Context Window: always visible */}
<tr className="border-b border-border">
<td className="py-1.5 font-medium text-muted-foreground">
<RulerDimensionLine className="size-4 inline-block mr-1.5" />
Context Window
</td>
<td className="py-1.5 text-right font-mono">{formatTokens(model.context_window)}</td>
</tr>
{/* Max Output Tokens: always visible on >=sm, expandable on mobile */}
<tr
className={["border-b border-border", expanded ? "table-row" : "hidden sm:table-row"].join(
" ",
)}>
<td className="py-1.5 font-medium text-muted-foreground">
<Expand className="size-4 inline-block mr-1.5" />
Max Output Tokens
</td>
<td className="py-1.5 text-right font-mono">{formatTokens(model.max_tokens)}</td>
</tr>
{/* Input Price: always visible */}
<tr className="border-b border-border">
<td className="py-1.5 font-medium text-muted-foreground">
<ArrowRightToLine className="size-4 inline-block mr-1.5" />
Input Price
</td>
<td className="py-1.5 text-right">
{inputPrice === 0 ? "Free" : `${formatCurrency(inputPrice)}/1M tokens`}
</td>
</tr>
{/* Output Price: always visible */}
<tr
className={[
"border-b border-border",
// Add subtle separation from toggle on mobile
].join(" ")}>
<td className="py-1.5 font-medium text-muted-foreground">
<ArrowLeftToLine className="size-4 inline-block mr-1.5" />
Output Price
</td>
<td className="py-1.5 text-right">
{outputPrice === 0 ? "Free" : `${formatCurrency(outputPrice)}/1M tokens`}
</td>
</tr>
{/* Cache pricing: only visible on mobile when expanded, always visible on >=sm */}
{cacheReadPrice > 0 && (
<tr
className={[
"border-b border-border",
expanded ? "table-row" : "hidden sm:table-row",
].join(" ")}>
<td className="py-1.5 font-medium text-muted-foreground">
<HardDriveUpload className="size-4 inline-block mr-1.5" />
Cache Read
</td>
<td className="py-1.5 text-right">{formatCurrency(cacheReadPrice)}/1M tokens</td>
</tr>
)}
{cacheWritePrice > 0 && (
<tr
className={[
"border-b border-border",
expanded ? "table-row" : "hidden sm:table-row",
].join(" ")}>
<td className="py-1.5 font-medium text-muted-foreground">
<HardDriveDownload className="size-4 inline-block mr-1.5" />
Cache Write
</td>
<td className="py-1.5 text-right">{formatCurrency(cacheWritePrice)}/1M tokens</td>
</tr>
)}
{/* Tags row: only show if there are vision or reasoning tags */}
{displayTags.length > 0 && (
<tr className={[expanded ? "table-row" : "hidden sm:table-row"].join(" ")}>
<td className="py-1.5 font-medium text-muted-foreground align-top">Features</td>
<td className="py-1.5">
{displayTags.map((tag) => (
<span key={tag} className="flex justify-end items-center text-xs capitalize">
<Check className="size-3 m-1" />
{tag}
</span>
))}
</td>
</tr>
)}
{/* Mobile-only toggle row */}
<tr className="sm:hidden">
<td colSpan={2} className="pt-3">
<button
type="button"
onClick={() => setExpanded((v) => !v)}
className="w-full inline-flex items-center justify-center gap-1.5 text-xs font-medium text-primary">
{expanded ? "Less" : "More"}
{expanded ? <ChevronUp className="size-3" /> : <ChevronDown className="size-3" />}
</button>
</td>
</tr>
</tbody>
</table>
</div>
</div>
)
}

View file

@ -0,0 +1,253 @@
"use client"
import { useEffect, useMemo, useState } from "react"
import { ModelCard } from "./components/model-card"
import { Model, ModelWithTotalPrice, ModelsResponse, SortOption } from "@/lib/types/models"
import Link from "next/link"
import { ChevronDown, CircleX, Loader, LoaderCircle, Search } from "lucide-react"
const API_URL = "https://api.roocode.com/proxy/v1/models?include_paid=true"
const faqs = [
{
question: "What are AI model providers?",
answer: "AI model providers offer various language models with different capabilities and pricing.",
},
{
question: "How is pricing calculated?",
answer: "Pricing is based on token usage for input and output, measured per million tokens, like pretty much any other provider out there.",
},
{
question: "What is the Roo Code Cloud Provider?",
answer: (
<>
<p>This is our very own model provider, optimized to work seamlessly with Roo Code Cloud.</p>
<p>
It offers a selection of state-of-the-art LLMs (both closed and open weight) we know work well with
Roo for you to choose, with no markup.
</p>
<p>
We also often feature 100% free models which labs share with us for the community to use and provide
feedback.
</p>
</>
),
},
{
question: "But how much does the Roo Code Cloud service cost?",
answer: (
<>
Our{" "}
<Link href="/pricing" className="underline hover:no-underline">
service pricing is here.
</Link>
</>
),
},
]
function calculateTotalPrice(model: Model): number {
return parseFloat(model.pricing.input) + parseFloat(model.pricing.output)
}
function enrichModelWithTotalPrice(model: Model): ModelWithTotalPrice {
return {
...model,
totalPrice: calculateTotalPrice(model),
}
}
export default function ProviderPricingPage() {
const [models, setModels] = useState<ModelWithTotalPrice[]>([])
const [loading, setLoading] = useState(true)
const [error, setError] = useState<string | null>(null)
const [searchQuery, setSearchQuery] = useState("")
const [sortOption, setSortOption] = useState<SortOption>("alphabetical")
useEffect(() => {
async function fetchModels() {
try {
setLoading(true)
setError(null)
const response = await fetch(API_URL)
if (!response.ok) {
throw new Error(`Failed to fetch models: ${response.statusText}`)
}
const data: ModelsResponse = await response.json()
const enrichedModels = data.data.map(enrichModelWithTotalPrice)
setModels(enrichedModels)
} catch (err) {
setError(err instanceof Error ? err.message : "An error occurred while fetching models")
} finally {
setLoading(false)
}
}
fetchModels()
}, [])
const filteredAndSortedModels = useMemo(() => {
// Filter out deprecated models
let filtered = models.filter((model) => !model.deprecated)
// Filter by search query
if (searchQuery.trim()) {
const query = searchQuery.toLowerCase()
filtered = filtered.filter((model) => {
return (
model.name.toLowerCase().includes(query) ||
model.owned_by?.toLowerCase().includes(query) ||
model.description.toLowerCase().includes(query)
)
})
}
// Sort filtered results
const sorted = [...filtered]
switch (sortOption) {
case "alphabetical":
sorted.sort((a, b) => a.name.localeCompare(b.name))
break
case "price-asc":
sorted.sort((a, b) => a.totalPrice - b.totalPrice)
break
case "price-desc":
sorted.sort((a, b) => b.totalPrice - a.totalPrice)
break
case "context-window-asc":
sorted.sort((a, b) => a.context_window - b.context_window)
break
case "context-window-desc":
sorted.sort((a, b) => b.context_window - a.context_window)
break
}
return sorted
}, [models, searchQuery, sortOption])
// Count non-deprecated models for the display
const nonDeprecatedCount = useMemo(() => models.filter((model) => !model.deprecated).length, [models])
return (
<>
<section className="relative overflow-hidden py-16">
<div className="container relative z-10 mx-auto px-4 sm:px-6 lg:px-8">
<div className="text-center">
<h1 className="text-4xl md:text-5xl font-bold tracking-tight">
Roo Code Cloud Provider Pricing
</h1>
<p className="mx-auto mt-4 max-w-2xl md:text-lg text-muted-foreground">
See pricing and features for all models we offer in our selection.
<br />
You can always bring your own key (
<Link href="#faq" className="underline hover:no-underline">
FAQ
</Link>
).
</p>
</div>
</div>
</section>
<section className="py-10 relative border-t border-b">
<div className="absolute inset-0 bg-gradient-to-br from-violet-500/0 via-violet-500/10 to-violet-500/0 dark:from-blue-500/10 dark:via-cyan-500/10 dark:to-purple-500/10" />
<div className="container mx-auto px-4 sm:px-6 lg:px-8">
<div className="mx-auto max-w-4xl">
<div className="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
<div className="flex-1">
<div className="relative">
<Search className="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
<input
type="text"
placeholder="Search models..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="w-full rounded-full border border-input bg-background px-10 py-2 text-base ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
/>
<div className="text-sm cursor-default text-muted-foreground absolute bg-background right-0 top-0 m-0.5 px-3 py-2 rounded-full">
{filteredAndSortedModels.length} of {nonDeprecatedCount} models
</div>
</div>
</div>
<div className="flex-shrink-0">
<div className="flex items-center gap-2 relative">
<select
id="sort"
value={sortOption}
onChange={(e) => setSortOption(e.target.value as SortOption)}
className="rounded-full cursor-pointer border border-input bg-background hover:bg-muted pl-4 w-full md:w-auto pr-9 py-2.5 text-base ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 relative appearance-none">
<option value="alphabetical">Alphabetical</option>
<option value="price-asc">Price: Low to High</option>
<option value="price-desc">Price: High to Low</option>
<option value="context-window-asc">Context Window: Small to Large</option>
<option value="context-window-desc">Context Window: Large to Small</option>
</select>
<ChevronDown className="size-4 absolute right-3" />
</div>
</div>
</div>
</div>
</div>
<div className="container mx-auto px-4 sm:px-6 lg:px-8 ">
<div className="mx-auto max-w-6xl">
{loading && (
<div className="text-center pt-12 space-y-2 mb-4">
<LoaderCircle className="size-8 text-muted-foreground mx-auto animate-spin" />
<p className="text-lg">Loading model list...</p>
</div>
)}
{error && (
<div className="text-center pt-12 space-y-2">
<CircleX className="size-8 text-muted-foreground mx-auto mb-4" />
<p className="text-lg">Oops, couldn&apos;t load the model list.</p>
<p className="text-muted-foreground">Try again in a bit please.</p>
</div>
)}
{!loading && !error && filteredAndSortedModels.length === 0 && (
<div className="text-center pt-12 space-y-2">
<Loader className="size-8 text-muted-foreground mx-auto mb-4" />
<p className="text-lg">No models match your search.</p>
<p className="text-muted-foreground">
Keep in mind we don&apos;t have every model under the sun only the ones we think
are worth using.
<br />
You can always use a third-party provider to access a wider selection.
</p>
</div>
)}
{!loading && !error && filteredAndSortedModels.length > 0 && (
<div className="grid gap-4 pt-8 md:grid-cols-2 lg:grid-cols-3">
{filteredAndSortedModels.map((model) => (
<ModelCard key={model.id} model={model} />
))}
</div>
)}
</div>
</div>
</section>
{/* FAQ Section */}
<section className="bg-background my-16 relative z-50">
<a id="faq" />
<div className="container mx-auto px-4 sm:px-6 lg:px-8">
<div className="mx-auto max-w-3xl text-center">
<h2 className="text-3xl font-bold tracking-tight sm:text-4xl">Frequently Asked Questions</h2>
</div>
<div className="mx-auto mt-12 grid max-w-5xl gap-8 md:grid-cols-2">
{faqs.map((faq, index) => (
<div key={index} className="rounded-lg border border-border bg-card p-6">
<h3 className="font-semibold">{faq.question}</h3>
<p className="mt-2 text-sm text-muted-foreground">{faq.answer}</p>
</div>
))}
</div>
</div>
</section>
</>
)
}

View file

@ -0,0 +1,22 @@
const formatter = new Intl.NumberFormat("en-US", {
style: "currency",
currency: "USD",
})
export const formatCurrency = (amount: number) => formatter.format(amount)
export const formatTokens = (tokens: number) => {
if (tokens < 1000) {
return tokens.toString()
}
if (tokens < 1000000) {
return `${(tokens / 1000).toFixed(1)}K`
}
if (tokens < 1000000000) {
return `${(tokens / 1000000).toFixed(1)}M`
}
return `${(tokens / 1000000000).toFixed(1)}B`
}

View file

@ -0,0 +1,31 @@
export interface ModelPricing {
input: string
output: string
input_cache_read: string
input_cache_write: string
}
export interface Model {
id: string
object: string
created: number
owned_by: string
name: string
description: string
context_window: number
max_tokens: number
type: string
tags: string[]
pricing: ModelPricing
deprecated?: boolean
}
export interface ModelsResponse {
data: Model[]
}
export interface ModelWithTotalPrice extends Model {
totalPrice: number
}
export type SortOption = "alphabetical" | "price-asc" | "price-desc" | "context-window-asc" | "context-window-desc"