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 @@ -5392,6 +5392,22 @@ export function BasetenIcon(props: SVGProps<SVGSVGElement>) {
)
}

export function PrismIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg
{...props}
fill='currentColor'
height='1em'
width='1em'
viewBox='0 0 24 24'
xmlns='http://www.w3.org/2000/svg'
>
<title>Prism</title>
<path d='M0 23.61 11.96.17l3.85 8.18L5.3 23.61H0zm12.95 0 3.74-14.04 7.23 14.04H12.95z' />
</svg>
)
}

export function CohereIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg {...props} height='1em' width='1em' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/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 |
| Prism | Open-source model inference (BYOK only) |
| Ollama Cloud | LLM calls |
| Fal.ai | Image and video generation |
</Tab>
Expand Down
3 changes: 2 additions & 1 deletion apps/docs/content/docs/workflows/blocks/agent.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -104,10 +104,11 @@ While an agent runs, Sim can stream its thinking and tool lifecycle live — the

{/* agent-stream-capabilities:begin — generated by `bun run agent-stream-docs:generate`; do not edit between markers */}

Live tool-call chips stream for **OpenAI, Anthropic, Azure Anthropic, Google, Vertex AI, DeepSeek, Groq, AWS Bedrock** models. Other providers run tools without live chips and project the settled final answer when the run completes; they do not ask the model to regenerate that answer just to create a stream.
Live tool-call chips stream for **Prism, OpenAI, Anthropic, Azure Anthropic, Google, Vertex AI, DeepSeek, Groq, AWS Bedrock** models. Other providers run tools without live chips and project the settled final answer when the run completes; they do not ask the model to regenerate that answer just to create a stream.

| Provider | Streamed thinking | Models |
|----------|-------------------|--------|
| Prism | Full thinking deltas | `prism/deepseek-v4.1-flash`, `prism/deepseek-v4-flash` |
| OpenAI | Summaries only — Requires OpenAI organization verification; falls back to no summaries. | `gpt-6-astra`, `gpt-5.6-sol`, `gpt-5.6-terra`, `gpt-5.6-luna`, `gpt-5.5-pro`, `gpt-5.5`, `gpt-5.4-pro`, `gpt-5.4`, `gpt-5.4-mini`, `gpt-5.4-nano`, `gpt-5.2-pro`, `gpt-5.2`, `gpt-5.1`, `gpt-5-pro`, `gpt-5`, `gpt-5-mini`, `gpt-5-nano`, `o4-mini`, `o3`, `o3-mini`, `o1` |
| Anthropic | Summaries only — These generations omit full thinking; Sim requests summarized thinking on streaming runs. | `claude-fable-5-1`, `claude-fable-5`, `claude-sonnet-5`, `claude-opus-5`, `claude-opus-4-8`, `claude-opus-4-7`, `claude-opus-4-6`, `claude-sonnet-4-6`, `claude-opus-4-5`, `claude-sonnet-4-5`, `claude-haiku-4-5` |
| Azure OpenAI | Summaries only — Requires OpenAI organization verification; falls back to no summaries. | `azure/gpt-5.4`, `azure/gpt-5.4-mini`, `azure/gpt-5.4-nano`, `azure/gpt-5.2`, `azure/gpt-5.1`, `azure/gpt-5.1-codex`, `azure/gpt-5`, `azure/gpt-5-mini`, `azure/gpt-5-nano`, `azure/o3`, `azure/o4-mini` |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import {
ParallelIcon,
PeopleDataLabsIcon,
PerplexityIcon,
PrismIcon,
ProspeoIcon,
SerperIcon,
TinyFishIcon,
Expand Down Expand Up @@ -151,6 +152,13 @@ const PROVIDERS: (BYOKManagerProvider & { id: BYOKProviderId })[] = [
description: 'LLM calls',
placeholder: 'Enter your Baseten API key',
},
{
id: 'prism',
name: 'Prism',
icon: PrismIcon,
description: 'Open-source model inference',
placeholder: 'prism_sk_...',
},
{
id: 'ollama-cloud',
name: 'Ollama Cloud',
Expand Down Expand Up @@ -355,6 +363,7 @@ const PROVIDER_SECTIONS: BYOKProviderSection[] = [
'fireworks',
'together',
'baseten',
'prism',
'ollama-cloud',
'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 @@ -5392,6 +5392,22 @@ export function BasetenIcon(props: SVGProps<SVGSVGElement>) {
)
}

export function PrismIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg
{...props}
fill='currentColor'
height='1em'
width='1em'
viewBox='0 0 24 24'
xmlns='http://www.w3.org/2000/svg'
>
<title>Prism</title>
<path d='M0 23.61 11.96.17l3.85 8.18L5.3 23.61H0zm12.95 0 3.74-14.04 7.23 14.04H12.95z' />
</svg>
)
}

