diff --git a/packages/sdk/ts/src/supported-models/chat/gemini.ts b/packages/sdk/ts/src/supported-models/chat/gemini.ts
index 53197b7f5..92073a22d 100644
--- a/packages/sdk/ts/src/supported-models/chat/gemini.ts
+++ b/packages/sdk/ts/src/supported-models/chat/gemini.ts
@@ -10,13 +10,11 @@ export type GeminiModel =
| 'gemini-2.0-flash-lite-001'
| 'gemini-2.0-flash-lite-preview'
| 'gemini-2.0-flash-lite-preview-02-05'
- | 'gemini-2.0-flash-preview-image-generation'
| 'gemini-2.0-flash-thinking-exp'
| 'gemini-2.0-flash-thinking-exp-01-21'
| 'gemini-2.0-flash-thinking-exp-1219'
| 'gemini-2.5-flash'
| 'gemini-2.5-flash-image'
- | 'gemini-2.5-flash-image-preview'
| 'gemini-2.5-flash-lite'
| 'gemini-2.5-flash-lite-preview-06-17'
| 'gemini-2.5-flash-lite-preview-09-2025'
@@ -78,12 +76,6 @@ export const GeminiModels: SupportedModel[] = [
output_cost_per_token: 3e-7,
provider: 'Gemini',
},
- {
- model_id: 'gemini-2.0-flash-preview-image-generation',
- input_cost_per_token: 1e-7,
- output_cost_per_token: 4e-7,
- provider: 'Gemini',
- },
{
model_id: 'gemini-2.0-flash-thinking-exp',
input_cost_per_token: 1e-7,
@@ -114,12 +106,6 @@ export const GeminiModels: SupportedModel[] = [
output_cost_per_token: 0.0000025,
provider: 'Gemini',
},
- {
- model_id: 'gemini-2.5-flash-image-preview',
- input_cost_per_token: 3e-7,
- output_cost_per_token: 0.0000025,
- provider: 'Gemini',
- },
{
model_id: 'gemini-2.5-flash-lite',
input_cost_per_token: 1e-7,
diff --git a/packages/tests/provider-smoke/gemini-generate-text.test.ts b/packages/tests/provider-smoke/gemini-generate-text.test.ts
index da2339df1..8867a7704 100644
--- a/packages/tests/provider-smoke/gemini-generate-text.test.ts
+++ b/packages/tests/provider-smoke/gemini-generate-text.test.ts
@@ -15,7 +15,6 @@ import {
beforeAll(assertEnv);
export const BLACKLISTED_MODELS = new Set([
- 'gemini-2.0-flash-preview-image-generation',
'veo-3.0-fast-generate',
'gemini-2.0-flash-exp',
'gemini-2.0-flash-thinking-exp-1219',
diff --git a/templates/assistant-ui/.cursor/rules/echo_rules.mdc b/templates/assistant-ui/.cursor/rules/echo_rules.mdc
new file mode 100644
index 000000000..bf586fa4d
--- /dev/null
+++ b/templates/assistant-ui/.cursor/rules/echo_rules.mdc
@@ -0,0 +1,40 @@
+---
+description: Guidelines for building with Echo and assistant-ui components
+globs: ["**/*.ts", "**/*.tsx"]
+alwaysApply: true
+---
+
+# Echo + assistant-ui Guidelines
+
+## What Echo does
+Echo is a user-pays AI layer. Users fund their own AI calls. `` handles credit top-ups.
+
+## assistant-ui integration
+This template uses [assistant-ui](https://www.assistant-ui.com/) for the chat interface and Echo for the AI backend.
+
+- The `AssistantRuntimeProvider` wraps the chat interface with a runtime that calls your Echo-backed route.
+- Keep the Echo route handler pattern the same as in `next-chat` — `streamText` + `toDataStreamResponse()`.
+
+## Pattern
+```tsx
+// src/app/layout.tsx
+import { AssistantRuntimeProvider } from '@assistant-ui/react';
+import { useVercelUseChatRuntime } from '@assistant-ui/react-ai-sdk';
+import { useChat } from 'ai/react';
+
+function Providers({ children }) {
+ const chat = useChat({ api: '/api/chat' });
+ const runtime = useVercelUseChatRuntime(chat);
+ return (
+
+ {children}
+
+ );
+}
+```
+
+## Rules
+- The Echo route handler is the same as `next-chat` — don't duplicate model logic in the runtime config.
+- Place `` in the layout or sidebar, not inside the thread component.
+- Don't override assistant-ui's default streaming behaviour — it's compatible with `toDataStreamResponse()`.
+- Handle `402` errors in the `onError` callback of `useChat` to show a credits prompt.
diff --git a/templates/authjs/.cursor/rules/echo_rules.mdc b/templates/authjs/.cursor/rules/echo_rules.mdc
new file mode 100644
index 000000000..d0fedb06a
--- /dev/null
+++ b/templates/authjs/.cursor/rules/echo_rules.mdc
@@ -0,0 +1,49 @@
+---
+description: Guidelines for building with Echo and Auth.js (NextAuth) in Next.js
+globs: ["**/*.ts", "**/*.tsx"]
+alwaysApply: true
+---
+
+# Echo + Auth.js (NextAuth) Guidelines
+
+## What Echo does
+Echo is a user-pays AI layer. In this template, Auth.js handles user identity and Echo handles AI billing. The user's Echo API key is stored in the session after sign-in.
+
+## Auth flow
+1. User signs in via Auth.js (e.g., GitHub OAuth).
+2. During the `jwt` callback, attach the user's Echo API key to the token.
+3. Route Handlers read the key from the session and pass it to `EchoClient`.
+
+## Session extension
+```ts
+// auth.ts — store Echo key in JWT
+callbacks: {
+ jwt({ token, account }) {
+ if (account?.echoApiKey) token.echoApiKey = account.echoApiKey;
+ return token;
+ },
+ session({ session, token }) {
+ session.echoApiKey = token.echoApiKey as string;
+ return session;
+ }
+}
+```
+
+## Route Handler pattern
+```ts
+import { auth } from '@/auth';
+import { EchoClient } from '@merit-systems/echo-typescript-sdk';
+
+export async function POST(req: Request) {
+ const session = await auth();
+ if (!session) return new Response('Unauthorized', { status: 401 });
+ const client = new EchoClient({ apiKey: session.echoApiKey });
+ // ...
+}
+```
+
+## Rules
+- Never expose `echoApiKey` to the client — access it only in Route Handlers and Server Actions.
+- Protect all AI routes with `auth()` — return `401` before attempting any Echo call.
+- Handle `402` (no credits) separately from `401` (not authenticated) in client error handling.
+- Extend `Session` and `JWT` TypeScript types in `next-auth.d.ts` to include `echoApiKey`.
diff --git a/templates/echo-cli/.cursor/rules/echo_rules.mdc b/templates/echo-cli/.cursor/rules/echo_rules.mdc
new file mode 100644
index 000000000..cd6493405
--- /dev/null
+++ b/templates/echo-cli/.cursor/rules/echo_rules.mdc
@@ -0,0 +1,34 @@
+---
+description: Guidelines for building with the Echo CLI SDK
+globs: ["**/*.ts"]
+alwaysApply: true
+---
+
+# Echo CLI SDK Guidelines
+
+## What Echo does
+Echo is a user-pays AI infrastructure layer. The CLI SDK (`@merit-systems/echo-typescript-sdk`) lets you call AI models from Node.js scripts and CLIs, billed to the authenticated user's Echo account.
+
+## Setup
+```ts
+import { EchoClient } from '@merit-systems/echo-typescript-sdk';
+
+// Uses stored credentials from `echo login`, or pass apiKey explicitly
+const client = new EchoClient();
+```
+
+## Common patterns
+```ts
+// Check balance
+const { balance } = await client.getBalance();
+
+// Create a payment/top-up link
+const { paymentLink } = await client.createPaymentLink({ amount: 10.0 });
+```
+
+## Rules
+- Never hardcode API keys — use `echo login` for interactive auth or `ECHO_API_KEY` env var for CI.
+- Wrap calls in try/catch and handle `402` (no credits) explicitly: print a message with the top-up URL.
+- For long-running CLI tools, check balance before starting a batch job to avoid mid-run failures.
+- Log token usage after each call when in verbose mode so users can track spend.
+- Keep `EchoClient` instantiation at the top of the script/command, not inside loops.
diff --git a/templates/next-chat/.cursor/rules/echo_rules.mdc b/templates/next-chat/.cursor/rules/echo_rules.mdc
new file mode 100644
index 000000000..96231d9e8
--- /dev/null
+++ b/templates/next-chat/.cursor/rules/echo_rules.mdc
@@ -0,0 +1,40 @@
+---
+description: Guidelines for building with Echo in a Next.js AI chat app
+globs: ["**/*.ts", "**/*.tsx"]
+alwaysApply: true
+---
+
+# Echo + Next.js Chat Guidelines
+
+## What Echo does
+Echo is a user-pays AI layer. Users buy their own credits — you never pay for their usage. The `` component handles top-ups.
+
+## Setup
+- Echo providers live in `src/echo.ts`. Always import `openai`, `anthropic`, etc. from `@/echo`, not directly from provider packages.
+- `ECHO_SECRET_KEY` goes in `.env.local`. Never expose it client-side.
+
+## Chat route pattern
+```ts
+// src/app/api/chat/route.ts
+import { openai } from '@/echo';
+import { convertToModelMessages, streamText } from 'ai';
+
+export const maxDuration = 30;
+
+export async function POST(req: Request) {
+ const { messages, model } = await req.json();
+ const result = streamText({
+ model: openai(model ?? 'gpt-4o-mini'),
+ messages: convertToModelMessages(messages),
+ });
+ return result.toDataStreamResponse();
+}
+```
+
+## Rules
+- Validate `model` and `messages` before calling `streamText` — return 400 for missing params.
+- Always use `convertToModelMessages` to normalise `UIMessage[]` before passing to the model.
+- Stream with `toDataStreamResponse()` — don't buffer.
+- Catch `402` errors and surface a "top up credits" message in the UI.
+- The `` component must be reachable from the chat page (typically in the layout or nav).
+- Let users select the model in the UI — pass it as a request body field, not hardcoded in the route.
diff --git a/templates/next-image/.cursor/rules/echo_rules.mdc b/templates/next-image/.cursor/rules/echo_rules.mdc
new file mode 100644
index 000000000..0154a242e
--- /dev/null
+++ b/templates/next-image/.cursor/rules/echo_rules.mdc
@@ -0,0 +1,38 @@
+---
+description: Guidelines for building with Echo in a Next.js image generation app
+globs: ["**/*.ts", "**/*.tsx"]
+alwaysApply: true
+---
+
+# Echo + Next.js Image Generation Guidelines
+
+## What Echo does
+Echo is a user-pays AI layer. Users spend their own credits on image generation — you pay nothing per image. `` handles top-ups.
+
+## Supported image models (current)
+- OpenAI: `dall-e-3`, `dall-e-2`, `gpt-image-1`
+- Google: `gemini-2.5-flash-image` (use this, not the `-preview` variant which is deprecated)
+- Stability AI: `stable-diffusion-v3-*`
+
+## Route pattern
+```ts
+// src/app/api/generate-image/route.ts
+import { openai } from '@/echo';
+import { experimental_generateImage as generateImage } from 'ai';
+
+export async function POST(req: Request) {
+ const { prompt, model } = await req.json();
+ const { image } = await generateImage({
+ model: openai.image(model ?? 'dall-e-3'),
+ prompt,
+ });
+ return Response.json({ base64: image.base64 });
+}
+```
+
+## Rules
+- Never use deprecated preview model IDs like `gemini-2.5-flash-image-preview` or `gemini-2.0-flash-preview-image-generation` — use `gemini-2.5-flash-image`.
+- Image routes are non-streaming — use `generateImage`, not `streamText`.
+- Validate `prompt` length server-side (DALL-E 3 max is 4000 chars).
+- Return base64 from the route; let the client convert to a blob URL if needed.
+- Handle `402` (no credits) and `400` (content policy) distinctly in the UI.
diff --git a/templates/next-image/src/app/api/edit-image/google.ts b/templates/next-image/src/app/api/edit-image/google.ts
index 527c4879d..616f53a3f 100644
--- a/templates/next-image/src/app/api/edit-image/google.ts
+++ b/templates/next-image/src/app/api/edit-image/google.ts
@@ -28,7 +28,7 @@ export async function handleGoogleEdit(
];
const result = await generateText({
- model: google('gemini-2.5-flash-image-preview'),
+ model: google('gemini-2.5-flash-image'),
prompt: [
{
role: 'user',
diff --git a/templates/next-image/src/app/api/generate-image/google.ts b/templates/next-image/src/app/api/generate-image/google.ts
index 4fcdffb3a..1a1710be9 100644
--- a/templates/next-image/src/app/api/generate-image/google.ts
+++ b/templates/next-image/src/app/api/generate-image/google.ts
@@ -12,7 +12,7 @@ import { ERROR_MESSAGES } from '@/lib/constants';
export async function handleGoogleGenerate(prompt: string): Promise {
try {
const result = await generateText({
- model: google('gemini-2.5-flash-image-preview'),
+ model: google('gemini-2.5-flash-image'),
prompt,
});
diff --git a/templates/next-video-template/.cursor/rules/echo_rules.mdc b/templates/next-video-template/.cursor/rules/echo_rules.mdc
new file mode 100644
index 000000000..ac5756e7c
--- /dev/null
+++ b/templates/next-video-template/.cursor/rules/echo_rules.mdc
@@ -0,0 +1,37 @@
+---
+description: Guidelines for building with Echo in a Next.js video generation app
+globs: ["**/*.ts", "**/*.tsx"]
+alwaysApply: true
+---
+
+# Echo + Next.js Video Generation Guidelines
+
+## What Echo does
+Echo is a user-pays AI layer. Users spend credits on video generation — you have no per-video cost. `` handles top-ups.
+
+## Key differences from image generation
+- Video generation is asynchronous: submit a job, poll for completion.
+- Jobs can take 30 seconds to several minutes — never block a request waiting for completion.
+- Return a job ID immediately; let the client poll `GET /api/video-status/[jobId]`.
+
+## Route pattern
+```ts
+// src/app/api/generate-video/route.ts — submit job
+export async function POST(req: Request) {
+ const { prompt, model } = await req.json();
+ // submit to video model, return job id
+ return Response.json({ jobId });
+}
+
+// src/app/api/video-status/[jobId]/route.ts — poll status
+export async function GET(req: Request, { params }) {
+ // check job status, return { status, videoUrl? }
+}
+```
+
+## Rules
+- Never use long-polling or blocking waits inside a Route Handler — Next.js has a default 30s timeout.
+- Use `maxDuration` export only if your hosting plan supports extended timeouts.
+- Poll from the client on a reasonable interval (3-5 seconds) with exponential backoff.
+- Handle `402` (no credits) at the submit step, before the job is created.
+- `` should be accessible from the generation UI.
diff --git a/templates/next/.cursor/rules/echo_rules.mdc b/templates/next/.cursor/rules/echo_rules.mdc
new file mode 100644
index 000000000..02b722c59
--- /dev/null
+++ b/templates/next/.cursor/rules/echo_rules.mdc
@@ -0,0 +1,34 @@
+---
+description: Guidelines for building with Echo in a Next.js app
+globs: ["**/*.ts", "**/*.tsx"]
+alwaysApply: true
+---
+
+# Echo + Next.js Guidelines
+
+## What Echo does
+Echo is a user-pays AI infrastructure layer. Your users fund their own API calls — you never pay for their usage. The `` component handles the entire payment and token top-up flow.
+
+## Setup
+- Echo SDK is initialised in `src/echo.ts`. Import providers from there (`openai`, `anthropic`, etc.) — never import directly from `ai` SDK or provider packages for model calls.
+- Set `ECHO_SECRET_KEY` in `.env.local`. Never expose it to the client.
+
+## Making AI calls
+```ts
+// In a Next.js Route Handler (src/app/api/*/route.ts)
+import { openai } from '@/echo';
+import { streamText } from 'ai';
+
+export async function POST(req: Request) {
+ const { messages } = await req.json();
+ const result = streamText({ model: openai('gpt-4o'), messages });
+ return result.toDataStreamResponse();
+}
+```
+
+## Rules
+- AI calls belong in Route Handlers (`src/app/api/`), never in Server Components or client components.
+- Always stream responses with `toDataStreamResponse()` — don't await the full response.
+- The `` component must be rendered somewhere in the layout so users can top up.
+- Don't hardcode model names in multiple places — define a `DEFAULT_MODEL` constant.
+- Handle the `402 Payment Required` response: it means the user is out of credits. Prompt them to top up.
diff --git a/templates/nextjs-api-key-template/.cursor/rules/echo_rules.mdc b/templates/nextjs-api-key-template/.cursor/rules/echo_rules.mdc
new file mode 100644
index 000000000..bff1b2b7e
--- /dev/null
+++ b/templates/nextjs-api-key-template/.cursor/rules/echo_rules.mdc
@@ -0,0 +1,27 @@
+---
+description: Guidelines for building with Echo using API key auth in Next.js
+globs: ["**/*.ts", "**/*.tsx"]
+alwaysApply: true
+---
+
+# Echo + Next.js API Key Template Guidelines
+
+## What Echo does
+Echo is a user-pays AI layer. In this template, users authenticate with an API key rather than the hosted payment widget. Users pre-fund their account and calls are debited automatically.
+
+## Auth pattern
+```ts
+// Pass the user's Echo API key in the Authorization header
+const client = new EchoClient({ apiKey: userApiKey });
+```
+
+## Setup
+- `ECHO_SECRET_KEY` is your platform key (server-only).
+- Users provide their own Echo API key — store it in the session or pass it per-request.
+- Never log or persist user API keys beyond what the session needs.
+
+## Rules
+- Validate the user API key format before forwarding to Echo — keys follow the `echo_*` prefix pattern.
+- Return `401` for missing/invalid keys, `402` for insufficient credits, `429` for rate limits.
+- Don't hardcode a fallback API key — if the user has no key, surface a clear setup prompt.
+- Keep all Echo client instantiation in Route Handlers or server actions, never in client components.
diff --git a/templates/react-chat/.cursor/rules/echo_rules.mdc b/templates/react-chat/.cursor/rules/echo_rules.mdc
new file mode 100644
index 000000000..94f21a7cc
--- /dev/null
+++ b/templates/react-chat/.cursor/rules/echo_rules.mdc
@@ -0,0 +1,28 @@
+---
+description: Guidelines for building with Echo in a React (Vite) chat app
+globs: ["**/*.ts", "**/*.tsx"]
+alwaysApply: true
+---
+
+# Echo + React Chat Guidelines
+
+## What Echo does
+Echo is a user-pays AI layer. Your users buy credits and spend them on AI calls — you have zero per-request costs. The `` component handles credit top-ups.
+
+## Setup
+- Frontend: `@merit-systems/echo-react-sdk` — provides `` and hooks.
+- AI calls must go through a backend (Express, Hono, etc.) — never call AI APIs directly from the browser.
+- Set `ECHO_SECRET_KEY` as a server-only environment variable.
+
+## Chat flow
+1. User types a message in the React UI.
+2. React sends `POST /api/chat` with `{ messages, model }`.
+3. Backend calls `streamText` via an Echo-wrapped provider.
+4. Backend returns `toDataStreamResponse()`.
+5. React consumes the stream with `useChat` from `ai/react`.
+
+## Rules
+- Use `useChat` from `ai/react` for streaming state management — don't hand-roll fetch + ReadableStream.
+- Validate `model` and `messages` server-side before forwarding to the AI.
+- Surface `402` errors as a "buy credits" prompt, not a generic error toast.
+- `` should be visible from the chat UI, not buried in settings.
diff --git a/templates/react-image/.cursor/rules/echo_rules.mdc b/templates/react-image/.cursor/rules/echo_rules.mdc
new file mode 100644
index 000000000..40e1ff5ec
--- /dev/null
+++ b/templates/react-image/.cursor/rules/echo_rules.mdc
@@ -0,0 +1,41 @@
+---
+description: Guidelines for building with Echo in a React (Vite) image generation app
+globs: ["**/*.ts", "**/*.tsx"]
+alwaysApply: true
+---
+
+# Echo + React Image Generation Guidelines
+
+## What Echo does
+Echo is a user-pays AI layer. Users spend their own credits on image generation. `` handles top-ups.
+
+## Setup
+- AI calls must go through a backend — never call image generation APIs from the browser.
+- Set `ECHO_SECRET_KEY` server-side only.
+- Use `@merit-systems/echo-react-sdk` for ``.
+
+## Supported image models (current)
+- OpenAI: `dall-e-3`, `dall-e-2`, `gpt-image-1`
+- Google: `gemini-2.5-flash-image` (not the deprecated `-preview` variants)
+
+## React usage
+```tsx
+const [imageUrl, setImageUrl] = useState(null);
+
+async function generate(prompt: string) {
+ const res = await fetch('/api/generate-image', {
+ method: 'POST',
+ body: JSON.stringify({ prompt, model: 'dall-e-3' }),
+ headers: { 'Content-Type': 'application/json' },
+ });
+ if (res.status === 402) { /* prompt top-up */ return; }
+ const { base64 } = await res.json();
+ setImageUrl(`data:image/png;base64,${base64}`);
+}
+```
+
+## Rules
+- Show a loading spinner during generation (can take 5-15 seconds).
+- Distinguish `402` (no credits) from `400` (content policy violation) in error handling.
+- Don't use deprecated Gemini preview model IDs — use `gemini-2.5-flash-image`.
+- Render `` somewhere accessible in the app.
diff --git a/templates/react/.cursor/rules/echo_rules.mdc b/templates/react/.cursor/rules/echo_rules.mdc
new file mode 100644
index 000000000..a77100110
--- /dev/null
+++ b/templates/react/.cursor/rules/echo_rules.mdc
@@ -0,0 +1,29 @@
+---
+description: Guidelines for building with Echo in a React (Vite) app
+globs: ["**/*.ts", "**/*.tsx"]
+alwaysApply: true
+---
+
+# Echo + React (Vite) Guidelines
+
+## What Echo does
+Echo is a user-pays AI infrastructure layer. Users fund their own API calls with Echo credits. The `` component handles the payment UI.
+
+## Setup
+- Echo React SDK: `@merit-systems/echo-react-sdk`
+- Wrap your app with the Echo provider so `` has context.
+- API keys must never live in client-side code. If you need to call an AI model, proxy through a backend.
+
+## Component usage
+```tsx
+import { EchoTokens } from '@merit-systems/echo-react-sdk';
+
+// Render somewhere accessible in your app
+
+```
+
+## Rules
+- `` must render so users can purchase credits before hitting AI features.
+- Never call AI provider APIs directly from the browser — always use a backend proxy route.
+- If you add a Vite proxy (`vite.config.ts`) to forward `/api` to a backend, ensure the Echo secret key is only set on the server.
+- Keep model configuration in a single `constants.ts` file, not scattered across components.