Crux
GuidesPrompts

Execution

Run the same prompt with Vercel AI SDK, OpenAI, Google GenAI, Anthropic, or agent frameworks.

All adapters accept the same prompt() definitions — write once, run on any SDK.

generate.ts
import { generate, stream } from '@crux/ai'
import { openai } from '@ai-sdk/openai'

// Structured output
const result = await generate(editDraft, {
  model: openai('gpt-4o'),
  input: { instruction: 'Fix the intro' },
})
result.object // typed as z.infer<typeof OutputSchema>

// Text output
const text = await generate(greet, {
  model: openai('gpt-4o'),
  input: { name: 'Henri' },
})
text.text // string

// Streaming
const result = await stream(editDraft, {
  model: openai('gpt-4o'),
  input: { instruction: 'Fix the intro' },
})
for await (const partial of result.partialObjectStream) { ... }

Also re-exports tool, stepCountIs, hasToolCall, and types like LanguageModel, ToolSet, ToolChoice.

generate.ts
import { createOpenAI } from '@crux/openai'
import OpenAI from 'openai'

const adapter = createOpenAI(new OpenAI({ apiKey: '...' }))

// Structured output → chat.completions.parse
const result = await adapter.generate(editDraft, {
  model: 'gpt-4o',
  input: { instruction: 'Fix the intro' },
})
result.choices[0].message.parsed // typed

// Text output → chat.completions.create
const text = await adapter.generate(greet, {
  model: 'gpt-4o-mini',
  input: { name: 'Henri' },
})

// Streaming
const stream = await adapter.stream(greet, {
  model: 'gpt-4o',
  input: { name: 'Henri' },
})

Accepts OpenAI-native options: tools, tool_choice, parallel_tool_calls, and all OpenAISettings.

generate.ts
import { createGoogle } from '@crux/google'
import { GoogleGenAI } from '@google/genai'

const adapter = createGoogle(new GoogleGenAI({ apiKey: '...' }))

// Structured output → generateContent with JSON schema
const result = await adapter.generate(editDraft, {
  model: 'gemini-2.5-flash',
  input: { instruction: 'Fix the intro' },
})
result.object // parsed + validated with Zod

// Text output
const text = await adapter.generate(greet, {
  model: 'gemini-2.0-flash',
  input: { name: 'Henri' },
})

// Streaming
const stream = await adapter.stream(greet, {
  model: 'gemini-2.5-flash',
  input: { name: 'Henri' },
})

Accepts Google-native options: tools, temperature, maxOutputTokens, topP, topK, plus extra.cache.skip and extra.cache.ttlSeconds for per-call CachedContent control.

The Google adapter validates structured output with Zod after generation. If the model returns invalid JSON, you'll get a Zod error, not a Google SDK error.

generate.ts
import { createAnthropic } from '@crux/anthropic'
import Anthropic from '@anthropic-ai/sdk'

const adapter = createAnthropic(new Anthropic({ apiKey: '...' }))

// Structured output → messages.parse
const result = await adapter.generate(editDraft, {
  model: 'claude-sonnet-4-5-20250929',
  input: { instruction: 'Fix the intro' },
})
result.parsed_output // typed

// Text output → messages.create
const text = await adapter.generate(greet, {
  model: 'claude-haiku-4-5-20251001',
  input: { name: 'Henri' },
})

// Streaming
const stream = await adapter.stream(greet, {
  model: 'claude-sonnet-4-5-20250929',
  input: { name: 'Henri' },
})

Accepts Anthropic-native options: tools, tool_choice, thinking, metadata.

Anthropic requires max_tokens. If not set in your prompt's settings, the adapter defaults to 4096.

Agent frameworks

For AI SDK-based agent frameworks that manage their own model calls, @crux/ai/agent returns composed instructions and a wrapped model instead of executing directly:

agent.ts
import { resolve } from '@crux/ai/agent'

const { instructions, model } = await resolve(chatAgent, {
  model: languageModel,
  input: { mode },
})

// Pass to any AI SDK-compatible agent framework
const agent = createAgent({
  model,
  instructions,
  tools,
})

