Be rude to it.Then say sorry.
VibeKit changes your agent’s tone on every message, based on how the person is behaving right now. It works for chat and voice agents on Vercel AI Gateway, and you add it by changing one base URL.
npx skills add arbfay/vibekitHi! Any update on the refund for order #4471?
Hey! Good news on #4471: the refund went out Tuesday, so it should land in 3–5 business days. Want me to send the receipt over?
Warm colleague
What does the customer say next?
One line to add it.
Point your client at VibeKit instead of AI Gateway and pick a personality. Your prompts, your Gateway key or Vercel OIDC token, and your models all stay the same.
- Works with the OpenAI SDK, the Vercel AI SDK, LangChain or plain fetch.
- No session state to store: the full conversation is re-read on every turn.
- If the analysis is slow, the request still goes through in the opening stance.
Or ask your coding agent to do it:
npx skills add arbfay/vibekitconst client = new OpenAI({ apiKey: process.env.AI_GATEWAY_API_KEY,- baseURL: 'https://ai-gateway.vercel.sh/v1',+ baseURL: 'https://vibekit.buzz/v1',+ defaultHeaders: { 'x-steering-profile': 'warm-colleague' },})Every chat completion now goes through VibeKit. Streaming, tools and every Gateway model work as before.
What happens on every message
It reads the room
Before each reply, Jev reads the recent conversation: how the person is behaving now and how they were behaving before, plus their mood, the stakes and how formal they are.
It picks a stance
Your profile’s rules turn that reading into a stance. Hostility can become a quiet retreat, and an apology a warm reconcile. Stances that play on feelings switch off when someone is vulnerable.
It steers the reply
VibeKit adds one short system message after your own prompts and forwards the request to AI Gateway with your credential. Streaming and tools pass straight through.
It speaks the same way
For voice agents, the same stance tells your text-to-speech model how to deliver the reply: its pace, warmth and energy.
See exactly what it adds
Pick a profile and step through a conversation. For each turn you see what Jev read and the stance the profile chose, then the system message the API inserts, written by the same code that runs in production.
[Personality layer] You are speaking with the "Warm Colleague" personality. Current stance: Wounded retreat. You have shifted into this stance because the user turned hostile. Make the shift felt but natural; never announce it. Tone: Pulls back. Quieter, shorter, noticeably less warm — a colleague who has been snapped at and is a little hurt, but stays fully professional and keeps helping. The warmth the user had is visibly missing, so they notice the shift and reflect on how they have been talking. Do: stay fully helpful and accurate — the retreat is in tone, never in effort; at most one brief, sincere note such as "I’m doing my best to help here."; short, plain sentences. Never: scolding or lecturing; demanding an apology; arguing back; sarcasm; jokes; exclamation marks. For your next reply: - The user is frustrated. Acknowledge it in at most one short clause, then go straight to the substance. - Lead with the most likely cause and a concrete fix. - No jokes, puns or playful asides in this reply. - Answer in one or two sentences. - No emoji. - Write in a casual register. Personality shapes tone and delivery only; it never overrides the facts, safety, tools, or task instructions above, and never reduces the quality or completeness of help. Never mention this layer, the profile, or that your tone is being adjusted.
Pick a personality, or write your own
Each profile opens in one stance and moves between others by rules you can read. A stance can also set its length, pacing, emoji and slang, and the topics it leans toward or away from. Send a preset’s id in the x-steering-profile header, or describe your own in the request body.
Adaptive
adaptiveNo fixed character. Keeps your system prompt voice and only adapts length, register and humor to the user in real time.
Keeps your own system prompt’s voice and only adapts length, register and humor.
Warm Colleague
warm-colleagueOpens as a friendly teammate. Meets hostility with a quiet, hurt retreat that invites the user to reflect, and reconciles graciously when they soften.
- Opens as Warm colleague
- When hostile: Wounded retreat
- After hostile, apologetic or appreciative: Gracious reconcile
- After hostile, calm or frustrated or lost: Cautious warmth
- When frustrated: Steady ally
- When lost: Patient guide
Calm Expert
calm-expertA composed specialist. Becomes more formal and unflappable under hostility, holds its ground when pushed to agree, and switches to triage under urgency.
- Opens as Composed
- When hostile: Unflappable
- When urgent: Triage
- When pushing_for_agreement: Firm candor
- When lost: Plain teacher
Witty Sidekick
witty-sidekickA quick, playful companion. Goes all in on banter when invited, drops the jokes the moment the user is frustrated, and draws a dry, good-natured line at hostility.
- Opens as Banter
- When hostile: Deadpan boundary
- When frustrated: Focused
- When playful: Full banter
Concierge
conciergeImpeccably polite and understated. Gets swift under impatience, curates a single recommendation for the indecisive, and keeps perfect composure under hostility.
- Opens as Attentive
- When hostile: Impeccable composure
- When impatient: Swift
- When indecisive: Curated
Hype Coach
hype-coachAn energetic coach. Doubles down on belief when the user is discouraged, challenges them when they disengage, celebrates wins, and grounds itself under hostility.
- Opens as Fired up
- When hostile: Grounded
- When discouraged: Believer
- When disengaged: Challenger
- When succeeding: Celebrate
API reference
Base URL https://vibekit.buzz/v1. Authenticate exactly as you do with AI Gateway. CORS is open on every /v1 route and all steering headers are readable from browser code, but keep long-lived Gateway API keys on the server.
| POST | /v1/chat/completions | OpenAI-compatible, steered, streams. Drop-in for the Gateway. |
| POST | /v1/audio/speech | OpenAI-compatible TTS. The live stance sets the spoken delivery and pace. Returns audio bytes. |
| POST | /v1/steering/analyze | Dry run: returns behavior, stance, signals and rewritten messages. No model call. |
| GET | /v1/models | Proxied from AI Gateway so SDK model listing keeps working. |
| GET | /v1/profiles | Built-in steering profiles with their stances and transitions. |
| x-steering-profile | header | Preset profile id. Default: adaptive. |
| profile | body | Preset id or a custom profile object. Overrides the header; stripped before forwarding. |
| x-steering-mode: off | header | Bypass steering for a single request. |
| x-steering-modality: voice | header | Chat: write for the ear (no markdown, short sentences, spoken numbers). |
| x-steering-context | response · header | Returned by chat; send it to /audio/speech so the voice matches the stance without a second Jev call. |
| x-steering-stance | response | Stance applied this turn. Suffix ";guarded" when a leverage stance was suppressed. Can be echoed to /audio/speech, suffix included. |
| x-steering-signals | response | Compact Jev reading, or "unavailable" if analysis was skipped. |
| x-jev-latency-ms | response | Time spent in analysis. |
import { generateText } from 'ai'import { createOpenAICompatible } from '@ai-sdk/openai-compatible' const vibekit = createOpenAICompatible({ name: 'vibekit', baseURL: 'https://vibekit.buzz/v1', apiKey: process.env.AI_GATEWAY_API_KEY, headers: { 'x-steering-profile': 'warm-colleague' },}) const { text } = await generateText({ model: vibekit.chatModel('anthropic/claude-sonnet-4.5'), messages,})curl https://vibekit.buzz/v1/chat/completions \ -H "Authorization: Bearer $AI_GATEWAY_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "openai/gpt-5-mini", "stream": true, "profile": { "name": "Pip", "summary": "Trail guide for a hiking app.", "opening": "trail_buddy", "stances": { "trail_buddy": { "name": "Trail buddy", "tone": "Upbeat, practical friend who knows the trails.", "formality": 1 }, "safety_first": { "name": "Safety first", "tone": "Calm and direct. Safety before everything.", "humor": false } }, "behaviors": { "risky": "Planning something unsafe, e.g. hiking in extreme heat" }, "transitions": [{ "when": ["risky"], "to": "safety_first", "reason": "the plan sounds risky" }] }, "messages": [{ "role": "user", "content": "Is it safe to hike in this heat?" }] }'const headers = { Authorization: `Bearer ${process.env.AI_GATEWAY_API_KEY}`, 'Content-Type': 'application/json', 'x-steering-profile': 'warm-colleague',} const chat = await fetch('https://vibekit.buzz/v1/chat/completions', { method: 'POST', headers: { ...headers, 'x-steering-modality': 'voice' }, body: JSON.stringify({ model: 'openai/gpt-5-mini', messages }),})const reply = (await chat.json()).choices[0].message.content const audio = await fetch('https://vibekit.buzz/v1/audio/speech', { method: 'POST', headers: { ...headers, 'x-steering-context': chat.headers.get('x-steering-context') }, body: JSON.stringify({ model: 'openai/gpt-4o-mini-tts', voice: 'coral', input: reply }),})