Skip to content

Commit 7545c9b

Browse files
committed
feat(providers): add Runcrate provider
1 parent bc8b55f commit 7545c9b

19 files changed

Lines changed: 405 additions & 1 deletion

File tree

‎apps/docs/content/docs/platform/costs.mdx‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@ The settings page groups keys into **Models**, **Search & web**, and **Enrichmen
127127
| Together AI | Model calls |
128128
| Baseten | Model calls |
129129
| Nebius | Model calls |
130+
| Runcrate | Model calls |
130131
| Ollama Cloud | Model calls |
131132
| Kie | Model calls |
132133
| Thinking Machines | Model calls |

‎apps/docs/content/docs/workflows/blocks/agent.mdx‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,12 @@ Select a model beginning with `nebius/` to run an open model through Nebius Toke
4444

4545
Bring your own Nebius Token Factory API key. On Sim Cloud, a workspace admin can add it under **Workspace settings → BYOK → Nebius**. On self-hosted Sim, provide the key in the Agent block's **API Key** field, using a [secret reference](/platform/credentials). Model features such as image input and structured outputs depend on the selected model.
4646

47+
#### Runcrate
48+
49+
Select a model beginning with `runcrate/` to run an open model through the Runcrate inference API, an OpenAI-compatible endpoint. Sim includes DeepSeek, Gemma, Llama, MiniMax, Qwen, and GLM models in the catalog.
50+
51+
Bring your own Runcrate API key. On Sim Cloud, a workspace admin can add it under **Workspace settings → BYOK → Runcrate**. On self-hosted Sim, provide the key in the Agent block's **API Key** field, using a [secret reference](/platform/credentials). File attachments are not sent. A forced tool choice is only sent to models that honor it; other models use automatic tool choice.
52+
4753
#### Thinking Machines
4854

4955
Select a model beginning with `thinkingmachines/` to run Inkling, Inkling-Small, or Inkling 2 Small through Tinker serverless inference. To sample a checkpoint you trained on Tinker, enter `thinkingmachines/` followed by its `tinker://` path. Custom checkpoints have no catalog pricing.

