Skip to content
Merged
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
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 @@ -129,6 +129,7 @@ The settings page groups keys into **Models**, **Search & web**, and **Enrichmen
| Nebius | Model calls |
| Ollama Cloud | Model calls |
| Kie | Model calls |
| Thinking Machines | Model calls |
| Fal.ai | Image and video generation |
</Tab>
<Tab>
Expand Down
6 changes: 6 additions & 0 deletions apps/docs/content/docs/workflows/blocks/agent.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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.

#### 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.

Bring your own Thinking Machines Tinker API key. On Sim Cloud, a workspace admin can add it under **Workspace settings → BYOK → Thinking Machines**. On self-hosted Sim, provide the key in the Agent block's **API Key** field, using a [secret reference](/platform/credentials). Tinker's serverless inference is in beta: file attachments are not sent, tools run with automatic tool choice only, and structured outputs are requested through the prompt.

#### Jev evaluation models

Select `jev-latest` from TypeSafe in the Agent model selector. Hosted Sim supplies a key and bills model usage through the normal credit system; workspace or organization BYOK keys override the hosted key without model charges. Self-hosted users enter their TypeSafe key in the block. Use `jev-1.13.0` to pin a version or `jev-preview` to follow preview releases. These models use **State** and **Questions** in place of conversational messages. State accepts text or a reference to a JSON object or array. Questions is a JSON object keyed by the answer names you want:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ import {
ZeroBounceIcon,
} from '@/components/icons'
import { NebiusIcon } from '@/components/icons/nebius'
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'
import { useDeploymentShape } from '@/lib/core/config/deployment-shape'
Expand Down Expand Up @@ -182,6 +183,13 @@ const PROVIDERS: (BYOKManagerProvider & { id: BYOKProviderId })[] = [
description: 'LLM calls',
placeholder: 'Enter your Kie API key',
},
{
id: 'thinkingmachines',
name: 'Thinking Machines',
icon: ThinkingMachinesIcon,
description: 'LLM calls',
placeholder: 'Enter your Thinking Machines Tinker API key',
},
{
id: 'falai',
name: 'Fal.ai',
Expand Down Expand Up @@ -383,6 +391,7 @@ const PROVIDER_SECTIONS: BYOKProviderSection[] = [
'nebius',
'ollama-cloud',
'kie',
'thinkingmachines',
'falai',
],
},
Expand Down
15 changes: 15 additions & 0 deletions apps/sim/components/icons/thinking-machines.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import type { SVGProps } from 'react'

interface ThinkingMachinesIconProps extends SVGProps<SVGSVGElement> {}

