
Security News
The Code You Didn't Write Is Still Yours to Defend
AI agents are pulling packages into environments no scanner is watching, creating exposure before security teams can see it.
@ottorouter/ai-sdk
Advanced tools
A drop-in SDK for accessing AI models (OpenAI, Anthropic, Google, Moonshot, MiniMax, Z.AI) through the [Setu](https://github.com/slashforge/setu) proxy with automatic x402 payments via Solana USDC.
A drop-in SDK for accessing AI models (OpenAI, Anthropic, Google, Moonshot, MiniMax, Z.AI) through the Setu proxy with automatic x402 payments via Solana USDC.
All you need is a Solana wallet — the SDK handles authentication, payment negotiation, and provider routing automatically.
Normal API requests use bearer auth. The SDK signs a wallet nonce once to exchange for a short-lived Setu token, reuses that token across requests, and refreshes it automatically when needed.
bun add @ottocode/ai-sdk ai
# or
npm install @ottocode/ai-sdk ai
import { createOttoRouter } from '@ottocode/ai-sdk';
import { generateText } from 'ai';
const setu = createOttoRouter({
auth: { privateKey: process.env.SOLANA_PRIVATE_KEY! },
});
const { text } = await generateText({
model: setu.model('claude-sonnet-4-20250514'),
prompt: 'Hello!',
});
console.log(text);
The SDK auto-resolves which provider to use based on the model name. It returns ai-sdk compatible model instances that work directly with generateText(), streamText(), etc.
Under the hood, the first protected request exchanges wallet auth headers for a bearer token via POST /v1/auth/wallet-token. Subsequent requests reuse Authorization: Bearer <token> until refresh is needed.
Models are resolved to providers by prefix:
| Prefix | Provider | API Format |
|---|---|---|
claude- | Anthropic | Messages |
gpt-, o1, o3, o4, codex- | OpenAI | Responses |
gemini- | Native | |
kimi- | Moonshot | OpenAI Chat |
MiniMax- | MiniMax | Messages |
z1- | Z.AI | OpenAI Chat |
setu.model('claude-sonnet-4-20250514'); // → anthropic
setu.model('gpt-4o'); // → openai
setu.model('gemini-2.5-pro'); // → google
setu.model('kimi-k2'); // → moonshot
Override auto-resolution when needed:
const model = setu.provider('openai').model('gpt-4o');
const model = setu.provider('anthropic', 'anthropic-messages').model('claude-sonnet-4-20250514');
const setu = createOttoRouter({
// Required: Solana wallet private key (base58)
auth: { privateKey: '...' },
// Optional: Setu API base URL (default: https://api.ottorouter.org)
baseURL: 'https://api.ottorouter.org',
// Optional: Solana RPC URL (default: https://api.mainnet-beta.solana.com)
rpcURL: 'https://api.mainnet-beta.solana.com',
// Optional: Payment callbacks
callbacks: { /* see Payment Callbacks */ },
// Optional: Cache configuration
cache: { /* see Caching */ },
// Optional: Payment options
payment: { /* see Payment Options */ },
// Optional: Custom model→provider mappings
modelMap: {
'my-custom-model': 'openai',
},
// Optional: Register custom providers
providers: [
{
id: 'my-provider',
apiFormat: 'openai-chat',
modelPrefix: 'myp-',
},
],
});
Monitor and control the payment lifecycle:
Request authentication and payment signing are separate: bearer auth is used for normal Setu HTTP requests, while your wallet still signs the x402 payment transaction during topups.
const setu = createOttoRouter({
auth: { privateKey: '...' },
callbacks: {
// Called when a 402 is received and payment is needed
onPaymentRequired: (amountUsd, currentBalance) => {
console.log(`Payment required: $${amountUsd}`);
},
// Called when the SDK is signing a transaction
onPaymentSigning: () => {
console.log('Signing payment...');
},
// Called after successful payment
onPaymentComplete: ({ amountUsd, newBalance, transactionId }) => {
console.log(`Paid $${amountUsd}, balance: $${newBalance}`);
},
// Called on payment failure
onPaymentError: (error) => {
console.error('Payment failed:', error);
},
// Called after each request with cost info (streaming & non-streaming)
onBalanceUpdate: ({ costUsd, balanceRemaining, inputTokens, outputTokens }) => {
console.log(`Cost: $${costUsd}, remaining: $${balanceRemaining}`);
},
// Optional: interactive approval before payment
onPaymentApproval: async ({ amountUsd, currentBalance }) => {
// return 'crypto' to pay, 'fiat' for fiat flow, 'cancel' to abort
return 'crypto';
},
},
});
const setu = createOttoRouter({
auth: { privateKey: '...' },
payment: {
// 'auto' (default) — pay automatically
// 'approval' — call onPaymentApproval before each payment
topupApprovalMode: 'auto',
// Auto-pay without approval if wallet USDC balance >= threshold
autoPayThresholdUsd: 5.0,
// Max retries for a single API request (default: 3)
maxRequestAttempts: 3,
// Max total payment attempts per wallet (default: 20)
maxPaymentAttempts: 20,
},
});
By default, the SDK automatically injects cache_control: { type: 'ephemeral' } on the first system block and the last message for Anthropic models. This saves ~90% on cached token costs.
// Default: auto caching (1 system + 1 message breakpoint)
createOttoRouter({ auth });
// Disable completely
createOttoRouter({ auth, cache: { anthropicCaching: false } });
// Manual: SDK won't inject cache_control — set it yourself in messages
createOttoRouter({ auth, cache: { anthropicCaching: { strategy: 'manual' } } });
// Custom breakpoint count and placement
createOttoRouter({
auth,
cache: {
anthropicCaching: {
systemBreakpoints: 2, // cache first 2 system blocks
systemPlacement: 'first', // 'first' | 'last' | 'all'
messageBreakpoints: 3, // cache last 3 messages
messagePlacement: 'last', // 'first' | 'last' | 'all'
},
},
});
// Full custom transform
createOttoRouter({
auth,
cache: {
anthropicCaching: {
strategy: 'custom',
transform: (body) => {
// modify body however you want
return body;
},
},
},
});
| Option | Default | Description |
|---|---|---|
strategy | 'auto' | 'auto', 'manual', 'custom', or false |
systemBreakpoints | 1 | Number of system blocks to cache |
messageBreakpoints | 1 | Number of messages to cache |
systemPlacement | 'first' | Which system blocks: 'first', 'last', 'all' |
messagePlacement | 'last' | Which messages: 'first', 'last', 'all' |
cacheType | 'ephemeral' | The cache_control.type value |
Provider-agnostic caching at the Setu proxy layer:
createOttoRouter({
auth,
cache: {
promptCacheKey: 'my-session-123',
promptCacheRetention: 'in_memory', // or '24h'
},
});
cachedContent at the application level// Setu account balance
const balance = await setu.balance();
// { walletAddress, balance, totalSpent, totalTopups, requestCount }
// On-chain USDC balance
const wallet = await setu.walletBalance('mainnet');
// { walletAddress, usdcBalance, network }
// Wallet address
console.log(setu.walletAddress);
Register providers at init or runtime:
// At init
const setu = createOttoRouter({
auth,
providers: [
{ id: 'my-provider', apiFormat: 'openai-chat', modelPrefix: 'myp-' },
],
});
// At runtime
setu.registry.register({
id: 'another-provider',
apiFormat: 'anthropic-messages',
models: ['specific-model-id'],
});
// Map a specific model to a provider
setu.registry.mapModel('some-model', 'openai');
| Format | Description | Used by |
|---|---|---|
openai-responses | OpenAI Responses API | OpenAI |
anthropic-messages | Anthropic Messages API | Anthropic, MiniMax |
openai-chat | OpenAI Chat Completions (compatible) | Moonshot, Z.AI |
google-native | Google GenerativeAI native |
Use the x402-aware fetch wrapper directly:
setu.fetch() uses bearer auth for normal requests and automatically refreshes the Setu access token on 401 once before retrying.
const customFetch = setu.fetch();
const response = await customFetch('https://api.ottorouter.org/v1/messages', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ model: 'claude-sonnet-4-20250514', messages: [...] }),
});
import {
fetchBalance,
fetchWalletUsdcBalance,
getPublicKeyFromPrivate,
addAnthropicCacheControl,
createOttoRouterFetch,
createWalletContext,
} from '@ottocode/ai-sdk';
// Get wallet address from private key
const address = getPublicKeyFromPrivate(privateKey);
// Fetch balance without creating a full Setu instance
const balance = await fetchBalance({ privateKey });
// Fetch on-chain USDC
const usdc = await fetchWalletUsdcBalance({ privateKey }, 'mainnet');
// Create a standalone x402-aware fetch
const ottorouterFetch = createOttoRouterFetch({
wallet: createWalletContext({ privateKey }),
baseURL: 'https://api.ottorouter.org',
});
createWalletContext() remains available for advanced usage. Its wallet headers are now intended for token exchange only; regular API traffic should go through createOttoRouter(), setu.fetch(), createOttoRouterFetch(), or fetchBalance() so bearer auth refresh is handled automatically.
setu.model('claude-sonnet-4-20250514') — the SDK resolves this to Anthropic@ai-sdk/anthropic) pointed at the Setu proxyAuthorization: Bearer <token> into normal API requests401 by refreshing the bearer token once and retrying/v1/topup HTTP request itself uses bearer authai SDK v6+ as a peer dependencyMIT
FAQs
A drop-in SDK for accessing AI models (OpenAI, Anthropic, Google, Kimi, MiniMax, Z.AI) through the [OttoRouter](https://github.com/slashforge/ottorouter) proxy with automatic x402 payments via Solana USDC.
The npm package @ottorouter/ai-sdk receives a total of 320 weekly downloads. As such, @ottorouter/ai-sdk popularity was classified as not popular.
We found that @ottorouter/ai-sdk demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
AI agents are pulling packages into environments no scanner is watching, creating exposure before security teams can see it.

Security News
GitHub Actions checkout now blocks risky pull_request_target checkouts by default to help prevent pwn request supply chain attacks.

Product
Socket now supports Custom Roles and Repository Access Permissions so organizations can control who can access specific repositories and actions.