diff --git a/apps/docs/content/docs/platform/costs.mdx b/apps/docs/content/docs/platform/costs.mdx index 31a95b8d67f..e47e591f794 100644 --- a/apps/docs/content/docs/platform/costs.mdx +++ b/apps/docs/content/docs/platform/costs.mdx @@ -127,6 +127,7 @@ The settings page groups keys into **Models**, **Search & web**, and **Enrichmen | Together AI | Model calls | | Baseten | Model calls | | Nebius | Model calls | + | Runcrate | Model calls | | Ollama Cloud | Model calls | | Kie | Model calls | | Thinking Machines | Model calls | diff --git a/apps/docs/content/docs/workflows/blocks/agent.mdx b/apps/docs/content/docs/workflows/blocks/agent.mdx index b94b4fa818b..47cee5d4c8a 100644 --- a/apps/docs/content/docs/workflows/blocks/agent.mdx +++ b/apps/docs/content/docs/workflows/blocks/agent.mdx @@ -44,6 +44,12 @@ Select a model beginning with `nebius/` to run an open model through Nebius Toke 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. +#### Runcrate + +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. + +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. + #### Thinking Machines 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. diff --git a/apps/sim/app/workspace/[workspaceId]/settings/components/byok/byok.tsx b/apps/sim/app/workspace/[workspaceId]/settings/components/byok/byok.tsx index c3ea986599f..3f355fb1a83 100644 --- a/apps/sim/app/workspace/[workspaceId]/settings/components/byok/byok.tsx +++ b/apps/sim/app/workspace/[workspaceId]/settings/components/byok/byok.tsx @@ -47,6 +47,7 @@ import { ZeroBounceIcon, } from '@/components/icons' import { NebiusIcon } from '@/components/icons/nebius' +import { RuncrateIcon } from '@/components/icons/runcrate' import { ThinkingMachinesIcon } from '@/components/icons/thinking-machines' import { canMutateWorkspaceSettingsSection } from '@/components/settings/navigation' import { type BYOKProviderId, MAX_BYOK_KEYS_PER_PROVIDER } from '@/lib/api/contracts/byok-keys' @@ -183,6 +184,13 @@ const PROVIDERS: (BYOKManagerProvider & { id: BYOKProviderId })[] = [ description: 'LLM calls', placeholder: 'Enter your Kie API key', }, + { + id: 'runcrate', + name: 'Runcrate', + icon: RuncrateIcon, + description: 'LLM calls', + placeholder: 'Enter your Runcrate API key', + }, { id: 'thinkingmachines', name: 'Thinking Machines', @@ -391,6 +399,7 @@ const PROVIDER_SECTIONS: BYOKProviderSection[] = [ 'nebius', 'ollama-cloud', 'kie', + 'runcrate', 'thinkingmachines', 'falai', ], diff --git a/apps/sim/components/icons/runcrate.tsx b/apps/sim/components/icons/runcrate.tsx new file mode 100644 index 00000000000..773f7d7b5e0 --- /dev/null +++ b/apps/sim/components/icons/runcrate.tsx @@ -0,0 +1,20 @@ +import type { SVGProps } from 'react' + +interface RuncrateIconProps extends SVGProps {} + +/** Runcrate logo mark; accepts standard SVG props. */ +export function RuncrateIcon(props: RuncrateIconProps) { + return ( + + + + + + ) +} diff --git a/apps/sim/ee/organization-usage/components/usage-consumers.tsx b/apps/sim/ee/organization-usage/components/usage-consumers.tsx index 347bef20b2b..b95b76288bb 100644 --- a/apps/sim/ee/organization-usage/components/usage-consumers.tsx +++ b/apps/sim/ee/organization-usage/components/usage-consumers.tsx @@ -43,6 +43,7 @@ import { ZaiIcon, } from '@/components/icons' import { NebiusIcon } from '@/components/icons/nebius' +import { RuncrateIcon } from '@/components/icons/runcrate' import { ThinkingMachinesIcon } from '@/components/icons/thinking-machines' import type { OrganizationUsageBreakdown, @@ -90,6 +91,7 @@ const PROVIDER_ICONS: Readonly { }) }) +describe('getApiKeyWithBYOK for Runcrate', () => { + beforeEach(() => { + resetDbChainMock() + mockDecryptSecret.mockImplementation(async (encrypted: string) => ({ + decrypted: encrypted.replace('encrypted-', 'decrypted-'), + })) + }) + + it('uses the configured workspace key instead of a caller key', async () => { + dbChainMockFns.orderBy.mockResolvedValueOnce([storedKey('runcrate-workspace')]) + await expect( + getApiKeyWithBYOK( + 'runcrate', + 'runcrate/Qwen/Qwen3-235B-A22B-Instruct-2507', + uniqueWorkspaceId(), + 'caller-key' + ) + ).resolves.toEqual({ + apiKey: 'decrypted-runcrate-workspace', + isBYOK: true, + scope: 'workspace', + }) + }) + + it('falls back to the caller key when no workspace key is configured', async () => { + dbChainMockFns.orderBy.mockResolvedValueOnce([]) + await expect( + getApiKeyWithBYOK( + 'runcrate', + 'runcrate/Qwen/Qwen3-235B-A22B-Instruct-2507', + uniqueWorkspaceId(), + 'caller-key' + ) + ).resolves.toEqual({ apiKey: 'caller-key', isBYOK: false }) + }) +}) + describe('BYOK lookup retry lifetime', () => { beforeEach(() => { vi.useFakeTimers() diff --git a/apps/sim/lib/api-key/byok.ts b/apps/sim/lib/api-key/byok.ts index 6c41150b4f3..cc5396cb0c2 100644 --- a/apps/sim/lib/api-key/byok.ts +++ b/apps/sim/lib/api-key/byok.ts @@ -345,6 +345,13 @@ export async function getApiKeyWithBYOK( throw new Error(`API key is required for Nebius ${model}`) } + if (provider === 'runcrate') { + const byokResult = await getBYOKKey(workspaceId, 'runcrate', { signal }) + if (byokResult) return byokResult + if (userProvidedKey) return { apiKey: userProvidedKey, isBYOK: false } + throw new Error(`API key is required for Runcrate ${model}`) + } + if (provider === 'thinkingmachines') { const byokResult = await getBYOKKey(workspaceId, 'thinkingmachines', { signal }) if (byokResult) { diff --git a/apps/sim/lib/api/contracts/byok-keys.ts b/apps/sim/lib/api/contracts/byok-keys.ts index a5498313fff..fd87cb91d62 100644 --- a/apps/sim/lib/api/contracts/byok-keys.ts +++ b/apps/sim/lib/api/contracts/byok-keys.ts @@ -17,6 +17,7 @@ export const byokProviderIdSchema = z.enum([ 'nebius', 'ollama-cloud', 'kie', + 'runcrate', 'thinkingmachines', 'falai', 'firecrawl', diff --git a/apps/sim/providers/attachments.ts b/apps/sim/providers/attachments.ts index 9592791c333..04682b0c81c 100644 --- a/apps/sim/providers/attachments.ts +++ b/apps/sim/providers/attachments.ts @@ -48,6 +48,7 @@ export type AttachmentProvider = | 'zai' | 'kimi' | 'thinkingmachines' + | 'runcrate' export interface PreparedProviderAttachment { file: UserFile @@ -163,6 +164,7 @@ const UNSUPPORTED_FILE_PROVIDERS = new Set([ 'meta', 'zai', 'thinkingmachines', + 'runcrate', ]) const PROVIDER_SUPPORTED_LABELS: Record = { @@ -189,6 +191,7 @@ const PROVIDER_SUPPORTED_LABELS: Record = { zai: 'no file attachments in the current API adapter', kimi: 'images through image_url message parts on multimodal models', thinkingmachines: 'no file attachments in the current API adapter', + runcrate: 'no file attachments in the current API adapter', } export function getAttachmentProvider(providerId: ProviderId | string): AttachmentProvider | null { @@ -215,6 +218,7 @@ export function getAttachmentProvider(providerId: ProviderId | string): Attachme if (providerId === 'zai') return 'zai' if (providerId === 'kimi') return 'kimi' if (providerId === 'thinkingmachines') return 'thinkingmachines' + if (providerId === 'runcrate') return 'runcrate' return null } @@ -432,6 +436,7 @@ function isMimeTypeSupportedByProvider( case 'meta': case 'zai': case 'thinkingmachines': + case 'runcrate': return false default: { const _exhaustive: never = provider diff --git a/apps/sim/providers/history-adapters.ts b/apps/sim/providers/history-adapters.ts index 82108983141..8c7139c863c 100644 --- a/apps/sim/providers/history-adapters.ts +++ b/apps/sim/providers/history-adapters.ts @@ -118,6 +118,7 @@ export const providerHistoryProtocols: Record< baseten: 'chat-completions', nebius: 'chat-completions', thinkingmachines: 'chat-completions', + runcrate: 'chat-completions', vllm: 'chat-completions', litellm: 'chat-completions', } diff --git a/apps/sim/providers/models.ts b/apps/sim/providers/models.ts index 6e6c89100f1..292f89ba5c6 100644 --- a/apps/sim/providers/models.ts +++ b/apps/sim/providers/models.ts @@ -36,6 +36,7 @@ import { ZaiIcon, } from '@/components/icons' import { NebiusIcon } from '@/components/icons/nebius' +import { RuncrateIcon } from '@/components/icons/runcrate' import { ThinkingMachinesIcon } from '@/components/icons/thinking-machines' import { LARGE_VALUE_THRESHOLD_BYTES } from '@/lib/execution/payloads/large-value-ref' import type { ModelPricing, ProviderId } from '@/providers/types' @@ -442,6 +443,80 @@ export const PROVIDER_DEFINITIONS: Record = { }, ], }, + runcrate: { + id: 'runcrate', + name: 'Runcrate', + description: 'Open models via the Runcrate inference API', + defaultModel: 'runcrate/Qwen/Qwen3-235B-A22B-Instruct-2507', + modelPatterns: [/^runcrate\//], + icon: RuncrateIcon, + color: '#1C2E7F', + isReseller: true, + capabilities: { + temperature: { min: 0, max: 2 }, + toolUsageControl: true, + }, + models: [ + { + id: 'runcrate/deepseek-ai/DeepSeek-V3.2', + pricing: { input: 0.273, cachedInput: 0.1365, output: 0.399, updatedAt: '2026-11-08' }, + capabilities: { toolUsageControl: true, nativeStructuredOutputs: true }, + contextWindow: 163840, + }, + { + id: 'runcrate/google/gemma-3-27b-it', + pricing: { input: 0.084, cachedInput: 0.084, output: 0.168, updatedAt: '2026-11-08' }, + capabilities: { toolUsageControl: true, nativeStructuredOutputs: true }, + contextWindow: 131072, + }, + { + id: 'runcrate/meta-llama/Llama-3.3-70B-Instruct-Turbo', + pricing: { input: 0.105, cachedInput: 0.105, output: 0.336, updatedAt: '2026-11-08' }, + capabilities: { + toolUsageControl: true, + forcedToolUse: false, + nativeStructuredOutputs: true, + }, + contextWindow: 131072, + }, + { + id: 'runcrate/MiniMaxAI/MiniMax-M3', + pricing: { input: 0.294, cachedInput: 0.0588, output: 1.155, updatedAt: '2026-11-08' }, + capabilities: { + toolUsageControl: true, + forcedToolUse: false, + nativeStructuredOutputs: true, + }, + contextWindow: 524288, + }, + { + id: 'runcrate/Qwen/Qwen3-235B-A22B-Instruct-2507', + pricing: { input: 0.0945, cachedInput: 0.0945, output: 0.5775, updatedAt: '2026-11-08' }, + capabilities: { + toolUsageControl: true, + forcedToolUse: false, + nativeStructuredOutputs: true, + }, + contextWindow: 262144, + }, + { + id: 'runcrate/Qwen/Qwen3-Coder-480B-A35B-Instruct-Turbo', + pricing: { input: 0.315, cachedInput: 0.105, output: 1.05, updatedAt: '2026-11-08' }, + capabilities: { + toolUsageControl: true, + forcedToolUse: false, + nativeStructuredOutputs: true, + }, + contextWindow: 262144, + }, + { + id: 'runcrate/zai-org/GLM-5.3', + pricing: { input: 0.590625, cachedInput: 0.13125, output: 2.625, updatedAt: '2026-11-08' }, + capabilities: { toolUsageControl: true, nativeStructuredOutputs: true }, + contextWindow: 1048576, + }, + ], + }, thinkingmachines: { id: 'thinkingmachines', name: 'Thinking Machines', diff --git a/apps/sim/providers/openai-compat/chat-completions.ts b/apps/sim/providers/openai-compat/chat-completions.ts index f8e3e99f6ef..df7b5ded384 100644 --- a/apps/sim/providers/openai-compat/chat-completions.ts +++ b/apps/sim/providers/openai-compat/chat-completions.ts @@ -59,7 +59,14 @@ export type ChatCompletionPayload = ChatCompletionCreateParamsNonStreaming & { } interface ChatCompletionExecutionConfig { - providerId: 'baseten' | 'together' | 'fireworks' | 'openrouter' | 'nebius' | 'thinkingmachines' + providerId: + | 'baseten' + | 'together' + | 'fireworks' + | 'openrouter' + | 'nebius' + | 'thinkingmachines' + | 'runcrate' providerName: string client: OpenAI requestedModel: string diff --git a/apps/sim/providers/registry.ts b/apps/sim/providers/registry.ts index 9d9fab67fb1..2a53803a88e 100644 --- a/apps/sim/providers/registry.ts +++ b/apps/sim/providers/registry.ts @@ -20,6 +20,7 @@ import { ollamaProvider } from '@/providers/ollama' import { ollamaCloudProvider } from '@/providers/ollama-cloud' import { openaiProvider } from '@/providers/openai' import { openRouterProvider } from '@/providers/openrouter' +import { runcrateProvider } from '@/providers/runcrate' import { sakanaProvider } from '@/providers/sakana' import { thinkingMachinesProvider } from '@/providers/thinkingmachines' import { togetherProvider } from '@/providers/together' @@ -58,6 +59,7 @@ const providerRegistry: Record = { together: togetherProvider, baseten: basetenProvider, nebius: nebiusProvider, + runcrate: runcrateProvider, thinkingmachines: thinkingMachinesProvider, ollama: ollamaProvider, 'ollama-cloud': ollamaCloudProvider, diff --git a/apps/sim/providers/runcrate/index.test.ts b/apps/sim/providers/runcrate/index.test.ts new file mode 100644 index 00000000000..1c31ef09317 --- /dev/null +++ b/apps/sim/providers/runcrate/index.test.ts @@ -0,0 +1,156 @@ +import { collectStream } from '@sim/testing/helpers/async' +import { jsonResponse } from '@sim/testing/helpers/http' +import { providersMock } from '@sim/testing/mocks/providers.mock' +import { providersTraceEnrichmentMock } from '@sim/testing/mocks/providers-trace-enrichment.mock' +import { beforeEach, describe, expect, it, vi } from 'vitest' +import type { StreamingExecution } from '@/executor/types' +import { runcrateProvider } from '@/providers/runcrate' +import type { ProviderResponse, ProviderToolConfig } from '@/providers/types' + +vi.mock('@/providers', () => providersMock) +vi.mock('@/providers/trace-enrichment', () => providersTraceEnrichmentMock) + +const MODEL = 'runcrate/Qwen/Qwen3-235B-A22B-Instruct-2507' +const WIRE_MODEL = 'Qwen/Qwen3-235B-A22B-Instruct-2507' +const REQUEST = { + model: MODEL, + apiKey: 'runcrate-test-key', + messages: [{ role: 'user' as const, content: 'Return the answer.' }], +} + +function completion(content: string) { + return { + id: 'chatcmpl-runcrate', + object: 'chat.completion', + created: 1, + model: WIRE_MODEL, + choices: [{ index: 0, message: { role: 'assistant', content }, finish_reason: 'stop' }], + usage: { prompt_tokens: 10, completion_tokens: 5, total_tokens: 15 }, + } +} + +describe('Runcrate Chat Completions wire contract', () => { + beforeEach(() => { + vi.stubGlobal('fetch', vi.fn()) + }) + + it.each(['RUNCRATE/Qwen/Qwen3-235B-A22B-Instruct-2507', MODEL.toLowerCase()])( + 'authenticates at Runcrate and preserves catalog identity for %s', + async (selectedModel) => { + vi.mocked(fetch).mockImplementationOnce(async (url, init) => { + expect(String(url)).toBe('https://api.runcrate.ai/v1/chat/completions') + expect(new Headers(init?.headers).get('authorization')).toBe('Bearer runcrate-test-key') + const payload = JSON.parse(String(init?.body)) + expect(payload.model).toBe(WIRE_MODEL) + return jsonResponse(completion('42')) + }) + const result = (await runcrateProvider.executeRequest({ + ...REQUEST, + model: selectedModel, + })) as ProviderResponse + expect(result.model).toBe(selectedModel) + expect(result.tokens).toEqual({ input: 10, output: 5, total: 15 }) + } + ) + + it('sends the strict JSON schema envelope', async () => { + const schema = { + type: 'object', + properties: { answer: { type: 'number' } }, + required: ['answer'], + } + vi.mocked(fetch).mockImplementationOnce(async (_url, init) => { + const payload = JSON.parse(String(init?.body)) + expect(payload.response_format).toEqual({ + type: 'json_schema', + json_schema: { name: 'answer', schema, strict: true }, + }) + return jsonResponse(completion('{"answer":42}')) + }) + const result = (await runcrateProvider.executeRequest({ + ...REQUEST, + responseFormat: { name: 'answer', schema }, + })) as ProviderResponse + expect(JSON.parse(result.content)).toEqual({ answer: 42 }) + }) + + it.each([ + { model: MODEL, forced: false }, + { model: 'runcrate/meta-llama/Llama-3.3-70B-Instruct-Turbo', forced: false }, + { model: 'runcrate/google/gemma-3-27b-it', forced: true }, + { model: 'runcrate/deepseek-ai/DeepSeek-V3.2', forced: true }, + ])('honors the forced-tool wire contract for $model', async (scenario) => { + const tools: ProviderToolConfig[] = [ + { + id: 'http_request', + description: 'Fetch a URL', + params: {}, + parameters: { type: 'object', properties: {}, required: [] }, + usageControl: 'force', + }, + { + id: 'disabled_tool', + description: 'Disabled tool', + params: {}, + parameters: { type: 'object', properties: {}, required: [] }, + usageControl: 'none', + }, + ] + const payloads: Array<{ + tool_choice: unknown + tools: Array<{ function: { name: string } }> + }> = [] + vi.mocked(fetch).mockImplementationOnce(async (_url, init) => { + payloads.push(JSON.parse(String(init?.body))) + return jsonResponse(completion('42')) + }) + const result = (await runcrateProvider.executeRequest({ + ...REQUEST, + model: scenario.model, + tools, + })) as ProviderResponse + expect(payloads[0]?.tool_choice).toEqual( + scenario.forced ? { type: 'function', function: { name: 'http_request' } } : 'auto' + ) + expect(payloads[0]?.tools.map((tool) => tool.function.name)).toEqual(['http_request']) + expect(result.content).toBe('42') + expect(tools[0].usageControl).toBe('force') + expect(tools[1].usageControl).toBe('none') + }) + + it('decodes SSE reasoning and usage while retaining Runcrate pricing', async () => { + vi.mocked(fetch).mockImplementationOnce(async (_url, init) => { + const payload = JSON.parse(String(init?.body)) + expect(payload.stream_options).toEqual({ include_usage: true }) + const chunks = [ + { choices: [{ index: 0, delta: { reasoning_content: 'Compute.' } }] }, + { choices: [{ index: 0, delta: { content: '42' }, finish_reason: 'stop' }] }, + { choices: [], usage: { prompt_tokens: 10, completion_tokens: 5, total_tokens: 15 } }, + ] + return new Response( + `${chunks.map((chunk) => `data: ${JSON.stringify(chunk)}\n\n`).join('')}data: [DONE]\n\n`, + { + headers: { 'content-type': 'text/event-stream' }, + } + ) + }) + const result = (await runcrateProvider.executeRequest({ + ...REQUEST, + stream: true, + })) as StreamingExecution + const chunks = await collectStream(result.stream) + const events = JSON.stringify(chunks) + expect(events).toContain('Compute.') + const output = await result.execution + expect(output.output.content).toBe('42') + expect(output.output.tokens).toEqual({ input: 10, output: 5, total: 15 }) + expect(output.output.cost?.total).toBeCloseTo(0.00000383, 8) + }) + + it('rejects missing credentials before any outbound call', async () => { + await expect( + runcrateProvider.executeRequest({ ...REQUEST, apiKey: undefined }) + ).rejects.toThrow('API key is required') + expect(fetch).not.toHaveBeenCalled() + }) +}) diff --git a/apps/sim/providers/runcrate/index.ts b/apps/sim/providers/runcrate/index.ts new file mode 100644 index 00000000000..e620f29598e --- /dev/null +++ b/apps/sim/providers/runcrate/index.ts @@ -0,0 +1,76 @@ +import { createLogger } from '@sim/logger' +import OpenAI from 'openai' +import type { ChatCompletionMessageParam } from 'openai/resources/chat/completions' +import type { StreamingExecution } from '@/executor/types' +import { + getProviderDefaultModel, + getProviderModels, + supportsForcedToolUse, +} from '@/providers/models' +import { + type ChatCompletionPayload, + executeChatCompletionRequest, +} from '@/providers/openai-compat/chat-completions' +import { buildJsonSchemaResponseFormat } from '@/providers/response-format' +import { openAICompatTransport } from '@/providers/transport' +import type { ProviderConfig, ProviderRequest, ProviderResponse } from '@/providers/types' + +const logger = createLogger('RuncrateProvider') + +/** Runcrate accepts strict JSON schemas on every catalog model. */ +async function applyResponseFormat( + payload: ChatCompletionPayload, + messages: ChatCompletionMessageParam[], + responseFormat: NonNullable +): Promise { + payload.response_format = buildJsonSchemaResponseFormat(responseFormat) + return messages +} + +/** + * Runcrate provider for the OpenAI-compatible inference API at `api.runcrate.ai`. + * Requires an API key (workspace key or block-level). Named tool choice is downgraded + * to `auto` on models that cannot honor it. + */ +export const runcrateProvider: ProviderConfig = { + id: 'runcrate', + name: 'Runcrate', + description: 'Open models via the Runcrate inference API', + version: '1.0.0', + models: getProviderModels('runcrate'), + defaultModel: getProviderDefaultModel('runcrate'), + executeRequest: async ( + request: ProviderRequest + ): Promise => { + if (!request.apiKey) throw new Error('API key is required for Runcrate') + const client = new OpenAI({ + ...openAICompatTransport(), + apiKey: request.apiKey, + baseURL: 'https://api.runcrate.ai/v1', + }) + const catalogModel = getProviderModels('runcrate').find( + (model) => model.toLowerCase() === request.model.toLowerCase() + ) + const requestedModel = (catalogModel ?? request.model).replace(/^runcrate\//i, '') + // Runcrate rejects a named tool_choice on models that cannot honor it, so downgrade to auto. + const tools = supportsForcedToolUse(`runcrate/${requestedModel}`) + ? request.tools + : request.tools?.map((tool) => + tool.usageControl === 'force' ? { ...tool, usageControl: 'auto' as const } : tool + ) + return executeChatCompletionRequest( + { ...request, tools }, + { + providerId: 'runcrate', + providerName: 'Runcrate', + client, + requestedModel, + reportedModel: request.model, + logger, + applyResponseFormat, + reasoningFields: ['reasoning_content', 'reasoning'], + recordPendingUsage: true, + } + ) + }, +} diff --git a/apps/sim/providers/types.ts b/apps/sim/providers/types.ts index 3be510f8771..6b40ac6a445 100644 --- a/apps/sim/providers/types.ts +++ b/apps/sim/providers/types.ts @@ -29,6 +29,7 @@ export type ProviderId = | 'together' | 'baseten' | 'nebius' + | 'runcrate' | 'thinkingmachines' | 'vllm' | 'litellm' diff --git a/apps/sim/providers/utils.ts b/apps/sim/providers/utils.ts index 07ef9be1b39..3265add9272 100644 --- a/apps/sim/providers/utils.ts +++ b/apps/sim/providers/utils.ts @@ -164,6 +164,7 @@ export const providers: Record = { together: buildProviderMetadata('together'), baseten: buildProviderMetadata('baseten'), nebius: buildProviderMetadata('nebius'), + runcrate: buildProviderMetadata('runcrate'), thinkingmachines: buildProviderMetadata('thinkingmachines'), } diff --git a/apps/sim/tools/types.ts b/apps/sim/tools/types.ts index c013e7b6121..d8411a2a8bc 100644 --- a/apps/sim/tools/types.ts +++ b/apps/sim/tools/types.ts @@ -21,6 +21,7 @@ export type BYOKProviderId = | 'nebius' | 'ollama-cloud' | 'kie' + | 'runcrate' | 'thinkingmachines' | 'falai' | 'firecrawl' diff --git a/packages/testing/src/mocks/providers-attachments.mock.ts b/packages/testing/src/mocks/providers-attachments.mock.ts index 566bd0ecd2e..8e9a0368188 100644 --- a/packages/testing/src/mocks/providers-attachments.mock.ts +++ b/packages/testing/src/mocks/providers-attachments.mock.ts @@ -30,6 +30,7 @@ const ATTACHMENT_PROVIDER_BY_ID: Record = { zai: 'zai', kimi: 'kimi', thinkingmachines: 'thinkingmachines', + runcrate: 'runcrate', } const UNSUPPORTED_FILE_PROVIDERS = new Set([ @@ -40,6 +41,7 @@ const UNSUPPORTED_FILE_PROVIDERS = new Set([ 'meta', 'zai', 'thinkingmachines', + 'runcrate', ]) function getAttachmentProvider(providerId: string): string | null {