export function ThinkingMachinesIcon(props: ThinkingMachinesIconProps) {
return (
<svg {...props} viewBox='-4 -4 99.7998 42' fill='none' xmlns='http://www.w3.org/2000/svg'>
<rect x='-4' y='-4' width='99.7998' height='42' rx='4' fill='#FFFFFF' />
<path
d='M5.77637 30.1113H5.8125L9.61621 21.5127H11.6064V34H9.63379V25.6514H9.59863L6.46973 32.4121H5.11914L2.00879 25.6514H1.97266V34H0V21.5127H1.97266L5.77637 30.1113ZM24.8838 34H22.7861L21.7197 31.0391H17.0811L16.0146 34H13.917L18.4316 21.5127H20.3867L24.8838 34ZM36.5635 23.4922V25.1338H34.501V24.2236L33.5596 23.2959H29.8262L28.8135 24.2949V31.2168L29.8262 32.2158H33.5596L34.501 31.2881V30.3789H36.5635V32.0195L34.5898 34H28.7598L26.752 31.9844V23.5283L28.7598 21.5127H34.5898L36.5635 23.4922ZM41.3877 26.8818H47.04V21.5127H49.1025V34H47.04V28.666H41.3877V34H39.3262V21.5127H41.3877V26.8818ZM54.6377 34H52.5762V21.5127H54.6377V34ZM65.8467 30.5928H65.8828V21.5127H67.8555V34H65.9717L60.1055 24.9375H60.0703V34H58.0977V21.5127H59.9814L65.8467 30.5928ZM79.915 23.2783H73.3027V26.8467H79.3994V28.5947H73.3027V32.2334H79.915V34H71.2412V21.5127H79.915V23.2783ZM91.6582 23.3496V24.9375H89.6318V23.9746L88.9385 23.2959H85.2412L84.5479 23.9746V26.0439L85.2412 26.7217H89.9688L91.7998 28.5586V32.127L89.9336 34H84.3164L82.4863 32.1621V30.5928H84.5127V31.5381L85.1875 32.2158H89.0625L89.7734 31.5029V29.2012L89.0801 28.5059H84.3525L82.5215 26.6855V23.3496L84.3525 21.5127H89.8271L91.6582 23.3496ZM17.5781 29.3086H21.2402L19.4092 24.0273H19.374L17.5781 29.3086ZM12.7705 1.76562H9.03711V12.4873H6.97559V1.76562H3.24316V0H12.7705V1.76562ZM17.3232 5.36914H22.9756V0H25.0381V12.4873H22.9756V7.15332H17.3232V12.4873H15.2617V0H17.3232V5.36914ZM30.5732 12.4873H28.5117V0H30.5732V12.4873ZM41.7822 9.08008H41.8184V0H43.791V12.4873H41.9072L36.041 3.4248H36.0059V12.4873H34.0322V0H35.917L41.7822 9.08008ZM49.2383 5.36914H51.46L54.5352 0H56.8281L53.2197 6.22559L57.1299 12.4873H54.7832L51.46 7.15332H49.2383V12.4873H47.1758V0H49.2383V5.36914ZM61.585 12.4873H59.5234V0H61.585V12.4873ZM72.7939 9.08008H72.8301V0H74.8027V12.4873H72.9189L67.0527 3.4248H67.0176V12.4873H65.0449V0H66.9287L72.7939 9.08008ZM87.9287 1.98047V3.62109H85.8662V2.71191L84.9248 1.78418H81.085L80.0723 2.7832V9.70508L81.085 10.7041H84.9248L85.9199 9.72266V7.45703H83.2002V5.67285H87.9287V10.4717L85.9199 12.4873H80.0186L78.0107 10.4717V2.01562L80.0186 0H85.9551L87.9287 1.98047Z'
fill='#000000'
/>
</svg>
)
}
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 @@ -43,6 +43,7 @@ import {
ZaiIcon,
} from '@/components/icons'
import { NebiusIcon } from '@/components/icons/nebius'
import { ThinkingMachinesIcon } from '@/components/icons/thinking-machines'
import type {
OrganizationUsageBreakdown,
OrganizationUsageBreakdownRow,
Expand Down Expand Up @@ -90,6 +91,7 @@ const PROVIDER_ICONS: Readonly<Record<string, ComponentType<{ className?: string
openai: OpenAIIcon,
openrouter: OpenRouterIcon,
sakana: SakanaIcon,
thinkingmachines: ThinkingMachinesIcon,
together: TogetherIcon,
typesafe: TypeSafeIcon,
vertex: VertexIcon,
Expand Down
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 @@ -345,6 +345,20 @@ export async function getApiKeyWithBYOK(
throw new Error(`API key is required for Nebius ${model}`)
}

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

if (provider === 'ollama-cloud') {
if (workspaceId) {
const byokResult = await getBYOKKey(workspaceId, 'ollama-cloud', { signal })
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 @@ -17,6 +17,7 @@ export const byokProviderIdSchema = z.enum([
'nebius',
'ollama-cloud',
'kie',
'thinkingmachines',
'falai',
'firecrawl',
'exa',
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 @@ -47,6 +47,7 @@ export type AttachmentProvider =
| 'meta'
| 'zai'
| 'kimi'
| 'thinkingmachines'

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

const PROVIDER_SUPPORTED_LABELS: Record<AttachmentProvider, string> = {
Expand All @@ -186,6 +188,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',
thinkingmachines: 'no file attachments in the current API adapter',
}

export function getAttachmentProvider(providerId: ProviderId | string): AttachmentProvider | null {
Expand All @@ -211,6 +214,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 === 'thinkingmachines') return 'thinkingmachines'
return null
}

Expand Down Expand Up @@ -427,6 +431,7 @@ function isMimeTypeSupportedByProvider(
case 'nvidia':
case 'meta':
case 'zai':
case 'thinkingmachines':
return false
default: {
const _exhaustive: never = provider
Expand Down
1 change: 1 addition & 0 deletions apps/sim/providers/history-adapters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ export const providerHistoryProtocols: Record<
together: 'chat-completions',
baseten: 'chat-completions',
nebius: 'chat-completions',
thinkingmachines: 'chat-completions',
vllm: 'chat-completions',
litellm: 'chat-completions',
}
Expand Down
37 changes: 37 additions & 0 deletions apps/sim/providers/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import {
ZaiIcon,
} from '@/components/icons'
import { NebiusIcon } from '@/components/icons/nebius'
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'

Expand Down Expand Up @@ -441,6 +442,42 @@ export const PROVIDER_DEFINITIONS: Record<string, ProviderDefinition> = {
},
],
},
thinkingmachines: {
id: 'thinkingmachines',
name: 'Thinking Machines',
description: 'Inkling models via Tinker serverless inference',
defaultModel: 'thinkingmachines/inkling',
modelPatterns: [/^thinkingmachines\//],
icon: ThinkingMachinesIcon,
color: '#111111',
capabilities: {
temperature: { min: 0, max: 1 },
toolUsageControl: true,
forcedToolUse: false,
},
models: [
{
id: 'thinkingmachines/inkling',
pricing: { input: 1.0, cachedInput: 0.17, output: 4.05, updatedAt: '2026-11-05' },
capabilities: {},
contextWindow: 262144,
releaseDate: '2026-07-16',
},
{
id: 'thinkingmachines/inkling-2-small',
pricing: { input: 0.3, cachedInput: 0.03, output: 1.0, updatedAt: '2026-11-05' },
capabilities: {},
contextWindow: 262144,
},
{
id: 'thinkingmachines/inkling-small',
pricing: { input: 0.3, cachedInput: 0.06, output: 1.2, updatedAt: '2026-11-05' },
capabilities: {},
contextWindow: 262144,
releaseDate: '2026-07-31',
},
],
},
together: {
id: 'together',
fileAttachment: { maxBytes: 50 * 1024 * 1024, strategy: 'remote-url' },
Expand Down
2 changes: 1 addition & 1 deletion apps/sim/providers/openai-compat/chat-completions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ export type ChatCompletionPayload = ChatCompletionCreateParamsNonStreaming & {
}

interface ChatCompletionExecutionConfig {
providerId: 'baseten' | 'together' | 'fireworks' | 'openrouter' | 'nebius'
providerId: 'baseten' | 'together' | 'fireworks' | 'openrouter' | 'nebius' | 'thinkingmachines'
providerName: string
client: OpenAI
requestedModel: string
Expand Down
2 changes: 2 additions & 0 deletions apps/sim/providers/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { ollamaCloudProvider } from '@/providers/ollama-cloud'
import { openaiProvider } from '@/providers/openai'
import { openRouterProvider } from '@/providers/openrouter'
import { sakanaProvider } from '@/providers/sakana'
import { thinkingMachinesProvider } from '@/providers/thinkingmachines'
import { togetherProvider } from '@/providers/together'
import type { ProviderConfig, ProviderId } from '@/providers/types'
import { typesafeProvider } from '@/providers/typesafe'
Expand Down Expand Up @@ -57,6 +58,7 @@ const providerRegistry: Record<ProviderId, ProviderConfig> = {
together: togetherProvider,
baseten: basetenProvider,
nebius: nebiusProvider,
thinkingmachines: thinkingMachinesProvider,
ollama: ollamaProvider,
'ollama-cloud': ollamaCloudProvider,
bedrock: bedrockProvider,
Expand Down
142 changes: 142 additions & 0 deletions apps/sim/providers/thinkingmachines/index.live.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
import { writeFile } from 'node:fs/promises'
import { getErrorMessage } from '@sim/utils/errors'
import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest'
import type { StreamingExecution } from '@/executor/types'
import { executeProviderRequest } from '@/providers'
import { getProviderModels } from '@/providers/models'
import type { ProviderRequest, ProviderResponse } from '@/providers/types'

vi.unmock('@/tools/registry')
vi.unmock('@/tools/metadata')
vi.unmock('@/tools/metadata-outputs')

const results: Array<{
name: string
status: 'passed' | 'failed'
durationMs: number
error?: string
}> = []
const apiKey = process.env.THINKINGMACHINES_API_KEY
const reportPath = process.env.THINKINGMACHINES_REPORT_PATH
const model = 'thinkingmachines/inkling-2-small'

async function check(name: string, run: () => Promise<void>) {
const start = Date.now()
try {
await run()
results.push({ name, status: 'passed', durationMs: Date.now() - start })
} catch (error) {
results.push({
name,
status: 'failed',
durationMs: Date.now() - start,
error: getErrorMessage(error),
})
throw error
}
}

async function execute(request: Partial<ProviderRequest> = {}) {
expect(vi.isMockFunction(globalThis.fetch)).toBe(false)
return executeProviderRequest('thinkingmachines', {
model,
apiKey,
temperature: 0,
maxTokens: 2048,
abortSignal: AbortSignal.timeout(90_000),
messages: [{ role: 'user', content: 'Reply with only the result of 6 times 7.' }],
...request,
})
}

beforeAll(async () => {
await check('configuration', async () => {
if (!reportPath)
throw new Error('Set THINKINGMACHINES_REPORT_PATH to a writable JSON report path')
if (!apiKey) throw new Error('Set THINKINGMACHINES_API_KEY to a funded Tinker API key')
})
})

afterAll(async () => {
if (reportPath)
await writeFile(
reportPath,
JSON.stringify({ recordedAt: new Date().toISOString(), results }, null, 2)
)
})

describe('Thinking Machines live acceptance', () => {
it('rejects invalid credentials over the real API', async () => {
await check('invalid-credentials', async () => {
await expect(execute({ apiKey: 'invalid-tinker-test-key' })).rejects.toThrow(/401|403/)
})
}, 60_000)

it.each(getProviderModels('thinkingmachines'))(
'serves catalog model %s',
async (catalogModel) => {
await check(`catalog:${catalogModel}`, async () => {
const result = (await execute({ model: catalogModel })) as ProviderResponse
expect(result.content).toContain('42')
expect(result.content).not.toMatch(/<think|<\/think>/i)
expect(result.tokens?.total).toBeGreaterThan(0)
expect(result.model).toBe(catalogModel)
})
},
120_000
)

it('streams the answer and final usage', async () => {
await check('stream', async () => {
const result = (await execute({ stream: true })) as StreamingExecution
const reader = result.stream.getReader()
while (!(await reader.read()).done) {}
const execution = await result.execution
expect(execution.output.content).toContain('42')
expect(execution.output.tokens?.total).toBeGreaterThan(0)
})
}, 60_000)

it('returns a JSON-schema answer through the prompt fallback', async () => {
await check('structured-output', async () => {
const result = (await execute({
responseFormat: {
name: 'answer',
schema: {
type: 'object',
properties: { answer: { type: 'integer' } },
required: ['answer'],
additionalProperties: false,
},
},
})) as ProviderResponse
expect(JSON.parse(result.content)).toEqual({ answer: 42 })
})
}, 60_000)

it('executes an HTTP tool and feeds its result back into the answer', async () => {
await check('tool-loop', async () => {
const result = (await execute({
messages: [
{
role: 'user',
content: 'Fetch https://example.com using the tool, then report the HTTP status code.',
},
],
tools: [
{
id: 'http_request',
description: 'Fetch the example page',
usageControl: 'auto',
params: { url: 'https://example.com', method: 'GET' },
parameters: { type: 'object', properties: {}, required: [] },
},
],
})) as ProviderResponse
expect(result.toolCalls?.some((call) => call.name === 'http_request' && call.success)).toBe(
true
)
expect(result.content).toContain('200')
})
}, 120_000)
})
Loading
Loading