AI
Build AI features on @repo/ai, a thin provider-agnostic re-export of the AI SDK.
@repo/ai is a deliberately thin, provider-agnostic re-export of the AI SDK v6 core (ai@^6). It ships no provider, no client, and no @repo/config flag - bring your own model SDK in the package that calls an LLM, so AI is opt-in by import alone.
What it exports
The whole package is one barrel (packages/ai/src/index.ts) re-exporting the core surface. It depends only on ai and is sideEffects: false, so unused exports tree-shake away.
| Export | Kind | Purpose |
|---|---|---|
streamText | fn | Stream tokens - pipe straight to an HTTP response |
generateText | fn | One-shot text completion |
generateObject | fn | Typed JSON output validated by a Zod schema |
tool | fn | Define a function the model can call |
convertToModelMessages | fn | Map UI messages to model messages |
UIMessage | type | Shape your UI sends |
ModelMessage | type | Shape the model expects |
LanguageModel | type | A configured provider model |
Bring your own provider
@repo/ai depends on no provider SDK by design. Pick one and add it to the package that calls the model.
| Provider | Package | Key env var |
|---|---|---|
| OpenAI | @ai-sdk/openai | OPENAI_API_KEY |
| Claude | @ai-sdk/anthropic | ANTHROPIC_API_KEY |
| Many models, one key | @openrouter/ai-sdk-provider | OPENROUTER_API_KEY |
Install the provider into the package that owns the model call:
pnpm --filter @repo/api add @ai-sdk/openaiNever expose a provider key to the client. Store keys as environment variables (document them in .env.example, see Environment Variables) and keep every model call server-side on the shared backend (API).
Streaming and structured output
streamText returns a stream you can pipe to an HTTP response; convertToModelMessages maps the UIMessage[] your UI sends into the ModelMessage[] the model expects.
import { streamText, convertToModelMessages } from "@repo/ai";
import { openai } from "@ai-sdk/openai";
const result = streamText({
model: openai("gpt-4o-mini"),
messages: await convertToModelMessages(uiMessages),
});
return result.toUIMessageStreamResponse();For typed JSON instead of free text, use generateObject with a Zod schema:
const { object } = await generateObject({
model: openai("gpt-4o-mini"),
schema: z.object({ title: z.string(), tags: z.array(z.string()) }),
prompt: "Summarize this article…",
});tool defines a function the model can call - pass tools to streamText or generateText to let the model invoke your own logic.
Frequently asked questions
Which feature flag turns AI on?
None. There is no @repo/config flag - the package does nothing until your code imports it and calls a model, so hide any AI UI behind your own conditional.
Does importing @repo/ai add bundle weight?
No. It re-exports only ai and is sideEffects: false, so anything you don't use tree-shakes away and no LLM is ever called implicitly.
Where do model calls belong?
On the shared backend so the provider key never reaches the browser. Define the endpoint once in @repo/api; streaming-vs-fetch and client hooks differ per framework, see Data Fetching.