Skip to content
Open
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
16 changes: 16 additions & 0 deletions apps/docs/components/icons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5614,6 +5614,22 @@ export function TogetherIcon(props: SVGProps<SVGSVGElement>) {
)
}

export function AtlasCloudIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg
{...props}
fill='currentColor'
fillRule='evenodd'
height='1em'
width='1em'
viewBox='0 0 24 24'
xmlns='http://www.w3.org/2000/svg'
>
<path d='M20.2,18.01L12,0.47,3.8,18.01l-2.58,5.52c1.62,-1.05,3.39,-1.86,5.26,-2.41,1.76,-0.51,3.61,-0.79,5.52,-0.79,0.98,0,1.95,0.08,2.9,0.22l-1.86,-4.3c-0.53,-0.1,-2.87,-0.1,-4.59,0.3l3.56,-8.28,5.52,12.85c0.01,0,0.02,0.01,0.03,0.01,1.86,0.55,3.62,1.36,5.23,2.4l-2.58,-5.52Z' />
</svg>
)
}
Comment on lines +5617 to +5631

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Docs icon is unused

The docs export AtlasCloudIcon, but nothing in the docs app imports it. The repository forbids exports that nothing imports. Remove this unused copy or connect it to an actual docs consumer. This repository requirement must be satisfied before merging.

Suggested change
export function AtlasCloudIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg
{...props}
fill='currentColor'
fillRule='evenodd'
height='1em'
width='1em'
viewBox='0 0 24 24'
xmlns='http://www.w3.org/2000/svg'
>
<path d='M20.2,18.01L12,0.47,3.8,18.01l-2.58,5.52c1.62,-1.05,3.39,-1.86,5.26,-2.41,1.76,-0.51,3.61,-0.79,5.52,-0.79,0.98,0,1.95,0.08,2.9,0.22l-1.86,-4.3c-0.53,-0.1,-2.87,-0.1,-4.59,0.3l3.56,-8.28,5.52,12.85c0.01,0,0.02,0.01,0.03,0.01,1.86,0.55,3.62,1.36,5.23,2.4l-2.58,-5.52Z' />
</svg>
)
}

Context Used: CLAUDE.md (source)

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!


export function BasetenIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg
Expand Down
1 change: 1 addition & 0 deletions apps/docs/content/docs/platform/costs.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@ The BYOK settings page groups providers the same way.
| Fireworks | LLM calls |
| Together AI | LLM calls |
| Baseten | LLM calls |
| Atlas Cloud | LLM calls |
| Ollama Cloud | LLM calls |
| Fal.ai | Image and video generation |
</Tab>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ import { Callout } from 'fumadocs-ui/components/callout'
| `TOGETHER_API_KEY` | Together AI |
| `FIREWORKS_API_KEY` | Fireworks AI |
| `BASETEN_API_KEY` | Baseten |
| `ATLASCLOUD_API_KEY` | Atlas Cloud |
| `COHERE_API_KEY` | Cohere — required for the Knowledge block reranker |
| `OLLAMA_URL` | Ollama (default: `http://localhost:11434`) |
| `VERTEX_PROJECT` / `VERTEX_LOCATION` | Google Vertex AI — GCP project ID and region (region defaults to `us-central1`) |
Expand Down
2 changes: 1 addition & 1 deletion apps/docs/content/docs/workflows/blocks/agent.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ The model that runs the step. Defaults to `claude-sonnet-5-5`. Type or pick any

For a custom cloud deployment, enter its provider prefix and model ID: `azure/my-deployment`, `azure-anthropic/my-deployment`, `bedrock/my-inference-profile`, or `vertex/my-gemini-model`. The prefix selects the provider and shows its credential fields even when the ID is absent from the catalog. Bedrock accepts full inference profile ARNs after `bedrock/`; Vertex uses the Gemini API and accepts Google model resource names. The deployment must support the selected provider's API. Custom IDs have no catalog pricing or token limits.

Ollama Cloud, OpenRouter, Fireworks, Together AI, Baseten, Ollama, vLLM, and LiteLLM load their available models from the configured provider. New models appear through that discovery without a Sim catalog release. You can also enter a namespaced ID directly, such as `ollama-cloud/deepseek-v4.1-flash`, `openrouter/provider/model`, or `ollama/my-local-model`. Provider prefixes are case-insensitive; the model ID after the prefix keeps its original casing.
Ollama Cloud, OpenRouter, Fireworks, Together AI, Baseten, Atlas Cloud, Ollama, vLLM, and LiteLLM load their available models from the configured provider. New models appear through that discovery without a Sim catalog release. You can also enter a namespaced ID directly, such as `ollama-cloud/deepseek-v4.1-flash`, `openrouter/provider/model`, or `ollama/my-local-model`. Provider prefixes are case-insensitive; the model ID after the prefix keeps its original casing.

