Wrap a Client: OpenAI Chatbot with Budget Controls
Wrap a Client: OpenAI Chatbot with Budget Controls
ks.wrap() rewrites a single SDK client's baseURL to point at the Keystore vault. Unlike interceptAll(), it targets one client at a time — useful when you want fine-grained control over which clients are proxied.
What you'll build
A multi-turn chatbot powered by OpenAI, with all requests routed through Keystore. You'll configure a monthly budget in the dashboard and see how Keystore enforces it.
Prerequisites
- A Keystore account with an agent token
- An OpenAI key in your vault
- Node.js 18+
Setup
Install dependencies
npm install @keystore/sdk openai readlineWrap the OpenAI client
import Keystore from "@keystore/sdk";
import OpenAI from "openai";
const ks = new Keystore({ agentToken: process.env.KS_TOKEN! });
// wrap() rewrites the client's baseURL to vault.keystore.com
const openai = ks.wrap(new OpenAI());After wrap(), openai.baseURL points to the Keystore vault. The apiKey field is replaced with your agent token. Everything else works identically.
Build the chat loop
import * as readline from "readline";
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
const messages: OpenAI.Chat.ChatCompletionMessageParam[] = [
{ role: "system", content: "You are a helpful assistant." },
];
function ask(prompt: string): Promise<string> {
return new Promise((resolve) => rl.question(prompt, resolve));
}
async function chat() {
console.log("Chatbot ready. Type 'exit' to quit.\n");
while (true) {
const input = await ask("You: ");
if (input.toLowerCase() === "exit") break;
messages.push({ role: "user", content: input });
const response = await openai.chat.completions.create({
model: "gpt-4o",
messages,
});
const reply = response.choices[0].message.content!;
messages.push({ role: "assistant", content: reply });
console.log(`\nAssistant: ${reply}\n`);
}
rl.close();
}
chat();Configure budget in the dashboard
In the Keystore dashboard:
- Navigate to your agent
- Set a monthly budget (e.g., $10.00)
- Optionally set a rate limit (e.g., 100 requests/minute)
When the budget is exceeded, the vault returns a 429 status code. Your agent should handle this gracefully.
Handling budget limits
try {
const response = await openai.chat.completions.create({
model: "gpt-4o",
messages,
});
// ... handle response
} catch (err: any) {
if (err.status === 429) {
console.log("Budget or rate limit reached. Try again later.");
} else {
throw err;
}
}wrap() vs interceptAll()
| Feature | wrap() | interceptAll() |
|---|---|---|
| Scope | Single client | All fetch calls |
| Setup | Per-client | Global, one-time |
| Cleanup | None needed | Call ks.restore() |
| Best for | Targeted proxying | Quick prototyping |
Use wrap() when you want some clients proxied and others direct. Use interceptAll() when every provider call should go through the vault.
Next steps
- Add Anthropic as a second provider with interceptAll()
- Integrate with LangChain using setupEnv()
- Set up production controls with webhooks