‎apps/sim/app/workspace/[workspaceId]/settings/components/byok/byok.tsx‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ import {
4747
ZeroBounceIcon,
4848
} from '@/components/icons'
4949
import { NebiusIcon } from '@/components/icons/nebius'
50+
import { RuncrateIcon } from '@/components/icons/runcrate'
5051
import { ThinkingMachinesIcon } from '@/components/icons/thinking-machines'
5152
import { canMutateWorkspaceSettingsSection } from '@/components/settings/navigation'
5253
import { type BYOKProviderId, MAX_BYOK_KEYS_PER_PROVIDER } from '@/lib/api/contracts/byok-keys'
@@ -183,6 +184,13 @@ const PROVIDERS: (BYOKManagerProvider & { id: BYOKProviderId })[] = [
183184
description: 'LLM calls',
184185
placeholder: 'Enter your Kie API key',
185186
},
187+
{
188+
id: 'runcrate',
189+
name: 'Runcrate',
190+
icon: RuncrateIcon,
191+
description: 'LLM calls',
192+
placeholder: 'Enter your Runcrate API key',
193+
},
186194
{
187195
id: 'thinkingmachines',
188196
name: 'Thinking Machines',
@@ -391,6 +399,7 @@ const PROVIDER_SECTIONS: BYOKProviderSection[] = [
391399
'nebius',
392400
'ollama-cloud',
393401
'kie',
402+
'runcrate',
394403
'thinkingmachines',
395404
'falai',
396405
],
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import type { SVGProps } from 'react'
2+
3+
interface RuncrateIconProps extends SVGProps<SVGSVGElement> {}
4+
5+
export function RuncrateIcon(props: RuncrateIconProps) {
6+
return (
7+
<svg {...props} viewBox='0 0 64 64' fill='none' xmlns='http://www.w3.org/2000/svg'>
8+
<rect width='64' height='64' rx='14' fill='#1C2E7F' />
9+
<path
10+
d='M18 20l14 12-14 12'
11+
stroke='#5B8CFF'
12+
strokeWidth='5'
13+
strokeLinecap='round'
14+
strokeLinejoin='round'
15+
/>
16+
<path d='M36 46h12' stroke='#5B8CFF' strokeWidth='5' strokeLinecap='round' />
17+
</svg>
18+
)
19+
}

‎apps/sim/ee/organization-usage/components/usage-consumers.tsx‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ import {
4343
ZaiIcon,
4444
} from '@/components/icons'
4545
import { NebiusIcon } from '@/components/icons/nebius'
46+
import { RuncrateIcon } from '@/components/icons/runcrate'
4647
import { ThinkingMachinesIcon } from '@/components/icons/thinking-machines'
4748
import type {
4849
OrganizationUsageBreakdown,
@@ -90,6 +91,7 @@ const PROVIDER_ICONS: Readonly<Record<string, ComponentType<{ className?: string
9091
'ollama-cloud': OllamaIcon,
9192
openai: OpenAIIcon,
9293
openrouter: OpenRouterIcon,
94+
runcrate: RuncrateIcon,
9395
sakana: SakanaIcon,
9496
thinkingmachines: ThinkingMachinesIcon,
9597
together: TogetherIcon,

‎apps/sim/lib/api-key/byok.test.ts‎

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -446,6 +446,43 @@ describe('getApiKeyWithBYOK for Nebius', () => {
446446
})
447447
})
448448

449+
describe('getApiKeyWithBYOK for Runcrate', () => {
450+
beforeEach(() => {
451+
resetDbChainMock()
452+
mockDecryptSecret.mockImplementation(async (encrypted: string) => ({
453+
decrypted: encrypted.replace('encrypted-', 'decrypted-'),
454+
}))
455+
})
456+
457+
it('uses the configured workspace key instead of a caller key', async () => {
458+
dbChainMockFns.orderBy.mockResolvedValueOnce([storedKey('runcrate-workspace')])
459+
await expect(
460+
getApiKeyWithBYOK(
461+
'runcrate',
462+
'runcrate/Qwen/Qwen3-235B-A22B-Instruct-2507',
463+
uniqueWorkspaceId(),
464+
'caller-key'
465+
)
466+
).resolves.toEqual({
467+
apiKey: 'decrypted-runcrate-workspace',
468+
isBYOK: true,
469+
scope: 'workspace',
470+
})
471+
})
472+
473+
it('falls back to the caller key when no workspace key is configured', async () => {
474+
dbChainMockFns.orderBy.mockResolvedValueOnce([])
475+
await expect(
476+
getApiKeyWithBYOK(
477+
'runcrate',
478+
'runcrate/Qwen/Qwen3-235B-A22B-Instruct-2507',
479+
uniqueWorkspaceId(),
480+
'caller-key'
481+
)
482+
).resolves.toEqual({ apiKey: 'caller-key', isBYOK: false })
483+
})
484+
})
485+
449486
describe('BYOK lookup retry lifetime', () => {
450487
beforeEach(() => {
451488
vi.useFakeTimers()

‎apps/sim/lib/api-key/byok.ts‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -345,6 +345,13 @@ export async function getApiKeyWithBYOK(
345345
throw new Error(`API key is required for Nebius ${model}`)
346346
}
347347

348+
if (provider === 'runcrate') {
349+
const byokResult = await getBYOKKey(workspaceId, 'runcrate', { signal })
350+
if (byokResult) return byokResult
351+
if (userProvidedKey) return { apiKey: userProvidedKey, isBYOK: false }
352+
throw new Error(`API key is required for Runcrate ${model}`)
353+
}
354+
348355
if (provider === 'thinkingmachines') {
349356
const byokResult = await getBYOKKey(workspaceId, 'thinkingmachines', { signal })
350357
if (byokResult) {

‎apps/sim/lib/api/contracts/byok-keys.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export const byokProviderIdSchema = z.enum([
1717
'nebius',
1818
'ollama-cloud',
1919
'kie',
20+
'runcrate',
2021
'thinkingmachines',
2122
'falai',
2223
'firecrawl',

‎apps/sim/providers/attachments.ts‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ export type AttachmentProvider =
4848
| 'zai'
4949
| 'kimi'
5050
| 'thinkingmachines'
51+
| 'runcrate'
5152

5253
export interface PreparedProviderAttachment {
5354
file: UserFile
@@ -163,6 +164,7 @@ const UNSUPPORTED_FILE_PROVIDERS = new Set<AttachmentProvider>([
163164
'meta',
164165
'zai',
165166
'thinkingmachines',
167+
'runcrate',
166168
])
167169

168170
const PROVIDER_SUPPORTED_LABELS: Record<AttachmentProvider, string> = {
@@ -189,6 +191,7 @@ const PROVIDER_SUPPORTED_LABELS: Record<AttachmentProvider, string> = {
189191
zai: 'no file attachments in the current API adapter',
190192
kimi: 'images through image_url message parts on multimodal models',
191193
thinkingmachines: 'no file attachments in the current API adapter',
194+
runcrate: 'no file attachments in the current API adapter',
192195
}
193196

194197
export function getAttachmentProvider(providerId: ProviderId | string): AttachmentProvider | null {
@@ -215,6 +218,7 @@ export function getAttachmentProvider(providerId: ProviderId | string): Attachme
215218
if (providerId === 'zai') return 'zai'
216219
if (providerId === 'kimi') return 'kimi'
217220
if (providerId === 'thinkingmachines') return 'thinkingmachines'
221+
if (providerId === 'runcrate') return 'runcrate'
218222
return null
219223
}
220224

@@ -432,6 +436,7 @@ function isMimeTypeSupportedByProvider(
432436
case 'meta':
433437
case 'zai':
434438
case 'thinkingmachines':
439+
case 'runcrate':
435440
return false
436441
default: {
437442
const _exhaustive: never = provider

‎apps/sim/providers/history-adapters.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ export const providerHistoryProtocols: Record<
118118
baseten: 'chat-completions',
119119
nebius: 'chat-completions',
120120
thinkingmachines: 'chat-completions',
121+
runcrate: 'chat-completions',
121122
vllm: 'chat-completions',
122123
litellm: 'chat-completions',
123124
}

0 commit comments

Comments
 (0)