### Jev evaluation models

Expand Down
1 change: 1 addition & 0 deletions apps/sim/app/(landing)/models/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ const PROVIDER_PREFIXES: Record<string, string[]> = {
fireworks: ['fireworks/'],
together: ['together/'],
baseten: ['baseten/'],
atlascloud: ['atlascloud/'],
'ollama-cloud': ['ollama-cloud/'],
groq: ['groq/'],
kie: ['kie/'],
Expand Down
82 changes: 82 additions & 0 deletions apps/sim/app/api/providers/atlascloud/models/route.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { authMockFns, createMockRequest, resetEnvMock, setEnv } from '@sim/testing'
import { jsonResponse } from '@sim/testing/helpers/http'
import { apiKeyByokMock, apiKeyByokMockFns } from '@sim/testing/mocks/api-key-byok.mock'
import { permissionsMock, permissionsMockFns } from '@sim/testing/mocks/permissions.mock'
import { providersUtilsMock, providersUtilsMockFns } from '@sim/testing/mocks/providers-utils.mock'
import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest'

vi.mock('@/providers/utils', () => providersUtilsMock)

vi.mock('@/lib/api-key/byok', () => apiKeyByokMock)

vi.mock('@/lib/workspaces/permissions/utils', () => permissionsMock)

import { GET } from '@/app/api/providers/atlascloud/models/route'

const mockGetSession = authMockFns.mockGetSession
const mockGetBYOKKey = apiKeyByokMockFns.mockGetBYOKKey
const { mockFilterBlacklistedModels, mockIsProviderBlacklisted } = providersUtilsMockFns
const { mockGetUserEntityPermissions } = permissionsMockFns

function setEnvKey(value: string | undefined): void {
setEnv({ ATLASCLOUD_API_KEY: value })
}

function authHeaderFromLastFetch(mockFetch: ReturnType<typeof vi.fn>): unknown {
const init = mockFetch.mock.calls.at(-1)?.[1] as RequestInit | undefined
return (init?.headers as Record<string, string> | undefined)?.Authorization
}

describe('GET /api/providers/atlascloud/models', () => {
let mockFetch: ReturnType<typeof vi.fn>

beforeEach(() => {
mockFetch = vi.fn()
vi.stubGlobal('fetch', mockFetch)

mockIsProviderBlacklisted.mockReturnValue(false)
mockFilterBlacklistedModels.mockImplementation((models: string[]) => models)
mockGetBYOKKey.mockResolvedValue(null)
mockGetSession.mockResolvedValue(null)
mockGetUserEntityPermissions.mockResolvedValue(null)
setEnvKey(undefined)
})

afterAll(() => {
resetEnvMock()
})

it('uses the BYOK key when workspaceId, session, and permission are present', async () => {
setEnvKey('env-key')
mockGetSession.mockResolvedValue({ user: { id: 'user-1' } })
mockGetUserEntityPermissions.mockResolvedValue('admin')
mockGetBYOKKey.mockResolvedValue({ apiKey: 'byok-key', isBYOK: true })
mockFetch.mockResolvedValueOnce(jsonResponse({ data: [{ id: 'model-a' }] }))

const res = await GET(
createMockRequest('GET', undefined, {}, 'http://localhost:3000/api/test?workspaceId=ws-1')
)

expect(res.status).toBe(200)
expect(await res.json()).toEqual({ models: ['atlascloud/model-a'] })

expect(mockGetBYOKKey).toHaveBeenCalledWith('ws-1', 'atlascloud')

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Tests assert mock calls

Both new suites assert mock calls, including mockGetBYOKKey here and mockExecuteTool in the provider suite. The repository explicitly says never to write tests that assert mocks were called. These checks depend on internal calls rather than proving the integration works. Replace them with tests through the real boundary that prove which key or tool result was used. This repository requirement must be satisfied before merging.

Context Used: CLAUDE.md (source)

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

expect(authHeaderFromLastFetch(mockFetch)).toBe('Bearer byok-key')
})

it('falls back to the env key when the user lacks workspace permission', async () => {
setEnvKey('env-key')
mockGetSession.mockResolvedValue({ user: { id: 'user-1' } })
mockGetUserEntityPermissions.mockResolvedValue(null)
mockFetch.mockResolvedValueOnce(jsonResponse({ data: [{ id: 'model-a' }] }))

const res = await GET(
createMockRequest('GET', undefined, {}, 'http://localhost:3000/api/test?workspaceId=ws-1')
)

expect(res.status).toBe(200)
expect(await res.json()).toEqual({ models: ['atlascloud/model-a'] })
expect(mockGetBYOKKey).not.toHaveBeenCalled()
expect(authHeaderFromLastFetch(mockFetch)).toBe('Bearer env-key')
})
})
93 changes: 93 additions & 0 deletions apps/sim/app/api/providers/atlascloud/models/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import { createLogger } from '@sim/logger'
import { getErrorMessage } from '@sim/utils/errors'
import { type NextRequest, NextResponse } from 'next/server'
import {
atlascloudProviderModelsQuerySchema,
atlascloudUpstreamResponseSchema,
providerModelsResponseSchema,
} from '@/lib/api/contracts/providers'
import { validationErrorResponse } from '@/lib/api/server'
import { getBYOKKey } from '@/lib/api-key/byok'
import { getSession } from '@/lib/auth'
import { env } from '@/lib/core/config/env'
import { withRouteHandler } from '@/lib/core/utils/with-route-handler'
import { getUserEntityPermissions } from '@/lib/workspaces/permissions/utils'
import { filterBlacklistedModels, isProviderBlacklisted } from '@/providers/utils'

const logger = createLogger('AtlasCloudModelsAPI')

export const GET = withRouteHandler(async (request: NextRequest) => {
if (isProviderBlacklisted('atlascloud')) {
logger.info('Atlas Cloud provider is blacklisted, returning empty models')
return NextResponse.json({ models: [] })
}

let apiKey: string | undefined

const queryValidation = atlascloudProviderModelsQuerySchema.safeParse({
workspaceId: request.nextUrl.searchParams.get('workspaceId') ?? undefined,
})
if (!queryValidation.success) return validationErrorResponse(queryValidation.error)
const { workspaceId } = queryValidation.data
if (workspaceId) {
const session = await getSession()
if (session?.user?.id) {
const permission = await getUserEntityPermissions(session.user.id, 'workspace', workspaceId)
if (permission) {
const byokResult = await getBYOKKey(workspaceId, 'atlascloud')
Comment on lines +35 to +37

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Route owns protected access

The handler decides workspace access and reads a protected key directly. The repository requires protected reads to enter through an authorized application use case and says route adapters must not decide resource authorization. Move the workspace check and key lookup into that shared use case, then call it from the route. This repository requirement must be satisfied before merging.

Context Used: CLAUDE.md (source)

if (byokResult) {
apiKey = byokResult.apiKey
}
}
}
}

if (!apiKey) {
apiKey = env.ATLASCLOUD_API_KEY
}

if (!apiKey) {
logger.info('No Atlas Cloud API key available, returning empty models')
return NextResponse.json({ models: [] })
}
Comment on lines +49 to +52

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Browsing requires a key

The route returns an empty catalog whenever neither a saved key nor ATLASCLOUD_API_KEY exists. If /v1/models is public as the PR description states, this unnecessarily prevents users from browsing before adding a key. Fetch the public catalog without requiring a key; keep the key requirement for inference.


try {
const response = await fetch('https://inference.atlascloud.co/v1/models', {
headers: {
Authorization: `Bearer ${apiKey}`,
'Content-Type': 'application/json',
},
cache: 'no-store',
})

if (!response.ok) {
logger.warn('Failed to fetch Atlas Cloud models', {
status: response.status,
statusText: response.statusText,
})
return NextResponse.json({ models: [] })
}

const data = atlascloudUpstreamResponseSchema.parse(await response.json())

const allModels: string[] = []
for (const model of data.data ?? []) {
allModels.push(`atlascloud/${model.id}`)
}

const uniqueModels = Array.from(new Set(allModels))
const models = filterBlacklistedModels(uniqueModels)

logger.info('Successfully fetched Atlas Cloud models', {
count: models.length,
filtered: uniqueModels.length - models.length,
})

return NextResponse.json(providerModelsResponseSchema.parse({ models }))
} catch (error) {
logger.error('Error fetching Atlas Cloud models', {
error: getErrorMessage(error, 'Unknown error'),
})
return NextResponse.json({ models: [] })
}
})
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { useParams } from 'next/navigation'
import { useQueryState } from 'nuqs'
import {
AnthropicIcon,
AtlasCloudIcon,
BasetenIcon,
BrandfetchIcon,
CohereIcon,
Expand Down Expand Up @@ -168,6 +169,13 @@ const PROVIDERS: (BYOKManagerProvider & { id: BYOKProviderId })[] = [
description: 'LLM calls',
placeholder: 'Enter your Nebius Token Factory API key',
},
{
id: 'atlascloud',
name: 'Atlas Cloud',
icon: AtlasCloudIcon,
description: 'LLM calls',
placeholder: 'Enter your Atlas Cloud API key',
},
{
id: 'ollama-cloud',
name: 'Ollama Cloud',
Expand Down Expand Up @@ -381,6 +389,7 @@ const PROVIDER_SECTIONS: BYOKProviderSection[] = [
'together',
'baseten',
'nebius',
'atlascloud',
'ollama-cloud',
'kie',
'falai',
Expand Down
16 changes: 16 additions & 0 deletions apps/sim/components/icons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5614,6 +5614,22 @@ export function TogetherIcon(props: SVGProps<SVGSVGElement>) {
)
}

export function AtlasCloudIcon(props: SVGProps<SVGSVGElement>) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Icon props lack an interface

The new AtlasCloudIcon components use SVGProps directly instead of a props interface. The component guide requires every component to have a props interface. Define AtlasCloudIconProps extending SVGProps<SVGSVGElement> and use it for these declarations. This repository requirement must be satisfied before merging.

Context Used: Component patterns and structure for React components (source)

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

return (
<svg
{...props}
fill='currentColor'
fillRule='evenodd'
height='1em'
width='1em'
viewBox='0 0 24 24'
xmlns='http://www.w3.org/2000/svg'
>
<path d='M20.2,18.01L12,0.47,3.8,18.01l-2.58,5.52c1.62,-1.05,3.39,-1.86,5.26,-2.41,1.76,-0.51,3.61,-0.79,5.52,-0.79,0.98,0,1.95,0.08,2.9,0.22l-1.86,-4.3c-0.53,-0.1,-2.87,-0.1,-4.59,0.3l3.56,-8.28,5.52,12.85c0.01,0,0.02,0.01,0.03,0.01,1.86,0.55,3.62,1.36,5.23,2.4l-2.58,-5.52Z' />
</svg>
)
}

export function BasetenIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg
Expand Down
2 changes: 2 additions & 0 deletions apps/sim/ee/organization-usage/components/usage-consumers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { Avatar, cn, disclosureChevronClass, formatChartCompactNumber } from '@s
import { ArrowRight, ChevronDown } from '@sim/emcn/icons'
import {
AnthropicIcon,
AtlasCloudIcon,
AzureIcon,
BasetenIcon,
BedrockIcon,
Expand Down Expand Up @@ -59,6 +60,7 @@ import { USAGE_TAB_EMPTY_COPY } from '@/ee/organization-usage/constants'
*/
const PROVIDER_ICONS: Readonly<Record<string, ComponentType<{ className?: string }>>> = {
anthropic: AnthropicIcon,
atlascloud: AtlasCloudIcon,
baseten: BasetenIcon,
bedrock: BedrockIcon,
cerebras: CerebrasIcon,
Expand Down
6 changes: 6 additions & 0 deletions apps/sim/hooks/queries/providers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { getErrorMessage } from '@sim/utils/errors'
import { queryOptions, type UseQueryResult, useQueries, useQuery } from '@tanstack/react-query'
import { requestJson } from '@/lib/api/client/request'
import {
getAtlascloudProviderModelsContract,
getBaseProviderModelsContract,
getBasetenProviderModelsContract,
getFireworksProviderModelsContract,
Expand Down Expand Up @@ -99,6 +100,11 @@ async function requestProviderModels(
query: { workspaceId },
signal,
})
case 'atlascloud':
return requestJson(getAtlascloudProviderModelsContract, {
query: { workspaceId },
signal,
})
}
}

Expand Down
21 changes: 21 additions & 0 deletions apps/sim/lib/api-key/byok.ts
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,27 @@ export async function getApiKeyWithBYOK(
throw new Error(`API key is required for Nebius ${model}`)
}

if (provider === 'atlascloud') {
if (workspaceId) {
const byokResult = await getBYOKKey(workspaceId, 'atlascloud')
if (byokResult) {
logger.info('Using BYOK key for Atlas Cloud', {
model,
workspaceId,
scope: byokResult.scope,
})
return byokResult
}
}
if (userProvidedKey) {
return { apiKey: userProvidedKey, isBYOK: false }
}
if (env.ATLASCLOUD_API_KEY) {
return { apiKey: env.ATLASCLOUD_API_KEY, isBYOK: false }
}
throw new Error(`API key is required for Atlas Cloud ${model}`)
}

if (provider === 'ollama-cloud') {
if (workspaceId) {
const byokResult = await getBYOKKey(workspaceId, 'ollama-cloud')
Expand Down
1 change: 1 addition & 0 deletions apps/sim/lib/api/contracts/byok-keys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export const byokProviderIdSchema = z.enum([
'together',
'baseten',
'nebius',
'atlascloud',
'ollama-cloud',
'kie',
'falai',
Expand Down
Loading