Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 3 additions & 21 deletions apps/sim/app/(auth)/signup/signup-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -99,8 +99,6 @@ function SignupFormContent({
const [showEmailValidationError, setShowEmailValidationError] = useState(false)
const [formError, setFormError] = useState<string | null>(null)
const turnstileRef = useRef<TurnstileInstance>(null)
const captchaResolveRef = useRef<((token: string) => void) | null>(null)
const captchaRejectRef = useRef<((reason: Error) => void) | null>(null)
const turnstileSiteKey = useMemo(() => getEnv('NEXT_PUBLIC_TURNSTILE_SITE_KEY'), [])
const redirectUrl = useMemo(
() => searchParams.get('redirect') || searchParams.get('callbackUrl') || '',
Expand Down Expand Up @@ -258,27 +256,14 @@ function SignupFormContent({
let token: string | undefined
const widget = turnstileRef.current
if (turnstileSiteKey && widget) {
let timeoutId: ReturnType<typeof setTimeout> | undefined
try {
widget.reset()
token = await Promise.race([
new Promise<string>((resolve, reject) => {
captchaResolveRef.current = resolve
captchaRejectRef.current = reject
widget.execute()
}),
new Promise<string>((_, reject) => {
timeoutId = setTimeout(() => reject(new Error('Captcha timed out')), 15_000)
}),
])
widget.execute()
token = await widget.getResponsePromise()
} catch {
setFormError('Captcha verification failed. Please try again.')
setIsLoading(false)
return
} finally {
clearTimeout(timeoutId)
captchaResolveRef.current = null
captchaRejectRef.current = null
}
}

Expand Down Expand Up @@ -535,10 +520,7 @@ function SignupFormContent({
<Turnstile
ref={turnstileRef}
siteKey={turnstileSiteKey}
onSuccess={(token) => captchaResolveRef.current?.(token)}
onError={() => captchaRejectRef.current?.(new Error('Captcha verification failed'))}
onExpire={() => captchaRejectRef.current?.(new Error('Captcha token expired'))}
options={{ execution: 'execute' }}
options={{ execution: 'execute', appearance: 'execute' }}
/>
)}

Expand Down
12 changes: 6 additions & 6 deletions apps/sim/app/(landing)/models/[provider]/[model]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
formatPrice,
formatTokenCount,
formatUpdatedAt,
getEffectiveMaxOutputTokens,
getModelBySlug,
getPricingBounds,
getProviderBySlug,
Expand Down Expand Up @@ -198,7 +199,8 @@ export default async function ModelPage({
</div>

<p className='max-w-[820px] text-[17px] text-[var(--landing-text-muted)] leading-relaxed'>
{model.summary} {model.bestFor}
{model.summary}
{model.bestFor ? ` ${model.bestFor}` : ''}
</p>

<div className='mt-8 flex flex-wrap gap-3'>
Expand Down Expand Up @@ -229,13 +231,11 @@ export default async function ModelPage({
? `${formatPrice(model.pricing.cachedInput)}/1M`
: 'N/A'
}
compact
/>
<StatCard label='Output price' value={`${formatPrice(model.pricing.output)}/1M`} />
<StatCard
label='Context window'
value={model.contextWindow ? formatTokenCount(model.contextWindow) : 'Unknown'}
compact
/>
</section>

Expand Down Expand Up @@ -280,12 +280,12 @@ export default async function ModelPage({
label='Max output'
value={
model.capabilities.maxOutputTokens
? `${formatTokenCount(model.capabilities.maxOutputTokens)} tokens`
: 'Standard defaults'
? `${formatTokenCount(getEffectiveMaxOutputTokens(model.capabilities))} tokens`
: 'Not published'
}
/>
<DetailItem label='Provider' value={provider.name} />
<DetailItem label='Best for' value={model.bestFor} />
{model.bestFor ? <DetailItem label='Best for' value={model.bestFor} /> : null}
</div>
</section>

Expand Down
49 changes: 49 additions & 0 deletions apps/sim/app/(landing)/models/utils.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { describe, expect, it } from 'vitest'
import { buildModelCapabilityFacts, getEffectiveMaxOutputTokens, getModelBySlug } from './utils'

describe('model catalog capability facts', () => {
it.concurrent(
'shows structured outputs support and published max output tokens for gpt-4o',
() => {
const model = getModelBySlug('openai', 'gpt-4o')

expect(model).not.toBeNull()
expect(model).toBeDefined()

const capabilityFacts = buildModelCapabilityFacts(model!)
const structuredOutputs = capabilityFacts.find((fact) => fact.label === 'Structured outputs')
const maxOutputTokens = capabilityFacts.find((fact) => fact.label === 'Max output tokens')

expect(getEffectiveMaxOutputTokens(model!.capabilities)).toBe(16384)
expect(structuredOutputs?.value).toBe('Supported')
expect(maxOutputTokens?.value).toBe('16k')
}
)

it.concurrent('preserves native structured outputs labeling for claude models', () => {
const model = getModelBySlug('anthropic', 'claude-sonnet-4-6')

expect(model).not.toBeNull()
expect(model).toBeDefined()

const capabilityFacts = buildModelCapabilityFacts(model!)
const structuredOutputs = capabilityFacts.find((fact) => fact.label === 'Structured outputs')

expect(structuredOutputs?.value).toBe('Supported (native)')
})

it.concurrent('does not invent a max output token limit when one is not published', () => {
expect(getEffectiveMaxOutputTokens({})).toBeNull()
})

it.concurrent('keeps best-for copy for clearly differentiated models only', () => {
const researchModel = getModelBySlug('google', 'deep-research-pro-preview-12-2025')
const generalModel = getModelBySlug('xai', 'grok-4-latest')

expect(researchModel).not.toBeNull()
expect(generalModel).not.toBeNull()

expect(researchModel?.bestFor).toContain('research workflows')
expect(generalModel?.bestFor).toBeUndefined()
})
})
50 changes: 32 additions & 18 deletions apps/sim/app/(landing)/models/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ export interface CatalogModel {
capabilities: ModelCapabilities
capabilityTags: string[]
summary: string
bestFor: string
bestFor?: string
searchText: string
}

Expand Down Expand Up @@ -190,6 +190,14 @@ export function formatCapabilityBoolean(
return value ? positive : negative
}

function supportsCatalogStructuredOutputs(capabilities: ModelCapabilities): boolean {
return !capabilities.deepResearch
}

export function getEffectiveMaxOutputTokens(capabilities: ModelCapabilities): number | null {
return capabilities.maxOutputTokens ?? null
}

function trimTrailingZeros(value: string): string {
return value.replace(/\.0+$/, '').replace(/(\.\d*?)0+$/, '$1')
}
Expand Down Expand Up @@ -326,7 +334,7 @@ function buildCapabilityTags(capabilities: ModelCapabilities): string[] {
tags.push('Tool choice')
}

if (capabilities.nativeStructuredOutputs) {
if (supportsCatalogStructuredOutputs(capabilities)) {
tags.push('Structured outputs')
}

Expand Down Expand Up @@ -365,7 +373,7 @@ function buildBestForLine(model: {
pricing: PricingInfo
capabilities: ModelCapabilities
contextWindow: number | null
}): string {
}): string | null {
const { pricing, capabilities, contextWindow } = model

if (capabilities.deepResearch) {
Expand All @@ -376,10 +384,6 @@ function buildBestForLine(model: {
return 'Best for reasoning-heavy tasks that need more deliberate model control.'
}

if (pricing.input <= 0.2 && pricing.output <= 1.25) {
return 'Best for cost-sensitive automations, background tasks, and high-volume workloads.'
}

if (contextWindow && contextWindow >= 1000000) {
return 'Best for long-context retrieval, large documents, and high-memory workflows.'
}
Expand All @@ -388,7 +392,11 @@ function buildBestForLine(model: {
return 'Best for production workflows that need reliable typed outputs.'
}

return 'Best for general-purpose AI workflows inside Sim.'
if (pricing.input <= 0.2 && pricing.output <= 1.25) {
return 'Best for cost-sensitive automations, background tasks, and high-volume workloads.'
}

return null
}

function buildModelSummary(
Expand Down Expand Up @@ -437,6 +445,11 @@ const rawProviders = Object.values(PROVIDER_DEFINITIONS).map((provider) => {
const shortId = stripProviderPrefix(provider.id, model.id)
const mergedCapabilities = { ...provider.capabilities, ...model.capabilities }
const capabilityTags = buildCapabilityTags(mergedCapabilities)
const bestFor = buildBestForLine({
pricing: model.pricing,
capabilities: mergedCapabilities,
contextWindow: model.contextWindow ?? null,
})
const displayName = formatModelDisplayName(provider.id, model.id)
const modelSlug = slugify(shortId)
const href = `/models/${providerSlug}/${modelSlug}`
Expand All @@ -461,11 +474,7 @@ const rawProviders = Object.values(PROVIDER_DEFINITIONS).map((provider) => {
model.contextWindow ?? null,
capabilityTags
),
bestFor: buildBestForLine({
pricing: model.pricing,
capabilities: mergedCapabilities,
contextWindow: model.contextWindow ?? null,
}),
...(bestFor ? { bestFor } : {}),
searchText: [
provider.name,
providerDisplayName,
Expand Down Expand Up @@ -683,6 +692,7 @@ export function buildModelFaqs(provider: CatalogProvider, model: CatalogModel):

export function buildModelCapabilityFacts(model: CatalogModel): CapabilityFact[] {
const { capabilities } = model
const supportsStructuredOutputs = supportsCatalogStructuredOutputs(capabilities)

return [
{
Expand Down Expand Up @@ -711,7 +721,11 @@ export function buildModelCapabilityFacts(model: CatalogModel): CapabilityFact[]
},
{
label: 'Structured outputs',
value: formatCapabilityBoolean(capabilities.nativeStructuredOutputs),
value: supportsStructuredOutputs
? capabilities.nativeStructuredOutputs
? 'Supported (native)'
: 'Supported'
: 'Not supported',
},
{
label: 'Tool choice',
Expand All @@ -732,8 +746,8 @@ export function buildModelCapabilityFacts(model: CatalogModel): CapabilityFact[]
{
label: 'Max output tokens',
value: capabilities.maxOutputTokens
? formatTokenCount(capabilities.maxOutputTokens)
: 'Standard defaults',
? formatTokenCount(getEffectiveMaxOutputTokens(capabilities))
: 'Not published',
},
]
}
Expand All @@ -752,8 +766,8 @@ export function getProviderCapabilitySummary(provider: CatalogProvider): Capabil
const reasoningCount = provider.models.filter(
(model) => model.capabilities.reasoningEffort || model.capabilities.thinking
).length
const structuredCount = provider.models.filter(
(model) => model.capabilities.nativeStructuredOutputs
const structuredCount = provider.models.filter((model) =>
supportsCatalogStructuredOutputs(model.capabilities)
).length
const deepResearchCount = provider.models.filter(
(model) => model.capabilities.deepResearch
Expand Down
47 changes: 22 additions & 25 deletions apps/sim/app/llms.txt/route.ts
Original file line number Diff line number Diff line change
@@ -1,42 +1,44 @@
import { getBaseUrl } from '@/lib/core/utils/urls'

export async function GET() {
export function GET() {
const baseUrl = getBaseUrl()

const llmsContent = `# Sim
const content = `# Sim

> Sim is the open-source platform to build AI agents and run your agentic workforce. Connect 1,000+ integrations and LLMs to deploy and orchestrate agentic workflows.
> Sim is the open-source platform to build AI agents and run your agentic workforce. Connect integrations and LLMs to deploy and orchestrate agentic workflows.

Sim lets teams create agents, workflows, knowledge bases, tables, and docs. Over 100,000 builders use Sim — from startups to Fortune 500 companies. SOC2 compliant.
Sim lets teams create agents, workflows, knowledge bases, tables, and docs. It supports both product discovery pages and deeper technical documentation.

## Core Pages
## Preferred URLs

- [Homepage](${baseUrl}): Product overview, features, and pricing
- [Homepage](${baseUrl}): Product overview and primary entry point
- [Integrations directory](${baseUrl}/integrations): Public catalog of integrations and automation capabilities
- [Models directory](${baseUrl}/models): Public catalog of AI models, pricing, context windows, and capabilities
- [Blog](${baseUrl}/blog): Announcements, guides, and product context
- [Changelog](${baseUrl}/changelog): Product updates and release notes
- [Sim Blog](${baseUrl}/blog): Announcements, insights, and guides

## Documentation

- [Documentation](https://docs.sim.ai): Complete guides and API reference
- [Quickstart](https://docs.sim.ai/quickstart): Get started in 5 minutes
- [API Reference](https://docs.sim.ai/api): REST API documentation
- [Documentation](https://docs.sim.ai): Product guides and technical reference
- [Quickstart](https://docs.sim.ai/quickstart): Fastest path to getting started
- [API Reference](https://docs.sim.ai/api): API documentation

## Key Concepts

- **Workspace**: Container for workflows, data sources, and executions
- **Workflow**: Directed graph of blocks defining an agentic process
- **Block**: Individual step (LLM call, tool call, HTTP request, code execution)
- **Block**: Individual step such as an LLM call, tool call, HTTP request, or code execution
- **Trigger**: Event or schedule that initiates workflow execution
- **Execution**: A single run of a workflow with logs and outputs
- **Knowledge Base**: Vector-indexed document store for retrieval-augmented generation
- **Knowledge Base**: Document store used for retrieval-augmented generation

## Capabilities

- AI agent creation and deployment
- Agentic workflow orchestration
- 1,000+ integrations (Slack, Gmail, Notion, Airtable, databases, and more)
- Multi-model LLM orchestration (OpenAI, Anthropic, Google, Mistral, xAI, Perplexity)
- Knowledge base creation with retrieval-augmented generation (RAG)
- Integrations across business tools, databases, and communication platforms
- Multi-model LLM orchestration
- Knowledge bases and retrieval-augmented generation
- Table creation and management
- Document creation and processing
- Scheduled and webhook-triggered executions
Expand All @@ -45,24 +47,19 @@ Sim lets teams create agents, workflows, knowledge bases, tables, and docs. Over

- AI agent deployment and orchestration
- Knowledge bases and RAG pipelines
- Document creation and processing
- Customer support automation
- Internal operations (sales, marketing, legal, finance)
- Internal operations workflows across sales, marketing, legal, and finance

## Links
## Additional Links

- [GitHub Repository](https://github.com/simstudioai/sim): Open-source codebase
- [Discord Community](https://discord.gg/Hr4UWYEcTT): Get help and connect with 100,000+ builders
- [X/Twitter](https://x.com/simdotai): Product updates and announcements

## Optional

- [Careers](https://jobs.ashbyhq.com/sim): Join the Sim team
- [Docs](https://docs.sim.ai): Canonical documentation source
- [Terms of Service](${baseUrl}/terms): Legal terms
- [Privacy Policy](${baseUrl}/privacy): Data handling practices
- [Sitemap](${baseUrl}/sitemap.xml): Public URL inventory
`

return new Response(llmsContent, {
return new Response(content, {
headers: {
'Content-Type': 'text/markdown; charset=utf-8',
'Cache-Control': 'public, max-age=86400, s-maxage=86400',
Expand Down
Loading
Loading