@ai-setu/client
v0.16.1
Published
AI Setu client — drop-in OpenAI SDK with workspace credit accounting and multi-provider routing.
Maintainers
Readme
@ai-setu/client
Read this first, agent. This package = drop-in for the official
openaiSDK, pointed at the AI Setu gateway. If you know OpenAI SDK, you know this. Same methods, same shapes, same streaming. Only thing change: one workspace key, andmodelcarry aprovider/prefix. Everything below is copy-paste safe.
TL;DR (the only three lines you need)
npm i @ai-setu/client
export AI_SETU_API_KEY=tt_live_…import { AiSetu } from '@ai-setu/client';
const client = new AiSetu();
const res = await client.chat.completions.create({
model: 'openai/gpt-4o-mini',
messages: [{ role: 'user', content: 'hi' }],
});
console.log(res.choices[0].message.content);Done. new AiSetu() with no args read AI_SETU_API_KEY from env. No
baseURL, no createClient, no factory. One import, one symbol.
Rules for agent (follow these, no guess)
- Model string
provider/model(platform key) or@<slug>/<model>(your BYOK Connection — see below). Good:openai/gpt-4o-mini,anthropic/claude-sonnet-4-5,@azure-qdc/gpt-4o-mini. A bare name with no prefix routes by catalog. - No pick endpoint. Client talk to gateway only. You no set URLs for
normal use. Provider chosen by the
modelprefix, not by base URL. - Method names = OpenAI SDK exactly.
chat.completions.create,embeddings.create, streaming bystream: true+for await. Whatever you learned onopenai, it transfer here unchanged. - Key go in env
AI_SETU_API_KEY(shapett_live_…). Or passnew AiSetu({ apiKey }). Never hardcode key in source you commit. - Need balance / usage / keys? That NOT this package. Use
@ai-setu/admin. This one stay pure inference. - Node ≥ 20 or edge runtime (Vercel, Cloudflare Workers).
Models — pick by prefix
openai/gpt-4o openai/gpt-4o-mini openai/text-embedding-3-small
anthropic/claude-sonnet-4-5 anthropic/claude-haiku-4-5
bedrock/us.anthropic.claude-haiku-4-5-20251001-v1:0 (BYOK — your AWS account)Streaming (same as OpenAI)
const stream = await client.chat.completions.create({
model: 'anthropic/claude-sonnet-4-5',
messages: [{ role: 'user', content: 'Write me a haiku.' }],
stream: true,
});
for await (const chunk of stream) {
process.stdout.write(chunk.choices[0]?.delta?.content ?? '');
}Embeddings
const e = await client.embeddings.create({
model: 'openai/text-embedding-3-small',
input: 'hello world',
});Errors — catch logic from OpenAI SDK transfer straight
Use type guards, no instanceof on subclasses. Reason: the openai SDK make
APIError itself from inside its pipeline, so subclass drift every release.
Guard narrow the type back to APIError — you still get err.code,
err.status, err.requestID.
import {
AuthenticationError,
RateLimitError,
isInsufficientCreditsError,
getInsufficientCreditsDetails,
isProviderError,
} from '@ai-setu/client';
try {
await client.chat.completions.create({
/* … */
});
} catch (err) {
if (err instanceof AuthenticationError) {
/* bad key */
} else if (err instanceof RateLimitError) {
/* slow down */
} else if (isInsufficientCreditsError(err)) {
const { balanceMicros, topupUrl } = getInsufficientCreditsDetails(err);
console.error(`Out of credits (balance ${balanceMicros} micros). Top up: ${topupUrl}`);
} else if (isProviderError(err)) {
/* upstream provider broke */
}
}Environments — one switch move whole app
Default = production (gateway.aisetu.ai). Point at a different gateway (e.g. a
self-hosted deployment) with a full URL override:
export AI_SETU_BASE_URL=https://gateway.your-deployment.example.comPrecedence (first match win):
new AiSetu({ baseUrl })— explicit per-instanceAI_SETU_BASE_URL— full URL override (self-host / on-prem)- production default
(Base always normalise to end in /v1.)
Runtime + connection pooling (you usually no touch this)
- Node ≥ 20 — one process-wide keep-alive
undici.Agentback everyAiSetu. Sequential calls reuse the TCP connection (~5ms p50 vs ~30ms cold). - Edge (Cloudflare Workers / Vercel) — fall back to platform fetch, which
already pool.
undiciis optional dep, edge bundlers skip it.
Pool defaults: connections 32, allowH2 true, keepAliveTimeout 30s,
keepAliveMaxTimeout 90s (match gateway idle), bodyTimeout 0 (no cap —
long thinking-model streams can run for minutes; cancel with AbortSignal).
Override the pool only if you must:
import { Agent, ProxyAgent } from 'undici';
import { AiSetu } from '@ai-setu/client';
// Corporate proxy:
const proxied = new AiSetu({ dispatcher: new ProxyAgent({ uri: 'http://proxy:8080' }) });
// Self-signed CA (baseUrl normalise to …/v1):
const onPrem = new AiSetu({
baseUrl: 'https://gateway.internal.example',
dispatcher: new Agent({ connect: { ca: fs.readFileSync('ca.pem') } }),
});
// No pooling at all (platform fetch on Node):
const unpooled = new AiSetu({ dispatcher: null });Process-wide swap (before any AiSetu construct), or share the singleton with
sibling SDKs:
import { setSharedDispatcher, getSharedDispatcher } from '@ai-setu/client';
import { Agent } from 'undici';
import { AiSetuAdmin } from '@ai-setu/admin';
setSharedDispatcher(new Agent({ connections: 64, allowH2: true }));
const admin = new AiSetuAdmin({ dispatcher: getSharedDispatcher() });BYOK: route per request with @<slug>/<model>
Your provider keys live in Connections (each has a slug). Pick one per
request inside the model string — @<slug>/<model> — and the gateway routes
through that Connection's key. One client, mix Connections freely, no re-init.
A leading @ marks a slug (so a slug can equal a provider name); an unknown or
out-of-scope slug fails closed (4xx), never a silent platform-key fallback.
const c = new AiSetu();
await c.chat.completions.create({
model: '@azure-qdc/gpt-5.4-mini', // route via the Connection slug "azure-qdc"
messages: [{ role: 'user', content: 'hi' }],
});
await c.chat.completions.create({
model: 'openai/gpt-4o-mini', // platform key (no @slug)
messages: [{ role: 'user', content: 'hi' }],
});Pin one Connection for the whole client with the connection option (a leading
@ is optional):
const c = new AiSetu({ connection: '@azure-qdc' });Verify your BYOK Connection served (not the platform key)
The gateway stamps attribution headers on every response; the SDK parses them so you can confirm — per request — that your own key served:
const c = new AiSetu({
onRouting: (r) => {
if (!r.byok) throw new Error('expected BYOK, got platform key — bad slug?');
},
});
await c.chat.completions.create({
model: '@azure-qdc/gpt-5.4-mini',
messages: [{ role: 'user', content: 'hi' }],
});
// Or read synchronously after any call:
c.lastRouting;
// { provider: 'openai', byok: true,
// credentialId: '…uuid…', connectionSlug: 'azure-qdc' }RoutingInfo:
| field | meaning |
| ----------------- | ------------------------------------------------------------------ |
| provider | provider that served (openai, anthropic, …) |
| byok | true = your key served; false = platform key (or unknown slug) |
| credentialId | the Credential that served (BYOK only) |
| connectionSlug | the Connection slug that served (BYOK only) |
| credentialLabel | deprecated alias of connectionSlug (same value) |
Callback is best-effort — an exception inside onRouting is swallowed so a
faulty listener can't break inference. Headers absent (gateway older than this
feature) → no callback, lastRouting stays undefined. Credential id / slug
are not secrets — you own the Connection.
Legacy: the
workspaceCredentialoption + thelabel/modelmodel string are deprecated.workspaceCredentialstill works (alias ofconnection); the barelabel/modelform does not — use@<slug>/<model>.
Why not raw OpenAI SDK with custom baseURL?
You can. AiSetu just do it for you and add: env-var-first construct
(new AiSetu() no args), keep-alive pool tuned to the gateway, and per-request
BYOK routing (@<slug>/<model> / the connection option). No care about those?
Point openai at the gateway yourself.
The four AI Setu packages — which one you grab
| Package | Use it when |
| ------------------------------------------------------------------ | -------------------------------------------------------------------------------- |
| @ai-setu/client | Runtime agent / app call inference. Drop-in for openai. |
| @ai-setu/admin | Scripts / CI / builder agent run control-plane ops in TypeScript. |
| @ai-setu/cli | Same control-plane ops from the shell. |
| @ai-setu/mcp | Builder agent (Claude Code, Cursor) drive onboarding + ops conversationally. |
Full agent runbook = llms.txt.
License
MIT.