See Framework Guides for specific integration examples with Convex Agent and other frameworks.

Same prompt, multiple SDKs

The same prompt works across all adapters without modification:

multi-sdk.ts
import { generate } from '@crux/ai'
import { createOpenAI } from '@crux/openai'
import { createGoogle } from '@crux/google'
import { createAnthropic } from '@crux/anthropic'

// Vercel AI SDK
await generate(sentiment, { model: openai('gpt-4o'), input })

// OpenAI SDK
const oai = createOpenAI(new OpenAI())
await oai.generate(sentiment, { model: 'gpt-4o', input })

// Google GenAI
const google = createGoogle(new GoogleGenAI({ apiKey: '...' }))
await google.generate(sentiment, { model: 'gemini-2.5-flash', input })

// Anthropic SDK
const anthropic = createAnthropic(new Anthropic({ apiKey: '...' }))
await anthropic.generate(sentiment, { model: 'claude-sonnet-4-5-20250929', input })

Message conversion

Each adapter provides toMessages() and fromMessages() to convert between SDK-specific message formats and Crux's canonical Message type. Use these when integrating with existing chat UIs or migrating from raw SDK calls.

import { toMessages, fromMessages } from '@crux/ai'

// SDK format → canonical
const messages = toMessages(coreMessages)

// Canonical → SDK format
const sdkMessages = fromMessages(messages)
import { toMessages, fromMessages } from '@crux/openai'

// ChatCompletionMessageParam[] → canonical
const messages = toMessages(chatMessages)

// Canonical → OpenAI format
const sdkMessages = fromMessages(messages)
import { toMessages, fromMessages } from '@crux/google'

// Google Content[] → canonical
const messages = toMessages(contents)

// Canonical → Google format
const sdkMessages = fromMessages(messages)
import { toMessages, fromMessages } from '@crux/anthropic'

// Anthropic MessageParam[] → canonical
const messages = toMessages(anthropicMessages)

// Canonical → Anthropic format
const sdkMessages = fromMessages(messages)

Framework-agnostic generate functions

Some @crux/core APIs (like llmJudge, extractKeyFacts, summarizeMessages) accept a generic GenerateObjectFn or GenerateTextFn instead of being tied to a specific SDK. Each adapter exports these wrappers:

import { generateObjectFn, generateTextFn } from '@crux/ai'

const judge = llmJudge({ generate: generateObjectFn, model /* ... */ })
const result = await summarizeMessages({ generate: generateTextFn, model, messages })
import { createGenerateObjectFn, createGenerateTextFn } from '@crux/openai'

const generateObj = createGenerateObjectFn(client, 'gpt-4o')
const generateTxt = createGenerateTextFn(client, 'gpt-4o')
import { createGenerateObjectFn, createGenerateTextFn } from '@crux/google'

const generateObj = createGenerateObjectFn(client, 'gemini-2.5-flash')
const generateTxt = createGenerateTextFn(client, 'gemini-2.5-flash')
import { createGenerateObjectFn, createGenerateTextFn } from '@crux/anthropic'

const generateObj = createGenerateObjectFn(client, 'claude-sonnet-4-5-20250929')
const generateTxt = createGenerateTextFn(client, 'claude-haiku-4-5-20251001')

These helpers are provider-native, lightweight wrappers. They pass the schema to the provider structured-output API and return {object}, but they do not run Crux prompt resolution, validation retry, safety, cassettes, tools, memory capture, or instrumentation. Use full adapter generate() for normal prompt execution.

When a core API needs a GenerateObjectFn but you also need the full adapter runtime, bridge an existing adapter generate() function:

import { createGenerateObjectFnFromGenerate } from '@crux/core/compaction'
import { generate } from '@crux/ai'

const generateObj = createGenerateObjectFnFromGenerate(generate, {
  promptId: 'quality.judge',
})

The Vercel AI SDK adapter exports pre-bound functions (generateObjectFn, generateTextFn) since the model is passed per-call. Its generateObjectFn shares the same structured-attempt mechanics as prompt structured generation. The OpenAI, Google, and Anthropic adapters use factory functions that bind the client and model upfront.

Next steps

On this page