Skip to main content

    Cursor Prompt: Add Behest to Your App

    Paste this into Cursor's chat (Cmd-L). Assumes you already have a React/Next/Node app and want to add a production-quality Behest-backed chat feature.


    Context to give Cursor

    Before pasting the prompt, attach these files (@-mention in Cursor):

    • Your auth setup (auth.ts, supabase.ts, clerk, or wherever you read the current user).
    • Your main app entry + one example page.
    • package.json.

    The prompt

    SECURITY NON-NEGOTIABLES (do not deviate)
    - BEHEST_KEY is server-only. If you put it under VITE_/NEXT_PUBLIC_/EXPO_PUBLIC_, the answer is wrong.
    - currentUser / user_id MUST come from a server-verified session check (auth.ts, getServerSession, supabase.auth.getUser, clerk auth(), etc.) — NOT from the request body, query string, or a client-controlled header.
    - tier MUST be read from your billing system (DB row, webhook metadata). Never hardcode, never accept from the client.
    - role is derived from the API key on the server. Do NOT pass role in the mint body; admin behaviour requires an admin-roled key.
    - Token-mint endpoints must NOT use wildcard CORS. If CORS is needed, use a specific origin allowlist.
    - The browser must NOT import @behest/client-ts. It fetches a minted JWT and calls Behest via the OpenAI SDK directly.
    - If local signing is used (BEHEST_KEY starts with behest_pk_): the private key stays server-only. The SDK refuses to load it in a browser — do not work around that.
    
    I want to add a production-ready AI chat feature to this app using Behest
    (an inference gateway — OpenAI-compatible proxy with per-user JWTs,
    persistent threads, and per-user rate limits).
    
    Requirements:
    
    1. Install @behest/client-ts on the server. Install `openai` for the browser.
    
    2. Add a server endpoint at /api/behest/token that:
       - Reads the current user from <MY_AUTH_SYSTEM> via a server-side session check
         (NOT from the request body or a client-controlled header).
       - Uses the v1.5 Behest SDK:
           import { Behest } from "@behest/client-ts";
           const behest = new Behest(); // reads BEHEST_KEY and BEHEST_BASE_URL
           const { token, ttl, sessionId, expiresAt } = await behest.auth.mint({
             user_id: currentUser.id,
             tier: currentUser.plan ?? 1,
             ttl: 900,
           });
       - Returns JSON: { token, ttl, sessionId, expiresAt }.
       - Returns 401 if no user is signed in.
       - NEVER exposes BEHEST_KEY to the client.
    
    3. Create a browser token helper with expiry-aware cache:
         import OpenAI from "openai";
         type TokenBundle = { token: string; ttl: number; sessionId: string; expiresAt: number };
         let cached: TokenBundle | null = null;
         async function getToken(): Promise<TokenBundle> {
           const now = Math.floor(Date.now() / 1000);
           if (cached && cached.expiresAt - now > 60) return cached;
           const r = await fetch("/api/behest/token", { method: "POST" });
           if (!r.ok) throw new Error("token mint failed");
           cached = await r.json();
           return cached!;
         }
         export async function getOpenAI() {
           const { token, sessionId } = await getToken();
           return new OpenAI({
             apiKey: token,
             baseURL: `${process.env.NEXT_PUBLIC_BEHEST_BASE_URL}/v1`,
             dangerouslyAllowBrowser: true,
             defaultHeaders: { "X-Session-Id": sessionId },
           });
         }
    
    4. Build a <Chat> component with:
       - Message list (user + assistant bubbles).
       - Input with Enter-to-send.
       - Stop button wired to AbortController.
       - On submit: const openai = await getOpenAI(); then openai.chat.completions.create({ messages, stream: true }, { headers: { "X-Thread-Id": threadId } }).
       - Uses a stable threadId per conversation (prop in, or crypto.randomUUID() on mount if none).
    
    5. Error handling in the browser (OpenAI SDK throws APIError with status/code/headers):
       - 402 → show an upgrade modal. Parse the response body's error.details for tier/limit/usage.
       - 429 → show a toast and wait for the Retry-After header (seconds, not ms) before retrying.
       - 401 → clear the cached token, force re-auth, fetch a new token, retry once.
    
    6. Add env vars to .env.example:
         BEHEST_KEY=behest_sk_live_YOUR_KEY_HERE              # server-only
         BEHEST_BASE_URL=https://your-slug.behest.app         # server-only
         NEXT_PUBLIC_BEHEST_BASE_URL=https://your-slug.behest.app  # safe to expose
    
    7. Don't add features I didn't ask for. No tool calls, no voice, no vision
       unless I ask in a follow-up.
    
    After implementing, tell me exactly:
    - Which existing files you changed.
    - What env vars I need to set.
    - A curl command I can use to test /api/behest/token.
    

    Follow-up prompts

    After the first pass works, paste any of these:

    Add a thread sidebar

    Add a left sidebar to the chat page that lists all threads for the current
    user. Thread listing requires a Behest JWT too, so do it via a small server
    route (GET /api/behest/threads) that uses the SDK — same pattern as the
    token route: const behest = new Behest(); await behest.auth.mint({ user_id });
    return await behest.threads.list(). Exposing thread data to the browser still
    needs server-side auth, so route it through the same endpoint rather than
    minting a browser token for it. When I click a thread, fetch its messages
    with behest.threads.messages(threadId). Delete uses behest.threads.delete(id).
    

    Add usage meter

    In the app header, show a usage meter "X / Y tokens today" by calling a
    new server route /api/behest/usage that uses behest.usage.get({ from, to }).
    Poll every 30s. Server-side scope is automatic from the JWT's uid claim.
    

    Switch to local signing

    Swap BEHEST_KEY from behest_sk_live_ (apiKey mode) to behest_pk_ (sign mode,
    a tenant RSA PEM from dashboard → Keys → Signing). Add BEHEST_KID,
    BEHEST_TENANT_ID, BEHEST_PROJECT_ID to .env. No code change — the v1.5 SDK
    auto-detects the mode by key prefix. Verify by checking that the /api/behest/token
    endpoint no longer makes an outbound HTTP call on mint.
    

    See also

    Enterprise Token FinOps: Enforce hard budgets and attribute costs per session.

    Learn more