> ## Documentation Index
> Fetch the complete documentation index at: https://docs.vibestrap.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# AI providers

> Six pluggable providers, picked by env, with fallback and per-model pricing.

AI providers move fast — pricing shifts, new models drop, vendors come and go. vibestrap
wraps **six** behind a single facade so your code never knows or cares which one is
active. Develop offline against the always-on `mock`, swap providers per environment,
and never ship a half-configured stack — `AI_PROVIDER` picks the active one and only
providers with their API keys configured register at startup.

## Prerequisites

* A working install (`pnpm dev` boots).
* One real API key — OpenRouter is the easiest if you only want to pick one
  ([openrouter.ai](https://openrouter.ai/docs)).
* Read [`src/ai/index.ts`](https://github.com/larryworld/vibestrap/blob/main/src/ai/index.ts) — it's 80 lines and shows the whole story.

## The six providers

| Name         | Source file                  | Operations              | Notes                                       |
| ------------ | ---------------------------- | ----------------------- | ------------------------------------------- |
| `mock`       | `providers/mock.ts`          | chat, chatStream, image | Default. Returns canned text + picsum URLs. |
| `openrouter` | `providers/openai-compat.ts` | chat, chatStream        | OpenAI-shape API; routes 100+ models.       |
| `openai`     | `providers/openai-compat.ts` | chat, chatStream        | Direct OpenAI endpoint.                     |
| `anthropic`  | `providers/anthropic.ts`     | chat, chatStream        | Distinct Messages API + SSE event types.    |
| `replicate`  | `providers/replicate.ts`     | image                   | Poll-based prediction API.                  |
| `fal`        | `providers/fal.ts`           | image                   | Sync queue, fast for FLUX schnell.          |

Calling an unsupported method (e.g. `image()` on `anthropic`) throws `AIUnsupportedError`.
The manager catches it and tries `opts.fallback` if you pass one.

## Step-by-step: switch to a real provider

1. Pick a provider and add its key to `.env.local`:

   ```bash theme={null}
   AI_PROVIDER=openrouter
   OPENROUTER_API_KEY=sk-or-v1-...
   # OPENROUTER_BASE_URL defaults to https://openrouter.ai/api/v1
   ```

2. Restart the dev server. Conditional registration runs at import time:

   ```ts theme={null}
   if (env.OPENROUTER_API_KEY) {
     PROVIDERS.openrouter = createOpenAICompatProvider({ ... });
   }
   ```

3. Make a call from a server action or route handler:

   ```ts theme={null}
   import { chat } from '@/ai/manager';

   const result = await chat(
     { model: 'openai/gpt-4o-mini', messages: [{ role: 'user', content: 'hi' }] },
     { userId: ctx.user.id }
   );
   ```

   `result` is `ChatResult | InsufficientCredits`. Always narrow with
   `isInsufficientCredits(result)` before reading `.text`.

## Adding a model to the price book

`src/ai/pricing.ts` is the single source of truth for cost. Keys are
`provider:model`. Numbers are per-1k tokens in **micro-cents** (1/100 of a cent —
\$0.0001 = 100 micro-cents). Edit, save, done:

```ts theme={null}
const TOKEN_PRICES: Record<string, ModelPrice> = {
  'openai:gpt-4o-mini': { inputPer1kMicroCents: 1500, outputPer1kMicroCents: 6000 },
  // add a new line ↓
  'openai:gpt-4o-2026-q1': { inputPer1kMicroCents: 1200, outputPer1kMicroCents: 4800 },
};
```

If you forget to add a row, `getTokenPrice` falls back to `mock:any` (cheap),
so the call still goes through but cost reports will under-count. Search for
"forgetting price book" in [Observability](/ai/observability) for the fix.

## Errors you can catch

```ts theme={null}
import { AIProviderError, AIUnsupportedError } from '@/ai/types';
import { isInsufficientCredits } from '@/ai/manager';

try {
  const r = await chat(req, { userId, fallback: 'mock' });
  if (isInsufficientCredits(r)) return { error: 'topup', needed: r.needed };
  return { text: r.text };
} catch (err) {
  if (err instanceof AIProviderError) console.error(err.provider, err.status);
  if (err instanceof AIUnsupportedError) console.error('try a different provider');
  throw err;
}
```

`opts.fallback` is your retry knob. The manager invokes it once on
`AIProviderError` or `AIUnsupportedError` — not on `InsufficientCredits` (refunds
already handled). Use `mock` as a fallback in dev so demos never break.

## Verify it works

1. Set `AI_PROVIDER=mock` (default). Hit the chat demo at `/playground/chat` —
   you should see the "(mock provider …)" canned response.
2. Set a real key and `AI_PROVIDER=<name>`. Restart. Same demo should now
   stream a real reply.
3. Tail the dev server. The `ai_call` insert log line includes
   `provider=<name>` — confirms the active selection.
4. `psql` into your dev DB and run
   `SELECT provider, model, status FROM ai_call ORDER BY created_at DESC LIMIT 5;`

## Common pitfalls

* **`AI_PROVIDER` set but no key.** Manager silently falls back to `mock`. Look
  at `Object.keys(PROVIDERS)` in `getProvider` — your provider is missing.
* **OpenRouter model id format.** It's `vendor/model` (e.g. `openai/gpt-4o-mini`),
  not just `gpt-4o-mini`. Different from the direct OpenAI provider.
* **Anthropic system messages.** They live at the top level, not in `messages`.
  The provider auto-splits, but if you build a request manually, system goes separate.
* **Replicate version pinning.** `model` is a version hash, not a friendly name.
  Use `black-forest-labs/flux-schnell` only after looking up its current version on Replicate.
* **`AIUnsupportedError` on image with Anthropic.** Anthropic doesn't do images.
  Set `fallback: 'replicate'` or `'fal'`, or branch on `operation` upstream.

## Official docs

* OpenRouter: [openrouter.ai/docs](https://openrouter.ai/docs)
* OpenAI: [platform.openai.com/docs](https://platform.openai.com/docs)
* Anthropic: [docs.anthropic.com](https://docs.anthropic.com/)
* Replicate: [replicate.com/docs](https://replicate.com/docs)
* fal.ai: [fal.ai/docs](https://fal.ai/docs)
* Source: `src/ai/index.ts`, `src/ai/manager.ts`, `src/ai/types.ts`