export function CohereIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg {...props} height='1em' width='1em' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/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 @@ -22,6 +22,7 @@ import {
OllamaIcon,
OpenAIIcon,
OpenRouterIcon,
PrismIcon,
SakanaIcon,
TogetherIcon,
VertexIcon,
Expand Down Expand Up @@ -73,6 +74,7 @@ const PROVIDER_ICONS: Readonly<Record<string, ComponentType<{ className?: string
'ollama-cloud': OllamaIcon,
openai: OpenAIIcon,
openrouter: OpenRouterIcon,
prism: PrismIcon,
sakana: SakanaIcon,
together: TogetherIcon,
vertex: VertexIcon,
Expand Down
49 changes: 49 additions & 0 deletions apps/sim/lib/api-key/byok.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -520,3 +520,52 @@ describe('getApiKeyWithBYOK for Fireworks', () => {
expect(result).toEqual({ apiKey: 'platform-fireworks-key', isBYOK: false })
})
})

describe('getApiKeyWithBYOK for Prism', () => {
const model = 'prism/deepseek-v4-flash'

beforeEach(() => {
vi.clearAllMocks()
resetDbChainMock()
mockDecryptSecret.mockImplementation(async (encrypted: string) => ({
decrypted: encrypted.replace('encrypted-', 'decrypted-'),
}))
mockIsOrganizationBYOKEntitled.mockResolvedValue(true)
mockEnv.PRISM_API_KEY = 'must-not-be-used'
;(useProvidersStore.getState as ReturnType<typeof vi.fn>).mockReturnValue({
providers: {
ollama: { models: [] },
vllm: { models: [] },
litellm: { models: [] },
},
})
})

it('prefers a stored BYOK key over an inline key', async () => {
dbChainMockFns.orderBy.mockResolvedValue([storedKey('prism-key')])

await expect(
getApiKeyWithBYOK('prism', model, uniqueWorkspaceId(), 'inline-key')
).resolves.toEqual({
apiKey: 'decrypted-prism-key',
isBYOK: true,
scope: 'workspace',
})
})

it('uses an inline key only when no stored key exists', async () => {
await expect(
getApiKeyWithBYOK('prism', model, uniqueWorkspaceId(), 'inline-key')
).resolves.toEqual({
apiKey: 'inline-key',
isBYOK: false,
})
})

it('never falls back to an environment or platform key', async () => {
await expect(getApiKeyWithBYOK('prism', model, uniqueWorkspaceId())).rejects.toThrow(
'API key is required for Prism'
)
expect(mockGetRotatingApiKey).not.toHaveBeenCalled()
})
})
14 changes: 14 additions & 0 deletions apps/sim/lib/api-key/byok.ts
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,20 @@ export async function getApiKeyWithBYOK(
return { apiKey: userProvidedKey || env.LITELLM_API_KEY || 'empty', isBYOK: false }
}

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

