I read the Stripe–OpenRouter news and then opened my own chat code
Stripe wants OpenRouter. Everyone is talking about model routers this week. I still send name checks and real questions through the same Gemini door. That has to change.
I saw the Stripe news this morning and then I opened server/chat.ts. That was a slightly embarrassing order of events.
Stripe has tabled a bid for OpenRouter. People are throwing around $8 billion. I have not seen a contract, so I am not going to pretend I have. What I did see is the same story in three places: OpenRouter as one API in front of a pile of models, Cursor shipping its own router in July, Ramp putting Router.com up on the same day. The point is boring and I missed it on my own site. You should not use the expensive model for every request.
Ask Ayush already has GEMINI_API_KEY and GEMINI_API_KEY_2. It walks gemini-3.6-flash, then 3.5, then flash-latest, then lite. I wrote that so the chat still answers when the free tier dies. Fine. That is just panic order. Yesterday someone typed asdfghjk in the name field and I still woke Gemini to ask if it was a name. That is the spend I am embarrassed about, not the $8 billion headline.
What I am changing first
Three kinds of work hit that chat. “What should I call you?” only needs yes or no. “What is your stack?” should stay inside the facts I already pasted into the prompt. “How does NestJS injection work?” is a real question and can use a better model. Right now those three look the same to the API. I am going to tag them before I pick a model. The regex is ugly. I do not care. I can replace it later.
export type LlmTask = "name-check" | "portfolio" | "general";
export function classifyTask(
text: string,
intro: "name" | "country" | "ready",
): LlmTask {
if (intro !== "ready") return "name-check";
// Good enough for the corner chat. I will tighten this when it lies.
if (/\b(ayush|hire|stack|project|nestjs|contact)\b/i.test(text)) {
return "portfolio";
}
return "general";
}
The env var is not a plan
I do not need OpenRouter this weekend. I need more than GEMINI_MODEL. Name checks can go to flash-lite. Portfolio answers can stay on flash. If someone asks a real coding question, then I try 3.6. handleChatRequest already loops keys and models when one call fails. I just never told it that a name check and a long answer are different jobs.
import type { LlmTask } from "./llm-task";
const ROUTES: Record<LlmTask, string[]> = {
"name-check": ["gemini-flash-lite-latest", "gemini-flash-latest"],
portfolio: ["gemini-3.5-flash", "gemini-flash-latest"],
general: ["gemini-3.6-flash", "gemini-3.5-flash", "gemini-flash-latest"],
};
export function modelsFor(task: LlmTask, preferred?: string) {
return [preferred, ...ROUTES[task]].filter(
(model, i, list): model is string =>
Boolean(model) && list.indexOf(model) === i,
);
}
If Gemini says 429, leave that model alone
I have sat on the free tier long enough to know what happens next. The good model returns 429, I retry the same key, and then the chat is dead until morning. Next time I get 429 or 503 I drop to lite. If I get 401 the key is done. I am not going to wrap this in a big theory about Stripe metering. I just do not want the afternoon lockout again.
export function shouldFailover(status: number) {
return status === 429 || status === 503 || status >= 500;
}
export function keyIsDead(status: number) {
return status === 401 || status === 403;
}
I cannot cut a bill I cannot see
OpenRouter and Ramp will sell you a dashboard. I am going to log the task, the model, which key I used, and how long it took. If name-check keeps showing 3.6-flash I will know I forgot to wire the router. Also I need to stop sending the whole FACTS block when I only asked “is this a name?” That prompt is huge and the question is tiny. I have been paying for that without looking.
- Two keys stay. One key still dies at the worst time.
- General questions get a daily cap. People can ask about NestJS. They should not drain the quota on a Saturday.
- Name check keeps its own short prompt. No project list, no blog dump.
- I am not migrating SyBazar or the portfolio to OpenRouter because of one headline. I am just going to stop using one model for every job.