diff --git a/apps/sim/app/(landing)/models/(shell)/[provider]/[model]/page.tsx b/apps/sim/app/(landing)/models/(shell)/[provider]/[model]/page.tsx index 89ca6621546..34afe44c09b 100644 --- a/apps/sim/app/(landing)/models/(shell)/[provider]/[model]/page.tsx +++ b/apps/sim/app/(landing)/models/(shell)/[provider]/[model]/page.tsx @@ -8,6 +8,8 @@ import { JsonLd } from '@/app/(landing)/components/json-ld' import { LandingFAQ } from '@/app/(landing)/components/landing-faq' import { LANDING_CONTENT_WIDTH, LANDING_GUTTER } from '@/app/(landing)/components/landing-layout' import { ShareButton } from '@/app/(landing)/components/share-button' +import { getModelComparisonsFor } from '@/app/(landing)/models/comparisons' +import { ComparisonLinks } from '@/app/(landing)/models/components/comparison-links' import { FeaturedModelCard, ProviderIcon } from '@/app/(landing)/models/components/model-primitives' import { buildModelCapabilityFacts, @@ -109,6 +111,7 @@ export default async function ModelPage({ const capabilityFacts = buildModelCapabilityFacts(model) const pricingBounds = getPricingBounds(model.pricing) const relatedModels = getRelatedModels(model, 6) + const comparisons = getModelComparisonsFor(model) const breadcrumbJsonLd = { '@context': 'https://schema.org', @@ -195,7 +198,7 @@ export default async function ModelPage({ Build with this model - + All {provider.name} models @@ -273,6 +276,21 @@ export default async function ModelPage({ )} + {comparisons.length > 0 && ( + <> +
+ + + )} +
diff --git a/apps/sim/app/(landing)/models/(shell)/[provider]/opengraph-image.tsx b/apps/sim/app/(landing)/models/(shell)/[provider]/opengraph-image.tsx index ff3df67e42a..444af6cff6e 100644 --- a/apps/sim/app/(landing)/models/(shell)/[provider]/opengraph-image.tsx +++ b/apps/sim/app/(landing)/models/(shell)/[provider]/opengraph-image.tsx @@ -1,6 +1,10 @@ import { notFound } from 'next/navigation' import { COVER_OG_SIZE, createCoverOgImage } from '@/lib/og/cover-image' -import { getProviderBySlug, MODEL_PROVIDERS_WITH_CATALOGS } from '@/app/(landing)/models/utils' +import { + getProviderBySlug, + getProviderPageTitle, + MODEL_PROVIDERS_WITH_CATALOGS, +} from '@/app/(landing)/models/utils' export const contentType = 'image/png' export const size = COVER_OG_SIZE @@ -21,7 +25,7 @@ export default async function Image({ params }: { params: Promise<{ provider: st } return createCoverOgImage({ - title: `${provider.name} models`, + title: getProviderPageTitle(provider), subtitle: `Browse ${provider.modelCount} tracked ${provider.name} models with pricing, context windows, default model selection, and capability coverage.`, }) } diff --git a/apps/sim/app/(landing)/models/(shell)/[provider]/page.tsx b/apps/sim/app/(landing)/models/(shell)/[provider]/page.tsx index 25c4bbb241c..a918d03bfb0 100644 --- a/apps/sim/app/(landing)/models/(shell)/[provider]/page.tsx +++ b/apps/sim/app/(landing)/models/(shell)/[provider]/page.tsx @@ -18,6 +18,8 @@ import { formatPrice, formatTokenCount, getProviderBySlug, + getProviderPageDescription, + getProviderPageTitle, MODEL_PROVIDERS_WITH_CATALOGS, MODEL_PROVIDERS_WITH_MODELS, TOP_MODEL_PROVIDERS, @@ -50,31 +52,35 @@ export async function generateMetadata({ return {} } - const providerFaqs = buildProviderFaqs(provider) + const title = getProviderPageTitle(provider) + const description = getProviderPageDescription(provider) return { - title: `${provider.name} Models`, - description: `Browse ${provider.modelCount} ${provider.name} models tracked in Sim. Compare pricing, context windows, default model selection, and capabilities for ${provider.name}'s AI model lineup.`, + title, + description, keywords: [ - `${provider.name} models`, - `${provider.name} pricing`, - `${provider.name} context window`, - `${provider.name} model list`, - `${provider.name} AI models`, - ...provider.models.slice(0, 6).map((model) => model.displayName), + ...new Set([ + `${provider.apiName} API pricing`, + `${provider.modelFamily} API pricing`, + `${provider.name} pricing`, + `${provider.modelFamily} context window`, + `${provider.name} models`, + `${provider.name} model list`, + ...provider.models.slice(0, 6).map((model) => model.displayName), + ]), ], // og:image/twitter:image come from the sibling opengraph-image.tsx - // Next serves it at a hash-suffixed URL, so hardcoding it here 404s. openGraph: { - title: `${provider.name} Models | Sim`, - description: `Explore ${provider.modelCount} ${provider.name} models with pricing and capability details.`, + title: `${title} | Sim`, + description, url: `${baseUrl}${provider.href}`, type: 'website', }, twitter: { card: 'summary_large_image', - title: `${provider.name} Models | Sim`, - description: providerFaqs[0]?.answer ?? provider.summary, + title: `${title} | Sim`, + description, }, alternates: { canonical: `${baseUrl}${provider.href}`, diff --git a/apps/sim/app/(landing)/models/(shell)/compare/[pair]/opengraph-image.tsx b/apps/sim/app/(landing)/models/(shell)/compare/[pair]/opengraph-image.tsx new file mode 100644 index 00000000000..e3ccc72e495 --- /dev/null +++ b/apps/sim/app/(landing)/models/(shell)/compare/[pair]/opengraph-image.tsx @@ -0,0 +1,29 @@ +import { notFound } from 'next/navigation' +import { COVER_OG_SIZE, createCoverOgImage } from '@/lib/og/cover-image' +import { + buildComparisonVerdict, + getModelComparisonBySlug, + MODEL_COMPARISONS, +} from '@/app/(landing)/models/comparisons' + +export const contentType = 'image/png' +export const size = COVER_OG_SIZE + +/** Pre-render catalog images separately from their sibling pages. */ +export async function generateStaticParams() { + return MODEL_COMPARISONS.map((comparison) => ({ pair: comparison.slug })) +} + +export default async function Image({ params }: { params: Promise<{ pair: string }> }) { + const { pair } = await params + const comparison = getModelComparisonBySlug(pair) + + if (!comparison) { + notFound() + } + + return createCoverOgImage({ + title: comparison.title, + subtitle: buildComparisonVerdict(comparison)[0], + }) +} diff --git a/apps/sim/app/(landing)/models/(shell)/compare/[pair]/page.tsx b/apps/sim/app/(landing)/models/(shell)/compare/[pair]/page.tsx new file mode 100644 index 00000000000..17fc02dad5a --- /dev/null +++ b/apps/sim/app/(landing)/models/(shell)/compare/[pair]/page.tsx @@ -0,0 +1,449 @@ +import { ChipLink, cn } from '@sim/emcn' +import type { Metadata } from 'next' +import Link from 'next/link' +import { notFound } from 'next/navigation' +import { SITE_URL } from '@/lib/core/utils/urls' +import { DOCS_URL } from '@/lib/help-links' +import { BackLink } from '@/app/(landing)/components' +import { JsonLd } from '@/app/(landing)/components/json-ld' +import { LandingFAQ } from '@/app/(landing)/components/landing-faq' +import { LANDING_CONTENT_WIDTH, LANDING_GUTTER } from '@/app/(landing)/components/landing-layout' +import { + buildComparisonFaqs, + buildComparisonVerdict, + getComparisonPageDescription, + getComparisonPageTitle, + getComparisonUpdatedAt, + getModelComparisonBySlug, + getModelComparisonsFor, + MODEL_COMPARISONS, + type ModelComparison, +} from '@/app/(landing)/models/comparisons' +import { ComparisonLinks } from '@/app/(landing)/models/components/comparison-links' +import { + MODEL_TABLE_CELL_CLASS, + MODEL_TABLE_HEADER_CELL_CLASS, +} from '@/app/(landing)/models/components/constants' +import { ProviderIcon } from '@/app/(landing)/models/components/model-primitives' +import { + buildModelCapabilityFacts, + formatPrice, + formatPricingTier, + formatTokenCount, + formatUpdatedAt, + getPricingBounds, + type PricingInfo, +} from '@/app/(landing)/models/utils' + +const baseUrl = SITE_URL + +const AGENT_BLOCK_DOCS_URL = `${DOCS_URL}/workflows/blocks/agent` + +/** + * Only curated pairs render, and no `loading.tsx` may wrap this segment ("Static rendering" in + * `(landing)/CLAUDE.md`). + */ +export const dynamicParams = false + +export async function generateStaticParams() { + return MODEL_COMPARISONS.map((comparison) => ({ pair: comparison.slug })) +} + +interface ModelComparisonPageProps { + params: Promise<{ pair: string }> +} + +export async function generateMetadata({ params }: ModelComparisonPageProps): Promise { + const { pair } = await params + const comparison = getModelComparisonBySlug(pair) + + if (!comparison) { + return {} + } + + const title = getComparisonPageTitle(comparison) + const description = getComparisonPageDescription(comparison) + const { a, b } = comparison + + return { + title, + description, + keywords: [ + `${a.model.displayName} vs ${b.model.displayName}`, + `${b.model.displayName} vs ${a.model.displayName}`, + `${a.model.displayName} pricing`, + `${b.model.displayName} pricing`, + `${a.model.displayName} context window`, + `${b.model.displayName} context window`, + ], + // og:image/twitter:image come from the sibling opengraph-image.tsx - + // Next serves it at a hash-suffixed URL, so hardcoding it here 404s. + openGraph: { + title: `${title} | Sim`, + description, + url: `${baseUrl}${comparison.href}`, + type: 'website', + }, + twitter: { + card: 'summary_large_image', + title: `${title} | Sim`, + description, + }, + alternates: { + canonical: `${baseUrl}${comparison.href}`, + }, + } +} + +export default async function ModelComparisonPage({ params }: ModelComparisonPageProps) { + const { pair } = await params + const comparison = getModelComparisonBySlug(pair) + + if (!comparison) { + notFound() + } + + const { a, b } = comparison + const verdict = buildComparisonVerdict(comparison) + const faqs = buildComparisonFaqs(comparison) + const updatedAt = getComparisonUpdatedAt(comparison) + const relatedComparisons = [ + ...new Set([...getModelComparisonsFor(a.model), ...getModelComparisonsFor(b.model)]), + ].filter((entry) => entry.slug !== comparison.slug) + + const breadcrumbJsonLd = { + '@context': 'https://schema.org', + '@type': 'BreadcrumbList', + itemListElement: [ + { '@type': 'ListItem', position: 1, name: 'Home', item: baseUrl }, + { '@type': 'ListItem', position: 2, name: 'Models', item: `${baseUrl}/models` }, + { + '@type': 'ListItem', + position: 3, + name: comparison.title, + item: `${baseUrl}${comparison.href}`, + }, + ], + } + + const itemListJsonLd = { + '@context': 'https://schema.org', + '@type': 'ItemList', + name: comparison.title, + description: verdict[0], + url: `${baseUrl}${comparison.href}`, + dateModified: updatedAt.toISOString(), + numberOfItems: 2, + itemListElement: [a, b].map(({ model, provider }, index) => { + const { lowPrice, highPrice } = getPricingBounds(model.pricing) + return { + '@type': 'ListItem', + position: index + 1, + item: { + '@type': 'Product', + name: model.displayName, + url: `${baseUrl}${model.href}`, + description: model.summary, + brand: provider.name, + category: 'AI language model', + sku: model.id, + offers: { + '@type': 'AggregateOffer', + priceCurrency: 'USD', + lowPrice: lowPrice.toString(), + highPrice: highPrice.toString(), + }, + }, + } + }), + } + + const faqJsonLd = { + '@context': 'https://schema.org', + '@type': 'FAQPage', + mainEntity: faqs.map((faq) => ({ + '@type': 'Question', + name: faq.question, + acceptedAnswer: { + '@type': 'Answer', + text: faq.answer, + }, + })), + } + + return ( + <> + + + + +
+
+

+ Sim is the open-source AI workspace where teams build, deploy, and manage AI agents. + This page compares {a.model.displayName} and {b.model.displayName} on API pricing per 1M + tokens, context window, max output, and capabilities. Both models are available in the + Sim Agent block, and each Agent block in a Sim agent can use a different model. +

+
+ +
+ +
+
+ + +
+
+

+ API model comparison +

+

+ {comparison.title} +

+
+
+ +
+ {verdict.map((sentence) => ( +

{sentence}

+ ))} +
+ +
+ + Build with these models + + + {a.model.displayName} details + + + {b.model.displayName} details + +
+
+ +
+ +
+
+
+
+

+ Pricing, context window, and capabilities +

+

+ API prices are per 1M tokens. Pricing last updated{' '} + + . +

+
+ +
+ +
+ +
+

+ Use {a.model.displayName} or {b.model.displayName} in a Sim agent +

+

+ Both models are available in the Sim Agent block. Pick either one from the model + picker, and each Agent block in the same agent can use a different model. +

+
+ + Agent block docs + + + All {a.provider.name} models + + {b.provider.id !== a.provider.id ? ( + + All {b.provider.name} models + + ) : null} + + Context window comparison + +
+
+ + {relatedComparisons.length > 0 && ( + <> +
+ + + )} + +
+ +
+

+ Frequently asked questions +

+
+ +
+
+
+
+ +
+
+ + ) +} + +/** Long-context surcharge tiers in one line, or a note that the model has none. */ +function formatPricingTiers(pricing: PricingInfo): string { + const tiers = pricing.tiers ?? [] + if (tiers.length === 0) return 'No long-context tier' + + return tiers.map((tier) => `${formatPricingTier(tier)}.`).join(' ') +} + +interface ComparisonTableProps { + comparison: ModelComparison +} + +function ComparisonTable({ comparison }: ComparisonTableProps) { + const { a, b } = comparison + const factsA = buildModelCapabilityFacts(a.model) + const factsB = new Map(buildModelCapabilityFacts(b.model).map((fact) => [fact.label, fact.value])) + const hasTiers = Boolean(a.model.pricing.tiers?.length || b.model.pricing.tiers?.length) + + const rows: Array<{ label: string; a: string; b: string }> = [ + { label: 'Provider', a: a.provider.name, b: b.provider.name }, + { + label: 'Input price', + a: `${formatPrice(a.model.pricing.input)}/1M`, + b: `${formatPrice(b.model.pricing.input)}/1M`, + }, + { + label: 'Cached input', + a: + a.model.pricing.cachedInput !== undefined + ? `${formatPrice(a.model.pricing.cachedInput)}/1M` + : 'N/A', + b: + b.model.pricing.cachedInput !== undefined + ? `${formatPrice(b.model.pricing.cachedInput)}/1M` + : 'N/A', + }, + { + label: 'Output price', + a: `${formatPrice(a.model.pricing.output)}/1M`, + b: `${formatPrice(b.model.pricing.output)}/1M`, + }, + ...(hasTiers + ? [ + { + label: 'Long-context pricing', + a: formatPricingTiers(a.model.pricing), + b: formatPricingTiers(b.model.pricing), + }, + ] + : []), + { + label: 'Context window', + a: a.model.contextWindow ? `${formatTokenCount(a.model.contextWindow)} tokens` : 'Unknown', + b: b.model.contextWindow ? `${formatTokenCount(b.model.contextWindow)} tokens` : 'Unknown', + }, + { + label: 'Released', + a: a.model.releaseDate ? formatUpdatedAt(a.model.releaseDate) : 'Unknown', + b: b.model.releaseDate ? formatUpdatedAt(b.model.releaseDate) : 'Unknown', + }, + ...factsA.map((fact) => ({ + label: fact.label, + a: fact.value, + b: factsB.get(fact.label) ?? 'Unknown', + })), + ] + + return ( +
+ + + + + + {[a, b].map(({ model }) => ( + + ))} + + + + {rows.map((row) => ( + + + + + + ))} + +
+ {comparison.title}: pricing, context window, and capabilities +
+ Metric + + + {model.displayName} + +
+ {row.label} + {row.a}{row.b}
+
+ ) +} diff --git a/apps/sim/app/(landing)/models/(shell)/context-windows/opengraph-image.tsx b/apps/sim/app/(landing)/models/(shell)/context-windows/opengraph-image.tsx new file mode 100644 index 00000000000..7af6bd2be90 --- /dev/null +++ b/apps/sim/app/(landing)/models/(shell)/context-windows/opengraph-image.tsx @@ -0,0 +1,12 @@ +import { COVER_OG_SIZE, createCoverOgImage } from '@/lib/og/cover-image' +import { CONTEXT_WINDOW_MODELS } from '@/app/(landing)/models/context-windows' + +export const contentType = 'image/png' +export const size = COVER_OG_SIZE + +export default async function Image() { + return createCoverOgImage({ + title: 'LLM Context Window Comparison', + subtitle: `Context windows, max output tokens, and API prices for ${CONTEXT_WINDOW_MODELS.length} models, from largest to smallest.`, + }) +} diff --git a/apps/sim/app/(landing)/models/(shell)/context-windows/page.tsx b/apps/sim/app/(landing)/models/(shell)/context-windows/page.tsx new file mode 100644 index 00000000000..59c4786b50c --- /dev/null +++ b/apps/sim/app/(landing)/models/(shell)/context-windows/page.tsx @@ -0,0 +1,312 @@ +import { cn } from '@sim/emcn' +import type { Metadata } from 'next' +import Link from 'next/link' +import { SITE_URL } from '@/lib/core/utils/urls' +import { BackLink } from '@/app/(landing)/components' +import { JsonLd } from '@/app/(landing)/components/json-ld' +import { LandingFAQ } from '@/app/(landing)/components/landing-faq' +import { LANDING_CONTENT_WIDTH, LANDING_GUTTER } from '@/app/(landing)/components/landing-layout' +import { + MODEL_TABLE_CELL_CLASS, + MODEL_TABLE_HEADER_CELL_CLASS, +} from '@/app/(landing)/models/components/constants' +import { + buildContextWindowFaqs, + CONTEXT_WINDOW_MODELS, + CONTEXT_WINDOW_PROVIDER_COUNT, + getLargestContextByProvider, +} from '@/app/(landing)/models/context-windows' +import { + type CatalogModel, + formatPrice, + formatTokenCount, + formatUpdatedAt, + getEffectiveMaxOutputTokens, + getLatestModelUpdate, +} from '@/app/(landing)/models/utils' + +const baseUrl = SITE_URL +const PAGE_PATH = '/models/context-windows' +const PAGE_TITLE = 'LLM Context Window Comparison' +const PAGE_DESCRIPTION = `Context windows and max output tokens for ${CONTEXT_WINDOW_MODELS.length} LLMs, including Claude, GPT, Gemini, and Grok, with API prices. Use any of them in Sim's AI agents.` + +export const metadata: Metadata = { + title: PAGE_TITLE, + description: PAGE_DESCRIPTION, + keywords: [ + 'LLM context window', + 'context window comparison', + 'Claude context window', + 'Gemini context window', + 'GPT-5 context window', + 'Grok context window', + 'max output tokens', + 'largest context window LLM', + ], + // og:image/twitter:image come from the sibling opengraph-image.tsx - + // Next serves it at a hash-suffixed URL, so hardcoding it here 404s. + openGraph: { + title: `${PAGE_TITLE} | Sim`, + description: PAGE_DESCRIPTION, + url: `${baseUrl}${PAGE_PATH}`, + type: 'website', + }, + twitter: { + card: 'summary_large_image', + title: `${PAGE_TITLE} | Sim`, + description: PAGE_DESCRIPTION, + }, + alternates: { + canonical: `${baseUrl}${PAGE_PATH}`, + }, +} + +export default function ContextWindowsPage() { + const faqs = buildContextWindowFaqs() + const largestByProvider = getLargestContextByProvider() + const updatedAt = getLatestModelUpdate(CONTEXT_WINDOW_MODELS) + + const breadcrumbJsonLd = { + '@context': 'https://schema.org', + '@type': 'BreadcrumbList', + itemListElement: [ + { '@type': 'ListItem', position: 1, name: 'Home', item: baseUrl }, + { '@type': 'ListItem', position: 2, name: 'Models', item: `${baseUrl}/models` }, + { + '@type': 'ListItem', + position: 3, + name: 'Context windows', + item: `${baseUrl}${PAGE_PATH}`, + }, + ], + } + + const itemListJsonLd = { + '@context': 'https://schema.org', + '@type': 'ItemList', + name: PAGE_TITLE, + description: `${CONTEXT_WINDOW_MODELS.length} AI models tracked in Sim, ordered by context window from largest to smallest.`, + url: `${baseUrl}${PAGE_PATH}`, + dateModified: updatedAt.toISOString(), + numberOfItems: CONTEXT_WINDOW_MODELS.length, + itemListOrder: 'https://schema.org/ItemListOrderDescending', + itemListElement: CONTEXT_WINDOW_MODELS.map((model, index) => ({ + '@type': 'ListItem', + position: index + 1, + url: `${baseUrl}${model.href}`, + name: `${model.displayName}: ${formatTokenCount(model.contextWindow)} tokens`, + })), + } + + const faqJsonLd = { + '@context': 'https://schema.org', + '@type': 'FAQPage', + mainEntity: faqs.map((faq) => ({ + '@type': 'Question', + name: faq.question, + acceptedAnswer: { + '@type': 'Answer', + text: faq.answer, + }, + })), + } + + return ( + <> + + + + +
+
+

+ Sim is the open-source AI workspace where teams build, deploy, and manage AI agents. + This page ranks the context windows and max output tokens of{' '} + {CONTEXT_WINDOW_MODELS.length} models from {CONTEXT_WINDOW_PROVIDER_COUNT} providers, + including Claude, GPT, Gemini, and Grok, with API prices per 1M tokens. Every model + listed is available in the Sim Agent block. +

+
+ +
+ +

+ LLM context window comparison +

+

+ Context windows and max output tokens for {CONTEXT_WINDOW_MODELS.length} models from{' '} + {CONTEXT_WINDOW_PROVIDER_COUNT} providers, sorted from largest to smallest, with API + prices per 1M tokens. Every model listed is available in the Sim Agent block. Data last + updated{' '} + + . +

+
+ +
+ +
+
+
+
+

+ Largest context window by provider +

+

+ The biggest context window each provider offers in Sim, and the model that has it. +

+
+ {largestByProvider.map(({ provider, model }) => ( + + + {provider.apiName} + + + {formatTokenCount(model.contextWindow)} tokens · {model.displayName} + + + ))} +
+ +
+ +
+
+

+ Every model by context window +

+

+ Sorted by context window, largest first. Prices are standard API rates per 1M + tokens. +

+
+ +
+ +
+ +
+

+ Frequently asked questions +

+
+ +
+
+
+
+ +
+
+ + ) +} + +function getMaxOutputLabel(model: CatalogModel): string { + const maxOutputTokens = getEffectiveMaxOutputTokens(model.capabilities) + return maxOutputTokens ? formatTokenCount(maxOutputTokens) : '—' +} + +interface ContextWindowTableProps { + models: CatalogModel[] +} + +function ContextWindowTable({ models }: ContextWindowTableProps) { + return ( +
+ + + + + + + + + + + + + + {models.map((model) => ( + + + + + + + + + ))} + +
+ Context window, max output tokens, and API prices for every model tracked in Sim +
+ Model + + Provider + + Context + + Max output + + Input + + Output +
+ + {model.displayName} + + {model.deprecated ? ( + + Legacy + + ) : null} + + {model.providerName} + + {formatTokenCount(model.contextWindow)} + + {getMaxOutputLabel(model)} + + {formatPrice(model.pricing.input)} + + {formatPrice(model.pricing.output)} +
+
+ ) +} diff --git a/apps/sim/app/(landing)/models/(shell)/page.tsx b/apps/sim/app/(landing)/models/(shell)/page.tsx index 5320b5dc8b1..f7f31e200a2 100644 --- a/apps/sim/app/(landing)/models/(shell)/page.tsx +++ b/apps/sim/app/(landing)/models/(shell)/page.tsx @@ -1,16 +1,21 @@ import type { Metadata } from 'next' +import Link from 'next/link' import type { SearchParams } from 'nuqs/server' import { SITE_URL } from '@/lib/core/utils/urls' import { withFilteredNoindex } from '@/lib/landing/seo' import { JsonLd } from '@/app/(landing)/components/json-ld' import { LandingFAQ } from '@/app/(landing)/components/landing-faq' +import { MODEL_COMPARISONS } from '@/app/(landing)/models/comparisons' +import { ComparisonLinks } from '@/app/(landing)/models/components/comparison-links' import { ModelComparisonCharts } from '@/app/(landing)/models/components/model-comparison-charts' import { ModelDirectory } from '@/app/(landing)/models/components/model-directory' import { FeaturedModelCard, FeaturedProviderCard, } from '@/app/(landing)/models/components/model-primitives' +import { ModelToolLink } from '@/app/(landing)/models/components/model-tool-link' import { modelsSearchParamsCache } from '@/app/(landing)/models/search-params' +import { MODEL_TOOLS } from '@/app/(landing)/models/tools' import { ALL_CATALOG_MODELS, getPricingBounds, @@ -223,10 +228,51 @@ export default async function ModelsPage({ )} + +
+
+
+

+ Head-to-head comparisons +

+

+ API pricing, context window, max output, and capabilities for two models side by + side. For every model at once, see the{' '} + + LLM context window comparison + + . +

+ +
+ +
+

= { + input: inputTokens, + output: outputTokens, + cached: cachedPercent, + requests: requestsPerDay, + } + const cachedTokens = Math.round((inputTokens * cachedPercent) / 100) + + const selectedModels = resolveToolModels(params.models) + const rows = selectedModels + .map((model) => buildCostRow(model, inputTokens, cachedTokens, outputTokens)) + .sort((a, b) => a.perRequest - b.perRequest) + + return ( +
+
+
+ + Models + +
+ model.id)} + onChange={(models) => setParams({ models })} + /> +
+
+ +
+ {COUNT_FIELDS.map((field) => ( +
+ + + setParams( + { [field.key]: parseCount(event.target.value, field.max) }, + NUMBER_URL_WRITE + ) + } + /> +
+ ))} +
+
+ +
+ + {rows.length === 0 ? ( +

+ Select one or more models to compare their cost. +

+ ) : ( +
+ + + + + + + + + + + + {rows.map((row) => ( + + ))} + +
+ Estimated cost per model for {inputTokens.toLocaleString('en-US')} input tokens ( + {cachedPercent}% cached) and {outputTokens.toLocaleString('en-US')} output tokens per + request, at {requestsPerDay.toLocaleString('en-US')} requests per day +
+ Model + + Per request + + Per day + + Per month +
+
+ )} + +

+ Rates are list prices in USD per 1M tokens, and a month is {DAYS_PER_MONTH} days. Reasoning + tokens bill as output, so count them in output tokens. Prompt-cache writes and tool calls + are not included. +

+
+ ) +} + +interface CostTableRowProps { + row: CostRow + inputTokens: number + cachedTokens: number + requestsPerDay: number +} + +function CostTableRow({ row, inputTokens, cachedTokens, requestsPerDay }: CostTableRowProps) { + const { model } = row + const notes: string[] = [] + + if (row.tierThreshold !== null) { + notes.push(`Long-context rate: input above ${formatTokenCount(row.tierThreshold)} tokens`) + } + if (cachedTokens > 0 && row.cachedInputRate === undefined) { + notes.push('No cached-input rate, so cached tokens bill at the input rate') + } + if (model.contextWindow !== null && inputTokens > model.contextWindow) { + notes.push(`Exceeds the ${formatTokenCount(model.contextWindow)} context window`) + } + + return ( + + + + {model.displayName} + +

+ {model.providerName} · Input {formatPrice(row.inputRate)} · Cached{' '} + {row.cachedInputRate === undefined ? 'N/A' : formatPrice(row.cachedInputRate)} · + Output {formatPrice(row.outputRate)} +

+ {notes.map((note) => ( +

+ {note} +

+ ))} + + + {formatUsd(row.perRequest)} + + + {formatUsd(row.perRequest * requestsPerDay)} + + + {formatUsd(row.perRequest * requestsPerDay * DAYS_PER_MONTH)} + + + ) +} diff --git a/apps/sim/app/(landing)/models/(shell)/pricing-calculator/opengraph-image.tsx b/apps/sim/app/(landing)/models/(shell)/pricing-calculator/opengraph-image.tsx new file mode 100644 index 00000000000..c39b84f8479 --- /dev/null +++ b/apps/sim/app/(landing)/models/(shell)/pricing-calculator/opengraph-image.tsx @@ -0,0 +1,12 @@ +import { COVER_OG_SIZE, createCoverOgImage } from '@/lib/og/cover-image' +import { TOOL_MODELS } from '@/app/(landing)/models/tools' + +export const contentType = 'image/png' +export const size = COVER_OG_SIZE + +export default async function Image() { + return createCoverOgImage({ + title: 'LLM API Pricing Calculator', + subtitle: `Estimate cost per request and per month across ${TOOL_MODELS.length} models from OpenAI, Anthropic, Google, and more.`, + }) +} diff --git a/apps/sim/app/(landing)/models/(shell)/pricing-calculator/page.tsx b/apps/sim/app/(landing)/models/(shell)/pricing-calculator/page.tsx new file mode 100644 index 00000000000..90bd38d9cdb --- /dev/null +++ b/apps/sim/app/(landing)/models/(shell)/pricing-calculator/page.tsx @@ -0,0 +1,281 @@ +import { cn } from '@sim/emcn' +import type { Metadata } from 'next' +import type { SearchParams } from 'nuqs/server' +import { SITE_URL } from '@/lib/core/utils/urls' +import { BackLink } from '@/app/(landing)/components' +import { JsonLd } from '@/app/(landing)/components/json-ld' +import { LandingFAQ } from '@/app/(landing)/components/landing-faq' +import { LANDING_CONTENT_WIDTH, LANDING_GUTTER } from '@/app/(landing)/components/landing-layout' +import { PricingCalculator } from '@/app/(landing)/models/(shell)/pricing-calculator/components/pricing-calculator' +import { pricingCalculatorSearchParamsCache } from '@/app/(landing)/models/(shell)/pricing-calculator/search-params' +import { ModelToolLink } from '@/app/(landing)/models/components/model-tool-link' +import { MODEL_TOOLS, TOOL_MODELS, TOOL_PRICING_UPDATED_AT } from '@/app/(landing)/models/tools' +import { formatPrice, formatTokenCount, formatUpdatedAt } from '@/app/(landing)/models/utils' + +const PAGE_PATH = '/models/pricing-calculator' +const PAGE_URL = `${SITE_URL}${PAGE_PATH}` +const AGENT_COST_ARTICLE_PATH = '/library/how-much-does-it-cost-to-build-and-run-an-ai-agent' + +const TITLE = 'LLM API Pricing Calculator: OpenAI, Claude, Gemini' +const DESCRIPTION = `Free LLM API pricing calculator: estimate cost per request and per month for ${TOOL_MODELS.length} OpenAI, Claude, Gemini, and other models, with cached input.` + +const tieredModel = TOOL_MODELS.find((model) => (model.pricing.tiers?.length ?? 0) > 0) +const tieredExample = tieredModel?.pricing.tiers?.[0] +const cachedModel = TOOL_MODELS.find( + (model) => + model.pricing.cachedInput !== undefined && model.pricing.cachedInput < model.pricing.input +) + +const tierSentence = + tieredModel && tieredExample + ? ` For example, ${tieredModel.displayName} bills ${formatPrice(tieredModel.pricing.input)} per 1M input tokens up to ${formatTokenCount(tieredExample.aboveInputTokens)} input tokens and ${formatPrice(tieredExample.input)} per 1M once a request goes past that.` + : '' + +const cachedSentence = cachedModel?.pricing.cachedInput + ? ` ${cachedModel.displayName}, for instance, lists ${formatPrice(cachedModel.pricing.cachedInput)} per 1M cached input tokens against ${formatPrice(cachedModel.pricing.input)} per 1M uncached.` + : '' + +const COST_DRIVERS = [ + { + id: 'tokens', + title: 'Input and output tokens', + body: 'Every LLM API call is billed per token, with separate rates for the tokens you send (system prompt, conversation history, tool results, retrieved documents) and the tokens the model generates. Output tokens usually cost several times more than input tokens, and reasoning models bill their hidden reasoning tokens as output.', + }, + { + id: 'cached-input', + title: 'Cached input', + body: `When a provider supports prompt caching, a repeated prompt prefix such as a long system prompt is read from cache at a discounted cached-input rate.${cachedSentence} The calculator bills the cached share of input at that rate and the rest at the full input rate; models without a cached rate bill all input at the input rate.`, + }, + { + id: 'tiers', + title: 'Long-context pricing tiers', + body: `Some models charge a higher rate for the whole request once its input crosses a size threshold.${tierSentence} Sim resolves the tier from all input tokens, cached and uncached, and applies it to input, cached input, and output alike.`, + }, +] + +const faqItems = [ + { + question: 'How do I calculate the cost of an LLM API call?', + answer: + 'Multiply input tokens by the input rate and output tokens by the output rate, with both rates quoted per one million tokens, then add the two. If part of the input is served from a prompt cache, bill that part at the cached-input rate instead. Multiply the per-request cost by requests per day and by 30 for a monthly estimate. The Sim pricing calculator does this for every selected model at once.', + }, + { + question: 'Does this calculator work for OpenAI, Claude, and Gemini pricing?', + answer: `Yes. The Sim LLM API pricing calculator covers ${TOOL_MODELS.length} current models from OpenAI, Anthropic Claude, Google Gemini, xAI, Mistral, DeepSeek, and other providers, using the same pricing data shown on each Sim model page. Prices were last updated ${formatUpdatedAt(TOOL_PRICING_UPDATED_AT)}.`, + }, + { + question: 'How much does prompt caching save?', + answer: + 'Prompt caching discounts the input tokens a provider has already seen, so savings scale with how much of each request repeats. An agent that resends a long system prompt and tool definitions on every call can serve most of its input from cache. Set the cached input percentage in the Sim calculator to see the difference per model; cache writes, which some providers bill at a premium, are not included.', + }, + { + question: 'How many tokens is my prompt?', + answer: + 'A token is roughly four characters of English text, but the exact count depends on the model tokenizer. Paste your prompt into the Sim token counter for an exact count on OpenAI models and an estimate for others, then use that number as the input tokens here.', + }, + { + question: 'How do I control LLM costs in an AI agent?', + answer: + 'Pick the smallest model that handles the task, keep system prompts and tool definitions stable so they hit the prompt cache, trim conversation history and retrieved context, and cap output length. In Sim, every agent run records its token usage and cost per block in the logs, so you can see which step drives spend and swap models on that step alone.', + }, +] + +export async function generateMetadata(): Promise { + return { + title: TITLE, + description: DESCRIPTION, + keywords: [ + 'LLM pricing calculator', + 'LLM API pricing calculator', + 'OpenAI pricing calculator', + 'Claude API cost calculator', + 'Gemini API pricing calculator', + 'AI API cost calculator', + 'LLM cost per request', + 'prompt caching cost', + ], + // og:image/twitter:image come from the sibling opengraph-image.tsx. + openGraph: { + title: `${TITLE} | Sim`, + description: DESCRIPTION, + url: PAGE_URL, + type: 'website', + }, + twitter: { + card: 'summary_large_image', + title: `${TITLE} | Sim`, + description: DESCRIPTION, + }, + alternates: { + canonical: PAGE_URL, + }, + } +} + +interface PricingCalculatorPageProps { + searchParams: Promise +} + +export default async function PricingCalculatorPage({ searchParams }: PricingCalculatorPageProps) { + await pricingCalculatorSearchParamsCache.parse(searchParams) + + const breadcrumbJsonLd = { + '@context': 'https://schema.org', + '@type': 'BreadcrumbList', + itemListElement: [ + { '@type': 'ListItem', position: 1, name: 'Home', item: SITE_URL }, + { '@type': 'ListItem', position: 2, name: 'Models', item: `${SITE_URL}/models` }, + { '@type': 'ListItem', position: 3, name: 'LLM API pricing calculator', item: PAGE_URL }, + ], + } + + const applicationJsonLd = { + '@context': 'https://schema.org', + '@type': 'WebApplication', + '@id': `${PAGE_URL}#application`, + name: 'Sim LLM API Pricing Calculator', + description: DESCRIPTION, + url: PAGE_URL, + applicationCategory: 'DeveloperApplication', + operatingSystem: 'Web', + isAccessibleForFree: true, + dateModified: TOOL_PRICING_UPDATED_AT, + publisher: { '@id': `${SITE_URL}#organization` }, + offers: { '@type': 'Offer', price: '0', priceCurrency: 'USD' }, + featureList: [ + 'Cost per request, per day, and per month', + 'Side-by-side model comparison', + 'Cached input pricing', + 'Long-context pricing tiers', + 'Shareable links', + ], + } + + const faqJsonLd = { + '@context': 'https://schema.org', + '@type': 'FAQPage', + mainEntity: faqItems.map((item) => ({ + '@type': 'Question', + name: item.question, + acceptedAnswer: { '@type': 'Answer', text: item.answer }, + })), + } + + return ( + <> + + + + +
+
+

+ Sim is the open-source AI workspace where teams build, deploy, and manage AI agents. The + free Sim LLM API pricing calculator estimates cost per request, per day, and per month + for {TOOL_MODELS.length} OpenAI, Anthropic Claude, Google Gemini, and other models, + using the same rates and long-context tier rules Sim uses to cost model calls in its + execution logs. +

+
+ +
+

+ Free tool +

+

+ LLM API pricing calculator +

+

+ Estimate what an LLM API call costs across OpenAI, Anthropic Claude, Google Gemini, and{' '} + {TOOL_MODELS.length} current models. Enter tokens per request, the share of input served + from the prompt cache, and requests per day; Sim prices each model with the same rates + and long-context tier rules it uses to cost model calls in its execution logs. +

+

+ Prices last updated{' '} + +

+
+ +
+ +
+
+ + +
+ +
+

+ What drives LLM API cost +

+

+ LLM API cost is tokens times rate: input tokens at the input rate, cached input at + the cached rate, and output tokens at the output rate, all quoted per one million + tokens. Three things move the total. +

+
+ {COST_DRIVERS.map((driver) => ( +
+

{driver.title}

+

{driver.body}

+
+ ))} +
+
+ +
+ + + +
+ +
+

+ Frequently asked questions +

+ +
+
+
+ +
+
+ + ) +} diff --git a/apps/sim/app/(landing)/models/(shell)/pricing-calculator/search-params.ts b/apps/sim/app/(landing)/models/(shell)/pricing-calculator/search-params.ts new file mode 100644 index 00000000000..6c22a430814 --- /dev/null +++ b/apps/sim/app/(landing)/models/(shell)/pricing-calculator/search-params.ts @@ -0,0 +1,30 @@ +import { createSearchParamsCache, parseAsArrayOf, parseAsInteger, parseAsString } from 'nuqs/server' +import { DEFAULT_TOOL_MODEL_IDS } from '@/app/(landing)/models/tools' + +/** + * Shareable calculator inputs. Every value is small and discrete, so a pasted + * link reproduces the exact estimate; number fields debounce their URL writes. + * + * - `models`: selected catalog model ids. + * - `input` / `output`: tokens per request. + * - `cached`: percent of input tokens served from the prompt cache (0-100). + * - `requests`: requests per day. + */ +export const pricingCalculatorParsers = { + models: parseAsArrayOf(parseAsString).withDefault(DEFAULT_TOOL_MODEL_IDS), + input: parseAsInteger.withDefault(2_000), + output: parseAsInteger.withDefault(500), + cached: parseAsInteger.withDefault(0), + requests: parseAsInteger.withDefault(1_000), +} + +export const pricingCalculatorUrlKeys = { + history: 'replace', + clearOnDefault: true, +} as const + +/** + * Parsed by the page so the route renders dynamically and the island's + * `useQueryStates` reads the shared inputs during the server render. + */ +export const pricingCalculatorSearchParamsCache = createSearchParamsCache(pricingCalculatorParsers) diff --git a/apps/sim/app/(landing)/models/(shell)/token-counter/components/token-counter/index.ts b/apps/sim/app/(landing)/models/(shell)/token-counter/components/token-counter/index.ts new file mode 100644 index 00000000000..466225175ed --- /dev/null +++ b/apps/sim/app/(landing)/models/(shell)/token-counter/components/token-counter/index.ts @@ -0,0 +1 @@ +export { TokenCounter } from './token-counter' diff --git a/apps/sim/app/(landing)/models/(shell)/token-counter/components/token-counter/token-counter.tsx b/apps/sim/app/(landing)/models/(shell)/token-counter/components/token-counter/token-counter.tsx new file mode 100644 index 00000000000..53819a4fbe5 --- /dev/null +++ b/apps/sim/app/(landing)/models/(shell)/token-counter/components/token-counter/token-counter.tsx @@ -0,0 +1,176 @@ +'use client' + +import { useDeferredValue, useId, useMemo, useRef, useState } from 'react' +import { ChipTag, ChipTextarea } from '@sim/emcn' +import Link from 'next/link' +import { useQueryStates } from 'nuqs' +import { estimateTokenCount } from '@/lib/tokenization/estimators' +import { + tokenCounterParsers, + tokenCounterUrlKeys, +} from '@/app/(landing)/models/(shell)/token-counter/search-params' +import { ModelMultiSelect } from '@/app/(landing)/models/components/model-multi-select' +import { formatUsd, resolveToolModels } from '@/app/(landing)/models/tools' +import { formatPrice } from '@/app/(landing)/models/utils' +import { priceTokenUsage } from '@/providers/pricing' + +/** `js-tiktoken` and its ~2.5 MB of rank tables, loaded on first interaction. */ +type AccurateTokenizer = typeof import('@/lib/tokenization/accurate') + +/** Providers whose models tokenize with a public `js-tiktoken` encoding. */ +const EXACT_TOKENIZER_PROVIDER_IDS = new Set(['openai']) + +const COUNT_FORMAT = new Intl.NumberFormat('en-US') + +/** + * Counts tokens for pasted text entirely in the browser. OpenAI models get + * exact counts from `@/lib/tokenization/accurate`, which is imported only when + * the visitor first focuses or types in the text box so the tokenizer never + * ships in the page's initial bundle; every other provider uses the heuristic + * estimators and is labelled as an estimate. + */ +export function TokenCounter() { + const fieldId = useId() + const [{ models: modelIds }, setParams] = useQueryStates(tokenCounterParsers, tokenCounterUrlKeys) + const [text, setText] = useState('') + const [tokenizer, setTokenizer] = useState(null) + const tokenizerRequest = useRef | null>(null) + const deferredText = useDeferredValue(text) + + const loadTokenizer = () => { + if (tokenizerRequest.current) return + tokenizerRequest.current = import('@/lib/tokenization/accurate').then(setTokenizer, () => { + tokenizerRequest.current = null + }) + } + + const selectedModels = useMemo(() => resolveToolModels(modelIds), [modelIds]) + + const rows = useMemo(() => { + const exactCountByEncoding = new Map() + const countExact = (activeTokenizer: AccurateTokenizer, modelId: string) => { + const encodingName = activeTokenizer.resolveEncodingName(modelId) + if (!exactCountByEncoding.has(encodingName)) { + exactCountByEncoding.set( + encodingName, + activeTokenizer.getExactTokenCount(deferredText, modelId) + ) + } + return exactCountByEncoding.get(encodingName) ?? null + } + + return selectedModels.map((model) => { + const exactTokens = + tokenizer !== null && EXACT_TOKENIZER_PROVIDER_IDS.has(model.providerId) + ? countExact(tokenizer, model.id) + : null + const exact = exactTokens !== null + const tokens = exactTokens ?? estimateTokenCount(deferredText, model.providerId).count + const inputCost = priceTokenUsage(model.pricing, { input: tokens, output: 0 }).total + return { model, exact, tokens, inputCost } + }) + }, [selectedModels, tokenizer, deferredText]) + + return ( +
+
+
+ + { + loadTokenizer() + setText(event.target.value) + }} + /> +

+ {COUNT_FORMAT.format(text.length)} characters. Text stays in your browser and is never + sent to Sim. +

+
+ +
+ + Models + +
+ model.id)} + onChange={(models) => setParams({ models })} + /> +
+
+
+ +
+ + {rows.length === 0 ? ( +

+ Select one or more models to count tokens. +

+ ) : ( +
+ + + + + + + + + + + {rows.map(({ model, exact, tokens, inputCost }) => ( + + + + + + ))} + +
Token count and input cost per model
+ Model + + Tokens + + Input cost +
+ + {model.displayName} + +

+ {model.providerName} · Input {formatPrice(model.pricing.input)}/1M +

+
+ + {exact ? 'exact' : 'estimate'} + + {COUNT_FORMAT.format(tokens)} + + + + {formatUsd(inputCost)} +
+
+ )} + +

+ Exact counts use OpenAI's tiktoken encodings. Other providers do not publish a browser + tokenizer, so their counts are character-based estimates. Input cost uses list prices and + excludes the tokens a provider adds for message formatting. +

+
+ ) +} diff --git a/apps/sim/app/(landing)/models/(shell)/token-counter/opengraph-image.tsx b/apps/sim/app/(landing)/models/(shell)/token-counter/opengraph-image.tsx new file mode 100644 index 00000000000..69053f692a3 --- /dev/null +++ b/apps/sim/app/(landing)/models/(shell)/token-counter/opengraph-image.tsx @@ -0,0 +1,12 @@ +import { COVER_OG_SIZE, createCoverOgImage } from '@/lib/og/cover-image' + +export const contentType = 'image/png' +export const size = COVER_OG_SIZE + +export default async function Image() { + return createCoverOgImage({ + title: 'Token Counter', + subtitle: + 'Count tokens and estimate input cost for OpenAI, Claude, Gemini, and other models, right in your browser.', + }) +} diff --git a/apps/sim/app/(landing)/models/(shell)/token-counter/page.tsx b/apps/sim/app/(landing)/models/(shell)/token-counter/page.tsx new file mode 100644 index 00000000000..27bc048fcca --- /dev/null +++ b/apps/sim/app/(landing)/models/(shell)/token-counter/page.tsx @@ -0,0 +1,245 @@ +import { cn } from '@sim/emcn' +import type { Metadata } from 'next' +import type { SearchParams } from 'nuqs/server' +import { SITE_URL } from '@/lib/core/utils/urls' +import { BackLink } from '@/app/(landing)/components' +import { JsonLd } from '@/app/(landing)/components/json-ld' +import { LandingFAQ } from '@/app/(landing)/components/landing-faq' +import { LANDING_CONTENT_WIDTH, LANDING_GUTTER } from '@/app/(landing)/components/landing-layout' +import { TokenCounter } from '@/app/(landing)/models/(shell)/token-counter/components/token-counter' +import { tokenCounterSearchParamsCache } from '@/app/(landing)/models/(shell)/token-counter/search-params' +import { ModelToolLink } from '@/app/(landing)/models/components/model-tool-link' +import { MODEL_TOOLS, TOOL_MODELS } from '@/app/(landing)/models/tools' + +const PAGE_PATH = '/models/token-counter' +const PAGE_URL = `${SITE_URL}${PAGE_PATH}` + +const TITLE = 'Token Counter and Tokenizer for OpenAI and Claude' +const DESCRIPTION = + 'Free token counter: paste text to count tokens and characters and estimate input cost for OpenAI, Claude, Gemini, and other models. Runs in your browser.' + +const TOKENIZER_FACTS = [ + { + id: 'what-is-a-token', + title: 'What a token is', + body: 'A token is the unit a language model reads and writes: a whole word, part of a word, a punctuation mark, or a run of whitespace. In English, one token averages about four characters, so 1,000 tokens is roughly 750 words. Code, non-English text, and numbers usually take more tokens per character.', + }, + { + id: 'exact-vs-estimate', + title: 'Exact counts and estimates', + body: "OpenAI publishes its tokenizer encodings, so the Sim token counter loads OpenAI's tiktoken encodings in your browser and counts text exactly as the OpenAI API tokenizes it. Anthropic, Google, and most other providers do not publish a browser tokenizer, so their counts are character-based estimates and are labelled as such.", + }, + { + id: 'why-count', + title: 'Why token counts matter', + body: 'Tokens set both the price of a call and whether it fits the model context window. An agent that resends its system prompt, tool definitions, and conversation history on every turn can multiply a small prompt into a large bill, so counting the tokens in each piece shows where to trim.', + }, +] + +const faqItems = [ + { + question: 'How do I count tokens for OpenAI models?', + answer: + "Paste your text into the Sim token counter and select an OpenAI model. The count comes from OpenAI's tiktoken encodings (o200k_base for GPT-4o and newer models, cl100k_base for older GPT-4 and GPT-3.5 models), the same tokenizer the OpenAI API uses to bill input.", + }, + { + question: 'Can I count tokens for Claude?', + answer: + 'Anthropic does not publish a tokenizer that runs in the browser, so the Sim token counter estimates Claude token counts from character and word length and labels them as estimates. For an exact Claude count, call the Anthropic token counting API with the same messages you plan to send.', + }, + { + question: 'Is my text sent anywhere?', + answer: + 'No. The Sim token counter tokenizes text in your browser. Pasted text is never sent to Sim or stored in the page URL; only the selected models are kept in the link so you can share a configuration.', + }, + { + question: 'How many tokens is 1,000 words?', + answer: + 'About 1,300 tokens for typical English prose with OpenAI tokenizers, since one token averages about four characters. The ratio varies with vocabulary, formatting, and language, so paste the actual text for a real count.', + }, + { + question: 'How do token counts translate to cost?', + answer: `Each model bills input and output tokens at its own rate per one million tokens. The Sim token counter multiplies your token count by the input rate of each selected model, switching to the higher long-context rate when the count crosses a model's pricing tier threshold; use the Sim LLM API pricing calculator to add output tokens, prompt caching, and monthly volume across ${TOOL_MODELS.length} models.`, + }, +] + +export async function generateMetadata(): Promise { + return { + title: TITLE, + description: DESCRIPTION, + keywords: [ + 'token counter', + 'tokenizer', + 'OpenAI tokenizer', + 'token calculator', + 'Claude token counter', + 'tiktoken online', + 'GPT token counter', + 'count tokens', + ], + // og:image/twitter:image come from the sibling opengraph-image.tsx. + openGraph: { + title: `${TITLE} | Sim`, + description: DESCRIPTION, + url: PAGE_URL, + type: 'website', + }, + twitter: { + card: 'summary_large_image', + title: `${TITLE} | Sim`, + description: DESCRIPTION, + }, + alternates: { + canonical: PAGE_URL, + }, + } +} + +interface TokenCounterPageProps { + searchParams: Promise +} + +export default async function TokenCounterPage({ searchParams }: TokenCounterPageProps) { + await tokenCounterSearchParamsCache.parse(searchParams) + + const breadcrumbJsonLd = { + '@context': 'https://schema.org', + '@type': 'BreadcrumbList', + itemListElement: [ + { '@type': 'ListItem', position: 1, name: 'Home', item: SITE_URL }, + { '@type': 'ListItem', position: 2, name: 'Models', item: `${SITE_URL}/models` }, + { '@type': 'ListItem', position: 3, name: 'Token counter', item: PAGE_URL }, + ], + } + + const applicationJsonLd = { + '@context': 'https://schema.org', + '@type': 'WebApplication', + '@id': `${PAGE_URL}#application`, + name: 'Sim Token Counter', + description: DESCRIPTION, + url: PAGE_URL, + applicationCategory: 'DeveloperApplication', + operatingSystem: 'Web', + isAccessibleForFree: true, + publisher: { '@id': `${SITE_URL}#organization` }, + offers: { '@type': 'Offer', price: '0', priceCurrency: 'USD' }, + featureList: [ + 'Exact token counts for OpenAI models', + 'Token estimates for Claude, Gemini, and other models', + 'Character count', + 'Estimated input cost per model', + 'Runs entirely in the browser', + ], + } + + const faqJsonLd = { + '@context': 'https://schema.org', + '@type': 'FAQPage', + mainEntity: faqItems.map((item) => ({ + '@type': 'Question', + name: item.question, + acceptedAnswer: { '@type': 'Answer', text: item.answer }, + })), + } + + return ( + <> + + + + +
+
+

+ Sim is the open-source AI workspace where teams build, deploy, and manage AI agents. The + free Sim token counter counts the tokens and characters in pasted text for OpenAI, + Anthropic Claude, Google Gemini, and other models, exactly for OpenAI and as an estimate + for the rest, and shows each model's input cost. Text never leaves the browser. +

+
+ +
+

+ Free tool +

+

+ Token counter +

+

+ Paste a prompt or document to count its tokens and characters and see what it costs as + input to OpenAI, Anthropic Claude, Google Gemini, and other models. Counts are exact for + OpenAI models and estimated for the rest, and your text never leaves the browser. +

+
+ +
+ +
+
+ + +
+ +
+

+ How LLM tokenizers count text +

+

+ A tokenizer splits text into the tokens a model reads, and each provider uses its + own vocabulary, so the same text yields different counts on different models. +

+
+ {TOKENIZER_FACTS.map((fact) => ( +
+

{fact.title}

+

{fact.body}

+
+ ))} +
+
+ +
+ + + +
+ +
+

+ Frequently asked questions +

+ +
+
+
+ +
+
+ + ) +} diff --git a/apps/sim/app/(landing)/models/(shell)/token-counter/search-params.ts b/apps/sim/app/(landing)/models/(shell)/token-counter/search-params.ts new file mode 100644 index 00000000000..4af33fd2742 --- /dev/null +++ b/apps/sim/app/(landing)/models/(shell)/token-counter/search-params.ts @@ -0,0 +1,21 @@ +import { createSearchParamsCache, parseAsArrayOf, parseAsString } from 'nuqs/server' +import { DEFAULT_TOOL_MODEL_IDS } from '@/app/(landing)/models/tools' + +/** + * Only the model selection is shareable. The pasted text never enters the URL: + * it can be large, and it stays in the visitor's browser. + */ +export const tokenCounterParsers = { + models: parseAsArrayOf(parseAsString).withDefault(DEFAULT_TOOL_MODEL_IDS), +} + +export const tokenCounterUrlKeys = { + history: 'replace', + clearOnDefault: true, +} as const + +/** + * Parsed by the page so the route renders dynamically and the island's + * `useQueryStates` reads the selection during the server render. + */ +export const tokenCounterSearchParamsCache = createSearchParamsCache(tokenCounterParsers) diff --git a/apps/sim/app/(landing)/models/comparisons.ts b/apps/sim/app/(landing)/models/comparisons.ts new file mode 100644 index 00000000000..3dea1b111b3 --- /dev/null +++ b/apps/sim/app/(landing)/models/comparisons.ts @@ -0,0 +1,362 @@ +import { + ALL_CATALOG_MODELS, + type CatalogFaq, + type CatalogModel, + type CatalogProvider, + formatPrice, + formatTokenCount, + getEffectiveMaxOutputTokens, + getLatestModelUpdate, + getProviderBySlug, + hasPublishedPricing, + isPromptModel, +} from '@/app/(landing)/models/utils' +import { resolveModelTokenPricing } from '@/providers/pricing' + +/** + * Curated head-to-head comparisons, as pairs of first-party catalog model ids. Each pair renders at + * `/models/compare/{first}-vs-{second}` only, so list a pair once, in the order searchers usually + * type it. + * + * Every id must resolve to exactly one callable first-party model with published token prices. A + * pair that stops resolving (a model renamed, removed, or retired in `providers/models.ts`) throws + * when this module loads, which fails the build instead of shipping a broken or thin page. + */ +const MODEL_COMPARISON_PAIRS: ReadonlyArray = [ + ['claude-opus-5-5', 'claude-sonnet-5-5'], + ['claude-sonnet-5-5', 'claude-haiku-5-5'], + ['claude-opus-5-5', 'claude-haiku-5-5'], + ['claude-fable-5-1', 'claude-opus-5-5'], + ['claude-fable-5-1', 'claude-haiku-5-5'], + ['claude-opus-5-5', 'claude-opus-4-8'], + ['claude-sonnet-5-5', 'claude-sonnet-4-6'], + ['claude-opus-4-7', 'claude-sonnet-4-6'], + ['claude-haiku-5-5', 'claude-haiku-4-5'], + ['gpt-6-astra', 'gpt-6-sol'], + ['gpt-6-sol', 'gpt-6-luna'], + ['gpt-6-astra', 'gpt-6-luna'], + ['gpt-6.1-sol', 'gpt-6-sol'], + ['gpt-5.5', 'gpt-5.4'], + ['gpt-5.5', 'gpt-5.5-pro'], + ['gpt-5.4', 'gpt-5.4-mini'], + ['gpt-5.4-mini', 'gpt-5.4-nano'], + ['gpt-5', 'gpt-5-mini'], + ['gpt-5', 'gpt-4.1'], + ['gemini-4-argon', 'gemini-3.8-flash'], + ['gemini-3.8-flash', 'gemini-3.5-flash-lite'], + ['gemini-3.1-pro-preview', 'gemini-2.5-pro'], + ['gemini-2.5-pro', 'gemini-2.5-flash'], + ['gemini-2.5-flash', 'gemini-2.5-flash-lite'], + ['claude-opus-5-5', 'gpt-6-astra'], + ['claude-opus-5-5', 'gemini-4-argon'], + ['gpt-6-astra', 'gemini-4-argon'], + ['claude-sonnet-5-5', 'gpt-6-sol'], + ['claude-sonnet-5-5', 'gemini-4-argon'], + ['gpt-6-sol', 'gemini-4-argon'], + ['claude-opus-5-5', 'grok-4.7'], + ['gpt-6-astra', 'grok-4.7'], + ['gemini-4-argon', 'grok-4.7'], + ['claude-opus-5-5', 'deepseek-v4-pro'], + ['gpt-6-sol', 'deepseek-v4-pro'], + ['gemini-4-argon', 'deepseek-v4-pro'], + ['deepseek-v4-pro', 'kimi-k3'], + ['claude-sonnet-5-5', 'kimi-k3'], + ['claude-sonnet-5-5', 'glm-5.3'], + ['claude-opus-5-5', 'mistral-large-4'], + ['claude-haiku-5-5', 'gpt-6-luna'], + ['claude-haiku-5-5', 'gemini-3.8-flash'], + ['gpt-6-luna', 'gemini-3.8-flash'], + ['gemini-3.8-flash', 'deepseek-flash'], + ['gpt-5-mini', 'gemini-2.5-flash'], + ['gpt-5', 'claude-sonnet-4-5'], + ['gpt-5', 'gemini-2.5-pro'], +] + +/** Token volume behind the "typical agent call" cost in every verdict: 1,000 calls of 10k in, 1k out. */ +const SAMPLE_CALLS = 1000 +const SAMPLE_INPUT_TOKENS_PER_CALL = 10_000 +const SAMPLE_OUTPUT_TOKENS_PER_CALL = 1000 + +/** Ratios within this band read as "about the same" rather than a multiple. */ +const SAME_RATIO_THRESHOLD = 1.1 + +interface ComparedModel { + model: CatalogModel + provider: CatalogProvider +} + +/** A curated pair, rendered at `href`; `a` and `b` keep the pair's curated order. */ +export interface ModelComparison { + slug: string + href: string + title: string + a: ComparedModel + b: ComparedModel +} + +function resolveComparedModel(id: string): ComparedModel { + const matches = ALL_CATALOG_MODELS.filter((model) => model.id === id) + if (matches.length !== 1) { + throw new Error( + `Model comparison id "${id}" matches ${matches.length} first-party catalog models; update MODEL_COMPARISON_PAIRS` + ) + } + const [model] = matches + if (model.retired) { + throw new Error( + `Model comparison id "${id}" is retired by its provider; update MODEL_COMPARISON_PAIRS` + ) + } + if (!isPromptModel(model)) { + throw new Error(`Model comparison id "${id}" is an evaluation model, not a prompt model`) + } + if (!hasPublishedPricing(model.pricing)) { + throw new Error(`Model comparison id "${id}" has no published token pricing`) + } + const provider = getProviderBySlug(model.providerSlug) + if (!provider) { + throw new Error(`Model comparison id "${id}" has no provider page`) + } + return { model, provider } +} + +function buildComparisons(): ModelComparison[] { + const seenSlugs = new Set() + + return MODEL_COMPARISON_PAIRS.map(([firstId, secondId]) => { + if (firstId === secondId) { + throw new Error(`Model comparison pairs "${firstId}" with itself`) + } + + const a = resolveComparedModel(firstId) + const b = resolveComparedModel(secondId) + const slug = `${a.model.slug}-vs-${b.model.slug}` + const reverseSlug = `${b.model.slug}-vs-${a.model.slug}` + if (seenSlugs.has(slug) || seenSlugs.has(reverseSlug)) { + throw new Error(`Model comparison slug ${slug} collides with another pair`) + } + seenSlugs.add(slug) + seenSlugs.add(reverseSlug) + + return { + slug, + href: `/models/compare/${slug}`, + title: `${a.model.displayName} vs ${b.model.displayName}`, + a, + b, + } + }) +} + +/** Every curated comparison, in {@link MODEL_COMPARISON_PAIRS} order. */ +export const MODEL_COMPARISONS: ModelComparison[] = buildComparisons() + +/** Longest page title that keeps the rendered `` (with the " | Sim" template) at 60 chars. */ +const MAX_COMPARISON_TITLE_LENGTH = 54 + +/** Page title for a comparison, as specific as the 60-character `<title>` budget allows. */ +export function getComparisonPageTitle(comparison: ModelComparison): string { + const candidates = [ + `${comparison.title}: API Pricing & Context Window`, + `${comparison.title}: API Pricing & Specs`, + `${comparison.title} API Pricing`, + ] + return ( + candidates.find((candidate) => candidate.length <= MAX_COMPARISON_TITLE_LENGTH) ?? + comparison.title + ) +} + +/** Meta description for a comparison page; long model names drop the trailing Sim sentence. */ +export function getComparisonPageDescription(comparison: ModelComparison): string { + const lead = `${comparison.title}: API pricing per 1M tokens, context window, max output, and capabilities side by side.` + const withSim = `${lead} Use either in Sim's AI agents.` + return withSim.length <= 155 ? withSim : lead +} + +/** The curated comparison at `/models/compare/{slug}`; `null` for a reversed or unknown pair. */ +export function getModelComparisonBySlug(slug: string): ModelComparison | null { + return MODEL_COMPARISONS.find((comparison) => comparison.slug === slug) ?? null +} + +/** Curated comparisons that include the given model, in curated order. */ +export function getModelComparisonsFor(model: CatalogModel): ModelComparison[] { + return MODEL_COMPARISONS.filter( + (comparison) => comparison.a.model.href === model.href || comparison.b.model.href === model.href + ) +} + +/** Latest pricing update across both models in a comparison. */ +export function getComparisonUpdatedAt(comparison: ModelComparison): Date { + return getLatestModelUpdate([comparison.a.model, comparison.b.model]) +} + +/** + * Dollar cost of the sample workload. Rates resolve per call, so a long-context tier applies only + * when one call's input crosses its threshold, never because of the workload's total volume. + */ +function sampleWorkloadCost(model: CatalogModel): number { + const rates = resolveModelTokenPricing(model.pricing, SAMPLE_INPUT_TOKENS_PER_CALL) + const costPerCall = + (rates.input * SAMPLE_INPUT_TOKENS_PER_CALL + rates.output * SAMPLE_OUTPUT_TOKENS_PER_CALL) / + 1_000_000 + return SAMPLE_CALLS * costPerCall +} + +/** "3×", "1.5×" - a multiple rounded for reading, never shown below the same-ratio band. */ +function formatRatio(ratio: number): string { + const rounded = ratio >= 10 ? Math.round(ratio) : Math.round(ratio * 10) / 10 + return `${rounded}×` +} + +/** Larger value over smaller, or null when either side is missing or zero. */ +function ratioOf(left: number | null | undefined, right: number | null | undefined) { + if (!left || !right) return null + return Math.max(left, right) / Math.min(left, right) +} + +function supportsReasoning(model: CatalogModel): boolean { + return Boolean(model.capabilities.reasoningEffort || model.capabilities.thinking) +} + +const CAPABILITY_CHECKS: ReadonlyArray<{ + label: string + supports: (model: CatalogModel) => boolean +}> = [ + { label: 'configurable reasoning', supports: supportsReasoning }, + { + label: 'tool choice control', + supports: (model) => Boolean(model.capabilities.toolUsageControl), + }, + { + label: 'native structured outputs', + supports: (model) => Boolean(model.capabilities.nativeStructuredOutputs), + }, + { label: 'computer use', supports: (model) => Boolean(model.capabilities.computerUse) }, + { label: 'deep research', supports: (model) => Boolean(model.capabilities.deepResearch) }, +] + +function buildCostSentence({ a, b }: ModelComparison): string { + const costA = sampleWorkloadCost(a.model) + const costB = sampleWorkloadCost(b.model) + const workload = `${SAMPLE_CALLS.toLocaleString('en-US')} agent calls with ${formatTokenCount(SAMPLE_INPUT_TOKENS_PER_CALL)} input and ${formatTokenCount(SAMPLE_OUTPUT_TOKENS_PER_CALL)} output tokens each` + const ratio = ratioOf(costA, costB) + + if (ratio === null || ratio < SAME_RATIO_THRESHOLD) { + return `${a.model.displayName} and ${b.model.displayName} cost about the same: ${workload} cost ${formatPrice(costA)} and ${formatPrice(costB)} at standard rates.` + } + + const [cheaper, pricier] = costA < costB ? [a, b] : [b, a] + return `${cheaper.model.displayName} is ${formatRatio(ratio)} cheaper than ${pricier.model.displayName}: ${workload} cost ${formatPrice(sampleWorkloadCost(cheaper.model))} versus ${formatPrice(sampleWorkloadCost(pricier.model))} at standard rates.` +} + +function buildRateSentence({ a, b }: ModelComparison): string { + return `${a.model.displayName} charges ${formatPrice(a.model.pricing.input)} input and ${formatPrice(a.model.pricing.output)} output per 1M tokens; ${b.model.displayName} charges ${formatPrice(b.model.pricing.input)} and ${formatPrice(b.model.pricing.output)}.` +} + +function buildContextSentence({ a, b }: ModelComparison): string { + const contextA = a.model.contextWindow + const contextB = b.model.contextWindow + + if (contextA && contextB) { + const ratio = ratioOf(contextA, contextB) ?? 1 + if (ratio < SAME_RATIO_THRESHOLD) { + return contextA === contextB + ? `Both models have a ${formatTokenCount(contextA)} token context window.` + : `Their context windows are about the same size: ${formatTokenCount(contextA)} versus ${formatTokenCount(contextB)} tokens.` + } + const [larger, smaller] = contextA > contextB ? [a, b] : [b, a] + return `${larger.model.displayName} has a ${formatRatio(ratio)} larger context window: ${formatTokenCount(larger.model.contextWindow)} versus ${formatTokenCount(smaller.model.contextWindow)} tokens.` + } + + const known = contextA ? a : contextB ? b : null + return known + ? `${known.model.displayName} has a ${formatTokenCount(known.model.contextWindow)} token context window; Sim does not track a published context window for the other model.` + : 'Sim does not track a published context window for either model.' +} + +function buildMaxOutputSentence({ a, b }: ModelComparison): string | null { + const outputA = getEffectiveMaxOutputTokens(a.model.capabilities) + const outputB = getEffectiveMaxOutputTokens(b.model.capabilities) + if (!outputA || !outputB) return null + if (outputA === outputB) { + return `Both can generate up to ${formatTokenCount(outputA)} output tokens per response.` + } + const [larger, largerOutput, smallerOutput] = + outputA > outputB ? [a, outputA, outputB] : [b, outputB, outputA] + return `${larger.model.displayName} can generate up to ${formatTokenCount(largerOutput)} output tokens per response, versus ${formatTokenCount(smallerOutput)}.` +} + +function buildCapabilitySentence({ a, b }: ModelComparison): string { + const onlyA = CAPABILITY_CHECKS.filter( + (check) => check.supports(a.model) && !check.supports(b.model) + ) + const onlyB = CAPABILITY_CHECKS.filter( + (check) => check.supports(b.model) && !check.supports(a.model) + ) + + if (onlyA.length === 0 && onlyB.length === 0) { + const shared = CAPABILITY_CHECKS.filter((check) => check.supports(a.model)) + return shared.length > 0 + ? `Both support ${joinLabels(shared.map((check) => check.label))} in Sim.` + : 'Neither model exposes configurable reasoning, tool choice control, or native structured outputs in Sim.' + } + + return [ + onlyA.length > 0 + ? `Only ${a.model.displayName} supports ${joinLabels(onlyA.map((check) => check.label))} in Sim.` + : null, + onlyB.length > 0 + ? `Only ${b.model.displayName} supports ${joinLabels(onlyB.map((check) => check.label))} in Sim.` + : null, + ] + .filter(Boolean) + .join(' ') +} + +function joinLabels(labels: string[]): string { + if (labels.length <= 2) return labels.join(' and ') + return `${labels.slice(0, -1).join(', ')}, and ${labels[labels.length - 1]}` +} + +/** + * Plain-language verdict computed from catalog data: relative cost on a sample agent workload, + * per-token rates, context window, max output, and capability differences. + */ +export function buildComparisonVerdict(comparison: ModelComparison): string[] { + return [ + buildCostSentence(comparison), + buildRateSentence(comparison), + buildContextSentence(comparison), + buildMaxOutputSentence(comparison), + buildCapabilitySentence(comparison), + ].filter((sentence): sentence is string => sentence !== null) +} + +/** FAQ entries for a comparison page, built from the same sentences as the verdict. */ +export function buildComparisonFaqs(comparison: ModelComparison): CatalogFaq[] { + const { a, b } = comparison + const [cheaper, pricier] = + sampleWorkloadCost(a.model) <= sampleWorkloadCost(b.model) ? [a, b] : [b, a] + + return [ + { + question: `Is ${a.model.displayName} cheaper than ${b.model.displayName}?`, + answer: `${buildCostSentence(comparison)} ${buildRateSentence(comparison)}`, + }, + { + question: `Which has a larger context window, ${a.model.displayName} or ${b.model.displayName}?`, + answer: [buildContextSentence(comparison), buildMaxOutputSentence(comparison)] + .filter(Boolean) + .join(' '), + }, + { + question: `What is the difference between ${a.model.displayName} and ${b.model.displayName}?`, + answer: buildComparisonVerdict(comparison).join(' '), + }, + { + question: `Can I use ${a.model.displayName} and ${b.model.displayName} in the same Sim agent?`, + answer: `Yes. Both models are available in the Sim Agent block, and each Agent block selects its own model. One agent can run ${cheaper.model.displayName} on high-volume steps and ${pricier.model.displayName} on the steps that need it, and switching a block's model does not change the rest of the agent.`, + }, + ] +} diff --git a/apps/sim/app/(landing)/models/components/comparison-links/comparison-links.tsx b/apps/sim/app/(landing)/models/components/comparison-links/comparison-links.tsx new file mode 100644 index 00000000000..7e9aa985059 --- /dev/null +++ b/apps/sim/app/(landing)/models/components/comparison-links/comparison-links.tsx @@ -0,0 +1,18 @@ +import { ChipLink } from '@sim/emcn' +import type { ModelComparison } from '@/app/(landing)/models/comparisons' + +interface ComparisonLinksProps { + comparisons: ModelComparison[] +} + +export function ComparisonLinks({ comparisons }: ComparisonLinksProps) { + return ( + <div className='flex flex-wrap gap-2'> + {comparisons.map((comparison) => ( + <ChipLink key={comparison.slug} href={comparison.href} variant='outline'> + {comparison.title} + </ChipLink> + ))} + </div> + ) +} diff --git a/apps/sim/app/(landing)/models/components/comparison-links/index.ts b/apps/sim/app/(landing)/models/components/comparison-links/index.ts new file mode 100644 index 00000000000..51100d40535 --- /dev/null +++ b/apps/sim/app/(landing)/models/components/comparison-links/index.ts @@ -0,0 +1 @@ +export { ComparisonLinks } from './comparison-links' diff --git a/apps/sim/app/(landing)/models/components/constants.ts b/apps/sim/app/(landing)/models/components/constants.ts index e7e210cd56a..2728870de94 100644 --- a/apps/sim/app/(landing)/models/components/constants.ts +++ b/apps/sim/app/(landing)/models/components/constants.ts @@ -51,3 +51,11 @@ const colorMap = new Map( export function getProviderColor(providerId: string): string { return colorMap.get(providerId) ?? FALLBACK_COLOR } + +/** Header cell of the catalog's data tables (context windows, model comparisons). */ +export const MODEL_TABLE_HEADER_CELL_CLASS = + 'px-6 py-3 text-left align-bottom font-normal text-[var(--text-muted)] text-xs uppercase tracking-[0.1em]' + +/** Body cell of the catalog's data tables. */ +export const MODEL_TABLE_CELL_CLASS = + 'px-6 py-4 align-top text-[var(--text-primary)] text-sm leading-snug' diff --git a/apps/sim/app/(landing)/models/components/model-multi-select/index.ts b/apps/sim/app/(landing)/models/components/model-multi-select/index.ts new file mode 100644 index 00000000000..145a60e9866 --- /dev/null +++ b/apps/sim/app/(landing)/models/components/model-multi-select/index.ts @@ -0,0 +1 @@ +export { ModelMultiSelect } from './model-multi-select' diff --git a/apps/sim/app/(landing)/models/components/model-multi-select/model-multi-select.tsx b/apps/sim/app/(landing)/models/components/model-multi-select/model-multi-select.tsx new file mode 100644 index 00000000000..892abb34335 --- /dev/null +++ b/apps/sim/app/(landing)/models/components/model-multi-select/model-multi-select.tsx @@ -0,0 +1,42 @@ +'use client' + +import { ChipCombobox, type ComboboxOptionGroup } from '@sim/emcn' +import { TOOL_MODELS } from '@/app/(landing)/models/tools' +import { MODEL_PROVIDERS_WITH_CATALOGS } from '@/app/(landing)/models/utils' + +const TOOL_MODEL_IDS = new Set(TOOL_MODELS.map((model) => model.id)) + +const MODEL_OPTION_GROUPS: ComboboxOptionGroup[] = MODEL_PROVIDERS_WITH_CATALOGS.map( + (provider) => ({ + section: provider.name, + items: provider.models + .filter((model) => TOOL_MODEL_IDS.has(model.id)) + .map((model) => ({ label: model.displayName, value: model.id, icon: provider.icon })), + }) +).filter((group) => group.items.length > 0) + +interface ModelMultiSelectProps { + /** Id of the visible label element naming the picker. */ + labelId: string + value: string[] + onChange: (modelIds: string[]) => void +} + +/** Searchable, provider-grouped picker over every current first-party catalog model. */ +export function ModelMultiSelect({ labelId, value, onChange }: ModelMultiSelectProps) { + return ( + <ChipCombobox + aria-labelledby={labelId} + options={[]} + groups={MODEL_OPTION_GROUPS} + multiSelect + multiSelectValues={value} + onMultiSelectChange={onChange} + searchable + searchPlaceholder='Search models…' + placeholder='Select models' + emptyMessage='No models match' + maxHeight={360} + /> + ) +} diff --git a/apps/sim/app/(landing)/models/components/model-tool-link/index.ts b/apps/sim/app/(landing)/models/components/model-tool-link/index.ts new file mode 100644 index 00000000000..b3a3f2073f5 --- /dev/null +++ b/apps/sim/app/(landing)/models/components/model-tool-link/index.ts @@ -0,0 +1 @@ +export { ModelToolLink } from './model-tool-link' diff --git a/apps/sim/app/(landing)/models/components/model-tool-link/model-tool-link.tsx b/apps/sim/app/(landing)/models/components/model-tool-link/model-tool-link.tsx new file mode 100644 index 00000000000..78532ee84d2 --- /dev/null +++ b/apps/sim/app/(landing)/models/components/model-tool-link/model-tool-link.tsx @@ -0,0 +1,20 @@ +import Link from 'next/link' + +interface ModelToolLinkProps { + href: string + title: string + description: string +} + +/** A bordered link card for a row of related pages; rows stack at `max-sm`. */ +export function ModelToolLink({ href, title, description }: ModelToolLinkProps) { + return ( + <Link + href={href} + className='flex flex-1 flex-col gap-1 border-[var(--border)] border-l p-6 transition-colors first:border-l-0 hover:bg-[var(--surface-hover)] max-sm:border-t max-sm:border-l-0 max-sm:first:border-t-0' + > + <span className='text-[var(--text-primary)] text-base'>{title}</span> + <span className='text-[var(--text-muted)] text-sm leading-[150%]'>{description}</span> + </Link> + ) +} diff --git a/apps/sim/app/(landing)/models/context-windows.ts b/apps/sim/app/(landing)/models/context-windows.ts new file mode 100644 index 00000000000..528c9b09fae --- /dev/null +++ b/apps/sim/app/(landing)/models/context-windows.ts @@ -0,0 +1,110 @@ +import { compareStrings } from '@sim/utils/string' +import { + ALL_CATALOG_MODELS, + type CatalogFaq, + type CatalogModel, + type CatalogProvider, + compareByContextWindow, + formatTokenCount, + isPromptModel, + MODEL_PROVIDERS_WITH_CATALOGS, +} from '@/app/(landing)/models/utils' + +/** + * Callable first-party prompt models with a published context window, ordered by + * {@link compareByContextWindow}. Retired models are left out: their API calls fail. + */ +export const CONTEXT_WINDOW_MODELS: CatalogModel[] = ALL_CATALOG_MODELS.filter( + (model) => + isPromptModel(model) && + !model.retired && + model.contextWindow !== null && + model.contextWindow > 0 +).sort(compareByContextWindow) + +/** Distinct providers across {@link CONTEXT_WINDOW_MODELS}. */ +export const CONTEXT_WINDOW_PROVIDER_COUNT = new Set( + CONTEXT_WINDOW_MODELS.map((model) => model.providerId) +).size + +/** + * Providers whose context windows get their own FAQ entry, in the order searchers ask about them. + * `label` overrides the model family in the question when the family name would undercount the + * lineup (OpenAI lists o-series models alongside GPT). + */ +const CONTEXT_WINDOW_FAQ_PROVIDERS: ReadonlyArray<{ id: string; label?: string }> = [ + { id: 'anthropic' }, + { id: 'openai', label: 'OpenAI' }, + { id: 'google' }, + { id: 'xai' }, + { id: 'deepseek' }, +] + +/** + * Providers with context-window data, each paired with its largest-context model: the provider's + * first entry in the already-ranked {@link CONTEXT_WINDOW_MODELS}. + */ +export function getLargestContextByProvider(): Array<{ + provider: CatalogProvider + model: CatalogModel +}> { + return MODEL_PROVIDERS_WITH_CATALOGS.flatMap((provider) => { + const model = CONTEXT_WINDOW_MODELS.find((entry) => entry.providerId === provider.id) + return model ? [{ provider, model }] : [] + }) +} + +/** FAQ entries for the context-window page: the overall largest window, then per-provider ranges. */ +export function buildContextWindowFaqs(): CatalogFaq[] { + const largest = CONTEXT_WINDOW_MODELS[0] + const faqs: CatalogFaq[] = [] + + if (largest?.contextWindow) { + const tiedCount = CONTEXT_WINDOW_MODELS.filter( + (model) => model.contextWindow === largest.contextWindow + ).length + faqs.push({ + question: 'Which LLM has the largest context window?', + answer: `The largest context window tracked in Sim is ${formatTokenCount(largest.contextWindow)} tokens, on ${largest.displayName} by ${largest.providerName}${tiedCount > 1 ? ` and ${tiedCount - 1} other model${tiedCount === 2 ? '' : 's'}` : ''}. Sim tracks context windows for ${CONTEXT_WINDOW_MODELS.length} models across ${CONTEXT_WINDOW_PROVIDER_COUNT} providers.`, + }) + } + + for (const { id, label } of CONTEXT_WINDOW_FAQ_PROVIDERS) { + const provider = MODEL_PROVIDERS_WITH_CATALOGS.find((entry) => entry.id === id) + const models = CONTEXT_WINDOW_MODELS.filter((model) => model.providerId === id) + if (!provider || models.length === 0) continue + + const name = label ?? provider.modelFamily + const top = models[0] + const smallest = models[models.length - 1] + const recent = models + .filter((model) => !model.deprecated) + .sort((a, b) => compareStrings(b.releaseDate ?? '', a.releaseDate ?? '')) + .slice(0, 3) + const range = + top.contextWindow === smallest.contextWindow + ? `Every ${name} model tracked in Sim has a ${formatTokenCount(top.contextWindow)} token context window.` + : `${name} context windows tracked in Sim range from ${formatTokenCount(smallest.contextWindow)} to ${formatTokenCount(top.contextWindow)} tokens.` + + faqs.push({ + question: `What is the ${name} context window?`, + answer: `${range}${ + recent.length > 0 + ? ` Recent models: ${recent + .map( + (model) => `${model.displayName} (${formatTokenCount(model.contextWindow)} tokens)` + ) + .join(', ')}.` + : '' + }`, + }) + } + + faqs.push({ + question: 'What is the difference between context window and max output tokens?', + answer: + 'The context window is the total number of tokens a model can work with in one call: the system prompt, conversation history, tool results, and documents you pass in. Max output tokens caps how much the model can generate in a single response. In a Sim agent, a larger context window holds more history and retrieved documents, while a larger max output lets one response carry a long report or a large block of code.', + }) + + return faqs +} diff --git a/apps/sim/app/(landing)/models/tools.ts b/apps/sim/app/(landing)/models/tools.ts new file mode 100644 index 00000000000..3c8609ce96c --- /dev/null +++ b/apps/sim/app/(landing)/models/tools.ts @@ -0,0 +1,77 @@ +import { + ALL_CATALOG_MODELS, + type CatalogModel, + isPromptModel, + MODEL_PROVIDERS_WITH_CATALOGS, +} from '@/app/(landing)/models/utils' + +/** A free tool page under `/models`, linked from the directory, sitemap, and llms.txt. */ +interface ModelTool { + name: string + href: string + description: string +} + +/** The free tools under `/models`, keyed so pages can link a specific tool; iterate with `Object.values`. */ +export const MODEL_TOOLS = { + pricingCalculator: { + name: 'LLM API pricing calculator', + href: '/models/pricing-calculator', + description: + 'Estimate cost per request and per month across OpenAI, Claude, Gemini, and other models', + }, + tokenCounter: { + name: 'Token counter', + href: '/models/token-counter', + description: 'Count tokens and estimate input cost for pasted text, in the browser', + }, +} as const satisfies Record<string, ModelTool> + +/** Every current first-party prompt model; deprecated and evaluation models drop out of the pickers. */ +export const TOOL_MODELS: CatalogModel[] = ALL_CATALOG_MODELS.filter( + (model) => isPromptModel(model) && !model.deprecated +) + +const TOOL_MODELS_BY_ID = new Map(TOOL_MODELS.map((model) => [model.id, model])) + +/** Selected model ids resolved to catalog models, deduplicated, unknown ids dropped. */ +export function resolveToolModels(ids: readonly string[]): CatalogModel[] { + return [...new Set(ids)] + .map((id) => TOOL_MODELS_BY_ID.get(id)) + .filter((model): model is CatalogModel => model !== undefined) +} + +const DEFAULT_TOOL_PROVIDER_IDS = ['openai', 'anthropic', 'google'] + +/** The lead model of each top provider, matching the featured cards on `/models`. */ +export const DEFAULT_TOOL_MODEL_IDS: string[] = DEFAULT_TOOL_PROVIDER_IDS.flatMap((providerId) => { + const provider = MODEL_PROVIDERS_WITH_CATALOGS.find((entry) => entry.id === providerId) + const lead = provider?.featuredModels.find((model) => !model.deprecated) + return lead ? [lead.id] : [] +}) + +/** Latest pricing `updatedAt` across the tool models, as an ISO date. */ +export const TOOL_PRICING_UPDATED_AT: string = TOOL_MODELS.reduce( + (latest, model) => (model.pricing.updatedAt > latest ? model.pricing.updatedAt : latest), + '' +) + +const USD_CENTS_FORMAT = new Intl.NumberFormat('en-US', { + style: 'currency', + currency: 'USD', + minimumFractionDigits: 2, + maximumFractionDigits: 2, +}) + +const USD_SUB_DOLLAR_FORMAT = new Intl.NumberFormat('en-US', { + style: 'currency', + currency: 'USD', + minimumSignificantDigits: 2, + maximumSignificantDigits: 3, +}) + +/** Formats a USD amount: cents from a dollar up, three significant digits below a dollar. */ +export function formatUsd(value: number): string { + if (value === 0) return '$0' + return value >= 1 ? USD_CENTS_FORMAT.format(value) : USD_SUB_DOLLAR_FORMAT.format(value) +} diff --git a/apps/sim/app/(landing)/models/utils.ts b/apps/sim/app/(landing)/models/utils.ts index 3761baaa8ed..84de98feec7 100644 --- a/apps/sim/app/(landing)/models/utils.ts +++ b/apps/sim/app/(landing)/models/utils.ts @@ -1,7 +1,7 @@ import type { ComponentType } from 'react' -import { slugify } from '@sim/utils/string' +import { compareStrings, slugify } from '@sim/utils/string' import { type ModelCapabilities, PROVIDER_DEFINITIONS } from '@/providers/models' -import type { ModelPricing } from '@/providers/types' +import type { ModelPricing, ModelPricingTier } from '@/providers/types' const PROVIDER_PREFIXES: Record<string, string[]> = { 'azure-openai': ['azure/'], @@ -19,6 +19,21 @@ const PROVIDER_PREFIXES: Record<string, string[]> = { vllm: ['vllm/'], } +/** + * How searchers name a provider's API when the model family differs from the company name + * ("Claude", not "Anthropic"). `family` names the models in copy ("the Claude context window"); + * `apiName` leads the provider page title and defaults to `{provider name} ({family})` + * ("Anthropic (Claude) API Pricing & Models"). Providers left out use their own name for both. + */ +const PROVIDER_MODEL_FAMILIES: Record<string, { family: string; apiName?: string }> = { + anthropic: { family: 'Claude' }, + 'azure-anthropic': { family: 'Claude' }, + google: { family: 'Gemini', apiName: 'Google Gemini' }, + openai: { family: 'GPT', apiName: 'OpenAI' }, + xai: { family: 'Grok' }, + zai: { family: 'GLM' }, +} + const TOKEN_REPLACEMENTS: Record<string, string> = { ai: 'AI', aws: 'AWS', @@ -95,6 +110,7 @@ const UPDATED_AT_DATE_FORMAT = new Intl.DateTimeFormat('en-US', { month: 'short', day: 'numeric', year: 'numeric', + timeZone: 'UTC', }) export type PricingInfo = ModelPricing @@ -120,7 +136,10 @@ export interface CatalogModel { providerSlug: string contextWindow: number | null releaseDate: string | null + /** Superseded or retired: the model carries any `sunset` status. */ deprecated: boolean + /** Retired by the provider (`sunset.status === 'deprecated'`): API calls to it now fail. */ + retired: boolean featured: boolean recommended: boolean pricing: PricingInfo @@ -142,6 +161,10 @@ export interface CatalogProvider { defaultModelDisplayName: string icon?: ComponentType<{ className?: string }> color?: string + /** Model family name searchers use ("Claude", "Gemini"); the provider name when it has none. */ + modelFamily: string + /** Name searchers pair with "API pricing" ("Anthropic (Claude)"); the provider name by default. */ + apiName: string isReseller: boolean contextInformationAvailable: boolean /** Max agent-block file attachment size in bytes when the provider exceeds the default. */ @@ -220,6 +243,25 @@ export function getEffectiveMaxOutputTokens(capabilities: ModelCapabilities): nu return capabilities.maxOutputTokens ?? null } +/** True when a model lists a non-zero input or output token price. */ +export function hasPublishedPricing(pricing: PricingInfo): boolean { + return pricing.input > 0 || pricing.output > 0 +} + +/** + * True for a model that takes a prompt. Evaluation models (`capabilities.evaluation`) take native + * state and questions instead, so prompt token, cost, and context-window comparisons leave them out, + * as the non-Agent model pickers do. + */ +export function isPromptModel(model: CatalogModel): boolean { + return !model.capabilities.evaluation +} + +/** One long-context surcharge tier as a sentence fragment, without trailing punctuation. */ +export function formatPricingTier(tier: ModelPricingTier): string { + return `Above ${formatTokenCount(tier.aboveInputTokens)} input tokens, the full request is priced at ${formatPrice(tier.input)}/1M input tokens${tier.cachedInput !== undefined ? `, ${formatPrice(tier.cachedInput)}/1M cached input tokens` : ''}, and ${formatPrice(tier.output)}/1M output tokens` +} + function trimTrailingZeros(value: string): string { return value.replace(/\.0+$/, '').replace(/(\.\d*?)0+$/, '$1') } @@ -482,6 +524,7 @@ const rawProviders = Object.values(PROVIDER_DEFINITIONS).map((provider) => { contextWindow: model.contextWindow ?? null, releaseDate: model.releaseDate ?? null, deprecated: !!model.sunset, + retired: model.sunset?.status === 'deprecated', featured: model.featured ?? false, recommended: model.recommended ?? false, pricing: model.pricing, @@ -516,6 +559,8 @@ const rawProviders = Object.values(PROVIDER_DEFINITIONS).map((provider) => { (provider.defaultModel ? formatModelDisplayName(provider.id, provider.defaultModel) : 'Dynamic') const featuredModels = [...models].sort(compareModelsByRelevance).slice(0, 6) + const naming = PROVIDER_MODEL_FAMILIES[provider.id] + const modelFamily = naming?.family ?? providerDisplayName return { id: provider.id, @@ -528,6 +573,10 @@ const rawProviders = Object.values(PROVIDER_DEFINITIONS).map((provider) => { defaultModelDisplayName, icon: provider.icon, color: provider.color, + modelFamily, + apiName: naming + ? (naming.apiName ?? `${providerDisplayName} (${modelFamily})`) + : providerDisplayName, isReseller: provider.isReseller ?? false, contextInformationAvailable: provider.contextInformationAvailable !== false, maxFileAttachmentBytes: provider.fileAttachment?.maxBytes ?? null, @@ -730,10 +779,7 @@ export function buildModelFaqs(provider: CatalogProvider, model: CatalogModel): { question: `How much does ${model.displayName} cost?`, answer: `${model.displayName} starts at ${formatPrice(model.pricing.input)}/1M input tokens${model.pricing.cachedInput !== undefined ? `, ${formatPrice(model.pricing.cachedInput)}/1M cached input tokens` : ''}, and ${formatPrice(model.pricing.output)}/1M output tokens.${pricingTiers - .map( - (tier) => - ` Above ${formatTokenCount(tier.aboveInputTokens)} input tokens, the full request is priced at ${formatPrice(tier.input)}/1M input tokens${tier.cachedInput !== undefined ? `, ${formatPrice(tier.cachedInput)}/1M cached input tokens` : ''}, and ${formatPrice(tier.output)}/1M output tokens.` - ) + .map((tier) => ` ${formatPricingTier(tier)}.`) .join('')}`, }, { @@ -828,9 +874,55 @@ export function getCheapestProviderModel(provider: CatalogProvider): CatalogMode return provider.models.reduce((min, m) => (m.pricing.input < min.pricing.input ? m : min)) } -export function getLargestContextProviderModel(provider: CatalogProvider): CatalogModel | null { - if (provider.models.length === 0) return null - return provider.models.reduce((max, m) => - (m.contextWindow ?? 0) > (max.contextWindow ?? 0) ? m : max +/** + * Largest context window first. Ties put current models before superseded or retired ones, then + * newer releases first, then the display name, so the order is stable between builds. + */ +export function compareByContextWindow(a: CatalogModel, b: CatalogModel): number { + return ( + (b.contextWindow ?? 0) - (a.contextWindow ?? 0) || + Number(a.deprecated) - Number(b.deprecated) || + compareStrings(b.releaseDate ?? '', a.releaseDate ?? '') || + compareStrings(a.displayName, b.displayName) ) } + +/** + * The provider's callable (not retired) model with the largest published context window, ranked + * by {@link compareByContextWindow} so provider FAQs agree with the context-window comparison. + */ +function getLargestContextProviderModel(provider: CatalogProvider): CatalogModel | null { + const candidates = provider.models.filter((model) => !model.retired && model.contextWindow) + if (candidates.length === 0) return null + return candidates.reduce((best, model) => + compareByContextWindow(model, best) < 0 ? model : best + ) +} + +function providerHasPublishedPricing(provider: CatalogProvider): boolean { + return provider.models.some((model) => hasPublishedPricing(model.pricing)) +} + +/** + * Provider page title. A provider with published token prices is titled for the API-pricing + * searches people run ("Anthropic (Claude) API Pricing & Models"); one without keeps a plain + * models title, so the page never promises pricing it does not list. + */ +export function getProviderPageTitle(provider: CatalogProvider): string { + return providerHasPublishedPricing(provider) + ? `${provider.apiName} API Pricing & Models` + : `${provider.name} Models` +} + +/** Provider meta description: per-1M-token pricing and context windows when prices are listed. */ +export function getProviderPageDescription(provider: CatalogProvider): string { + const models = `${provider.modelCount} model${provider.modelCount === 1 ? '' : 's'}` + return providerHasPublishedPricing(provider) + ? `${provider.apiName} API pricing per 1M input and output tokens, context windows, and capabilities for ${models}. Use any of them in Sim's AI agents.` + : `Context windows and capabilities for ${models}. Use any of them in Sim's AI agents.` +} + +/** Most recent `pricing.updatedAt` across a non-empty list of models. */ +export function getLatestModelUpdate(models: CatalogModel[]): Date { + return new Date(Math.max(...models.map((model) => new Date(model.pricing.updatedAt).getTime()))) +} diff --git a/apps/sim/app/llms.txt/route.ts b/apps/sim/app/llms.txt/route.ts index 9a7e0f4f5ae..bbd7770ccb6 100644 --- a/apps/sim/app/llms.txt/route.ts +++ b/apps/sim/app/llms.txt/route.ts @@ -10,6 +10,8 @@ import { DOCS_URL, SLACK_COMMUNITY_URL } from '@/lib/help-links' import { getAllPostMeta as getAllLibraryPostMeta } from '@/lib/library/registry' import { ALL_COMPETITORS } from '@/app/(landing)/comparisons/utils' import { PLATFORM_MENU } from '@/app/(landing)/components/navbar/components/nav-menu-chip' +import { MODEL_COMPARISONS } from '@/app/(landing)/models/comparisons' +import { MODEL_TOOLS } from '@/app/(landing)/models/tools' import { MODEL_PROVIDERS_WITH_CATALOGS } from '@/app/(landing)/models/utils' import { DOCS_LINE, @@ -91,10 +93,28 @@ export async function GET() { '/models', 'Every supported model with pricing, context window, and capabilities' ), + ...Object.values(MODEL_TOOLS).map((tool) => + linkLine(tool.name, tool.href, tool.description) + ), + linkLine( + 'LLM context window comparison', + '/models/context-windows', + 'Context windows, max output tokens, and API prices for every tracked model' + ), ...MODEL_PROVIDERS_WITH_CATALOGS.map((provider) => linkLine(`${provider.name} models`, provider.href, provider.description) ), ]), + section( + 'Model comparisons', + MODEL_COMPARISONS.map((comparison) => + linkLine( + comparison.title, + comparison.href, + 'API pricing, context window, max output, and capabilities side by side' + ) + ) + ), section('Docs', [ linkLine('Docs index', `${DOCS_URL}/llms.txt`, 'llms.txt index of the Sim documentation'), linkLine( diff --git a/apps/sim/app/sitemap.ts b/apps/sim/app/sitemap.ts index 4f9a7a04a58..5c935920339 100644 --- a/apps/sim/app/sitemap.ts +++ b/apps/sim/app/sitemap.ts @@ -12,8 +12,12 @@ import { getLatestVerifiedDate, SIM_LATEST_VERIFIED, } from '@/app/(landing)/comparisons/utils' +import { getComparisonUpdatedAt, MODEL_COMPARISONS } from '@/app/(landing)/models/comparisons' +import { CONTEXT_WINDOW_MODELS } from '@/app/(landing)/models/context-windows' +import { MODEL_TOOLS } from '@/app/(landing)/models/tools' import { - MODEL_PROVIDERS_WITH_CATALOGS, + ALL_CATALOG_MODELS, + getLatestModelUpdate, MODEL_PROVIDERS_WITH_MODELS, } from '@/app/(landing)/models/utils' @@ -38,7 +42,7 @@ function buildAuthorPages(posts: ContentMeta[], basePath: string): MetadataRoute /** * Generate the public sitemap by composing static landing pages with the * dynamic catalogs (blog posts, library posts, published customer stories, - * authors, integrations, model providers, and individual models). + * authors, integrations, model providers, individual models, and curated model comparisons). * Per-integration entries are emitted * under `/integrations/{slug}` to match the landing route at * `app/(landing)/integrations/[slug]`; slugs are guaranteed unique @@ -56,10 +60,7 @@ export default async function sitemap(): Promise<MetadataRoute.Sitemap> { const latestPostDateValue = latestModified(posts) const latestLibraryPostDate = latestModified(libraryPosts) - const modelTimes = MODEL_PROVIDERS_WITH_CATALOGS.flatMap((provider) => - provider.models.map((model) => new Date(model.pricing.updatedAt).getTime()) - ) - const latestModelDate = modelTimes.length > 0 ? new Date(Math.max(...modelTimes)) : undefined + const latestModelDate = getLatestModelUpdate(ALL_CATALOG_MODELS) const integrationsUpdatedAt = new Date(`${INTEGRATIONS_UPDATED_AT}T00:00:00Z`) @@ -150,6 +151,14 @@ export default async function sitemap(): Promise<MetadataRoute.Sitemap> { url: `${baseUrl}/models`, lastModified: latestModelDate, }, + ...Object.values(MODEL_TOOLS).map((tool) => ({ + url: `${baseUrl}${tool.href}`, + lastModified: latestModelDate, + })), + { + url: `${baseUrl}/models/context-windows`, + lastModified: getLatestModelUpdate(CONTEXT_WINDOW_MODELS), + }, { url: `${baseUrl}/terms`, lastModified: new Date('2024-10-14'), @@ -202,9 +211,7 @@ export default async function sitemap(): Promise<MetadataRoute.Sitemap> { return [ { url: `${baseUrl}${provider.href}`, - lastModified: new Date( - Math.max(...provider.models.map((model) => new Date(model.pricing.updatedAt).getTime())) - ), + lastModified: getLatestModelUpdate(provider.models), }, ] }) @@ -216,6 +223,11 @@ export default async function sitemap(): Promise<MetadataRoute.Sitemap> { lastModified: new Date(model.pricing.updatedAt), })) + const modelComparisonPages: MetadataRoute.Sitemap = MODEL_COMPARISONS.map((comparison) => ({ + url: `${baseUrl}${comparison.href}`, + lastModified: getComparisonUpdatedAt(comparison), + })) + // Matches the max(Sim, competitor) verified-date logic each detail page's own // JSON-LD `dateModified` uses, so the sitemap timestamp never lags behind it. const competitorLastModified = (competitor: (typeof ALL_COMPETITORS)[number]) => @@ -248,6 +260,7 @@ export default async function sitemap(): Promise<MetadataRoute.Sitemap> { ...integrationPages, ...providerPages, ...modelEntries, + ...modelComparisonPages, ...comparisonPages, ] diff --git a/apps/sim/content/blog/agent-as-yjs-peer/index.mdx b/apps/sim/content/blog/agent-as-yjs-peer/index.mdx index de578c988a8..1d60fd81daf 100644 --- a/apps/sim/content/blog/agent-as-yjs-peer/index.mdx +++ b/apps/sim/content/blog/agent-as-yjs-peer/index.mdx @@ -2,6 +2,7 @@ slug: agent-as-yjs-peer title: 'The Agent Is Just Another Peer' description: How we let an AI agent write into a live collaborative document by making it an ordinary Yjs peer, and the things that turned out to be easy, hard, and surprising. +seoDescription: 'How we let an AI agent edit a live collaborative document as an ordinary Yjs peer, and what turned out easy, hard, and surprising along the way.' date: 2026-08-03 updated: 2026-08-03 authors: diff --git a/apps/sim/content/blog/enterprise/index.mdx b/apps/sim/content/blog/enterprise/index.mdx index da34c418e26..220a957bb33 100644 --- a/apps/sim/content/blog/enterprise/index.mdx +++ b/apps/sim/content/blog/enterprise/index.mdx @@ -2,6 +2,7 @@ slug: enterprise title: 'Sim for Enterprise' description: 'Access control, BYOK, self-hosted deployments, on-prem Copilot, SSO & SAML, whitelabeling, Admin API, and flexible data retention—enterprise features for teams with strict security and compliance requirements.' +seoDescription: 'Sim for Enterprise: access control, BYOK, self-hosted deployments, SSO and SAML, whitelabeling, an Admin API, and flexible data retention.' date: 2026-02-11 updated: 2026-09-19 authors: diff --git a/apps/sim/content/blog/executor/index.mdx b/apps/sim/content/blog/executor/index.mdx index 8047a8d644a..fe3e318deb2 100644 --- a/apps/sim/content/blog/executor/index.mdx +++ b/apps/sim/content/blog/executor/index.mdx @@ -2,6 +2,7 @@ slug: executor title: 'Inside the Sim Executor - DAG Based Execution with Native Parallelism' description: 'How we built a DAG-based execution engine with native parallel processing, intelligent edge routing, and stateful pause/resume capabilities' +seoTitle: 'Inside the Sim Executor: DAG-Based Parallel Execution' date: 2025-11-10 updated: 2025-11-10 authors: diff --git a/apps/sim/content/blog/mothership/index.mdx b/apps/sim/content/blog/mothership/index.mdx index e53607fa8e0..807ade9d14d 100644 --- a/apps/sim/content/blog/mothership/index.mdx +++ b/apps/sim/content/blog/mothership/index.mdx @@ -2,6 +2,7 @@ slug: mothership title: 'Introducing Mothership' description: 'Sim v0.6 introduces Mothership—a central intelligence layer for orchestrating your AI agents—alongside Tables, Files, Knowledge Base Connectors, and Scheduled Tasks.' +seoDescription: 'Sim v0.6 adds a central intelligence layer that orchestrates your AI agents, plus Tables, Files, Knowledge Base connectors, and scheduled tasks.' date: 2026-03-17 updated: 2026-03-17 authors: diff --git a/apps/sim/content/blog/secret-provenance/index.mdx b/apps/sim/content/blog/secret-provenance/index.mdx index 7bc5a49f0f3..269dbcd90c7 100644 --- a/apps/sim/content/blog/secret-provenance/index.mdx +++ b/apps/sim/content/blog/secret-provenance/index.mdx @@ -2,6 +2,7 @@ slug: secret-provenance title: 'Tracking Secrets Through an Agent Run' description: 'How Sim tracks the secrets an agent run actually uses, carries that provenance across tools and storage, redacts values at every egress boundary, and records which run touched which credential.' +seoDescription: 'How Sim tracks which secrets an agent run uses, carries that provenance across tools and storage, redacts values on the way out, and logs each use.' date: 2026-08-08 updated: 2026-08-18 authors: diff --git a/apps/sim/content/blog/v0-5/index.mdx b/apps/sim/content/blog/v0-5/index.mdx index a13d9fa8f69..f776cf8c344 100644 --- a/apps/sim/content/blog/v0-5/index.mdx +++ b/apps/sim/content/blog/v0-5/index.mdx @@ -2,6 +2,7 @@ slug: v0-5 title: 'Introducing Sim v0.5' description: 'This new release brings a state of the art Copilot, seamless MCP server and tool deployment, 100+ integrations with 300+ tools, comprehensive execution logs, and realtime collaboration—built for teams shipping AI agents in production.' +seoDescription: 'Sim v0.5 adds an in-editor AI agent, MCP server deployment, 100+ integrations with 300+ tools, execution logs, and realtime collaboration for teams.' date: 2026-01-22 updated: 2026-01-22 authors: diff --git a/apps/sim/content/changelog/fivetran-gemini-and-confluence/index.mdx b/apps/sim/content/changelog/fivetran-gemini-and-confluence/index.mdx index ec7eaa5fbfa..affb656cb1a 100644 --- a/apps/sim/content/changelog/fivetran-gemini-and-confluence/index.mdx +++ b/apps/sim/content/changelog/fivetran-gemini-and-confluence/index.mdx @@ -2,6 +2,7 @@ slug: fivetran-gemini-and-confluence title: 'Fivetran workflows, Gemini 4 Argon, and Confluence images' description: 'Manage Fivetran syncs from Sim workflows, use Gemini 4 Argon in your agents, and search the text inside Confluence image attachments.' +seoTitle: 'Fivetran syncs, Gemini 4 Argon, and Confluence images' date: '2026-10-13T23:00:00Z' authors: [waleed] tags: [Integrations, Models, Knowledge] diff --git a/apps/sim/content/library/6-best-ai-observability-tools-for-production-agents-in-2026/index.mdx b/apps/sim/content/library/6-best-ai-observability-tools-for-production-agents-in-2026/index.mdx index e7886133b3e..3a5e8f6a4f2 100644 --- a/apps/sim/content/library/6-best-ai-observability-tools-for-production-agents-in-2026/index.mdx +++ b/apps/sim/content/library/6-best-ai-observability-tools-for-production-agents-in-2026/index.mdx @@ -2,6 +2,8 @@ slug: 6-best-ai-observability-tools-for-production-agents-in-2026 title: '6 Best AI Observability Tools for Production Agents in 2026' description: 'Compare the six best AI observability tools for production agents in 2026 — Braintrust, Galileo, Langfuse, Arize AX, Datadog, and PostHog — across tracing, evaluations, CI/CD checks, and developer access.' +seoTitle: '6 Best AI Observability Tools for LLM Agents (2026)' +seoDescription: 'Langfuse, Braintrust, Galileo, Arize, Datadog and PostHog compared for tracing, evals, cost tracking and self-hosting, with a pick for each use case.' date: 2026-09-04 updated: 2026-10-06 authors: diff --git a/apps/sim/content/library/aeo-vs-geo-what-answer-engine-and-generative-engine-optimization-actually-mean/index.mdx b/apps/sim/content/library/aeo-vs-geo-what-answer-engine-and-generative-engine-optimization-actually-mean/index.mdx index f00dd73119e..8021d71cd90 100644 --- a/apps/sim/content/library/aeo-vs-geo-what-answer-engine-and-generative-engine-optimization-actually-mean/index.mdx +++ b/apps/sim/content/library/aeo-vs-geo-what-answer-engine-and-generative-engine-optimization-actually-mean/index.mdx @@ -2,6 +2,8 @@ slug: aeo-vs-geo-what-answer-engine-and-generative-engine-optimization-actually-mean title: 'AEO vs GEO: What Answer Engine and Generative Engine Optimization Actually Mean' description: 'Understand AEO vs GEO, how answer engine and generative engine optimization differ from traditional SEO, and the content practices that make answers easier to cite.' +seoTitle: 'AEO vs GEO: Answer and Generative Engine Optimization' +seoDescription: 'AEO vs GEO explained: how answer engine and generative engine optimization differ from SEO, and the content practices that make pages easier to cite.' date: 2026-08-03 updated: 2026-10-06 authors: diff --git a/apps/sim/content/library/ai-agent-examples-by-department-and-industry/index.mdx b/apps/sim/content/library/ai-agent-examples-by-department-and-industry/index.mdx index 26cbd4e737f..35288820169 100644 --- a/apps/sim/content/library/ai-agent-examples-by-department-and-industry/index.mdx +++ b/apps/sim/content/library/ai-agent-examples-by-department-and-industry/index.mdx @@ -2,6 +2,7 @@ slug: ai-agent-examples-by-department-and-industry title: 'AI Agent Examples by Department and Industry: A Practical Roundup' description: 'A practical roundup of AI agent examples across sales, support, operations, engineering, marketing, HR, and five industries, with build patterns for Sim.' +seoTitle: 'AI Agent Examples by Department and Industry' date: 2026-09-17 updated: 2026-10-06 authors: diff --git a/apps/sim/content/library/ai-agent-ideas/index.mdx b/apps/sim/content/library/ai-agent-ideas/index.mdx index 7ce2bd6d6a0..b9df4184459 100644 --- a/apps/sim/content/library/ai-agent-ideas/index.mdx +++ b/apps/sim/content/library/ai-agent-ideas/index.mdx @@ -2,6 +2,8 @@ slug: ai-agent-ideas title: '10 AI Agent Ideas for Real Impact: Get Started With Sim' description: 'Explore practical AI agent ideas you can build today to automate real workflows. From email triage to lead enrichment, discover use cases that deliver fast, measurable impact.' +seoTitle: '10 AI Agent Ideas You Can Build Today' +seoDescription: '10 practical AI agent ideas, from email triage and lead enrichment to resume screening and report generation, with how to pick one and start small.' date: 2026-06-30 updated: 2026-09-03 authors: diff --git a/apps/sim/content/library/ai-agent-marketplace-vs-building-from-scratch/index.mdx b/apps/sim/content/library/ai-agent-marketplace-vs-building-from-scratch/index.mdx index 684181fb56d..9809b82997e 100644 --- a/apps/sim/content/library/ai-agent-marketplace-vs-building-from-scratch/index.mdx +++ b/apps/sim/content/library/ai-agent-marketplace-vs-building-from-scratch/index.mdx @@ -2,6 +2,7 @@ slug: ai-agent-marketplace-vs-building-from-scratch title: 'AI Agent Marketplace vs Building From Scratch: Where Sim Fits' description: 'Compare AI agent marketplaces, reusable templates, flexible builders, and custom development to decide where Sim fits your workflow and governance needs.' +seoTitle: 'AI Agent Marketplace vs Building From Scratch' date: 2026-09-17 updated: 2026-10-01 authors: diff --git a/apps/sim/content/library/ai-agent-observability/index.mdx b/apps/sim/content/library/ai-agent-observability/index.mdx index 651a2309d84..756eb694947 100644 --- a/apps/sim/content/library/ai-agent-observability/index.mdx +++ b/apps/sim/content/library/ai-agent-observability/index.mdx @@ -2,6 +2,8 @@ slug: ai-agent-observability title: 'What Is AI Agent Observability? Traces, Metrics, and Evals Explained' description: 'What AI agent observability is, why traditional monitoring misses agent failures, and what to instrument at each stage, from traces and logs to metrics and evaluations.' +seoTitle: 'AI Agent Observability: Traces, Metrics, and Evals' +seoDescription: 'What AI agent observability is, why traditional monitoring misses agent failures, and how traces, logs, metrics, and evals help you catch them.' date: 2026-07-19 updated: 2026-10-10 authors: diff --git a/apps/sim/content/library/ai-agent-version-control-rollback-platforms/index.mdx b/apps/sim/content/library/ai-agent-version-control-rollback-platforms/index.mdx index 11baef4ef4c..d17c97f34b2 100644 --- a/apps/sim/content/library/ai-agent-version-control-rollback-platforms/index.mdx +++ b/apps/sim/content/library/ai-agent-version-control-rollback-platforms/index.mdx @@ -2,6 +2,7 @@ slug: ai-agent-version-control-rollback-platforms title: 'AI Agent Version Control and Rollback: Platforms, Process, and Enterprise Checklist' description: 'Compare AI agent version control and rollback in Sim, Amazon Bedrock Agents, and n8n, then use a practical enterprise release and recovery checklist.' +seoTitle: 'AI Agent Version Control and Rollback for Enterprises' date: 2026-10-12 updated: 2026-10-12 authors: diff --git a/apps/sim/content/library/ai-agent-vs-chatbot/index.mdx b/apps/sim/content/library/ai-agent-vs-chatbot/index.mdx index 2ad7b36c603..f76ab833b4e 100644 --- a/apps/sim/content/library/ai-agent-vs-chatbot/index.mdx +++ b/apps/sim/content/library/ai-agent-vs-chatbot/index.mdx @@ -2,6 +2,7 @@ slug: ai-agent-vs-chatbot title: 'AI Agent vs Chatbot: Understanding the Differences' description: Understand the key differences between AI agents vs chatbots, from architecture to real-world use cases. Learn when to use each and how to choose the right approach for your workflows. +seoDescription: 'AI agents vs chatbots: how they differ in architecture and behavior, when to use each, and how to combine a chatbot with an agent that takes action.' date: 2026-06-28 updated: 2026-08-27 authors: diff --git a/apps/sim/content/library/ai-agent-workflow-builders-multi-step-tasks/index.mdx b/apps/sim/content/library/ai-agent-workflow-builders-multi-step-tasks/index.mdx index 2e02478a806..252eaf1d921 100644 --- a/apps/sim/content/library/ai-agent-workflow-builders-multi-step-tasks/index.mdx +++ b/apps/sim/content/library/ai-agent-workflow-builders-multi-step-tasks/index.mdx @@ -2,6 +2,7 @@ slug: ai-agent-workflow-builders-multi-step-tasks title: 'AI Agent Workflow Builders for Multi-Step Tasks: 6-Platform Comparison' description: 'Compare Sim, n8n, Gumloop, Dust, Relevance AI, and Dify for multi-step agent orchestration, memory, approvals, guardrails, debugging, logs, and deployment.' +seoTitle: 'AI Agent Workflow Builders for Multi-Step Tasks' date: 2026-09-28 updated: 2026-10-01 authors: diff --git a/apps/sim/content/library/ai-agents-asana-monday-task-automation/index.mdx b/apps/sim/content/library/ai-agents-asana-monday-task-automation/index.mdx index 79d8c01e110..97616afd143 100644 --- a/apps/sim/content/library/ai-agents-asana-monday-task-automation/index.mdx +++ b/apps/sim/content/library/ai-agents-asana-monday-task-automation/index.mdx @@ -2,6 +2,7 @@ slug: ai-agents-asana-monday-task-automation title: 'AI Agents for Asana and monday.com Task Automation: Platform and Workflow Guide' description: 'Compare AI agents for Asana and monday.com task automation, then learn how to design safe workflows for intake, approval, deduplication, and updates.' +seoTitle: 'AI Agents for Asana and monday.com Task Automation' date: 2026-10-09 updated: 2026-10-09 authors: diff --git a/apps/sim/content/library/ai-agents-for-marketing-automation/index.mdx b/apps/sim/content/library/ai-agents-for-marketing-automation/index.mdx index 03bd262f869..f839e5b131c 100644 --- a/apps/sim/content/library/ai-agents-for-marketing-automation/index.mdx +++ b/apps/sim/content/library/ai-agents-for-marketing-automation/index.mdx @@ -2,6 +2,8 @@ slug: ai-agents-for-marketing-automation title: 'AI Agents for Marketing Automation: Building Agentic Workflows Beyond HubSpot and Zapier' description: 'Learn how to evaluate and build AI agents for marketing automation, including workflow templates, integration requirements, model selection, governance, and platform comparisons.' +seoTitle: 'AI Agents for Marketing Automation: How to Build Them' +seoDescription: 'How to choose and build AI agents for marketing automation: workflows, integrations, model choice, governance, and how Sim, n8n, Zapier, and Make compare.' date: 2026-08-29 updated: 2026-10-01 authors: diff --git a/apps/sim/content/library/ai-agents-stripe-payment-automation-failed-payment-alerts/index.mdx b/apps/sim/content/library/ai-agents-stripe-payment-automation-failed-payment-alerts/index.mdx index e7ceb9c55e0..82e9d46f9dc 100644 --- a/apps/sim/content/library/ai-agents-stripe-payment-automation-failed-payment-alerts/index.mdx +++ b/apps/sim/content/library/ai-agents-stripe-payment-automation-failed-payment-alerts/index.mdx @@ -2,6 +2,7 @@ slug: ai-agents-stripe-payment-automation-failed-payment-alerts title: 'AI Agents for Stripe Payment Automation and Failed Payment Alerts' description: 'Learn how to build reliable AI agents for Stripe payment automation, failed-payment alerts, deterministic routing, and human approval with Sim.' +seoTitle: 'AI Agents for Stripe Payment Automation' date: 2026-10-10 updated: 2026-10-12 authors: diff --git a/apps/sim/content/library/ai-agents-vs-rpa/index.mdx b/apps/sim/content/library/ai-agents-vs-rpa/index.mdx index e68ffd31e46..9d4efa46fb9 100644 --- a/apps/sim/content/library/ai-agents-vs-rpa/index.mdx +++ b/apps/sim/content/library/ai-agents-vs-rpa/index.mdx @@ -2,6 +2,8 @@ slug: ai-agents-vs-rpa title: 'AI Agents vs RPA: When to Use Each for Enterprise Automation' description: 'Understand the key differences between AI agents vs RPA, from rule-based automation to intelligent decision-making. Learn when to use each and how to combine both for scalable workflows.' +seoTitle: 'AI Agents vs RPA: When to Use Each' +seoDescription: 'AI agents vs RPA: what rule-based bots and AI agents each do best, when to use one over the other, and how to combine both in a hybrid automation setup.' date: 2026-06-29 updated: 2026-10-09 authors: diff --git a/apps/sim/content/library/ai-coding-agents-vs-ai-workflow-agents/index.mdx b/apps/sim/content/library/ai-coding-agents-vs-ai-workflow-agents/index.mdx index b1d473d221e..5733160d9e4 100644 --- a/apps/sim/content/library/ai-coding-agents-vs-ai-workflow-agents/index.mdx +++ b/apps/sim/content/library/ai-coding-agents-vs-ai-workflow-agents/index.mdx @@ -2,6 +2,8 @@ slug: ai-coding-agents-vs-ai-workflow-agents title: 'AI Coding Agents vs. AI Workflow Agents: What''s the Difference?' description: 'Compare AI coding agents like Cursor, GitHub Copilot, Augment Code, and Devin with AI workflow agents like Sim, Zapier, Make, and n8n across context, task scope, integrations, setup, pricing, and oversight.' +seoTitle: 'AI Coding Agents vs AI Workflow Agents Explained' +seoDescription: 'How AI coding agents like Cursor, GitHub Copilot, and Devin differ from workflow agents like Sim, Zapier, and n8n in task scope, integrations, and pricing.' date: 2026-09-10 updated: 2026-10-01 authors: diff --git a/apps/sim/content/library/ai-native-workflow-automation-vs-traditional-automation/index.mdx b/apps/sim/content/library/ai-native-workflow-automation-vs-traditional-automation/index.mdx index 98d7026876c..00531b1941d 100644 --- a/apps/sim/content/library/ai-native-workflow-automation-vs-traditional-automation/index.mdx +++ b/apps/sim/content/library/ai-native-workflow-automation-vs-traditional-automation/index.mdx @@ -2,6 +2,8 @@ slug: ai-native-workflow-automation-vs-traditional-automation title: 'AI-Native Workflow Automation vs Traditional Automation Platforms' description: 'Compare AI-native workflow automation with traditional platforms such as Zapier, Make, and n8n across architecture, adaptability, reliability, licensing, and use cases.' +seoTitle: 'AI-Native vs Traditional Workflow Automation' +seoDescription: 'How AI-native workflow automation differs from Zapier, Make, and n8n in architecture, adaptability, reliability, and licensing, and when each fits best.' date: 2026-09-17 updated: 2026-09-30 authors: diff --git a/apps/sim/content/library/ai-personal-assistant-vs-ai-agent-builder/index.mdx b/apps/sim/content/library/ai-personal-assistant-vs-ai-agent-builder/index.mdx index ce5eb1a8f91..68344afeee1 100644 --- a/apps/sim/content/library/ai-personal-assistant-vs-ai-agent-builder/index.mdx +++ b/apps/sim/content/library/ai-personal-assistant-vs-ai-agent-builder/index.mdx @@ -2,6 +2,8 @@ slug: ai-personal-assistant-vs-ai-agent-builder title: 'AI Personal Assistant vs. AI Agent Builder: What''s the Difference?' description: 'Compare AI personal assistants with AI agent builders across autonomy, setup effort, and control, with guidance on when Lindy, Zapier, or Sim fits your workflow.' +seoTitle: 'AI Personal Assistant vs AI Agent Builder' +seoDescription: 'AI personal assistants vs AI agent builders compared on autonomy, setup effort, and control, with guidance on when Lindy, Zapier, or Sim fits best.' date: 2026-09-16 updated: 2026-09-16 authors: diff --git a/apps/sim/content/library/ai-workflow-automation-platform-buyers-checklist/index.mdx b/apps/sim/content/library/ai-workflow-automation-platform-buyers-checklist/index.mdx index 159e564146d..bc4cfadcf1f 100644 --- a/apps/sim/content/library/ai-workflow-automation-platform-buyers-checklist/index.mdx +++ b/apps/sim/content/library/ai-workflow-automation-platform-buyers-checklist/index.mdx @@ -2,6 +2,8 @@ slug: ai-workflow-automation-platform-buyers-checklist title: 'What to Look for in an AI Workflow Automation Platform: Buyer''s Checklist' description: 'A six-criteria buyer''s checklist for evaluating AI workflow automation platforms, covering agent support, orchestration, model flexibility, integrations, governance, and pricing.' +seoTitle: 'AI Workflow Automation Platform Buyer''s Checklist' +seoDescription: 'Six criteria for choosing an AI workflow automation platform, from agent support to pricing, with Sim, Zapier, n8n, Make, and Gumloop compared.' date: 2026-09-11 updated: 2026-09-11 authors: diff --git a/apps/sim/content/library/apache-2-0-vs-fair-code/index.mdx b/apps/sim/content/library/apache-2-0-vs-fair-code/index.mdx index bb52d594dec..263cbfc9773 100644 --- a/apps/sim/content/library/apache-2-0-vs-fair-code/index.mdx +++ b/apps/sim/content/library/apache-2-0-vs-fair-code/index.mdx @@ -2,6 +2,8 @@ slug: apache-2-0-vs-fair-code title: "Apache 2.0 vs Fair-Code: Why Sim's License Beats n8n's for Self-Hosting" description: 'Which AI agent builders are Apache 2.0 or MIT licensed? Compare Apache 2.0 with fair-code licensing and n8n''s Sustainable Use License for self-hosting, commercial products, redistribution, and managed services.' +seoTitle: 'Apache 2.0 vs Fair-Code Licenses: Sim vs n8n' +seoDescription: 'Apache 2.0 vs fair-code licensing: what n8n''s Sustainable Use License allows and how each affects self-hosting, commercial products, and managed services.' date: 2026-07-14 updated: 2026-10-17 authors: diff --git a/apps/sim/content/library/best-ai-agent-builders-with-mcp-support/index.mdx b/apps/sim/content/library/best-ai-agent-builders-with-mcp-support/index.mdx index ec471832ec3..b9df402c166 100644 --- a/apps/sim/content/library/best-ai-agent-builders-with-mcp-support/index.mdx +++ b/apps/sim/content/library/best-ai-agent-builders-with-mcp-support/index.mdx @@ -2,6 +2,7 @@ slug: best-ai-agent-builders-with-mcp-support title: 'Best AI Agent Builders with MCP Support' description: 'Compare the best AI agent builders with MCP support, including Sim, n8n, Zapier, Make, and Gumloop, across client and server roles, authentication, deployment, and enterprise controls.' +seoDescription: 'Sim, n8n, Zapier, Make, and Gumloop compared as AI agent builders with MCP support: client and server roles, auth, deployment, and enterprise controls.' date: 2026-09-19 updated: 2026-09-24 authors: diff --git a/apps/sim/content/library/best-ai-agent-evaluation-platforms-2026/index.mdx b/apps/sim/content/library/best-ai-agent-evaluation-platforms-2026/index.mdx index f2a191738c8..508d0f233e3 100644 --- a/apps/sim/content/library/best-ai-agent-evaluation-platforms-2026/index.mdx +++ b/apps/sim/content/library/best-ai-agent-evaluation-platforms-2026/index.mdx @@ -2,6 +2,8 @@ slug: best-ai-agent-evaluation-platforms-2026 title: 'Best AI Agent Evaluation Platforms in 2026: 6 Tools Compared' description: 'Compare the best AI agent evaluation platforms in 2026, including Sim, LangSmith, Braintrust, Arize Phoenix, Langfuse, and n8n for testing, tracing, and monitoring.' +seoTitle: '6 Best AI Agent Evaluation Tools in 2026, Compared' +seoDescription: 'LangSmith, Braintrust, Arize Phoenix, Langfuse, Sim and n8n compared for testing AI agents: tracing, test datasets, regression checks and pricing.' date: 2026-09-24 updated: 2026-10-01 authors: diff --git a/apps/sim/content/library/best-ai-agent-marketplaces-template-libraries/index.mdx b/apps/sim/content/library/best-ai-agent-marketplaces-template-libraries/index.mdx index b77ccbf5f06..839c9ba7ef2 100644 --- a/apps/sim/content/library/best-ai-agent-marketplaces-template-libraries/index.mdx +++ b/apps/sim/content/library/best-ai-agent-marketplaces-template-libraries/index.mdx @@ -2,6 +2,7 @@ slug: best-ai-agent-marketplaces-template-libraries title: '6 Best AI Agent Marketplaces and Template Libraries in 2026' description: 'Compare the best AI agent marketplaces and template libraries for editable workflows, hosted assistants, automation recipes, and reusable components.' +seoTitle: '6 Best AI Agent Marketplaces & Template Libraries 2026' date: 2026-10-08 updated: 2026-10-09 authors: diff --git a/apps/sim/content/library/best-ai-agent-platforms-for-agencies-consultants/index.mdx b/apps/sim/content/library/best-ai-agent-platforms-for-agencies-consultants/index.mdx index 49499494146..a6c600bce1a 100644 --- a/apps/sim/content/library/best-ai-agent-platforms-for-agencies-consultants/index.mdx +++ b/apps/sim/content/library/best-ai-agent-platforms-for-agencies-consultants/index.mdx @@ -2,6 +2,8 @@ slug: best-ai-agent-platforms-for-agencies-consultants title: 'Best AI Agent Platforms for Agencies and Consultants Managing Multiple Clients' description: 'Compare the best AI agent platforms for agencies and consultants managing multiple clients, with scores for isolation, permissions, cost attribution, reuse, and deployment control.' +seoTitle: 'Best AI Agent Platforms for Agencies and Consultants' +seoDescription: 'Sim, n8n, Make, Relevance AI, and Zapier scored for agencies managing multiple clients: client isolation, permissions, cost tracking, reuse, and deployment.' date: 2026-10-14 updated: 2026-10-14 authors: diff --git a/apps/sim/content/library/best-ai-agent-platforms-for-connecting-your-existing-tools/index.mdx b/apps/sim/content/library/best-ai-agent-platforms-for-connecting-your-existing-tools/index.mdx index f0b8dfd4fd7..f023bbeae82 100644 --- a/apps/sim/content/library/best-ai-agent-platforms-for-connecting-your-existing-tools/index.mdx +++ b/apps/sim/content/library/best-ai-agent-platforms-for-connecting-your-existing-tools/index.mdx @@ -2,6 +2,7 @@ slug: best-ai-agent-platforms-for-connecting-your-existing-tools title: 'Best AI Agent Platforms for Connecting Your Existing Tools' description: 'Compare the best AI agent platforms for connecting Slack, Notion, Airtable, and other existing tools, including Sim, Zapier, Make, n8n, Gumloop, and Workato.' +seoTitle: 'Best AI Agent Platforms for Connecting Your Tools' date: 2026-08-01 updated: 2026-10-01 authors: diff --git a/apps/sim/content/library/best-ai-agent-platforms-for-enterprise-teams-2026/index.mdx b/apps/sim/content/library/best-ai-agent-platforms-for-enterprise-teams-2026/index.mdx index 74e3b504619..460d9a917be 100644 --- a/apps/sim/content/library/best-ai-agent-platforms-for-enterprise-teams-2026/index.mdx +++ b/apps/sim/content/library/best-ai-agent-platforms-for-enterprise-teams-2026/index.mdx @@ -2,6 +2,8 @@ slug: best-ai-agent-platforms-for-enterprise-teams-2026 title: 'Best AI Agent Platforms for Enterprise Teams in 2026: SSO, Audit Logs, and Governance' description: 'Compare enterprise AI agent platforms in 2026 on SSO, SCIM, role-based access, audit logs, human approval, self-hosting, and licensing, with a procurement scorecard.' +seoTitle: 'Enterprise AI Agent Platforms 2026: SSO & Governance' +seoDescription: 'Compare enterprise AI agent platforms on SSO, SCIM, audit logs, approvals, self-hosting and licensing, with a procurement scorecard you can reuse.' date: 2026-08-09 updated: 2026-10-16 authors: diff --git a/apps/sim/content/library/best-ai-agents-ecommerce-operations-2026/index.mdx b/apps/sim/content/library/best-ai-agents-ecommerce-operations-2026/index.mdx index 06acf791345..4551d0cf941 100644 --- a/apps/sim/content/library/best-ai-agents-ecommerce-operations-2026/index.mdx +++ b/apps/sim/content/library/best-ai-agents-ecommerce-operations-2026/index.mdx @@ -2,6 +2,8 @@ slug: best-ai-agents-ecommerce-operations-2026 title: '6 Best AI Agent Options for Ecommerce Operations in 2026' description: 'Compare the best AI agent options for ecommerce operations across orders, returns, inventory, pricing, and customer support, with safeguards and weighted scores.' +seoTitle: '6 Best AI Agent Tools for Ecommerce Operations 2026' +seoDescription: 'Sim, n8n, Zapier, Make, Shopify Flow, and Gorgias compared for ecommerce orders, returns, inventory, pricing, and support, with scores and safeguards.' date: 2026-10-15 updated: 2026-10-15 authors: diff --git a/apps/sim/content/library/best-ai-agents-for-customer-support-automation/index.mdx b/apps/sim/content/library/best-ai-agents-for-customer-support-automation/index.mdx index e5ea42bae83..d320af94778 100644 --- a/apps/sim/content/library/best-ai-agents-for-customer-support-automation/index.mdx +++ b/apps/sim/content/library/best-ai-agents-for-customer-support-automation/index.mdx @@ -2,6 +2,7 @@ slug: best-ai-agents-for-customer-support-automation title: 'Best AI Agents for Customer Support Automation' description: 'Compare six AI agent platforms for end-to-end customer support automation: feedback-to-ticket workflows, inbox management, Zendesk ticket automation, reply drafting, tagging, knowledge grounding, helpdesk integrations, deployment, and self-hosting.' +seoDescription: 'Sim, n8n, Zapier, Make, Gumloop, and Dify compared for support automation: ticket triage, Zendesk replies and tagging, knowledge answers, and self-hosting.' date: 2026-07-23 updated: 2026-10-14 authors: diff --git a/apps/sim/content/library/best-ai-agents-for-data-extraction-and-rag-in-2026/index.mdx b/apps/sim/content/library/best-ai-agents-for-data-extraction-and-rag-in-2026/index.mdx index 4695fb684c5..d5caca88d73 100644 --- a/apps/sim/content/library/best-ai-agents-for-data-extraction-and-rag-in-2026/index.mdx +++ b/apps/sim/content/library/best-ai-agents-for-data-extraction-and-rag-in-2026/index.mdx @@ -2,6 +2,7 @@ slug: best-ai-agents-for-data-extraction-and-rag-in-2026 title: 'Best AI Agents for Data Extraction and RAG in 2026' description: 'Compare the best AI agents for data extraction and RAG in 2026, including Sim, n8n, Unstructured, and LlamaIndex for visual, automation, document, and code-first workflows.' +seoDescription: 'Compare AI agents that pull data from PDFs, emails and websites and answer questions from your documents: Sim, n8n, Unstructured and LlamaIndex.' date: 2026-07-01 updated: 2026-10-14 authors: diff --git a/apps/sim/content/library/best-ai-agents-for-executive-assistant-tasks/index.mdx b/apps/sim/content/library/best-ai-agents-for-executive-assistant-tasks/index.mdx index 6955f2a5bd1..f0d2c7f44cc 100644 --- a/apps/sim/content/library/best-ai-agents-for-executive-assistant-tasks/index.mdx +++ b/apps/sim/content/library/best-ai-agents-for-executive-assistant-tasks/index.mdx @@ -2,6 +2,8 @@ slug: best-ai-agents-for-executive-assistant-tasks title: 'Best AI Agents for Executive Assistant Tasks' description: 'Compare the best AI agents for executive assistant tasks across calendar management, email, research, approvals, integrations, human review, and deployment control.' +seoTitle: 'Best AI Tools for Executive Assistants in 2026' +seoDescription: 'Sim, n8n, Zapier, Make and Lindy compared for executive assistant work: calendar, email, research, approvals and human review controls.' date: 2026-08-04 updated: 2026-10-01 authors: diff --git a/apps/sim/content/library/best-ai-agents-for-it-helpdesk-teams/index.mdx b/apps/sim/content/library/best-ai-agents-for-it-helpdesk-teams/index.mdx index a4dd0ce877e..c95e966a2b0 100644 --- a/apps/sim/content/library/best-ai-agents-for-it-helpdesk-teams/index.mdx +++ b/apps/sim/content/library/best-ai-agents-for-it-helpdesk-teams/index.mdx @@ -2,6 +2,7 @@ slug: best-ai-agents-for-it-helpdesk-teams title: 'Best AI Agents for IT Helpdesk Teams: 6 Platforms Compared' description: 'Compare six AI agent platforms for IT helpdesk teams handling password resets, access requests, employee questions, and ticket resolution.' +seoTitle: 'Best AI Agents for IT Helpdesk Teams: 6 Compared' date: 2026-10-14 updated: 2026-10-14 authors: diff --git a/apps/sim/content/library/best-ai-agents-for-querying-sql-databases/index.mdx b/apps/sim/content/library/best-ai-agents-for-querying-sql-databases/index.mdx index 41418e368da..c4c6727d63f 100644 --- a/apps/sim/content/library/best-ai-agents-for-querying-sql-databases/index.mdx +++ b/apps/sim/content/library/best-ai-agents-for-querying-sql-databases/index.mdx @@ -2,6 +2,7 @@ slug: best-ai-agents-for-querying-sql-databases title: 'Best AI Agents for Querying SQL Databases in Plain English' description: 'Compare the best AI agents for querying PostgreSQL, BigQuery, and Snowflake in plain English, generating reports, and delivering results to Slack.' +seoTitle: 'Best AI Agents to Query SQL Databases in Plain English' date: 2026-10-15 updated: 2026-10-15 authors: diff --git a/apps/sim/content/library/best-ai-agents-for-recruiting-teams/index.mdx b/apps/sim/content/library/best-ai-agents-for-recruiting-teams/index.mdx index 7f3484b478c..8223e0544e3 100644 --- a/apps/sim/content/library/best-ai-agents-for-recruiting-teams/index.mdx +++ b/apps/sim/content/library/best-ai-agents-for-recruiting-teams/index.mdx @@ -2,6 +2,8 @@ slug: best-ai-agents-for-recruiting-teams title: '7 Best AI Agents for Recruiting Teams in 2026: Screening, Scheduling, ATS, and Outreach' description: 'Compare the best AI agents for recruiting teams across candidate screening, interview scheduling, ATS integration, outreach, human oversight, and deployment control.' +seoTitle: '7 Best AI Agents for Recruiting Teams in 2026' +seoDescription: 'Sim, n8n, Paradox, Humanly, Make, Zapier, and GoodTime compared for candidate screening, interview scheduling, ATS integration, outreach, and human review.' date: 2026-10-14 updated: 2026-10-14 authors: diff --git a/apps/sim/content/library/best-ai-agents-for-regulated-industry-workflows-healthcare-legal-procurement/index.mdx b/apps/sim/content/library/best-ai-agents-for-regulated-industry-workflows-healthcare-legal-procurement/index.mdx index bf94aedfbb4..fbe450debc6 100644 --- a/apps/sim/content/library/best-ai-agents-for-regulated-industry-workflows-healthcare-legal-procurement/index.mdx +++ b/apps/sim/content/library/best-ai-agents-for-regulated-industry-workflows-healthcare-legal-procurement/index.mdx @@ -2,6 +2,7 @@ slug: best-ai-agents-for-regulated-industry-workflows-healthcare-legal-procurement title: 'Best AI Agents for Regulated Industry Workflows (Healthcare, Legal, Procurement)' description: 'Compare AI agent platforms for regulated healthcare, legal, and procurement workflows, with a focus on deployment control, access governance, and auditability.' +seoTitle: 'Best AI Agents for Regulated Industry Workflows' date: 2026-07-29 updated: 2026-10-16 authors: diff --git a/apps/sim/content/library/best-ai-agents-for-scheduling-and-calendar-management-in-2026/index.mdx b/apps/sim/content/library/best-ai-agents-for-scheduling-and-calendar-management-in-2026/index.mdx index 033189f044f..3b262abb297 100644 --- a/apps/sim/content/library/best-ai-agents-for-scheduling-and-calendar-management-in-2026/index.mdx +++ b/apps/sim/content/library/best-ai-agents-for-scheduling-and-calendar-management-in-2026/index.mdx @@ -2,6 +2,8 @@ slug: best-ai-agents-for-scheduling-and-calendar-management-in-2026 title: 'Best AI Agents for Scheduling and Calendar Management in 2026' description: 'Compare the best AI scheduling agents, calendar assistants, and extensible automation platforms for booking, calendar optimization, follow-up, and custom coordination workflows.' +seoTitle: 'Best AI Agents for Scheduling and Calendars in 2026' +seoDescription: 'Sim, Calendly, Reclaim, Motion, Clockwise, Lindy, n8n, Zapier, and Make compared for booking meetings, calendar optimization, and custom scheduling agents.' date: 2026-07-29 updated: 2026-10-09 authors: diff --git a/apps/sim/content/library/best-ai-agents-for-slack/index.mdx b/apps/sim/content/library/best-ai-agents-for-slack/index.mdx index 32ba8138fa0..e0fd39be779 100644 --- a/apps/sim/content/library/best-ai-agents-for-slack/index.mdx +++ b/apps/sim/content/library/best-ai-agents-for-slack/index.mdx @@ -2,6 +2,7 @@ slug: best-ai-agents-for-slack title: 'Best AI Agents for Slack' description: 'Compare the best AI agents for Slack across custom workflows, support, knowledge retrieval, and Slack-to-CRM automation, with guidance on permissions, approvals, deployment, and self-hosting.' +seoDescription: 'Sim, ClearFeed, Dust, Botpress, n8n, and Zapier compared as AI agents for Slack: support, knowledge answers, CRM updates, permissions, and approvals.' date: 2026-08-29 updated: 2026-09-30 authors: diff --git a/apps/sim/content/library/best-ai-agents-invoice-processing-finance-reconciliation-2026/index.mdx b/apps/sim/content/library/best-ai-agents-invoice-processing-finance-reconciliation-2026/index.mdx index d3d7167141e..95b1e1a7411 100644 --- a/apps/sim/content/library/best-ai-agents-invoice-processing-finance-reconciliation-2026/index.mdx +++ b/apps/sim/content/library/best-ai-agents-invoice-processing-finance-reconciliation-2026/index.mdx @@ -2,6 +2,7 @@ slug: best-ai-agents-invoice-processing-finance-reconciliation-2026 title: '6 Best AI Agents for Invoice Processing and Finance Reconciliation in 2026' description: 'Compare the best AI agents for invoice processing and finance reconciliation in 2026, including Sim, UiPath, Power Automate, n8n, Zapier, and Make.' +seoTitle: '6 Best AI Agents for Invoice Processing in 2026' date: 2026-10-12 updated: 2026-10-14 authors: diff --git a/apps/sim/content/library/best-ai-agents-support-ticket-triage/index.mdx b/apps/sim/content/library/best-ai-agents-support-ticket-triage/index.mdx index 6006e29d43f..068a4bc864c 100644 --- a/apps/sim/content/library/best-ai-agents-support-ticket-triage/index.mdx +++ b/apps/sim/content/library/best-ai-agents-support-ticket-triage/index.mdx @@ -2,6 +2,8 @@ slug: best-ai-agents-support-ticket-triage title: 'Best AI Agents for Customer Support Ticket Triage and Routing' description: 'Compare Sim, n8n, Zendesk AI, and Intercom Fin for support ticket triage: classification, prioritization, routing, evaluation sets, human review, and prompt-injection safety.' +seoTitle: 'Best AI Agents for Support Ticket Triage and Routing' +seoDescription: 'Sim, Zendesk AI, Intercom Fin, n8n, Zapier, and Make compared for support ticket triage: classification, routing, human review, and prompt-injection safety.' date: 2026-08-08 updated: 2026-10-08 authors: diff --git a/apps/sim/content/library/best-ai-agents-web-scraping-market-research/index.mdx b/apps/sim/content/library/best-ai-agents-web-scraping-market-research/index.mdx index bf030164e32..2820aa8f078 100644 --- a/apps/sim/content/library/best-ai-agents-web-scraping-market-research/index.mdx +++ b/apps/sim/content/library/best-ai-agents-web-scraping-market-research/index.mdx @@ -2,6 +2,7 @@ slug: best-ai-agents-web-scraping-market-research title: 'Best AI Agents for Web Scraping and Market Research in 2026' description: 'Compare the best AI agents for web scraping, competitor monitoring, market analysis, and automated company research in 2026.' +seoTitle: 'Best AI Agents for Web Scraping & Market Research 2026' date: 2026-10-12 updated: 2026-10-12 authors: diff --git a/apps/sim/content/library/best-ai-automation-tools-2026/index.mdx b/apps/sim/content/library/best-ai-automation-tools-2026/index.mdx index fd7fc51fc4a..ec793dcf21d 100644 --- a/apps/sim/content/library/best-ai-automation-tools-2026/index.mdx +++ b/apps/sim/content/library/best-ai-automation-tools-2026/index.mdx @@ -2,6 +2,7 @@ slug: best-ai-automation-tools-2026 title: 'Best AI Automation Tools in 2026' description: 'Compare the best AI automation tools in 2026, including Sim, n8n, Zapier, Make, Gumloop, and Microsoft Power Automate, by deployment, licensing, AI depth, and billing model.' +seoDescription: 'Sim, n8n, Zapier, Make, Gumloop, and Power Automate compared on deployment, licensing, AI depth, and billing, with the best AI automation tool per use case.' date: 2026-08-01 updated: 2026-10-09 authors: diff --git a/apps/sim/content/library/best-ai-coding-agents-refactoring-debugging/index.mdx b/apps/sim/content/library/best-ai-coding-agents-refactoring-debugging/index.mdx index f26bbb051d4..8ecbef07f5a 100644 --- a/apps/sim/content/library/best-ai-coding-agents-refactoring-debugging/index.mdx +++ b/apps/sim/content/library/best-ai-coding-agents-refactoring-debugging/index.mdx @@ -2,6 +2,7 @@ slug: best-ai-coding-agents-refactoring-debugging title: 'Best AI Coding Agents for Refactoring and Debugging: 5 Tools Compared' description: 'Compare five AI coding agents for multi-file refactoring and complex debugging, including Claude Code, Cursor, GitHub Copilot, OpenAI Codex, and Gemini CLI.' +seoTitle: 'Best AI Coding Agents for Refactoring and Debugging' date: 2026-10-05 updated: 2026-10-05 authors: diff --git a/apps/sim/content/library/best-ai-workflow-builders/index.mdx b/apps/sim/content/library/best-ai-workflow-builders/index.mdx index 29b399519c2..f43d9a463d4 100644 --- a/apps/sim/content/library/best-ai-workflow-builders/index.mdx +++ b/apps/sim/content/library/best-ai-workflow-builders/index.mdx @@ -2,6 +2,8 @@ slug: best-ai-workflow-builders title: 'Best AI Workflow Builders for Technical and Semi-Technical Teams' description: 'Compare the best AI workflow builders for technical, semi-technical, and small teams, including Sim, n8n, Zapier, Make, and Gumloop.' +seoTitle: 'Best AI Workflow Builders 2026: Sim vs n8n vs Zapier' +seoDescription: 'Sim, n8n, Zapier, Make and Gumloop compared for building AI workflows: pricing, AI agent support, open source and the best pick for each team.' date: 2026-09-24 updated: 2026-10-01 authors: diff --git a/apps/sim/content/library/best-chatgpt-alternatives-ai-agents-workflow-automation/index.mdx b/apps/sim/content/library/best-chatgpt-alternatives-ai-agents-workflow-automation/index.mdx index 079ee0de5e0..661e34297ff 100644 --- a/apps/sim/content/library/best-chatgpt-alternatives-ai-agents-workflow-automation/index.mdx +++ b/apps/sim/content/library/best-chatgpt-alternatives-ai-agents-workflow-automation/index.mdx @@ -2,6 +2,8 @@ slug: best-chatgpt-alternatives-ai-agents-workflow-automation title: 'Best ChatGPT alternatives for building AI agents that automate real work' description: 'Compare the best ChatGPT alternatives for building AI agents that automate real work, including Sim, n8n, Zapier, Make, Dify, Langflow, Copilot Studio, and Vertex AI Agent Builder.' +seoTitle: 'Best ChatGPT Alternatives for Building AI Agents' +seoDescription: 'Sim, n8n, Zapier, Make, Dify, Langflow, Copilot Studio, and Vertex AI Agent Builder compared as ChatGPT alternatives for agents that automate real work.' date: 2026-09-29 updated: 2026-10-01 authors: diff --git a/apps/sim/content/library/best-conversational-ai-platforms-crm-helpdesk/index.mdx b/apps/sim/content/library/best-conversational-ai-platforms-crm-helpdesk/index.mdx index 76682388458..400464ba113 100644 --- a/apps/sim/content/library/best-conversational-ai-platforms-crm-helpdesk/index.mdx +++ b/apps/sim/content/library/best-conversational-ai-platforms-crm-helpdesk/index.mdx @@ -2,6 +2,7 @@ slug: best-conversational-ai-platforms-crm-helpdesk title: 'Best Conversational AI Platforms for CRM and Helpdesk: Multi-Model Chatbot Builders Compared' description: 'Compare multi-model conversational AI platforms for CRM, helpdesk, sales, and support, including Sim, n8n, Agentforce, Zendesk, Intercom, and Ada.' +seoTitle: 'Best Conversational AI Platforms for CRM and Helpdesk' date: 2026-10-05 updated: 2026-10-08 authors: diff --git a/apps/sim/content/library/best-crewai-alternatives-2026/index.mdx b/apps/sim/content/library/best-crewai-alternatives-2026/index.mdx index cfd56e20cdb..f00e2f9d34e 100644 --- a/apps/sim/content/library/best-crewai-alternatives-2026/index.mdx +++ b/apps/sim/content/library/best-crewai-alternatives-2026/index.mdx @@ -2,6 +2,7 @@ slug: best-crewai-alternatives-2026 title: '6 Best CrewAI Alternatives in 2026 for Visual and Flexible Agent Building' description: 'Compare the best CrewAI alternatives for visual agent building, flexible orchestration, self-hosting, and code-first control in 2026.' +seoTitle: '6 Best CrewAI Alternatives for AI Agents in 2026' date: 2026-10-16 updated: 2026-10-16 authors: diff --git a/apps/sim/content/library/best-discord-ai-bot-builders-2026/index.mdx b/apps/sim/content/library/best-discord-ai-bot-builders-2026/index.mdx index 80ae8dc21de..81cc79ae10b 100644 --- a/apps/sim/content/library/best-discord-ai-bot-builders-2026/index.mdx +++ b/apps/sim/content/library/best-discord-ai-bot-builders-2026/index.mdx @@ -2,6 +2,7 @@ slug: best-discord-ai-bot-builders-2026 title: '5 Best Discord AI Bot Builders for Community Automation in 2026' description: 'Compare the best Discord AI bot builders for community automation, documentation Q&A, moderation, and reusable Slack and Discord agents in 2026.' +seoTitle: '5 Best Discord AI Bot Builders in 2026' date: 2026-10-15 updated: 2026-10-15 authors: diff --git a/apps/sim/content/library/best-hubspot-alternatives-ai-marketing-automation-2026/index.mdx b/apps/sim/content/library/best-hubspot-alternatives-ai-marketing-automation-2026/index.mdx index 180f4040d46..e4035086f8c 100644 --- a/apps/sim/content/library/best-hubspot-alternatives-ai-marketing-automation-2026/index.mdx +++ b/apps/sim/content/library/best-hubspot-alternatives-ai-marketing-automation-2026/index.mdx @@ -2,6 +2,7 @@ slug: best-hubspot-alternatives-ai-marketing-automation-2026 title: 'Best HubSpot Alternatives for AI Marketing Automation in 2026' description: 'Compare the best HubSpot alternatives for AI marketing automation, including Sim, Marketo, Zapier, Make, n8n, and Gumloop.' +seoTitle: 'Best HubSpot Alternatives for AI Marketing in 2026' date: 2026-10-03 updated: 2026-10-03 authors: diff --git a/apps/sim/content/library/best-make-com-alternatives-ai-agents-2026/index.mdx b/apps/sim/content/library/best-make-com-alternatives-ai-agents-2026/index.mdx index ecd0923b527..441cf84bf1a 100644 --- a/apps/sim/content/library/best-make-com-alternatives-ai-agents-2026/index.mdx +++ b/apps/sim/content/library/best-make-com-alternatives-ai-agents-2026/index.mdx @@ -2,6 +2,7 @@ slug: best-make-com-alternatives-ai-agents-2026 title: '6 Best Make.com Alternatives for AI Agents in 2026' description: 'Compare the best Make.com alternatives for AI agents, including Sim, n8n, Dify, LangGraph, Zapier, and Gumloop across reasoning, pricing, usability, and deployment.' +seoDescription: 'Sim, n8n, Dify, LangGraph, Zapier, and Gumloop compared as Make.com alternatives for AI agents on reasoning, pricing, ease of use, and deployment.' date: 2026-10-01 updated: 2026-10-01 authors: diff --git a/apps/sim/content/library/best-marketing-automation-platforms-ai-workflows-2026/index.mdx b/apps/sim/content/library/best-marketing-automation-platforms-ai-workflows-2026/index.mdx index 2c93e846e32..646b59e2c1b 100644 --- a/apps/sim/content/library/best-marketing-automation-platforms-ai-workflows-2026/index.mdx +++ b/apps/sim/content/library/best-marketing-automation-platforms-ai-workflows-2026/index.mdx @@ -2,6 +2,7 @@ slug: best-marketing-automation-platforms-ai-workflows-2026 title: 'What are the best marketing automation platforms for AI workflows in 2026?' description: 'Compare the best marketing automation platforms for AI workflows in 2026, including HubSpot, Marketo, Sim, n8n, Zapier, Make, and Gumloop.' +seoTitle: 'Best Marketing Automation Platforms for AI in 2026' date: 2026-10-01 updated: 2026-10-01 authors: diff --git a/apps/sim/content/library/best-microsoft-copilot-studio-power-automate-alternatives/index.mdx b/apps/sim/content/library/best-microsoft-copilot-studio-power-automate-alternatives/index.mdx index 185ec415e22..63e8ccd682d 100644 --- a/apps/sim/content/library/best-microsoft-copilot-studio-power-automate-alternatives/index.mdx +++ b/apps/sim/content/library/best-microsoft-copilot-studio-power-automate-alternatives/index.mdx @@ -2,6 +2,7 @@ slug: best-microsoft-copilot-studio-power-automate-alternatives title: '6 Best Microsoft Copilot Studio and Power Automate Alternatives for AI Agents in 2026' description: 'Compare the best Microsoft Copilot Studio and Power Automate alternatives for AI agents, including Sim, n8n, Dify, Zapier, Make, and Gumloop.' +seoTitle: 'Copilot Studio & Power Automate Alternatives in 2026' date: 2026-10-16 updated: 2026-10-16 authors: diff --git a/apps/sim/content/library/best-relay-app-alternatives-2026/index.mdx b/apps/sim/content/library/best-relay-app-alternatives-2026/index.mdx index ddac692eacb..6746fe63762 100644 --- a/apps/sim/content/library/best-relay-app-alternatives-2026/index.mdx +++ b/apps/sim/content/library/best-relay-app-alternatives-2026/index.mdx @@ -2,6 +2,7 @@ slug: best-relay-app-alternatives-2026 title: 'Best Relay.app Alternatives in 2026' description: Relay.app is shutting down in 2026. Compare the best Relay.app alternatives - Sim, n8n, Zapier, Make, and Gumloop - with license, self-host, and migration-effort breakdowns to switch before your deadline. +seoDescription: 'Relay.app is shutting down in 2026. Compare Sim, n8n, Zapier, Make, and Gumloop on license, self-hosting, and migration effort before you switch.' date: 2026-07-17 updated: 2026-10-01 authors: diff --git a/apps/sim/content/library/best-relevance-ai-alternatives-2026/index.mdx b/apps/sim/content/library/best-relevance-ai-alternatives-2026/index.mdx index aa9d9df1b45..46969ae5371 100644 --- a/apps/sim/content/library/best-relevance-ai-alternatives-2026/index.mdx +++ b/apps/sim/content/library/best-relevance-ai-alternatives-2026/index.mdx @@ -2,6 +2,7 @@ slug: best-relevance-ai-alternatives-2026 title: '6 Best Relevance AI Alternatives for Flexible AI Agents in 2026' description: 'Compare the best Relevance AI alternatives for flexible AI agents, including Sim, LangGraph, n8n, Dify, Gumloop, and Zapier.' +seoTitle: '6 Best Relevance AI Alternatives in 2026' date: 2026-10-17 updated: 2026-10-17 authors: diff --git a/apps/sim/content/library/best-self-hosted-ai-workflow-automation-platforms-2026/index.mdx b/apps/sim/content/library/best-self-hosted-ai-workflow-automation-platforms-2026/index.mdx index 6d2611b2c85..8205af9b6f5 100644 --- a/apps/sim/content/library/best-self-hosted-ai-workflow-automation-platforms-2026/index.mdx +++ b/apps/sim/content/library/best-self-hosted-ai-workflow-automation-platforms-2026/index.mdx @@ -2,6 +2,8 @@ slug: best-self-hosted-ai-workflow-automation-platforms-2026 title: 'Best Self-Hosted AI Workflow Automation Platforms in 2026' description: 'Compare the best self-hosted AI workflow automation platforms in 2026 across licensing, AI-native design, deployment control, governance, and operating effort.' +seoTitle: 'Best Self-Hosted AI Workflow Automation Tools (2026)' +seoDescription: 'Sim, n8n, Dify, Flowise, Langflow and Activepieces compared for self-hosting: license, deployment, local models, governance and running costs.' date: 2026-09-24 updated: 2026-09-24 authors: diff --git a/apps/sim/content/library/best-voice-ai-agent-builders-phone-support-2026/index.mdx b/apps/sim/content/library/best-voice-ai-agent-builders-phone-support-2026/index.mdx index 0e391a35a1d..3cf9004451c 100644 --- a/apps/sim/content/library/best-voice-ai-agent-builders-phone-support-2026/index.mdx +++ b/apps/sim/content/library/best-voice-ai-agent-builders-phone-support-2026/index.mdx @@ -2,6 +2,7 @@ slug: best-voice-ai-agent-builders-phone-support-2026 title: '7 Best Voice AI Agent Builders for Phone Support in 2026' description: 'Compare the best voice AI agent builders for phone support in 2026, including Vapi, Retell AI, Synthflow, Sim, Bland AI, ElevenLabs, and n8n.' +seoTitle: '7 Best Voice AI Agent Builders for Phone Support 2026' date: 2026-10-14 updated: 2026-10-14 authors: diff --git a/apps/sim/content/library/best-zapier-alternatives/index.mdx b/apps/sim/content/library/best-zapier-alternatives/index.mdx index 5e87943970d..7c3afd816f0 100644 --- a/apps/sim/content/library/best-zapier-alternatives/index.mdx +++ b/apps/sim/content/library/best-zapier-alternatives/index.mdx @@ -2,6 +2,8 @@ slug: best-zapier-alternatives title: '7 Best Zapier Alternatives in 2026, Compared' description: 'Compare the seven best Zapier alternatives in 2026 on current pricing, AI agents, self-hosting, small-team fit, Microsoft environments, and enterprise governance.' +seoTitle: 'Best Zapier Alternatives 2026: 7 Compared on Pricing' +seoDescription: 'Make, n8n, Activepieces, Power Automate, Workato, Gumloop and Sim compared on price, free plans, open source, self-hosting and AI agents.' date: 2026-07-01 updated: 2026-10-17 authors: diff --git a/apps/sim/content/library/byok-multi-model-ai-agent-builder/index.mdx b/apps/sim/content/library/byok-multi-model-ai-agent-builder/index.mdx index 716c0ce8aba..c643b16e63d 100644 --- a/apps/sim/content/library/byok-multi-model-ai-agent-builder/index.mdx +++ b/apps/sim/content/library/byok-multi-model-ai-agent-builder/index.mdx @@ -2,6 +2,8 @@ slug: byok-multi-model-ai-agent-builder title: "BYOK Multi-Model AI Agent Builder: How Sim's Bring-Your-Own-Key Works" description: 'Learn how Sim BYOK separates customer-owned provider keys from hosted model access and self-hosted local models, including the 1.1x hosted rate, supported providers, billing, and security.' +seoTitle: 'BYOK AI Agent Builder: How Bring Your Own Key Works' +seoDescription: 'How bring-your-own-key works in Sim: hosted model access vs BYOK vs local models, supported providers, who pays for usage, and how keys are secured.' date: 2026-07-18 updated: 2026-10-16 authors: diff --git a/apps/sim/content/library/can-ai-agents-talk-to-each-other-multi-agent-communication-explained/index.mdx b/apps/sim/content/library/can-ai-agents-talk-to-each-other-multi-agent-communication-explained/index.mdx index b7ab385783e..69e270bf202 100644 --- a/apps/sim/content/library/can-ai-agents-talk-to-each-other-multi-agent-communication-explained/index.mdx +++ b/apps/sim/content/library/can-ai-agents-talk-to-each-other-multi-agent-communication-explained/index.mdx @@ -2,6 +2,8 @@ slug: can-ai-agents-talk-to-each-other-multi-agent-communication-explained title: 'Can AI Agents Talk to Each Other? Multi-Agent Communication Explained' description: 'Yes — AI agents can talk to each other. Learn the four core multi-agent communication patterns, when to split one agent into several, and how to choose single-agent vs multi-agent.' +seoTitle: 'Can AI Agents Talk to Each Other? Multi-Agent Guide' +seoDescription: 'Yes. Learn the four multi-agent communication patterns, how A2A and MCP differ, when to split one agent into several, and where multi-agent systems break.' date: 2026-09-07 updated: 2026-10-14 authors: diff --git a/apps/sim/content/library/confluence-knowledge-base-ai-agent-guide/index.mdx b/apps/sim/content/library/confluence-knowledge-base-ai-agent-guide/index.mdx index 9983d89a422..b4d3c3e1de2 100644 --- a/apps/sim/content/library/confluence-knowledge-base-ai-agent-guide/index.mdx +++ b/apps/sim/content/library/confluence-knowledge-base-ai-agent-guide/index.mdx @@ -2,6 +2,7 @@ slug: confluence-knowledge-base-ai-agent-guide title: 'How Do You Build an AI Agent That Answers Questions From Confluence?' description: 'Learn how to build a permission-aware Confluence knowledge base AI agent with grounded answers, source citations, secure retrieval, and measurable evaluation.' +seoTitle: 'Confluence AI Agent: Answer Questions From Your Docs' date: 2026-10-12 updated: 2026-10-12 authors: diff --git a/apps/sim/content/library/dify-alternatives/index.mdx b/apps/sim/content/library/dify-alternatives/index.mdx index 07a017dc3aa..a4c876ec2fa 100644 --- a/apps/sim/content/library/dify-alternatives/index.mdx +++ b/apps/sim/content/library/dify-alternatives/index.mdx @@ -2,6 +2,8 @@ slug: dify-alternatives title: 'Best Dify Alternatives in 2026: Open-Source and Self-Hosted Options' description: 'Five Dify alternatives ranked for 2026 (Sim, n8n, LangChain and LangGraph, RAGFlow, and Langflow) on license, self-hosting, workflow depth, MCP support, and pricing.' +seoTitle: '5 Best Dify Alternatives in 2026 (Open Source)' +seoDescription: 'Sim, n8n, LangChain and LangGraph, RAGFlow, and Langflow ranked as Dify alternatives on license, self-hosting, workflow depth, MCP support, and pricing.' date: 2026-08-27 updated: 2026-10-05 authors: diff --git a/apps/sim/content/library/enterprise-ai-agent-compliance-deployment-guide/index.mdx b/apps/sim/content/library/enterprise-ai-agent-compliance-deployment-guide/index.mdx index 7d356450446..9c687102193 100644 --- a/apps/sim/content/library/enterprise-ai-agent-compliance-deployment-guide/index.mdx +++ b/apps/sim/content/library/enterprise-ai-agent-compliance-deployment-guide/index.mdx @@ -2,6 +2,7 @@ slug: enterprise-ai-agent-compliance-deployment-guide title: 'Enterprise AI Agent Compliance and Deployment Guide: HIPAA, GDPR, Data Residency, On-Prem, and VPC' description: 'Evaluate enterprise AI agent platforms for HIPAA, GDPR, SOC 2, data residency, on-premises, VPC, and multi-region deployment requirements.' +seoTitle: 'Enterprise AI Agent Compliance: HIPAA, GDPR, On-Prem' date: 2026-10-09 updated: 2026-10-09 authors: diff --git a/apps/sim/content/library/github-mcp-server-ai-agent-builders/index.mdx b/apps/sim/content/library/github-mcp-server-ai-agent-builders/index.mdx index b61ba3de170..339e6a64bd7 100644 --- a/apps/sim/content/library/github-mcp-server-ai-agent-builders/index.mdx +++ b/apps/sim/content/library/github-mcp-server-ai-agent-builders/index.mdx @@ -2,6 +2,7 @@ slug: github-mcp-server-ai-agent-builders title: 'GitHub MCP Server for AI Agents: Builders, Security, and Setup' description: 'Compare GitHub MCP server clients for AI agents, understand security and permissions, and learn how to set up an approval-gated GitHub agent in Sim.' +seoTitle: 'GitHub MCP Server: Best AI Agent Builders Compared' date: 2026-10-08 updated: 2026-10-08 authors: diff --git a/apps/sim/content/library/github-mcp-server-ai-agents/index.mdx b/apps/sim/content/library/github-mcp-server-ai-agents/index.mdx index 46d3e464bd7..6e412fb9a31 100644 --- a/apps/sim/content/library/github-mcp-server-ai-agents/index.mdx +++ b/apps/sim/content/library/github-mcp-server-ai-agents/index.mdx @@ -2,6 +2,7 @@ slug: github-mcp-server-ai-agents title: 'How to Use the GitHub MCP Server With AI Agents: Builders, Security, and Setup' description: 'Compare AI agent builders for the official GitHub MCP Server, then learn how to configure secure repository access, approval gates, and write actions.' +seoTitle: 'How to Use the GitHub MCP Server With AI Agents' date: 2026-10-08 updated: 2026-10-08 authors: diff --git a/apps/sim/content/library/govern-ai-agents-multiple-teams-enterprise-workspace/index.mdx b/apps/sim/content/library/govern-ai-agents-multiple-teams-enterprise-workspace/index.mdx index f276fcf2435..9f28b9fa684 100644 --- a/apps/sim/content/library/govern-ai-agents-multiple-teams-enterprise-workspace/index.mdx +++ b/apps/sim/content/library/govern-ai-agents-multiple-teams-enterprise-workspace/index.mdx @@ -2,6 +2,7 @@ slug: govern-ai-agents-multiple-teams-enterprise-workspace title: 'Governing AI Agents Built by Different Teams in One Enterprise Workspace' description: 'Learn how centralized AI agent governance gives enterprise teams a shared workspace for identity, access control, audit evidence, deployment, and oversight.' +seoTitle: 'Enterprise AI Agent Governance Across Multiple Teams' date: 2026-08-17 updated: 2026-10-08 authors: diff --git a/apps/sim/content/library/how-to-build-ai-voice-agent-with-twilio/index.mdx b/apps/sim/content/library/how-to-build-ai-voice-agent-with-twilio/index.mdx index b71278753d0..9faa2c773c9 100644 --- a/apps/sim/content/library/how-to-build-ai-voice-agent-with-twilio/index.mdx +++ b/apps/sim/content/library/how-to-build-ai-voice-agent-with-twilio/index.mdx @@ -2,6 +2,8 @@ slug: how-to-build-ai-voice-agent-with-twilio title: 'How do you build an AI voice agent with Twilio and Sim?' description: 'Learn how to build a production AI voice agent with Twilio for calls and live audio, a low-latency voice adapter, and Sim for orchestration, tools, safeguards, and escalation.' +seoTitle: 'How to Build an AI Voice Agent With Twilio and Sim' +seoDescription: 'Build a production AI voice agent with Twilio for calls and live audio, a low-latency voice adapter, and Sim for tools, safeguards, and human handoff.' date: 2026-10-03 updated: 2026-10-03 authors: diff --git a/apps/sim/content/library/how-to-create-an-ai-agent/index.mdx b/apps/sim/content/library/how-to-create-an-ai-agent/index.mdx index 80e7d362a98..310b1b5fcd6 100644 --- a/apps/sim/content/library/how-to-create-an-ai-agent/index.mdx +++ b/apps/sim/content/library/how-to-create-an-ai-agent/index.mdx @@ -2,6 +2,8 @@ slug: how-to-create-an-ai-agent title: 'How to Create an AI Agent: A Beginner’s Step-by-Step Guide' description: 'Learn how to create an AI agent from scratch using a visual workspace. Connect tools and deploy in minutes. Build real-world agents that automate real work with Sim.' +seoTitle: 'How to Create an AI Agent: Step-by-Step Guide' +seoDescription: 'Learn how to create an AI agent step by step in a visual workspace: pick one task, connect tools, test each run, and deploy your first agent with Sim.' date: 2026-06-27 updated: 2026-10-09 authors: diff --git a/apps/sim/content/library/how-to-give-ai-agents-live-web-search-with-the-you-com-api-in-sim/index.mdx b/apps/sim/content/library/how-to-give-ai-agents-live-web-search-with-the-you-com-api-in-sim/index.mdx index 347445067dd..93cbf3c6409 100644 --- a/apps/sim/content/library/how-to-give-ai-agents-live-web-search-with-the-you-com-api-in-sim/index.mdx +++ b/apps/sim/content/library/how-to-give-ai-agents-live-web-search-with-the-you-com-api-in-sim/index.mdx @@ -2,6 +2,7 @@ slug: how-to-give-ai-agents-live-web-search-with-the-you-com-api-in-sim title: 'How to Give AI Agents Live Web Search with the You.com API in Sim' description: 'Give AI agents live web search in Sim with the You.com API, including cited answers, research effort tiers, structured output, and example workflows.' +seoTitle: 'Live Web Search for AI Agents With the You.com API' date: 2026-10-14 updated: 2026-10-14 authors: diff --git a/apps/sim/content/library/how-to-turn-a-workflow-into-a-reusable-mcp-tool/index.mdx b/apps/sim/content/library/how-to-turn-a-workflow-into-a-reusable-mcp-tool/index.mdx index ba7b94198cc..c4fab152ab7 100644 --- a/apps/sim/content/library/how-to-turn-a-workflow-into-a-reusable-mcp-tool/index.mdx +++ b/apps/sim/content/library/how-to-turn-a-workflow-into-a-reusable-mcp-tool/index.mdx @@ -2,6 +2,7 @@ slug: how-to-turn-a-workflow-into-a-reusable-mcp-tool title: 'How to Turn a Workflow Into a Reusable MCP Tool (Sim vs n8n)' description: 'Learn how to turn a workflow into a reusable MCP tool, publish it through Sim, connect clients securely, and compare Sim with n8n.' +seoTitle: 'How to Turn a Workflow Into an MCP Tool (Sim vs n8n)' date: 2026-08-14 updated: 2026-09-29 authors: diff --git a/apps/sim/content/library/hubspot-ai-agent-lead-enrichment-follow-up-workflow/index.mdx b/apps/sim/content/library/hubspot-ai-agent-lead-enrichment-follow-up-workflow/index.mdx index cf16cc145f3..683978ec4b1 100644 --- a/apps/sim/content/library/hubspot-ai-agent-lead-enrichment-follow-up-workflow/index.mdx +++ b/apps/sim/content/library/hubspot-ai-agent-lead-enrichment-follow-up-workflow/index.mdx @@ -2,6 +2,7 @@ slug: hubspot-ai-agent-lead-enrichment-follow-up-workflow title: 'How Do You Build a HubSpot AI Agent for Lead Enrichment and Follow-Up?' description: 'Build a HubSpot AI agent for lead enrichment and follow-up with structured CRM updates, confidence checks, human approval, and deterministic outreach rules.' +seoTitle: 'HubSpot AI Agent for Lead Enrichment and Follow-Up' date: 2026-10-10 updated: 2026-10-12 authors: diff --git a/apps/sim/content/library/langgraph-alternatives/index.mdx b/apps/sim/content/library/langgraph-alternatives/index.mdx index 8fff5432aa2..4c11be6932c 100644 --- a/apps/sim/content/library/langgraph-alternatives/index.mdx +++ b/apps/sim/content/library/langgraph-alternatives/index.mdx @@ -2,6 +2,8 @@ slug: langgraph-alternatives title: 'Best LangChain and LangGraph Alternatives for Production AI Agent Workflows' description: 'Compare LangChain and LangGraph alternatives for teams leaving because of Python complexity, prototype-to-production friction, or operational requirements.' +seoTitle: 'LangGraph Alternatives 2026: 12 Options for Production' +seoDescription: 'The best LangGraph and LangChain alternatives in 2026: CrewAI, PydanticAI, OpenAI Agents SDK, Mastra, Dify, n8n and Sim, picked by use case.' date: 2026-07-13 updated: 2026-10-16 authors: diff --git a/apps/sim/content/library/marketing-automation-platform-vs-ai-agent-builder/index.mdx b/apps/sim/content/library/marketing-automation-platform-vs-ai-agent-builder/index.mdx index 58a394f72a2..3c8c48f1295 100644 --- a/apps/sim/content/library/marketing-automation-platform-vs-ai-agent-builder/index.mdx +++ b/apps/sim/content/library/marketing-automation-platform-vs-ai-agent-builder/index.mdx @@ -2,6 +2,8 @@ slug: marketing-automation-platform-vs-ai-agent-builder title: 'Marketing Automation Platform vs AI Agent Builder: Which Does Your Team Need?' description: 'Compare marketing automation platforms and AI agent builders to decide how your team should manage governed campaigns, adaptive workflows, and cross-tool orchestration.' +seoTitle: 'Marketing Automation Platform vs AI Agent Builder' +seoDescription: 'When a marketing team needs a marketing automation platform, an AI agent builder, or both, compared on campaigns, CRM sync, adaptive workflows, and approvals.' date: 2026-09-30 updated: 2026-09-30 authors: diff --git a/apps/sim/content/library/mcp-security/index.mdx b/apps/sim/content/library/mcp-security/index.mdx index 46c2f5a1982..ab7fdb4adff 100644 --- a/apps/sim/content/library/mcp-security/index.mdx +++ b/apps/sim/content/library/mcp-security/index.mdx @@ -2,6 +2,7 @@ slug: mcp-security title: 'MCP Security: A Practical Guide to Secure MCP Server Development' description: 'MCP security covers the risks, auth flaws, and prompt-injection threats in Model Context Protocol servers; here''s how to build and deploy MCP servers securely.' +seoTitle: 'MCP Security: How to Build Secure MCP Servers' date: 2026-07-22 updated: 2026-10-06 authors: diff --git a/apps/sim/content/library/n8n-alternatives/index.mdx b/apps/sim/content/library/n8n-alternatives/index.mdx index 3861184d082..92e334a4200 100644 --- a/apps/sim/content/library/n8n-alternatives/index.mdx +++ b/apps/sim/content/library/n8n-alternatives/index.mdx @@ -2,6 +2,8 @@ slug: n8n-alternatives title: '10 Best n8n Alternatives for AI Agent Workflows in 2026' description: 'Comparing the 10 best n8n alternatives in 2026 for AI agent workflows - covering Sim, Make, Zapier, Activepieces, Pipedream, and more, with pricing and use cases.' +seoTitle: '10 Best n8n Alternatives in 2026: Open Source & Paid' +seoDescription: 'The best n8n alternatives for 2026, compared on pricing, free plans, license and self-hosting: Sim, Make, Zapier, Activepieces and more.' date: 2026-07-13 updated: 2026-10-16 authors: diff --git a/apps/sim/content/library/open-source-ai-agent-platforms/index.mdx b/apps/sim/content/library/open-source-ai-agent-platforms/index.mdx index 1ca792a2473..7b65c5adf50 100644 --- a/apps/sim/content/library/open-source-ai-agent-platforms/index.mdx +++ b/apps/sim/content/library/open-source-ai-agent-platforms/index.mdx @@ -2,6 +2,8 @@ slug: open-source-ai-agent-platforms title: 'Best Open Source AI Agent Platforms in 2026, Compared' description: 'Compare the best open source AI agent platforms by license, self hosting, integrations, and model support. See which fits your team and how to start.' +seoTitle: 'Open Source AI Agent Platforms (2026): Dify vs CrewAI' +seoDescription: 'Dify, LangGraph, CrewAI, AutoGen, n8n and Sim compared on license, self-hosting, local models and multi-agent support. Pick the right one fast.' date: 2026-07-13 updated: 2026-10-09 authors: diff --git a/apps/sim/content/library/openai-vs-n8n-vs-sim/index.mdx b/apps/sim/content/library/openai-vs-n8n-vs-sim/index.mdx index 52d3ec95adb..df8bf25d1e2 100644 --- a/apps/sim/content/library/openai-vs-n8n-vs-sim/index.mdx +++ b/apps/sim/content/library/openai-vs-n8n-vs-sim/index.mdx @@ -2,6 +2,8 @@ slug: openai-vs-n8n-vs-sim title: 'Sim vs n8n vs OpenAI AgentKit: AI Agent Builder Comparison (2026)' description: 'Sim vs n8n vs OpenAI AgentKit: how an agent-first, an automation-first, and an OpenAI-native agent builder differ on architecture, licensing, self-hosting, and model choice.' +seoTitle: 'Sim vs n8n vs OpenAI AgentKit: Which to Use in 2026?' +seoDescription: 'Sim, n8n and OpenAI AgentKit compared on model choice, self-hosting, licensing and pricing, with a clear pick for each type of team.' date: 2025-10-06 updated: 2026-10-01 authors: diff --git a/apps/sim/content/library/persistent-memory-ai-agents-across-sessions/index.mdx b/apps/sim/content/library/persistent-memory-ai-agents-across-sessions/index.mdx index e36783a73b8..e702f432f21 100644 --- a/apps/sim/content/library/persistent-memory-ai-agents-across-sessions/index.mdx +++ b/apps/sim/content/library/persistent-memory-ai-agents-across-sessions/index.mdx @@ -2,6 +2,7 @@ slug: persistent-memory-ai-agents-across-sessions title: 'How Do You Build an AI Agent That Remembers Context Across Multiple Sessions?' description: 'Learn how to build persistent memory for AI agents with durable storage, selective retrieval, context management, security controls, and cross-session state.' +seoTitle: 'Persistent Memory for AI Agents Across Sessions' date: 2026-10-16 updated: 2026-10-16 authors: diff --git a/apps/sim/content/library/pipedrive-crm-ai-agent-deal-follow-up/index.mdx b/apps/sim/content/library/pipedrive-crm-ai-agent-deal-follow-up/index.mdx index 523b1235312..072868c0c62 100644 --- a/apps/sim/content/library/pipedrive-crm-ai-agent-deal-follow-up/index.mdx +++ b/apps/sim/content/library/pipedrive-crm-ai-agent-deal-follow-up/index.mdx @@ -2,6 +2,7 @@ slug: pipedrive-crm-ai-agent-deal-follow-up title: 'How to Build a Pipedrive CRM AI Agent for Deal Follow-Ups' description: 'Build a Pipedrive CRM AI agent that evaluates deal context, drafts personalized follow-ups, requires human approval, and writes outcomes back safely.' +seoTitle: 'How to Build a Pipedrive AI Agent for Deal Follow-Ups' date: 2026-10-10 updated: 2026-10-10 authors: diff --git a/apps/sim/content/library/postgres-mcp-server-ai-agents/index.mdx b/apps/sim/content/library/postgres-mcp-server-ai-agents/index.mdx index bf62d64a28d..2ddc33969d7 100644 --- a/apps/sim/content/library/postgres-mcp-server-ai-agents/index.mdx +++ b/apps/sim/content/library/postgres-mcp-server-ai-agents/index.mdx @@ -2,6 +2,7 @@ slug: postgres-mcp-server-ai-agents title: 'How to Use a Postgres MCP Server With AI Agents: Security, Deployment, and Evaluation' description: 'Learn how to connect a Postgres MCP server to AI agents with restricted roles, bounded queries, private deployment, audit logs, and production evaluations.' +seoTitle: 'Postgres MCP Server for AI Agents: Security Guide' date: 2026-10-02 updated: 2026-10-02 authors: diff --git a/apps/sim/content/library/reproducible-ai-coding-agent-benchmark/index.mdx b/apps/sim/content/library/reproducible-ai-coding-agent-benchmark/index.mdx index a67de032605..238c61f1ff1 100644 --- a/apps/sim/content/library/reproducible-ai-coding-agent-benchmark/index.mdx +++ b/apps/sim/content/library/reproducible-ai-coding-agent-benchmark/index.mdx @@ -2,6 +2,7 @@ slug: reproducible-ai-coding-agent-benchmark title: 'AI coding-agent benchmark: a reproducible test of debugging, test generation, and refactoring' description: 'A reproducible AI coding-agent benchmark protocol for measuring debugging, unit-test generation, and multi-file refactoring performance with auditable runs.' +seoTitle: 'AI Coding Agent Benchmark: A Reproducible Test' date: 2026-09-20 updated: 2026-10-01 authors: diff --git a/apps/sim/content/library/salesforce-ai-agent-lead-scoring-workflow/index.mdx b/apps/sim/content/library/salesforce-ai-agent-lead-scoring-workflow/index.mdx index 66a3dffd58c..3657ce03315 100644 --- a/apps/sim/content/library/salesforce-ai-agent-lead-scoring-workflow/index.mdx +++ b/apps/sim/content/library/salesforce-ai-agent-lead-scoring-workflow/index.mdx @@ -2,6 +2,7 @@ slug: salesforce-ai-agent-lead-scoring-workflow title: 'How Do You Build a Salesforce AI Lead-Scoring Agent With Sim?' description: 'Build a Salesforce AI lead-scoring agent in Sim with deterministic scoring, human approval, controlled CRM writes, and repeatable verification.' +seoTitle: 'How to Build a Salesforce AI Lead-Scoring Agent' date: 2026-10-08 updated: 2026-10-08 authors: diff --git a/apps/sim/content/library/sim-open-source-zapier-alternative/index.mdx b/apps/sim/content/library/sim-open-source-zapier-alternative/index.mdx index 6dbc7583d10..814c7066c0a 100644 --- a/apps/sim/content/library/sim-open-source-zapier-alternative/index.mdx +++ b/apps/sim/content/library/sim-open-source-zapier-alternative/index.mdx @@ -2,6 +2,7 @@ slug: sim-open-source-zapier-alternative title: 'Sim vs Zapier: Open-Source AI Agents vs Zaps, Compared' description: 'Sim vs Zapier, head to head: an Apache 2.0-core, self-hostable, BYOK AI workspace versus proprietary cloud Zaps and Zapier Agents across licensing, building, agent depth, deployment, and pricing.' +seoDescription: 'Sim vs Zapier: an open-source, self-hostable AI workspace with BYOK vs Zapier''s cloud Zaps and Agents, compared on license, agents, deployment, and pricing.' date: 2026-09-01 updated: 2026-10-06 authors: diff --git a/apps/sim/content/library/sim-vs-dedicated-chatbot-builders/index.mdx b/apps/sim/content/library/sim-vs-dedicated-chatbot-builders/index.mdx index f572ea42187..98f6ba02bdf 100644 --- a/apps/sim/content/library/sim-vs-dedicated-chatbot-builders/index.mdx +++ b/apps/sim/content/library/sim-vs-dedicated-chatbot-builders/index.mdx @@ -2,6 +2,7 @@ slug: sim-vs-dedicated-chatbot-builders title: 'Sim vs. Dedicated Chatbot Builders' description: 'Compare Sim with dedicated chatbot builders including Botpress, Intercom, Zendesk, and Tidio across model flexibility, integrations, deployment, and support use cases.' +seoDescription: 'Sim vs Botpress, Intercom, Zendesk, and Tidio: how an AI workspace compares with dedicated chatbot builders on model choice, integrations, and deployment.' date: 2026-09-01 updated: 2026-10-14 authors: diff --git a/apps/sim/content/library/sim-vs-dify-open-source-ai-workspace-vs-llm-app-rag-platform/index.mdx b/apps/sim/content/library/sim-vs-dify-open-source-ai-workspace-vs-llm-app-rag-platform/index.mdx index 708e054b530..175d1173841 100644 --- a/apps/sim/content/library/sim-vs-dify-open-source-ai-workspace-vs-llm-app-rag-platform/index.mdx +++ b/apps/sim/content/library/sim-vs-dify-open-source-ai-workspace-vs-llm-app-rag-platform/index.mdx @@ -2,6 +2,8 @@ slug: sim-vs-dify-open-source-ai-workspace-vs-llm-app-rag-platform title: 'Sim vs Dify: Open-Source AI Workspace vs LLM App / RAG Platform' description: 'Sim vs Dify head to head: how the two compare on visual workflow building, RAG, self-hosting, integrations, licensing, pricing, and team fit, and when to choose each.' +seoTitle: 'Sim vs Dify: AI Workspace vs LLM App and RAG Platform' +seoDescription: 'Sim vs Dify compared on visual workflow building, RAG, self-hosting, integrations, licensing, and pricing, with clear guidance on when to choose each.' date: 2026-08-05 updated: 2026-09-30 authors: diff --git a/apps/sim/content/library/slack-ai-agent-security-permissions-guide/index.mdx b/apps/sim/content/library/slack-ai-agent-security-permissions-guide/index.mdx index 9289c179eec..d73bb364b32 100644 --- a/apps/sim/content/library/slack-ai-agent-security-permissions-guide/index.mdx +++ b/apps/sim/content/library/slack-ai-agent-security-permissions-guide/index.mdx @@ -2,6 +2,7 @@ slug: slack-ai-agent-security-permissions-guide title: 'Slack AI Agent Security: Permissions, Data, and Audit Guide' description: 'Secure a Slack AI agent with least-privilege OAuth scopes, controlled data access, protected credentials, approval gates, and complete audit evidence.' +seoTitle: 'Slack AI Agent Security: Permissions and Audit Guide' date: 2026-10-03 updated: 2026-10-03 authors: diff --git a/apps/sim/content/library/stripe-billing-failed-payment-ai-agent-guide/index.mdx b/apps/sim/content/library/stripe-billing-failed-payment-ai-agent-guide/index.mdx index 8f419e5ae08..50d9bef59f3 100644 --- a/apps/sim/content/library/stripe-billing-failed-payment-ai-agent-guide/index.mdx +++ b/apps/sim/content/library/stripe-billing-failed-payment-ai-agent-guide/index.mdx @@ -2,6 +2,7 @@ slug: stripe-billing-failed-payment-ai-agent-guide title: 'How to Build a Stripe Billing and Failed-Payment AI Agent' description: 'Build a Stripe billing AI agent that flags failed payments, classifies the operational response, and keeps sensitive actions behind human approval.' +seoTitle: 'How to Build a Stripe Failed-Payment AI Agent' date: 2026-10-10 updated: 2026-10-12 authors: diff --git a/apps/sim/content/library/top-ai-assistants-2026/index.mdx b/apps/sim/content/library/top-ai-assistants-2026/index.mdx index 64a6b0bd0a4..32d4d772b17 100644 --- a/apps/sim/content/library/top-ai-assistants-2026/index.mdx +++ b/apps/sim/content/library/top-ai-assistants-2026/index.mdx @@ -2,6 +2,7 @@ slug: top-ai-assistants-2026 title: 'Top AI Assistants in 2026' description: 'Compare the top AI assistants in 2026 for personal productivity, research, writing, Microsoft 365, and autonomous work, including capabilities, pricing, privacy, and tradeoffs.' +seoDescription: 'ChatGPT, Claude, Gemini, Perplexity, Microsoft Copilot, Vellum, and Lindy compared for productivity, research, and writing, with pricing and privacy notes.' date: 2026-09-10 updated: 2026-10-06 authors: diff --git a/apps/sim/content/library/what-is-an-agentic-workflow/index.mdx b/apps/sim/content/library/what-is-an-agentic-workflow/index.mdx index 61449330783..a0e347d5f34 100644 --- a/apps/sim/content/library/what-is-an-agentic-workflow/index.mdx +++ b/apps/sim/content/library/what-is-an-agentic-workflow/index.mdx @@ -2,6 +2,7 @@ slug: what-is-an-agentic-workflow title: 'What Is an Agentic Workflow? (And How It''s Different From Automation)' description: 'Learn what an agentic workflow is, how it differs from traditional automation, and when to combine AI agent reasoning with deterministic workflow controls.' +seoTitle: 'What Is an Agentic Workflow? Definition and Examples' date: 2026-08-10 updated: 2026-10-12 authors: diff --git a/apps/sim/content/library/what-is-an-ai-agent-definition-how-it-works-and-examples/index.mdx b/apps/sim/content/library/what-is-an-ai-agent-definition-how-it-works-and-examples/index.mdx index ceea3bf6de6..7301dfc450b 100644 --- a/apps/sim/content/library/what-is-an-ai-agent-definition-how-it-works-and-examples/index.mdx +++ b/apps/sim/content/library/what-is-an-ai-agent-definition-how-it-works-and-examples/index.mdx @@ -2,6 +2,7 @@ slug: what-is-an-ai-agent-definition-how-it-works-and-examples title: 'What Is an AI Agent? Definition, How It Works, and Examples' description: 'Learn what an AI agent is, how its reasoning, tools, memory, and goal loop works, and how agents differ from chatbots and raw LLMs.' +seoTitle: 'What Is an AI Agent? Definition and Examples' date: 2026-07-31 updated: 2026-10-14 authors: diff --git a/apps/sim/content/library/why-no-code-ai-agents-need-live-web-access/index.mdx b/apps/sim/content/library/why-no-code-ai-agents-need-live-web-access/index.mdx index 503c4bd299c..24e323d47fb 100644 --- a/apps/sim/content/library/why-no-code-ai-agents-need-live-web-access/index.mdx +++ b/apps/sim/content/library/why-no-code-ai-agents-need-live-web-access/index.mdx @@ -2,6 +2,7 @@ slug: why-no-code-ai-agents-need-live-web-access title: 'Why No-Code AI Agents Need Live Web Access (And How to Wire It Up)' description: 'How to pair a no-code AI agent builder with a dedicated live-web layer—TinyFish Search, Fetch, Browser, and Agent—and wire it into Sim workflows.' +seoTitle: 'Why No-Code AI Agents Need Live Web Access' date: 2026-09-11 updated: 2026-09-11 authors: diff --git a/apps/sim/lib/content/registry-factory.ts b/apps/sim/lib/content/registry-factory.ts index c1c1b0b9199..829334020e0 100644 --- a/apps/sim/lib/content/registry-factory.ts +++ b/apps/sim/lib/content/registry-factory.ts @@ -189,6 +189,8 @@ export function createContentRegistry(config: ContentRegistryConfig): ContentReg slug: fm.slug, title: fm.title, description: fm.description, + seoTitle: fm.seoTitle, + seoDescription: fm.seoDescription, date: toIsoDate(fm.date), updated: fm.updated ? toIsoDate(fm.updated) : undefined, author: authors[0], diff --git a/apps/sim/lib/content/schema.ts b/apps/sim/lib/content/schema.ts index 8b187acb9f7..9eee1e09e67 100644 --- a/apps/sim/lib/content/schema.ts +++ b/apps/sim/lib/content/schema.ts @@ -22,6 +22,16 @@ export const ContentFrontmatterSchema = z slug: z.string().min(1), title: z.string().min(5), description: z.string().min(20), + /** + * Search and social title when `title` is too long for search results; the H1, cards, + * feeds, and JSON-LD `headline` keep `title`. Length enforced by `check:library-content`. + */ + seoTitle: z.string().min(5).optional(), + /** + * Search and social description when `description` is too long for search results; the + * on-page summary and JSON-LD keep `description`. Length enforced by `check:library-content`. + */ + seoDescription: z.string().min(20).optional(), date: z.coerce.date(), updated: z.coerce.date().optional(), authors: z.array(z.string()).min(1), @@ -64,6 +74,10 @@ export interface ContentMeta { slug: string title: string description: string + /** Search and social title; falls back to `title`. */ + seoTitle?: string + /** Search and social description; falls back to `description`. */ + seoDescription?: string date: string // ISO updated?: string // ISO author: Author diff --git a/apps/sim/lib/content/seo.ts b/apps/sim/lib/content/seo.ts index eb5fa310602..785f2beb565 100644 --- a/apps/sim/lib/content/seo.ts +++ b/apps/sim/lib/content/seo.ts @@ -26,9 +26,11 @@ export interface ContentSection { export function buildPostMetadata(post: ContentMeta): Metadata { const base = new URL(post.canonical) const baseUrl = `${base.protocol}//${base.host}` + const searchTitle = post.seoTitle ?? post.title + const searchDescription = post.seoDescription ?? post.description return { - title: post.title, - description: post.description, + title: searchTitle, + description: searchDescription, keywords: post.tags, authors: (post.authors && post.authors.length > 0 ? post.authors : [post.author]).map((a) => ({ name: a.name, @@ -41,8 +43,8 @@ export function buildPostMetadata(post: ContentMeta): Metadata { : { index: true, follow: true, googleBot: { index: true, follow: true } }, alternates: { canonical: post.canonical }, openGraph: { - title: post.title, - description: post.description, + title: searchTitle, + description: searchDescription, url: post.canonical, siteName: 'Sim', locale: 'en_US', @@ -64,8 +66,8 @@ export function buildPostMetadata(post: ContentMeta): Metadata { }, twitter: { card: 'summary_large_image', - title: post.title, - description: post.description, + title: searchTitle, + description: searchDescription, images: [post.ogImage], creator: post.author.url?.includes('x.com') ? `@${post.author.xHandle || ''}` : undefined, site: '@simdotai', @@ -303,7 +305,7 @@ export function buildAuthorMetadata( const canonical = `${SITE_URL}${section.basePath}/authors/${encodeURIComponent(id)}` const description = `Read articles by ${name} on the Sim ${section.name.toLowerCase()}.` return { - title: `${name} | Sim ${section.name}`, + title: `${name} | ${section.name}`, description, alternates: { canonical }, openGraph: { diff --git a/apps/sim/lib/tokenization/accurate.ts b/apps/sim/lib/tokenization/accurate.ts index 12d243f366b..66ee4152daa 100644 --- a/apps/sim/lib/tokenization/accurate.ts +++ b/apps/sim/lib/tokenization/accurate.ts @@ -43,8 +43,10 @@ const O200K_MODEL_FAMILY = /^(?:gpt-(?:4o|4\.1|4\.5|5|6|oss)|chatgpt-4o|o\d)/ * The encoding for a model id. Exact OpenAI names resolve through `js-tiktoken`; newer * OpenAI ids resolve by family; every other model (Claude, Gemini, …) has no public * tiktoken encoding and is approximated with `cl100k_base`. + * + * @public The token counter reaches this through a lazy `import()` knip cannot trace. */ -function resolveEncodingName(modelName: string): TiktokenEncoding { +export function resolveEncodingName(modelName: string): TiktokenEncoding { const cached = encodingNameByModel.get(modelName) if (cached) return cached let encodingName: TiktokenEncoding @@ -74,24 +76,32 @@ if (typeof process !== 'undefined') { } /** - * Get accurate token count for text using tiktoken - * This is the exact count OpenAI's API will use + * The tiktoken count, or `null` when encoding fails, so a caller can label its fallback as an + * estimate rather than an exact count. + * + * @public The token counter reaches this through a lazy `import()` knip cannot trace. */ -export function getAccurateTokenCount(text: string, modelName = 'text-embedding-3-small'): number { +export function getExactTokenCount(text: string, modelName: string): number | null { if (!text || text.length === 0) { return 0 } try { - const encoding = getEncoding(modelName) - const tokens = encoding.encode(text) - return tokens.length + return getEncoding(modelName).encode(text).length } catch (error) { logger.error('Error counting tokens with tiktoken:', error) - return Math.ceil(text.length / 4) + return null } } +/** + * Get accurate token count for text using tiktoken + * This is the exact count OpenAI's API will use + */ +export function getAccurateTokenCount(text: string, modelName = 'text-embedding-3-small'): number { + return getExactTokenCount(text, modelName) ?? Math.ceil(text.length / 4) +} + /** * Get individual tokens as strings for visualization * Returns an array of token strings that can be displayed with colors diff --git a/apps/sim/lib/tokenization/estimators.ts b/apps/sim/lib/tokenization/estimators.ts index 7e3dac06d13..c54c687f42c 100644 --- a/apps/sim/lib/tokenization/estimators.ts +++ b/apps/sim/lib/tokenization/estimators.ts @@ -1,11 +1,28 @@ /** * Character-heuristic token estimation. Exact, tokenizer-backed counting lives * in `@/lib/tokenization/accurate`. + * + * Keep this module free of `@/providers/utils` so the public token counter can + * load it in the browser. */ +import { createLogger } from '@sim/logger' import { MIN_TEXT_LENGTH_FOR_ESTIMATION, TOKENIZATION_CONFIG } from '@/lib/tokenization/constants' -import type { TokenEstimate } from '@/lib/tokenization/types' -import { getProviderConfig } from '@/lib/tokenization/utils' +import type { ProviderTokenizationConfig, TokenEstimate } from '@/lib/tokenization/types' + +const logger = createLogger('TokenizationEstimators') + +function getProviderConfig(providerId: string): ProviderTokenizationConfig { + const config = + TOKENIZATION_CONFIG.providers[providerId as keyof typeof TOKENIZATION_CONFIG.providers] + + if (!config) { + logger.debug(`No specific config for provider ${providerId}, using fallback`, { providerId }) + return TOKENIZATION_CONFIG.fallback + } + + return config +} /** * Estimates token count for text using provider-specific heuristics diff --git a/apps/sim/lib/tokenization/index.ts b/apps/sim/lib/tokenization/index.ts index 78983d94638..5b7359a4822 100644 --- a/apps/sim/lib/tokenization/index.ts +++ b/apps/sim/lib/tokenization/index.ts @@ -16,7 +16,6 @@ export { processStreamingBlockLog, processStreamingBlockLogs } from '@/lib/token export { extractTextContent, formatTokenCount, - getProviderConfig, getProviderForTokenization, hasRealCostData, hasRealTokenData, diff --git a/apps/sim/lib/tokenization/utils.ts b/apps/sim/lib/tokenization/utils.ts index 7430aad8c2f..700b20b1451 100644 --- a/apps/sim/lib/tokenization/utils.ts +++ b/apps/sim/lib/tokenization/utils.ts @@ -6,27 +6,12 @@ import { createLogger } from '@sim/logger' import { toError } from '@sim/utils/errors' import { LLM_BLOCK_TYPES, TOKENIZATION_CONFIG } from '@/lib/tokenization/constants' import { createTokenizationError } from '@/lib/tokenization/errors' -import type { ProviderTokenizationConfig, TokenUsage } from '@/lib/tokenization/types' +import type { TokenUsage } from '@/lib/tokenization/types' import type { BlockTokens } from '@/executor/types' import { getProviderFromModel } from '@/providers/utils' const logger = createLogger('TokenizationUtils') -/** - * Gets tokenization configuration for a specific provider - */ -export function getProviderConfig(providerId: string): ProviderTokenizationConfig { - const config = - TOKENIZATION_CONFIG.providers[providerId as keyof typeof TOKENIZATION_CONFIG.providers] - - if (!config) { - logger.debug(`No specific config for provider ${providerId}, using fallback`, { providerId }) - return TOKENIZATION_CONFIG.fallback - } - - return config -} - /** * Extracts provider ID from model name */ diff --git a/apps/sim/providers/anthropic/usage.ts b/apps/sim/providers/anthropic/usage.ts index 35ba5dfcc8f..ff06bb83dc7 100644 --- a/apps/sim/providers/anthropic/usage.ts +++ b/apps/sim/providers/anthropic/usage.ts @@ -1,5 +1,6 @@ import type { BlockTokens } from '@/executor/types' -import { LIST_PRICE_POLICY, type ModelUsage, priceModelUsage } from '@/providers/cost-policy' +import { LIST_PRICE_POLICY, priceModelUsage } from '@/providers/cost-policy' +import type { ModelUsage } from '@/providers/pricing' import type { ModelPricing } from '@/providers/types' export interface AnthropicUsageLike { diff --git a/apps/sim/providers/bedrock/utils.ts b/apps/sim/providers/bedrock/utils.ts index 14f7e7cc785..a33b7c42720 100644 --- a/apps/sim/providers/bedrock/utils.ts +++ b/apps/sim/providers/bedrock/utils.ts @@ -9,7 +9,7 @@ import { getErrorMessage } from '@sim/utils/errors' import { randomFloat } from '@sim/utils/random' import { toAnthropicModelUsage } from '@/providers/anthropic/usage' import { GEO_PROFILE_PREFIX_PATTERN, getBedrockBaseModelId } from '@/providers/bedrock/model-id' -import type { ModelUsage } from '@/providers/cost-policy' +import type { ModelUsage } from '@/providers/pricing' import type { AgentStreamEvent } from '@/providers/stream-events' import { trackForcedToolUsage } from '@/providers/utils' diff --git a/apps/sim/providers/cost-policy.ts b/apps/sim/providers/cost-policy.ts index 7bd801db8ee..2923933f968 100644 --- a/apps/sim/providers/cost-policy.ts +++ b/apps/sim/providers/cost-policy.ts @@ -1,6 +1,12 @@ import { getCostMultiplier } from '@/lib/core/config/env-flags' import type { NormalizedBlockOutput } from '@/executor/types' -import { getModelPricing, resolveModelTokenPricing } from '@/providers/pricing' +import { + getModelPricing, + type ModelUsage, + priceTokenUsage, + roundCost, + type TokenUsageCost, +} from '@/providers/pricing' import type { ModelPricing } from '@/providers/types' import { calculateCost, shouldBillModelUsage } from '@/providers/utils' @@ -20,10 +26,7 @@ import { calculateCost, shouldBillModelUsage } from '@/providers/utils' */ /** Cost shape written onto block output. Mirrors `BlockCost`. */ -export interface ModelCost { - input: number - output: number - total: number +export interface ModelCost extends TokenUsageCost { toolCost?: number pricing?: ModelPricing } @@ -58,10 +61,6 @@ const NOT_BILLED_PRICING: ModelPricing = Object.freeze({ updatedAt: new Date(0).toISOString(), }) -function roundCost(value: number): number { - return Number.parseFloat(value.toFixed(8)) -} - /** * Resolves the billing policy for a model response. * @@ -114,34 +113,9 @@ export function applyModelCostPolicy( } } -/** A cache write bucket and its premium over the model's base input rate. */ -export interface CacheWriteUsage { - tokens: number - /** Anthropic: 1.25 (5m) or 2 (1h). OpenAI: 1.25 on GPT-5.6+, free before it. */ - inputRateMultiplier: number -} - -/** - * Normalized token usage for one model call. - * - * Providers report cache usage in incompatible shapes — Anthropic's - * `input_tokens` already excludes cache tokens, while OpenAI's `cached_tokens` - * and Gemini's `cachedContentTokenCount` are subsets of their prompt totals. - * Each provider adapter resolves that difference before building this; nothing - * downstream branches on provider again. - */ -export interface ModelUsage { - /** Tokens billed at the base input rate. Always EXCLUDES cache reads/writes. */ - input: number - output: number - /** Tokens served from the provider's cache, billed at the cachedInput rate. */ - cacheRead?: number - /** Tokens written to the cache, each bucket at its own premium. */ - cacheWrites?: CacheWriteUsage[] -} - /** - * The single cache-aware pricing function. + * Applies the billing policy to normalized usage and prices it, through `priceTokenUsage` when + * the model has registered pricing. * * Every surface that turns tokens into a charge routes through here, so a cache * hit is priced identically whether it came from Anthropic, OpenAI, Gemini, or @@ -162,36 +136,14 @@ export function priceModelUsage( return notBilledCost() } - const cacheRead = usage.cacheRead ?? 0 - const cacheWrites = (usage.cacheWrites ?? []).filter((write) => write.tokens > 0) const pricing = getModelPricing(model) if (pricing) { - const totalInputTokens = - usage.input + cacheRead + cacheWrites.reduce((total, write) => total + write.tokens, 0) - const tokenPricing = resolveModelTokenPricing(pricing, totalInputTokens) - const inputRate = tokenPricing.input / 1_000_000 - const cachedInputRate = (tokenPricing.cachedInput ?? tokenPricing.input) / 1_000_000 - - const uncachedInputCost = usage.input * inputRate - const cacheReadCost = cacheRead * cachedInputRate - const cacheWriteCost = cacheWrites.reduce( - (total, write) => total + write.tokens * inputRate * write.inputRateMultiplier, - 0 - ) - const input = roundCost( - (uncachedInputCost + cacheReadCost + cacheWriteCost) * policy.multiplier - ) - const output = roundCost(usage.output * (tokenPricing.output / 1_000_000) * policy.multiplier) - - return { - input, - output, - total: roundCost(input + output), - pricing, - } + return { ...priceTokenUsage(pricing, usage, policy.multiplier), pricing } } + const cacheRead = usage.cacheRead ?? 0 + const cacheWrites = (usage.cacheWrites ?? []).filter((write) => write.tokens > 0) const multiplier = policy.multiplier const base = calculateCost(model, usage.input, usage.output, false, multiplier, multiplier) const read = cacheRead > 0 ? calculateCost(model, cacheRead, 0, true, multiplier, 0) : undefined diff --git a/apps/sim/providers/openai/utils.ts b/apps/sim/providers/openai/utils.ts index 4917411b358..335a59e5ff5 100644 --- a/apps/sim/providers/openai/utils.ts +++ b/apps/sim/providers/openai/utils.ts @@ -6,7 +6,7 @@ import { getNativeConversationMessage, retainConversationMessageSource, } from '@/providers/conversation-metadata' -import type { ModelUsage } from '@/providers/cost-policy' +import type { ModelUsage } from '@/providers/pricing' import type { AgentStreamEvent } from '@/providers/stream-events' import type { Message } from '@/providers/types' diff --git a/apps/sim/providers/pricing.ts b/apps/sim/providers/pricing.ts index 2c335651a23..0502c463b0b 100644 --- a/apps/sim/providers/pricing.ts +++ b/apps/sim/providers/pricing.ts @@ -2,19 +2,19 @@ import { getEmbeddingModelPricing, getModelPricing as getModelPricingFromDefinitions, } from '@/providers/models' -import type { ModelPricing, ModelTokenPricing } from '@/providers/types' +import type { ModelPricing, ModelPricingTier } from '@/providers/types' /** Returns registered model pricing without applying the display-only fallback. */ export function getModelPricing(modelId: string): ModelPricing | null { return getEmbeddingModelPricing(modelId) ?? getModelPricingFromDefinitions(modelId) } -/** Resolves the token rates that apply to the full request input size. */ +/** Resolves the rates for the full request input size: the base pricing or the matching tier. */ export function resolveModelTokenPricing( pricing: ModelPricing, inputTokens: number -): ModelTokenPricing { - let resolved: ModelTokenPricing = pricing +): ModelPricing | ModelPricingTier { + let resolved: ModelPricing | ModelPricingTier = pricing let resolvedThreshold = Number.NEGATIVE_INFINITY for (const tier of pricing.tiers ?? []) { @@ -26,3 +26,80 @@ export function resolveModelTokenPricing( return resolved } + +/** A cache write bucket and its premium over the model's base input rate. */ +interface CacheWriteUsage { + tokens: number + /** Anthropic: 1.25 (5m) or 2 (1h). OpenAI: 1.25 on GPT-5.6+, free before it. */ + inputRateMultiplier: number +} + +/** + * Normalized token usage for one model call. + * + * Providers report cache usage in incompatible shapes — Anthropic's + * `input_tokens` already excludes cache tokens, while OpenAI's `cached_tokens` + * and Gemini's `cachedContentTokenCount` are subsets of their prompt totals. + * Each provider adapter resolves that difference before building this; nothing + * downstream branches on provider again. + */ +export interface ModelUsage { + /** Tokens billed at the base input rate. Always EXCLUDES cache reads/writes. */ + input: number + output: number + /** Tokens served from the provider's cache, billed at the cachedInput rate. */ + cacheRead?: number + /** Tokens written to the cache, each bucket at its own premium. */ + cacheWrites?: CacheWriteUsage[] +} + +/** USD cost of one model call, split into input and output. */ +export interface TokenUsageCost { + input: number + output: number + total: number +} + +/** Rounds a USD amount to the 8 decimal places every recorded cost carries. */ +export function roundCost(value: number): number { + return Number.parseFloat(value.toFixed(8)) +} + +/** + * Prices normalized usage against a model's pricing table: the input-size tier + * is resolved from every input token (uncached, cache reads, and cache writes), + * cache reads bill at that tier's `cachedInput` rate (the input rate when the + * model has none), and each cache write bills at the input rate times its + * premium. `multiplier` scales input and output before rounding. + * + * Pure and dependency-free so the billing path and public cost estimators + * share one computation. + */ +export function priceTokenUsage( + pricing: ModelPricing, + usage: ModelUsage, + multiplier = 1 +): TokenUsageCost { + const cacheRead = usage.cacheRead ?? 0 + const cacheWrites = (usage.cacheWrites ?? []).filter((write) => write.tokens > 0) + const totalInputTokens = + usage.input + cacheRead + cacheWrites.reduce((total, write) => total + write.tokens, 0) + const tokenPricing = resolveModelTokenPricing(pricing, totalInputTokens) + const inputRate = tokenPricing.input / 1_000_000 + const cachedInputRate = (tokenPricing.cachedInput ?? tokenPricing.input) / 1_000_000 + + const uncachedInputCost = usage.input * inputRate + const cacheReadCost = cacheRead * cachedInputRate + const cacheWriteCost = cacheWrites.reduce( + (total, write) => total + write.tokens * inputRate * write.inputRateMultiplier, + 0 + ) + const input = roundCost((uncachedInputCost + cacheReadCost + cacheWriteCost) * multiplier) + const output = roundCost(usage.output * (tokenPricing.output / 1_000_000) * multiplier) + + return { + input, + output, + total: roundCost(input + output), + } +} diff --git a/apps/sim/providers/types.ts b/apps/sim/providers/types.ts index ad38a079106..7dfdbc0479c 100644 --- a/apps/sim/providers/types.ts +++ b/apps/sim/providers/types.ts @@ -32,7 +32,7 @@ export type ProviderId = | 'litellm' | 'bedrock' -export interface ModelTokenPricing { +interface ModelTokenPricing { input: number // Per 1M tokens cachedInput?: number // Per 1M tokens (if supported) output: number // Per 1M tokens diff --git a/apps/sim/scripts/test-landing-seo-e2e.ts b/apps/sim/scripts/test-landing-seo-e2e.ts index 06c1ce93bca..22f71fab558 100644 --- a/apps/sim/scripts/test-landing-seo-e2e.ts +++ b/apps/sim/scripts/test-landing-seo-e2e.ts @@ -1,5 +1,5 @@ import assert from 'node:assert/strict' -import { mkdir, writeFile } from 'node:fs/promises' +import { mkdir, readFile, writeFile } from 'node:fs/promises' import { dirname, resolve } from 'node:path' import { LANDING_INTEGRATION_SLUGS, @@ -8,6 +8,7 @@ import { import integrationsJson from '@sim/deployment-config/integrations.json' import { createLogger } from '@sim/logger' import { getErrorMessage } from '@sim/utils/errors' +import matter from 'gray-matter' import { dustProfile, gumloopProfile, @@ -17,6 +18,7 @@ import { pipedreamProfile, vellumProfile, } from '@/lib/compare/data' +import { ContentFrontmatterSchema } from '@/lib/content/schema' import { INTEGRATION_SEO } from '@/app/(landing)/integrations/data/seo-content' /** @@ -93,6 +95,15 @@ async function get( return { status: response.status, location, body, headers: response.headers } } +function pageTitle(html: string): string { + return decodeHtml(/<title>([^<]*)<\/title>/.exec(html)?.[1] ?? '') +} + +function canonicalPath(html: string): string | null { + const href = /<link rel="canonical" href="([^"]+)"/.exec(html)?.[1] + return href ? new URL(href).pathname : null +} + function robotsDirectives(html: string): string[] { return [...html.matchAll(/<meta name="robots" content="([^"]*)"/g)].map((match) => match[1]) } @@ -164,6 +175,7 @@ async function run() { '/library/authors/not-a-real-author', '/models/not-a-real-provider', '/models/anthropic/not-a-real-model', + '/models/compare/not-a-real-pair', ] const failures: string[] = [] for (const path of unknown) { @@ -186,6 +198,10 @@ async function run() { firstPath(/^\/library\/(?!authors\/|tags)[^/]+$/), firstPath(/^\/integrations\/[^/]+$/), firstPath(/^\/models\/anthropic\/[^/]+$/), + '/models/context-windows', + '/models/pricing-calculator', + '/models/token-counter', + firstPath(/^\/models\/compare\/[^/]+$/), ] const failures: string[] = [] for (const path of known) { @@ -198,6 +214,98 @@ async function run() { assert.deepEqual(failures, []) }) + await check( + 'a library seoTitle and seoDescription are the search and social copy; title stays the H1', + async () => { + const slug = 'openai-vs-n8n-vs-sim' + const source = await readFile( + new URL(`../content/library/${slug}/index.mdx`, import.meta.url) + ) + const { title, seoTitle, description, seoDescription } = ContentFrontmatterSchema.parse( + matter(source.toString()).data + ) + assert(seoTitle && seoTitle !== title, `${slug} has no distinct seoTitle`) + assert( + seoDescription && seoDescription !== description, + `${slug} has no distinct seoDescription` + ) + const { status, body } = await get(`/library/${slug}`) + assert.equal(status, 200) + const capture = (pattern: RegExp) => { + const value = pattern.exec(body)?.[1] + return value === undefined ? undefined : decodeHtml(value) + } + assert.equal(capture(/<title>([^<]*)<\/title>/), `${seoTitle} | Sim`) + assert.equal(capture(/<meta property="og:title" content="([^"]*)"/), seoTitle) + assert.equal(capture(/<meta name="description" content="([^"]*)"/), seoDescription) + assert.equal(capture(/<h1[^>]*>([^<]*)<\/h1>/), title) + } + ) + + await check( + 'model pages carry distinct search titles within 60 characters and self-canonicalize', + async () => { + const comparePaths = sitemapPaths.filter((path) => path.startsWith('/models/compare/')) + assert( + comparePaths.length >= 5, + `The sitemap lists only ${comparePaths.length} compare pages` + ) + const paths = [ + '/models', + '/models/anthropic', + '/models/context-windows', + '/models/pricing-calculator', + '/models/token-counter', + ...comparePaths, + ] + const pathByTitle = new Map<string, string>() + let anthropicTitle = '' + const failures: string[] = [] + for (const path of paths) { + const { status, body } = await get(path) + const title = pageTitle(body) + const canonical = canonicalPath(body) + const duplicateOf = pathByTitle.get(title) + pathByTitle.set(title, duplicateOf ?? path) + if (path === '/models/anthropic') anthropicTitle = title + if ( + status !== 200 || + !title.endsWith(' | Sim') || + title.endsWith(' | Sim | Sim') || + title.length > 60 || + duplicateOf || + canonical !== path || + !sitemap.has(path) + ) { + failures.push( + `${path} → ${status}, title "${title}" (${title.length})${duplicateOf ? ` duplicates ${duplicateOf}` : ''}, canonical ${canonical}, in sitemap ${sitemap.has(path)}` + ) + } + } + assert.deepEqual(failures, []) + assert.match(anthropicTitle, /Claude/, '/models/anthropic does not name Claude in its title') + } + ) + + await check('token counter ships no js-tiktoken code in its initial load', async () => { + const { status, body } = await get('/models/token-counter') + assert.equal(status, 200) + // Every js-tiktoken rank table is keyed `bpe_ranks` and its encoder reads that property, so the + // name appears in any chunk that bundles the tokenizer, which must load only on interaction. + const marker = /bpe_ranks/ + assert(!marker.test(body), 'The initial HTML embeds js-tiktoken code') + const scripts = [...body.matchAll(/<script[^>]+src="([^"]+)"/g)] + .map((match) => new URL(decodeHtml(match[1]), baseUrl)) + .filter((url) => url.origin === baseUrl.origin) + assert(scripts.length > 0, 'The initial HTML references no scripts') + const offenders: string[] = [] + for (const url of scripts) { + const script = await get(`${url.pathname}${url.search}`) + if (marker.test(script.body)) offenders.push(url.pathname) + } + assert.deepEqual(offenders, [], 'An initial script chunk bundles js-tiktoken') + }) + await check('docs integration slugs redirect to their landing page', async () => { const failures: string[] = [] for (const [docsSlug, landingSlug] of LANDING_SLUG_BY_DOCS_SLUG) { diff --git a/scripts/check-library-content.test.ts b/scripts/check-library-content.test.ts index efc85dc8eb4..52ecfa18242 100644 --- a/scripts/check-library-content.test.ts +++ b/scripts/check-library-content.test.ts @@ -254,6 +254,27 @@ describe('check-library-content', () => { expect(findings[0]).toMatchObject({ line: 3, rule: 'frontmatter' }) }) + it('rejects a published search title over 60 or description over 160 characters at the key search reads, and exempts drafts', async () => { + const long = { title: 'T'.repeat(55), description: 'D'.repeat(161) } + writePost('library', 'post', 'Body.', long) + expect(await findingsFor('library', 'post')).toEqual([ + expect.objectContaining({ line: 3, rule: 'search-title' }), + expect.objectContaining({ line: 4, rule: 'search-description' }), + ]) + writePost('library', 'post', 'Body.', { + ...long, + seoTitle: 'S'.repeat(54), + seoDescription: 'E'.repeat(160), + }) + expect(await findingsFor('library', 'post')).toEqual([]) + writePost('library', 'post', 'Body.', { seoTitle: 'S'.repeat(55) }) + expect(await findingsFor('library', 'post')).toEqual([ + expect.objectContaining({ line: 8, rule: 'search-title' }), + ]) + writePost('library', 'post', 'Body.', { ...long, draft: 'true' }) + expect(await findingsFor('library', 'post')).toEqual([]) + }) + it('rejects invalid YAML', async () => { writePost('library', 'post', 'Body.', { title: 'Broken: [unclosed' }) expect((await findingsFor('library', 'post'))[0]).toMatchObject({ rule: 'frontmatter' }) diff --git a/scripts/check-library-content.ts b/scripts/check-library-content.ts index adb0993bcea..a2a7ba5fd2d 100644 --- a/scripts/check-library-content.ts +++ b/scripts/check-library-content.ts @@ -26,6 +26,9 @@ * belong to `check:site-urls`. * Changelog integration, model, and docs links also resolve against the generated catalogs, * docs source files, and generated OpenAPI operation pages, including reference-style links. + * - `search-title` / `search-description`: a published post's search title (`seoTitle ?? title` + * with the " | Sim" suffix) fits in 60 characters and its search description + * (`seoDescription ?? description`) in 160, the lengths Google shows before truncating. * * Run one post with `--slug <section>/<slug>` or `--slug <slug>`. */ @@ -52,7 +55,21 @@ import { foldDocsIndexPath } from '../apps/sim/lib/mothership/docs/path' export const SECTIONS = ['blog', 'library', 'customers', 'changelog'] as const export type Section = (typeof SECTIONS)[number] -export type Rule = 'frontmatter' | 'slug' | 'og-image' | 'mdx' | 'media' | 'faq' | 'internal-link' +export type Rule = + | 'frontmatter' + | 'slug' + | 'og-image' + | 'mdx' + | 'media' + | 'faq' + | 'internal-link' + | 'search-title' + | 'search-description' + +/** Appended to every post title by the root layout's `title.template`. */ +const SEARCH_TITLE_SUFFIX = ' | Sim' +const SEARCH_TITLE_MAX_LENGTH = 60 +const SEARCH_DESCRIPTION_MAX_LENGTH = 160 export interface Finding { file: string @@ -408,6 +425,28 @@ export async function checkPost( } } + if (result.success && !result.data.draft) { + const { seoTitle, title, seoDescription, description } = result.data + const searchTitle = `${seoTitle ?? title}${SEARCH_TITLE_SUFFIX}` + if (searchTitle.length > SEARCH_TITLE_MAX_LENGTH) { + report( + lineOfKey(frontmatterLines, seoTitle ? 'seoTitle' : 'title'), + 'search-title', + `Search title "${searchTitle}" is ${searchTitle.length} characters; Google truncates past ${SEARCH_TITLE_MAX_LENGTH}.`, + `${seoTitle ? 'Shorten seoTitle' : 'Add a seoTitle'} to at most ${SEARCH_TITLE_MAX_LENGTH - SEARCH_TITLE_SUFFIX.length} characters, leading with the searched keyword; title stays the H1.` + ) + } + const searchDescription = seoDescription ?? description + if (searchDescription.length > SEARCH_DESCRIPTION_MAX_LENGTH) { + report( + lineOfKey(frontmatterLines, seoDescription ? 'seoDescription' : 'description'), + 'search-description', + `Search description is ${searchDescription.length} characters; Google truncates past ${SEARCH_DESCRIPTION_MAX_LENGTH}.`, + `${seoDescription ? 'Shorten seoDescription' : 'Add a seoDescription'} to at most ${SEARCH_DESCRIPTION_MAX_LENGTH} characters; description stays the on-page summary.` + ) + } + } + if (Array.isArray(data.authors)) { for (const author of data.authors) { if (typeof author === 'string' && !authorIds.has(author)) { diff --git a/scripts/check-unused-exports.baseline.json b/scripts/check-unused-exports.baseline.json index 1ed84b49437..b8165c3fe12 100644 --- a/scripts/check-unused-exports.baseline.json +++ b/scripts/check-unused-exports.baseline.json @@ -186,7 +186,6 @@ "apps/sim/app/(landing)/models/utils.ts#formatCapabilityBoolean", "apps/sim/app/(landing)/models/utils.ts#formatFileSize", "apps/sim/app/(landing)/models/utils.ts#getCheapestProviderModel", - "apps/sim/app/(landing)/models/utils.ts#getLargestContextProviderModel", "apps/sim/app/(landing)/pricing/components/pricing-card/index.ts#PricingCardProps", "apps/sim/app/(landing)/pricing/components/pricing-plans/index.ts#PricingPlansProps", "apps/sim/app/(landing)/pricing/search-params.ts#BILLING_PERIODS", @@ -5010,7 +5009,6 @@ "apps/sim/lib/table/workflow-group-cancellation.ts#WorkflowGroupCancellationWrites", "apps/sim/lib/table/workflow-groups/deployment-mode.ts#DEFAULT_WORKFLOW_GROUP_DEPLOYMENT_MODE", "apps/sim/lib/terminal/tab-label.ts#namesItsCommand", - "apps/sim/lib/tokenization/accurate.ts#clearEncodingCache", "apps/sim/lib/tokenization/index.ts#LLM_BLOCK_TYPES", "apps/sim/lib/tokenization/index.ts#TOKENIZATION_CONFIG", "apps/sim/lib/tokenization/index.ts#TokenizationError", @@ -5019,7 +5017,6 @@ "apps/sim/lib/tokenization/index.ts#estimateInputTokens", "apps/sim/lib/tokenization/index.ts#estimateOutputTokens", "apps/sim/lib/tokenization/index.ts#extractTextContent", - "apps/sim/lib/tokenization/index.ts#getProviderConfig", "apps/sim/lib/tokenization/index.ts#getProviderForTokenization", "apps/sim/lib/tokenization/index.ts#hasRealCostData", "apps/sim/lib/tokenization/index.ts#hasRealTokenData", @@ -5269,7 +5266,6 @@ "apps/sim/providers/anthropic/streaming-tool-loop.ts#AnthropicStreamingToolLoopPayload", "apps/sim/providers/attachments.ts#AttachmentProvider", "apps/sim/providers/attachments.ts#getAttachmentProvider", - "apps/sim/providers/cost-policy.ts#CacheWriteUsage", "apps/sim/providers/gemini/core.ts#executeDeepResearchRequest", "apps/sim/providers/kie/utils.ts#KIE_API_BASE_URL", "apps/sim/providers/openai-compat/stream-events.ts#OpenAICompatStreamComplete", @@ -5283,7 +5279,6 @@ "apps/sim/providers/tool-call-id.ts#allocateExecutionLocalToolId", "apps/sim/providers/types.ts#EvaluationInput", "apps/sim/providers/types.ts#ModelPricingMap", - "apps/sim/providers/types.ts#ModelPricingTier", "apps/sim/providers/types.ts#providers", "apps/sim/providers/typesafe/schema.ts#parseJevJson", "apps/sim/providers/typesafe/schema.ts#parseJevQuestions",