const isFireworksModel =
provider === 'fireworks' ||
useProvidersStore.getState().providers.fireworks.models.includes(model)
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 @@ -13,6 +13,7 @@ export const byokProviderIdSchema = z.enum([
'fireworks',
'together',
'baseten',
'prism',
'ollama-cloud',
'falai',
'firecrawl',
Expand Down
4 changes: 2 additions & 2 deletions apps/sim/providers/attachments.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -355,11 +355,11 @@ describe('provider attachments', () => {
).toThrow('Supported attachments: images')
})

it('rejects providers without file attachment support', () => {
it.each(['deepseek', 'prism'])('rejects file attachments for %s', (provider) => {
expect(() =>
formatMessagesForProvider(
[{ role: 'user', content: 'Analyze this file', files: [imageFile] }],
'deepseek'
provider
)
).toThrow('not supported')
})
Expand Down
5 changes: 5 additions & 0 deletions apps/sim/providers/attachments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export type AttachmentProvider =
| 'meta'
| 'zai'
| 'kimi'
| 'prism'

export interface PreparedProviderAttachment {
file: UserFile
Expand Down Expand Up @@ -156,6 +157,7 @@ const UNSUPPORTED_FILE_PROVIDERS = new Set<AttachmentProvider>([
'nvidia',
'meta',
'zai',
'prism',
])

const PROVIDER_SUPPORTED_LABELS: Record<AttachmentProvider, string> = {
Expand All @@ -180,6 +182,7 @@ const PROVIDER_SUPPORTED_LABELS: Record<AttachmentProvider, string> = {
meta: 'no file attachments in the current API adapter',
zai: 'no file attachments in the current API adapter',
kimi: 'images through image_url message parts on multimodal models',
prism: 'no file attachments in the current API adapter',
}

export function getAttachmentProvider(providerId: ProviderId | string): AttachmentProvider | null {
Expand All @@ -204,6 +207,7 @@ export function getAttachmentProvider(providerId: ProviderId | string): Attachme
if (providerId === 'meta') return 'meta'
if (providerId === 'zai') return 'zai'
if (providerId === 'kimi') return 'kimi'
if (providerId === 'prism') return 'prism'
return null
}

Expand Down Expand Up @@ -419,6 +423,7 @@ function isMimeTypeSupportedByProvider(
case 'nvidia':
case 'meta':
case 'zai':
case 'prism':
return false
default: {
const _exhaustive: never = provider
Expand Down
44 changes: 44 additions & 0 deletions apps/sim/providers/models.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -513,6 +513,50 @@ describe('fireworks static catalog (the sim-auto pool)', () => {
})
})

describe('Prism provider definition', () => {
const modelIds = ['prism/deepseek-v4.1-flash', 'prism/deepseek-v4-flash']

it('registers its canonical models newest-first with the approved capabilities and pricing', () => {
expect(PROVIDER_DEFINITIONS.prism).toMatchObject({
id: 'prism',
defaultModel: modelIds[0],
models: [
{
id: modelIds[0],
pricing: { input: 0.3, cachedInput: 0.07, output: 1.2 },
capabilities: {
temperature: { min: 0, max: 2 },
toolUsageControl: true,
nativeStructuredOutputs: true,
maxOutputTokens: 384000,
reasoningEffort: { values: ['none', 'low', 'medium', 'high'] },
},
contextWindow: 1000000,
releaseDate: '2026-09-10',
},
{
id: modelIds[1],
pricing: { input: 0.14, cachedInput: 0.07, output: 0.28 },
capabilities: {
temperature: { min: 0, max: 2 },
toolUsageControl: true,
nativeStructuredOutputs: true,
maxOutputTokens: 384000,
reasoningEffort: { values: ['none', 'low', 'medium', 'high'] },
},
contextWindow: 1000000,
releaseDate: '2026-09-09',
},
],
})
expect(getProviderModels('prism')).toEqual(modelIds)
})

it('stays outside the hosted model set', () => {
for (const modelId of modelIds) expect(getHostedModels()).not.toContain(modelId)
})
})

describe('isModelDeprecated', () => {
it('returns true for a catalogued deprecated model (case-insensitive)', () => {
const id = firstDeprecatedModelId()
Expand Down
59 changes: 59 additions & 0 deletions apps/sim/providers/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import {
OllamaIcon,
OpenAIIcon,
OpenRouterIcon,
PrismIcon,
SakanaIcon,
TogetherIcon,
VertexIcon,
Expand Down Expand Up @@ -257,6 +258,64 @@ export const PROVIDER_DEFINITIONS: Record<string, ProviderDefinition> = {
contextInformationAvailable: false,
models: [],
},
prism: {
id: 'prism',
name: 'Prism',
description: 'Prism provides open-source model inference',
defaultModel: 'prism/deepseek-v4.1-flash',
modelPatterns: [/^prism\//],
icon: PrismIcon,
color: '#1F1D1A',
isReseller: true,
capabilities: {
temperature: { min: 0, max: 2 },
toolUsageControl: true,
nativeStructuredOutputs: true,
},
contextInformationAvailable: false,
models: [
{
id: 'prism/deepseek-v4.1-flash',
pricing: {
input: 0.3,
cachedInput: 0.07,
output: 1.2,
updatedAt: '2026-09-12',
},
capabilities: {
temperature: { min: 0, max: 2 },
toolUsageControl: true,
nativeStructuredOutputs: true,
maxOutputTokens: 384000,
reasoningEffort: {
values: ['none', 'low', 'medium', 'high'],
},
},
contextWindow: 1000000,
releaseDate: '2026-09-10',
},
{
id: 'prism/deepseek-v4-flash',
pricing: {
input: 0.14,
cachedInput: 0.07,
output: 0.28,
updatedAt: '2026-09-12',
},
capabilities: {
temperature: { min: 0, max: 2 },
toolUsageControl: true,
nativeStructuredOutputs: true,
maxOutputTokens: 384000,
reasoningEffort: {
values: ['none', 'low', 'medium', 'high'],
},
},
contextWindow: 1000000,
releaseDate: '2026-09-09',
},
],
},
openrouter: {
id: 'openrouter',
fileAttachment: { maxBytes: 50 * 1024 * 1024, strategy: 'remote-url' },
Expand Down
1 change: 1 addition & 0 deletions apps/sim/providers/openai-compat/streaming-tool-loop.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ export function createOpenAICompatStreamingToolLoopStream(
},
toolCalls:
toolCalls.length > 0 ? { list: toolCalls, count: toolCalls.length } : undefined,
toolResults: toolResults.length > 0 ? toolResults : undefined,
modelTime,
toolsTime,
firstResponseTime,
Expand Down
Loading