-
-
All tasks
- {!isCollapsed && (
-
-
-
-
-
-
-
- New task
-
-
-
+
+
+
- {isCollapsed ? (
-
- {tasksLoading ? (
-
-
- Loading...
-
- ) : (
- tasks.map((task) => (
-
- ))
+ {!isCollapsed && (
+
+
+
+
+
+
+
+ New task
+
+
+
+
)}
-
- ) : (
-
- {tasksLoading ? (
-
- ) : (
- <>
- {tasks.slice(0, visibleTaskCount).map((task) => {
- const isCurrentRoute = task.id !== 'new' && pathname === task.href
- const isRenaming = taskFlyoutRename.editingId === task.id
- const isSelected = task.id !== 'new' && selectedTasks.has(task.id)
-
- if (isRenaming) {
+
+ {isCollapsed ? (
+
+ {tasksLoading ? (
+
+
+ Loading...
+
+ ) : (
+ tasks.map((task) => (
+
+ ))
+ )}
+
+ ) : (
+
+ {tasksLoading ? (
+
+ ) : (
+ <>
+ {tasks.slice(0, visibleTaskCount).map((task) => {
+ const isCurrentRoute = task.id !== 'new' && pathname === task.href
+ const isRenaming = taskFlyoutRename.editingId === task.id
+ const isSelected = task.id !== 'new' && selectedTasks.has(task.id)
+
+ if (isRenaming) {
+ return (
+
+
+ taskFlyoutRename.setValue(e.target.value)}
+ onKeyDown={taskFlyoutRename.handleKeyDown}
+ onBlur={handleTaskRenameBlur}
+ className='min-w-0 flex-1 border-none bg-transparent font-base text-[14px] text-[var(--text-body)] outline-none'
+ />
+
+ )
+ }
+
return (
-
-
- taskFlyoutRename.setValue(e.target.value)}
- onKeyDown={taskFlyoutRename.handleKeyDown}
- onBlur={handleTaskRenameBlur}
- className='min-w-0 flex-1 border-none bg-transparent font-base text-[14px] text-[var(--text-body)] outline-none'
- />
-
+ task={task}
+ isCurrentRoute={isCurrentRoute}
+ isSelected={isSelected}
+ isActive={!!task.isActive}
+ isUnread={!!task.isUnread}
+ isMenuOpen={menuOpenTaskId === task.id}
+ showCollapsedTooltips={showCollapsedTooltips}
+ onMultiSelectClick={handleTaskClick}
+ onContextMenu={handleTaskContextMenu}
+ onMorePointerDown={handleTaskMorePointerDown}
+ onMoreClick={handleTaskMoreClick}
+ />
)
- }
-
- return (
-
- )
- })}
- {tasks.length > visibleTaskCount && (
-
- )}
- >
- )}
-
- )}
-
+ })}
+ {tasks.length > visibleTaskCount && (
+
+ )}
+ >
+ )}
+
+ )}
+
-
-
-
Workflows
- {!isCollapsed && (
-
-
+
+
+
+ Workflows
+
+ {!isCollapsed && (
+
+
+
+
+
+
+
+
+
+ More actions
+
+
+
+
+
+ {isImporting ? 'Importing...' : 'Import workflow'}
+
+
+
+ {isCreatingFolder ? 'Creating folder...' : 'Create folder'}
+
+
+
-
-
-
+
- More actions
+ {isCreatingWorkflow ? (
+ Creating workflow...
+ ) : (
+
+ New workflow
+
+ )}
-
-
-
- {isImporting ? 'Importing...' : 'Import workflow'}
-
-
-
- {isCreatingFolder ? 'Creating folder...' : 'Create folder'}
-
-
-
-
-
-
-
-
- {isCreatingWorkflow ? (
- Creating workflow...
- ) : (
-
- New workflow
-
- )}
-
-
-
- )}
-
- {isCollapsed ? (
-
- {workflowsLoading && regularWorkflows.length === 0 ? (
-
-
- Loading...
-
- ) : regularWorkflows.length === 0 ? (
- No workflows yet
- ) : (
- <>
-
- {(workflowsByFolder.root || []).map((workflow) => (
-
+ )}
+
+ {isCollapsed ? (
+
+ {workflowsLoading && regularWorkflows.length === 0 ? (
+
+
+ Loading...
+
+ ) : regularWorkflows.length === 0 ? (
+ No workflows yet
+ ) : (
+ <>
+ handleCollapsedWorkflowOpenInNewTab(workflow)}
- onRename={() => handleCollapsedWorkflowRename(workflow)}
- canRename={canEdit}
+ onWorkflowOpenInNewTab={handleCollapsedWorkflowOpenInNewTab}
+ onWorkflowRename={handleCollapsedWorkflowRename}
+ canRenameWorkflow={canEdit}
/>
- ))}
- >
- )}
-
- ) : (
-
- {workflowsLoading && regularWorkflows.length === 0 && (
-
- )}
-
-
- )}
+ {(workflowsByFolder.root || []).map((workflow) => (
+
+ handleCollapsedWorkflowOpenInNewTab(workflow)
+ }
+ onRename={() => handleCollapsedWorkflowRename(workflow)}
+ canRename={canEdit}
+ />
+ ))}
+ >
+ )}
+
+ ) : (
+
+ {workflowsLoading && regularWorkflows.length === 0 && (
+
+ )}
+
+
+ )}
+
diff --git a/apps/sim/components/emcn/icons/index.ts b/apps/sim/components/emcn/icons/index.ts
index bf109bf5ba0..7df4c5fba0e 100644
--- a/apps/sim/components/emcn/icons/index.ts
+++ b/apps/sim/components/emcn/icons/index.ts
@@ -72,6 +72,8 @@ export { Table } from './table'
export { TableX } from './table-x'
export { TagIcon } from './tag'
export { TerminalWindow } from './terminal-window'
+export { ThumbsDown } from './thumbs-down'
+export { ThumbsUp } from './thumbs-up'
export { Trash } from './trash'
export { TrashOutline } from './trash-outline'
export { Trash2 } from './trash2'
diff --git a/apps/sim/components/emcn/icons/thumbs-down.tsx b/apps/sim/components/emcn/icons/thumbs-down.tsx
new file mode 100644
index 00000000000..41b0a8f2e4e
--- /dev/null
+++ b/apps/sim/components/emcn/icons/thumbs-down.tsx
@@ -0,0 +1,28 @@
+import type { SVGProps } from 'react'
+
+/**
+ * ThumbsDown icon component
+ * @param props - SVG properties including className, fill, etc.
+ */
+export function ThumbsDown(props: SVGProps
) {
+ return (
+
+ )
+}
diff --git a/apps/sim/components/emcn/icons/thumbs-up.tsx b/apps/sim/components/emcn/icons/thumbs-up.tsx
new file mode 100644
index 00000000000..a82445f7ed5
--- /dev/null
+++ b/apps/sim/components/emcn/icons/thumbs-up.tsx
@@ -0,0 +1,26 @@
+import type { SVGProps } from 'react'
+
+/**
+ * ThumbsUp icon component
+ * @param props - SVG properties including className, fill, etc.
+ */
+export function ThumbsUp(props: SVGProps) {
+ return (
+
+ )
+}
diff --git a/apps/sim/hooks/queries/copilot-feedback.ts b/apps/sim/hooks/queries/copilot-feedback.ts
new file mode 100644
index 00000000000..6901f93a2de
--- /dev/null
+++ b/apps/sim/hooks/queries/copilot-feedback.ts
@@ -0,0 +1,39 @@
+import { createLogger } from '@sim/logger'
+import { useMutation } from '@tanstack/react-query'
+
+const logger = createLogger('CopilotFeedbackMutation')
+
+interface SubmitFeedbackVariables {
+ chatId: string
+ userQuery: string
+ agentResponse: string
+ isPositiveFeedback: boolean
+ feedback?: string
+}
+
+interface SubmitFeedbackResponse {
+ success: boolean
+ feedbackId: string
+}
+
+export function useSubmitCopilotFeedback() {
+ return useMutation({
+ mutationFn: async (variables: SubmitFeedbackVariables): Promise => {
+ const response = await fetch('/api/copilot/feedback', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify(variables),
+ })
+
+ if (!response.ok) {
+ const data = await response.json().catch(() => ({}))
+ throw new Error(data.error || 'Failed to submit feedback')
+ }
+
+ return response.json()
+ },
+ onError: (error) => {
+ logger.error('Failed to submit copilot feedback:', error)
+ },
+ })
+}
From 893e322a49bc3af7e64265fe5d972f29a0e58979 Mon Sep 17 00:00:00 2001
From: Waleed
Date: Sat, 4 Apr 2026 11:22:52 -0700
Subject: [PATCH 2/4] fix(envvars): restore workflowUserId fallback for
scheduled execution env var resolution (#3941)
* fix(envvars): restore workflowUserId fallback for scheduled execution env var resolution
* test(envvars): add coverage for env var user resolution branches
---
.../workflows/executor/execution-core.test.ts | 89 +++++++++++++++++++
.../lib/workflows/executor/execution-core.ts | 7 +-
2 files changed, 94 insertions(+), 2 deletions(-)
diff --git a/apps/sim/lib/workflows/executor/execution-core.test.ts b/apps/sim/lib/workflows/executor/execution-core.test.ts
index 048ba62492a..689ebe5b2d0 100644
--- a/apps/sim/lib/workflows/executor/execution-core.test.ts
+++ b/apps/sim/lib/workflows/executor/execution-core.test.ts
@@ -123,6 +123,7 @@ describe('executeWorkflowCore terminal finalization sequencing', () => {
requestId: 'req-1',
workflowId: 'workflow-1',
userId: 'user-1',
+ workflowUserId: 'workflow-owner',
workspaceId: 'workspace-1',
triggerType: 'api',
executionId: 'execution-1',
@@ -755,4 +756,92 @@ describe('executeWorkflowCore terminal finalization sequencing', () => {
expect(safeCompleteWithErrorMock).not.toHaveBeenCalled()
expect(wasExecutionFinalizedByCore(envError, 'execution-no-log-start')).toBe(false)
})
+
+ it('uses sessionUserId for env resolution when isClientSession is true', async () => {
+ const snapshot = {
+ ...createSnapshot(),
+ metadata: {
+ ...createSnapshot().metadata,
+ isClientSession: true,
+ sessionUserId: 'session-user',
+ workflowUserId: 'workflow-owner',
+ },
+ }
+
+ getPersonalAndWorkspaceEnvMock.mockResolvedValue({
+ personalEncrypted: {},
+ workspaceEncrypted: {},
+ personalDecrypted: {},
+ workspaceDecrypted: {},
+ })
+ safeStartMock.mockResolvedValue(true)
+ executorExecuteMock.mockResolvedValue({
+ output: { done: true },
+ logs: [],
+ metadata: { duration: 123, startTime: 'start', endTime: 'end' },
+ })
+
+ await executeWorkflowCore({
+ snapshot: snapshot as any,
+ callbacks: {},
+ loggingSession: loggingSession as any,
+ })
+
+ expect(getPersonalAndWorkspaceEnvMock).toHaveBeenCalledWith('session-user', 'workspace-1')
+ })
+
+ it('uses workflowUserId for env resolution in server-side execution', async () => {
+ const snapshot = {
+ ...createSnapshot(),
+ metadata: {
+ ...createSnapshot().metadata,
+ isClientSession: false,
+ sessionUserId: undefined,
+ workflowUserId: 'workflow-owner',
+ userId: 'billing-actor',
+ },
+ }
+
+ getPersonalAndWorkspaceEnvMock.mockResolvedValue({
+ personalEncrypted: {},
+ workspaceEncrypted: {},
+ personalDecrypted: {},
+ workspaceDecrypted: {},
+ })
+ safeStartMock.mockResolvedValue(true)
+ executorExecuteMock.mockResolvedValue({
+ output: { done: true },
+ logs: [],
+ metadata: { duration: 123, startTime: 'start', endTime: 'end' },
+ })
+
+ await executeWorkflowCore({
+ snapshot: snapshot as any,
+ callbacks: {},
+ loggingSession: loggingSession as any,
+ })
+
+ expect(getPersonalAndWorkspaceEnvMock).toHaveBeenCalledWith('workflow-owner', 'workspace-1')
+ })
+
+ it('throws when workflowUserId is missing in server-side execution', async () => {
+ const snapshot = {
+ ...createSnapshot(),
+ metadata: {
+ ...createSnapshot().metadata,
+ isClientSession: false,
+ sessionUserId: undefined,
+ workflowUserId: undefined,
+ userId: 'billing-actor',
+ },
+ }
+
+ await expect(
+ executeWorkflowCore({
+ snapshot: snapshot as any,
+ callbacks: {},
+ loggingSession: loggingSession as any,
+ })
+ ).rejects.toThrow('Missing workflowUserId in execution metadata')
+ })
})
diff --git a/apps/sim/lib/workflows/executor/execution-core.ts b/apps/sim/lib/workflows/executor/execution-core.ts
index aa96e5668a5..b5b352d701f 100644
--- a/apps/sim/lib/workflows/executor/execution-core.ts
+++ b/apps/sim/lib/workflows/executor/execution-core.ts
@@ -325,10 +325,13 @@ export async function executeWorkflowCore(
const mergedStates = mergeSubblockStateWithValues(blocks)
- const personalEnvUserId = metadata.sessionUserId || metadata.userId
+ const personalEnvUserId =
+ metadata.isClientSession && metadata.sessionUserId
+ ? metadata.sessionUserId
+ : metadata.workflowUserId
if (!personalEnvUserId) {
- throw new Error('Missing execution actor for environment resolution')
+ throw new Error('Missing workflowUserId in execution metadata')
}
const { personalEncrypted, workspaceEncrypted, personalDecrypted, workspaceDecrypted } =
From 4a9439e952bd25ad34a5ecd33fac22dba8a37b21 Mon Sep 17 00:00:00 2001
From: Waleed
Date: Sat, 4 Apr 2026 11:53:54 -0700
Subject: [PATCH 3/4] improvement(models): tighten model metadata and crawl
discovery (#3942)
* improvement(models): tighten model metadata and crawl discovery
Made-with: Cursor
* revert hardcoded FF
* fix(models): narrow structured output ranking signal
Made-with: Cursor
* fix(models): remove generic best-for copy
Made-with: Cursor
* fix(models): restore best-for with stricter criteria
Made-with: Cursor
* fix
* models
---
.../models/[provider]/[model]/page.tsx | 12 ++--
apps/sim/app/(landing)/models/utils.test.ts | 49 +++++++++++++++
apps/sim/app/(landing)/models/utils.ts | 50 ++++++++++------
apps/sim/app/llms.txt/route.ts | 47 +++++++--------
apps/sim/app/sitemap.ts | 60 +++++++++++--------
apps/sim/providers/models.ts | 51 +++++++++++++---
apps/sim/providers/utils.test.ts | 43 +++++++++++--
7 files changed, 228 insertions(+), 84 deletions(-)
create mode 100644 apps/sim/app/(landing)/models/utils.test.ts
diff --git a/apps/sim/app/(landing)/models/[provider]/[model]/page.tsx b/apps/sim/app/(landing)/models/[provider]/[model]/page.tsx
index fd7557e37c7..c539d739daf 100644
--- a/apps/sim/app/(landing)/models/[provider]/[model]/page.tsx
+++ b/apps/sim/app/(landing)/models/[provider]/[model]/page.tsx
@@ -18,6 +18,7 @@ import {
formatPrice,
formatTokenCount,
formatUpdatedAt,
+ getEffectiveMaxOutputTokens,
getModelBySlug,
getPricingBounds,
getProviderBySlug,
@@ -198,7 +199,8 @@ export default async function ModelPage({
- {model.summary} {model.bestFor}
+ {model.summary}
+ {model.bestFor ? ` ${model.bestFor}` : ''}
@@ -229,13 +231,11 @@ export default async function ModelPage({
? `${formatPrice(model.pricing.cachedInput)}/1M`
: 'N/A'
}
- compact
/>
@@ -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'
}
/>
-
+ {model.bestFor ? : null}
diff --git a/apps/sim/app/(landing)/models/utils.test.ts b/apps/sim/app/(landing)/models/utils.test.ts
new file mode 100644
index 00000000000..894c74500c9
--- /dev/null
+++ b/apps/sim/app/(landing)/models/utils.test.ts
@@ -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()
+ })
+})
diff --git a/apps/sim/app/(landing)/models/utils.ts b/apps/sim/app/(landing)/models/utils.ts
index cdf79f87b7c..8e649c95c6b 100644
--- a/apps/sim/app/(landing)/models/utils.ts
+++ b/apps/sim/app/(landing)/models/utils.ts
@@ -112,7 +112,7 @@ export interface CatalogModel {
capabilities: ModelCapabilities
capabilityTags: string[]
summary: string
- bestFor: string
+ bestFor?: string
searchText: string
}
@@ -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')
}
@@ -326,7 +334,7 @@ function buildCapabilityTags(capabilities: ModelCapabilities): string[] {
tags.push('Tool choice')
}
- if (capabilities.nativeStructuredOutputs) {
+ if (supportsCatalogStructuredOutputs(capabilities)) {
tags.push('Structured outputs')
}
@@ -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) {
@@ -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.'
}
@@ -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(
@@ -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}`
@@ -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,
@@ -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 [
{
@@ -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',
@@ -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',
},
]
}
@@ -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
diff --git a/apps/sim/app/llms.txt/route.ts b/apps/sim/app/llms.txt/route.ts
index 79c79d086ec..89fbc5a67f4 100644
--- a/apps/sim/app/llms.txt/route.ts
+++ b/apps/sim/app/llms.txt/route.ts
@@ -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
@@ -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',
diff --git a/apps/sim/app/sitemap.ts b/apps/sim/app/sitemap.ts
index 6c95b859370..a558525950e 100644
--- a/apps/sim/app/sitemap.ts
+++ b/apps/sim/app/sitemap.ts
@@ -8,6 +8,34 @@ export default async function sitemap(): Promise
{
const baseUrl = getBaseUrl()
const now = new Date()
+ const integrationPages: MetadataRoute.Sitemap = integrations.map((integration) => ({
+ url: `${baseUrl}/integrations/${integration.slug}`,
+ lastModified: now,
+ }))
+ const modelHubPages: MetadataRoute.Sitemap = [
+ {
+ url: `${baseUrl}/integrations`,
+ lastModified: now,
+ },
+ {
+ url: `${baseUrl}/models`,
+ lastModified: now,
+ },
+ {
+ url: `${baseUrl}/partners`,
+ lastModified: now,
+ },
+ ]
+ const providerPages: MetadataRoute.Sitemap = MODEL_PROVIDERS_WITH_CATALOGS.map((provider) => ({
+ url: `${baseUrl}${provider.href}`,
+ lastModified: new Date(
+ Math.max(...provider.models.map((model) => new Date(model.pricing.updatedAt).getTime()))
+ ),
+ }))
+ const modelPages: MetadataRoute.Sitemap = ALL_CATALOG_MODELS.map((model) => ({
+ url: `${baseUrl}${model.href}`,
+ lastModified: new Date(model.pricing.updatedAt),
+ }))
const staticPages: MetadataRoute.Sitemap = [
{
@@ -26,14 +54,6 @@ export default async function sitemap(): Promise {
// url: `${baseUrl}/templates`,
// lastModified: now,
// },
- {
- url: `${baseUrl}/integrations`,
- lastModified: now,
- },
- {
- url: `${baseUrl}/models`,
- lastModified: now,
- },
{
url: `${baseUrl}/changelog`,
lastModified: now,
@@ -54,20 +74,12 @@ export default async function sitemap(): Promise {
lastModified: new Date(p.updated ?? p.date),
}))
- const integrationPages: MetadataRoute.Sitemap = integrations.map((i) => ({
- url: `${baseUrl}/integrations/${i.slug}`,
- lastModified: now,
- }))
-
- const providerPages: MetadataRoute.Sitemap = MODEL_PROVIDERS_WITH_CATALOGS.map((provider) => ({
- url: `${baseUrl}${provider.href}`,
- lastModified: now,
- }))
-
- const modelPages: MetadataRoute.Sitemap = ALL_CATALOG_MODELS.map((model) => ({
- url: `${baseUrl}${model.href}`,
- lastModified: new Date(model.pricing.updatedAt),
- }))
-
- return [...staticPages, ...blogPages, ...integrationPages, ...providerPages, ...modelPages]
+ return [
+ ...staticPages,
+ ...modelHubPages,
+ ...integrationPages,
+ ...providerPages,
+ ...modelPages,
+ ...blogPages,
+ ]
}
diff --git a/apps/sim/providers/models.ts b/apps/sim/providers/models.ts
index 3465f223230..5d293dd6047 100644
--- a/apps/sim/providers/models.ts
+++ b/apps/sim/providers/models.ts
@@ -271,6 +271,7 @@ export const PROVIDER_DEFINITIONS: Record = {
verbosity: {
values: ['low', 'medium', 'high'],
},
+ maxOutputTokens: 128000,
},
contextWindow: 400000,
},
@@ -290,6 +291,7 @@ export const PROVIDER_DEFINITIONS: Record = {
verbosity: {
values: ['low', 'medium', 'high'],
},
+ maxOutputTokens: 128000,
},
contextWindow: 400000,
},
@@ -324,6 +326,7 @@ export const PROVIDER_DEFINITIONS: Record = {
verbosity: {
values: ['low', 'medium', 'high'],
},
+ maxOutputTokens: 128000,
},
contextWindow: 400000,
},
@@ -342,6 +345,7 @@ export const PROVIDER_DEFINITIONS: Record = {
verbosity: {
values: ['low', 'medium', 'high'],
},
+ maxOutputTokens: 128000,
},
contextWindow: 400000,
},
@@ -360,6 +364,7 @@ export const PROVIDER_DEFINITIONS: Record = {
verbosity: {
values: ['low', 'medium', 'high'],
},
+ maxOutputTokens: 128000,
},
contextWindow: 400000,
},
@@ -373,6 +378,7 @@ export const PROVIDER_DEFINITIONS: Record = {
},
capabilities: {
temperature: { min: 0, max: 2 },
+ maxOutputTokens: 16384,
},
contextWindow: 128000,
},
@@ -449,6 +455,7 @@ export const PROVIDER_DEFINITIONS: Record = {
reasoningEffort: {
values: ['low', 'medium', 'high'],
},
+ maxOutputTokens: 100000,
},
contextWindow: 200000,
},
@@ -463,6 +470,7 @@ export const PROVIDER_DEFINITIONS: Record = {
},
capabilities: {
temperature: { min: 0, max: 2 },
+ maxOutputTokens: 16384,
},
contextWindow: 128000,
},
@@ -509,7 +517,7 @@ export const PROVIDER_DEFINITIONS: Record = {
capabilities: {
temperature: { min: 0, max: 1 },
nativeStructuredOutputs: true,
- maxOutputTokens: 128000,
+ maxOutputTokens: 64000,
thinking: {
levels: ['low', 'medium', 'high', 'max'],
default: 'high',
@@ -741,6 +749,7 @@ export const PROVIDER_DEFINITIONS: Record = {
verbosity: {
values: ['low', 'medium', 'high'],
},
+ maxOutputTokens: 128000,
},
contextWindow: 400000,
},
@@ -759,6 +768,7 @@ export const PROVIDER_DEFINITIONS: Record = {
verbosity: {
values: ['low', 'medium', 'high'],
},
+ maxOutputTokens: 128000,
},
contextWindow: 400000,
},
@@ -777,6 +787,7 @@ export const PROVIDER_DEFINITIONS: Record = {
verbosity: {
values: ['low', 'medium', 'high'],
},
+ maxOutputTokens: 128000,
},
contextWindow: 400000,
},
@@ -795,6 +806,7 @@ export const PROVIDER_DEFINITIONS: Record = {
verbosity: {
values: ['low', 'medium', 'high'],
},
+ maxOutputTokens: 128000,
},
contextWindow: 400000,
},
@@ -813,6 +825,7 @@ export const PROVIDER_DEFINITIONS: Record = {
verbosity: {
values: ['low', 'medium', 'high'],
},
+ maxOutputTokens: 128000,
},
contextWindow: 400000,
},
@@ -831,6 +844,7 @@ export const PROVIDER_DEFINITIONS: Record = {
verbosity: {
values: ['low', 'medium', 'high'],
},
+ maxOutputTokens: 128000,
},
contextWindow: 400000,
},
@@ -1067,6 +1081,7 @@ export const PROVIDER_DEFINITIONS: Record = {
levels: ['minimal', 'low', 'medium', 'high'],
default: 'high',
},
+ maxOutputTokens: 65536,
},
contextWindow: 1048576,
},
@@ -1084,6 +1099,7 @@ export const PROVIDER_DEFINITIONS: Record = {
levels: ['minimal', 'low', 'medium', 'high'],
default: 'minimal',
},
+ maxOutputTokens: 65536,
},
contextWindow: 1048576,
},
@@ -1101,6 +1117,7 @@ export const PROVIDER_DEFINITIONS: Record = {
levels: ['minimal', 'low', 'medium', 'high'],
default: 'high',
},
+ maxOutputTokens: 65536,
},
contextWindow: 1000000,
},
@@ -1114,6 +1131,7 @@ export const PROVIDER_DEFINITIONS: Record = {
},
capabilities: {
temperature: { min: 0, max: 2 },
+ maxOutputTokens: 65536,
},
contextWindow: 1048576,
},
@@ -1127,6 +1145,7 @@ export const PROVIDER_DEFINITIONS: Record = {
},
capabilities: {
temperature: { min: 0, max: 2 },
+ maxOutputTokens: 65536,
},
contextWindow: 1048576,
},
@@ -1140,6 +1159,7 @@ export const PROVIDER_DEFINITIONS: Record = {
},
capabilities: {
temperature: { min: 0, max: 2 },
+ maxOutputTokens: 65536,
},
contextWindow: 1048576,
},
@@ -1153,6 +1173,7 @@ export const PROVIDER_DEFINITIONS: Record = {
},
capabilities: {
temperature: { min: 0, max: 2 },
+ maxOutputTokens: 8192,
},
contextWindow: 1048576,
},
@@ -1165,6 +1186,7 @@ export const PROVIDER_DEFINITIONS: Record = {
},
capabilities: {
temperature: { min: 0, max: 2 },
+ maxOutputTokens: 8192,
},
contextWindow: 1048576,
},
@@ -1178,6 +1200,7 @@ export const PROVIDER_DEFINITIONS: Record = {
capabilities: {
deepResearch: true,
memory: false,
+ maxOutputTokens: 65536,
},
contextWindow: 1000000,
},
@@ -2094,6 +2117,7 @@ export const PROVIDER_DEFINITIONS: Record = {
capabilities: {
temperature: { min: 0, max: 1 },
nativeStructuredOutputs: true,
+ maxOutputTokens: 64000,
},
contextWindow: 200000,
},
@@ -2107,6 +2131,7 @@ export const PROVIDER_DEFINITIONS: Record = {
capabilities: {
temperature: { min: 0, max: 1 },
nativeStructuredOutputs: true,
+ maxOutputTokens: 64000,
},
contextWindow: 200000,
},
@@ -2120,6 +2145,7 @@ export const PROVIDER_DEFINITIONS: Record = {
capabilities: {
temperature: { min: 0, max: 1 },
nativeStructuredOutputs: true,
+ maxOutputTokens: 64000,
},
contextWindow: 200000,
},
@@ -2133,6 +2159,7 @@ export const PROVIDER_DEFINITIONS: Record = {
capabilities: {
temperature: { min: 0, max: 1 },
nativeStructuredOutputs: true,
+ maxOutputTokens: 64000,
},
contextWindow: 200000,
},
@@ -2337,6 +2364,7 @@ export const PROVIDER_DEFINITIONS: Record = {
},
capabilities: {
temperature: { min: 0, max: 1 },
+ maxOutputTokens: 32768,
},
contextWindow: 128000,
},
@@ -2373,6 +2401,7 @@ export const PROVIDER_DEFINITIONS: Record = {
},
capabilities: {
temperature: { min: 0, max: 1 },
+ maxOutputTokens: 16384,
},
contextWindow: 128000,
},
@@ -2385,6 +2414,7 @@ export const PROVIDER_DEFINITIONS: Record = {
},
capabilities: {
temperature: { min: 0, max: 1 },
+ maxOutputTokens: 40000,
},
contextWindow: 128000,
},
@@ -2397,6 +2427,7 @@ export const PROVIDER_DEFINITIONS: Record = {
},
capabilities: {
temperature: { min: 0, max: 1 },
+ maxOutputTokens: 8192,
},
contextWindow: 128000,
},
@@ -2409,6 +2440,7 @@ export const PROVIDER_DEFINITIONS: Record = {
},
capabilities: {
temperature: { min: 0, max: 1 },
+ maxOutputTokens: 8192,
},
contextWindow: 128000,
},
@@ -2421,6 +2453,7 @@ export const PROVIDER_DEFINITIONS: Record = {
},
capabilities: {
temperature: { min: 0, max: 1 },
+ maxOutputTokens: 8192,
},
contextWindow: 128000,
},
@@ -2863,13 +2896,17 @@ export function getModelsWithoutMemory(): string[] {
export function getMaxOutputTokensForModel(modelId: string): number {
const normalizedModelId = modelId.toLowerCase()
const STANDARD_MAX_OUTPUT_TOKENS = 4096
+ const allModels = Object.values(PROVIDER_DEFINITIONS).flatMap((provider) => provider.models)
- for (const provider of Object.values(PROVIDER_DEFINITIONS)) {
- for (const model of provider.models) {
- const baseModelId = model.id.toLowerCase()
- if (normalizedModelId === baseModelId || normalizedModelId.startsWith(`${baseModelId}-`)) {
- return model.capabilities.maxOutputTokens || STANDARD_MAX_OUTPUT_TOKENS
- }
+ const exactMatch = allModels.find((model) => model.id.toLowerCase() === normalizedModelId)
+ if (exactMatch) {
+ return exactMatch.capabilities.maxOutputTokens || STANDARD_MAX_OUTPUT_TOKENS
+ }
+
+ for (const model of allModels) {
+ const baseModelId = model.id.toLowerCase()
+ if (normalizedModelId.startsWith(`${baseModelId}-`)) {
+ return model.capabilities.maxOutputTokens || STANDARD_MAX_OUTPUT_TOKENS
}
}
diff --git a/apps/sim/providers/utils.test.ts b/apps/sim/providers/utils.test.ts
index 9e1491d9aca..0b46003ca4a 100644
--- a/apps/sim/providers/utils.test.ts
+++ b/apps/sim/providers/utils.test.ts
@@ -664,6 +664,45 @@ describe('Model Capabilities', () => {
describe('Max Output Tokens', () => {
describe('getMaxOutputTokensForModel', () => {
+ it.concurrent('should return published max for OpenAI GPT-4o', () => {
+ expect(getMaxOutputTokensForModel('gpt-4o')).toBe(16384)
+ })
+
+ it.concurrent('should return published max for OpenAI GPT-5.1', () => {
+ expect(getMaxOutputTokensForModel('gpt-5.1')).toBe(128000)
+ })
+
+ it.concurrent('should return published max for OpenAI GPT-5 Chat', () => {
+ expect(getMaxOutputTokensForModel('gpt-5-chat-latest')).toBe(16384)
+ })
+
+ it.concurrent('should return published max for OpenAI o1', () => {
+ expect(getMaxOutputTokensForModel('o1')).toBe(100000)
+ })
+
+ it.concurrent('should return updated max for Claude Sonnet 4.6', () => {
+ expect(getMaxOutputTokensForModel('claude-sonnet-4-6')).toBe(64000)
+ })
+
+ it.concurrent('should return published max for Gemini 2.5 Pro', () => {
+ expect(getMaxOutputTokensForModel('gemini-2.5-pro')).toBe(65536)
+ })
+
+ it.concurrent('should return published max for Azure GPT-5.2', () => {
+ expect(getMaxOutputTokensForModel('azure/gpt-5.2')).toBe(128000)
+ })
+
+ it.concurrent('should return standard default for models without maxOutputTokens', () => {
+ expect(getMaxOutputTokensForModel('deepseek-reasoner')).toBe(4096)
+ expect(getMaxOutputTokensForModel('grok-4-latest')).toBe(4096)
+ })
+
+ it.concurrent('should return published max for Bedrock Claude Opus 4.1', () => {
+ expect(getMaxOutputTokensForModel('bedrock/anthropic.claude-opus-4-1-20250805-v1:0')).toBe(
+ 64000
+ )
+ })
+
it.concurrent('should return correct max for Claude Opus 4.6', () => {
expect(getMaxOutputTokensForModel('claude-opus-4-6')).toBe(128000)
})
@@ -676,10 +715,6 @@ describe('Max Output Tokens', () => {
expect(getMaxOutputTokensForModel('claude-opus-4-1')).toBe(32000)
})
- it.concurrent('should return standard default for models without maxOutputTokens', () => {
- expect(getMaxOutputTokensForModel('gpt-4o')).toBe(4096)
- })
-
it.concurrent('should return standard default for unknown models', () => {
expect(getMaxOutputTokensForModel('unknown-model')).toBe(4096)
})
From c2b12cf21fac4ca4be27796d9ba3d094e805d3b5 Mon Sep 17 00:00:00 2001
From: Waleed
Date: Sat, 4 Apr 2026 12:34:53 -0700
Subject: [PATCH 4/4] fix(captcha): use getResponsePromise for Turnstile
execute-on-submit flow (#3943)
---
apps/sim/app/(auth)/signup/signup-form.tsx | 24 +++-------------------
1 file changed, 3 insertions(+), 21 deletions(-)
diff --git a/apps/sim/app/(auth)/signup/signup-form.tsx b/apps/sim/app/(auth)/signup/signup-form.tsx
index 7ddf2abbcaa..1b0bd50c05b 100644
--- a/apps/sim/app/(auth)/signup/signup-form.tsx
+++ b/apps/sim/app/(auth)/signup/signup-form.tsx
@@ -99,8 +99,6 @@ function SignupFormContent({
const [showEmailValidationError, setShowEmailValidationError] = useState(false)
const [formError, setFormError] = useState(null)
const turnstileRef = useRef(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') || '',
@@ -258,27 +256,14 @@ function SignupFormContent({
let token: string | undefined
const widget = turnstileRef.current
if (turnstileSiteKey && widget) {
- let timeoutId: ReturnType | undefined
try {
widget.reset()
- token = await Promise.race([
- new Promise((resolve, reject) => {
- captchaResolveRef.current = resolve
- captchaRejectRef.current = reject
- widget.execute()
- }),
- new Promise((_, 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
}
}
@@ -535,10 +520,7 @@ function SignupFormContent({
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' }}
/>
)}