@ottocode/ai-sdk 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +346 -0
- package/package.json +1 -1
- package/src/providers/factory.ts +19 -4
- package/src/providers/registry.ts +5 -0
- package/src/setu.ts +2 -1
- package/src/types.ts +3 -0
package/README.md
ADDED
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
# @ottocode/ai-sdk
|
|
2
|
+
|
|
3
|
+
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.
|
|
4
|
+
|
|
5
|
+
All you need is a Solana wallet — the SDK handles authentication, payment negotiation, and provider routing automatically.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
bun add @ottocode/ai-sdk ai
|
|
11
|
+
# or
|
|
12
|
+
npm install @ottocode/ai-sdk ai
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Quick Start
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
import { createSetu } from '@ottocode/ai-sdk';
|
|
19
|
+
import { generateText } from 'ai';
|
|
20
|
+
|
|
21
|
+
const setu = createSetu({
|
|
22
|
+
auth: { privateKey: process.env.SOLANA_PRIVATE_KEY! },
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
const { text } = await generateText({
|
|
26
|
+
model: setu.model('claude-sonnet-4-20250514'),
|
|
27
|
+
prompt: 'Hello!',
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
console.log(text);
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
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.
|
|
34
|
+
|
|
35
|
+
## Provider Auto-Resolution
|
|
36
|
+
|
|
37
|
+
Models are resolved to providers by prefix:
|
|
38
|
+
|
|
39
|
+
| Prefix | Provider | API Format |
|
|
40
|
+
|---|---|---|
|
|
41
|
+
| `claude-` | Anthropic | Messages |
|
|
42
|
+
| `gpt-`, `o1`, `o3`, `o4`, `codex-` | OpenAI | Responses |
|
|
43
|
+
| `gemini-` | Google | Native |
|
|
44
|
+
| `kimi-` | Moonshot | OpenAI Chat |
|
|
45
|
+
| `MiniMax-` | MiniMax | Messages |
|
|
46
|
+
| `z1-` | Z.AI | OpenAI Chat |
|
|
47
|
+
|
|
48
|
+
```ts
|
|
49
|
+
setu.model('claude-sonnet-4-20250514'); // → anthropic
|
|
50
|
+
setu.model('gpt-4o'); // → openai
|
|
51
|
+
setu.model('gemini-2.5-pro'); // → google
|
|
52
|
+
setu.model('kimi-k2'); // → moonshot
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Explicit Provider
|
|
56
|
+
|
|
57
|
+
Override auto-resolution when needed:
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
const model = setu.provider('openai').model('gpt-4o');
|
|
61
|
+
const model = setu.provider('anthropic', 'anthropic-messages').model('claude-sonnet-4-20250514');
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Configuration
|
|
65
|
+
|
|
66
|
+
```ts
|
|
67
|
+
const setu = createSetu({
|
|
68
|
+
// Required: Solana wallet private key (base58)
|
|
69
|
+
auth: { privateKey: '...' },
|
|
70
|
+
|
|
71
|
+
// Optional: Setu API base URL (default: https://api.setu.ottocode.io)
|
|
72
|
+
baseURL: 'https://api.setu.ottocode.io',
|
|
73
|
+
|
|
74
|
+
// Optional: Solana RPC URL (default: https://api.mainnet-beta.solana.com)
|
|
75
|
+
rpcURL: 'https://api.mainnet-beta.solana.com',
|
|
76
|
+
|
|
77
|
+
// Optional: Payment callbacks
|
|
78
|
+
callbacks: { /* see Payment Callbacks */ },
|
|
79
|
+
|
|
80
|
+
// Optional: Cache configuration
|
|
81
|
+
cache: { /* see Caching */ },
|
|
82
|
+
|
|
83
|
+
// Optional: Payment options
|
|
84
|
+
payment: { /* see Payment Options */ },
|
|
85
|
+
|
|
86
|
+
// Optional: Custom model→provider mappings
|
|
87
|
+
modelMap: {
|
|
88
|
+
'my-custom-model': 'openai',
|
|
89
|
+
},
|
|
90
|
+
|
|
91
|
+
// Optional: Register custom providers
|
|
92
|
+
providers: [
|
|
93
|
+
{
|
|
94
|
+
id: 'my-provider',
|
|
95
|
+
apiFormat: 'openai-chat',
|
|
96
|
+
modelPrefix: 'myp-',
|
|
97
|
+
},
|
|
98
|
+
],
|
|
99
|
+
});
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Payment Callbacks
|
|
103
|
+
|
|
104
|
+
Monitor and control the payment lifecycle:
|
|
105
|
+
|
|
106
|
+
```ts
|
|
107
|
+
const setu = createSetu({
|
|
108
|
+
auth: { privateKey: '...' },
|
|
109
|
+
callbacks: {
|
|
110
|
+
// Called when a 402 is received and payment is needed
|
|
111
|
+
onPaymentRequired: (amountUsd, currentBalance) => {
|
|
112
|
+
console.log(`Payment required: $${amountUsd}`);
|
|
113
|
+
},
|
|
114
|
+
|
|
115
|
+
// Called when the SDK is signing a transaction
|
|
116
|
+
onPaymentSigning: () => {
|
|
117
|
+
console.log('Signing payment...');
|
|
118
|
+
},
|
|
119
|
+
|
|
120
|
+
// Called after successful payment
|
|
121
|
+
onPaymentComplete: ({ amountUsd, newBalance, transactionId }) => {
|
|
122
|
+
console.log(`Paid $${amountUsd}, balance: $${newBalance}`);
|
|
123
|
+
},
|
|
124
|
+
|
|
125
|
+
// Called on payment failure
|
|
126
|
+
onPaymentError: (error) => {
|
|
127
|
+
console.error('Payment failed:', error);
|
|
128
|
+
},
|
|
129
|
+
|
|
130
|
+
// Called after each request with cost info (streaming & non-streaming)
|
|
131
|
+
onBalanceUpdate: ({ costUsd, balanceRemaining, inputTokens, outputTokens }) => {
|
|
132
|
+
console.log(`Cost: $${costUsd}, remaining: $${balanceRemaining}`);
|
|
133
|
+
},
|
|
134
|
+
|
|
135
|
+
// Optional: interactive approval before payment
|
|
136
|
+
onPaymentApproval: async ({ amountUsd, currentBalance }) => {
|
|
137
|
+
// return 'crypto' to pay, 'fiat' for fiat flow, 'cancel' to abort
|
|
138
|
+
return 'crypto';
|
|
139
|
+
},
|
|
140
|
+
},
|
|
141
|
+
});
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## Payment Options
|
|
145
|
+
|
|
146
|
+
```ts
|
|
147
|
+
const setu = createSetu({
|
|
148
|
+
auth: { privateKey: '...' },
|
|
149
|
+
payment: {
|
|
150
|
+
// 'auto' (default) — pay automatically
|
|
151
|
+
// 'approval' — call onPaymentApproval before each payment
|
|
152
|
+
topupApprovalMode: 'auto',
|
|
153
|
+
|
|
154
|
+
// Auto-pay without approval if wallet USDC balance >= threshold
|
|
155
|
+
autoPayThresholdUsd: 5.0,
|
|
156
|
+
|
|
157
|
+
// Max retries for a single API request (default: 3)
|
|
158
|
+
maxRequestAttempts: 3,
|
|
159
|
+
|
|
160
|
+
// Max total payment attempts per wallet (default: 20)
|
|
161
|
+
maxPaymentAttempts: 20,
|
|
162
|
+
},
|
|
163
|
+
});
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
## Caching
|
|
167
|
+
|
|
168
|
+
### Anthropic Cache Control
|
|
169
|
+
|
|
170
|
+
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.
|
|
171
|
+
|
|
172
|
+
```ts
|
|
173
|
+
// Default: auto caching (1 system + 1 message breakpoint)
|
|
174
|
+
createSetu({ auth });
|
|
175
|
+
|
|
176
|
+
// Disable completely
|
|
177
|
+
createSetu({ auth, cache: { anthropicCaching: false } });
|
|
178
|
+
|
|
179
|
+
// Manual: SDK won't inject cache_control — set it yourself in messages
|
|
180
|
+
createSetu({ auth, cache: { anthropicCaching: { strategy: 'manual' } } });
|
|
181
|
+
|
|
182
|
+
// Custom breakpoint count and placement
|
|
183
|
+
createSetu({
|
|
184
|
+
auth,
|
|
185
|
+
cache: {
|
|
186
|
+
anthropicCaching: {
|
|
187
|
+
systemBreakpoints: 2, // cache first 2 system blocks
|
|
188
|
+
systemPlacement: 'first', // 'first' | 'last' | 'all'
|
|
189
|
+
messageBreakpoints: 3, // cache last 3 messages
|
|
190
|
+
messagePlacement: 'last', // 'first' | 'last' | 'all'
|
|
191
|
+
},
|
|
192
|
+
},
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
// Full custom transform
|
|
196
|
+
createSetu({
|
|
197
|
+
auth,
|
|
198
|
+
cache: {
|
|
199
|
+
anthropicCaching: {
|
|
200
|
+
strategy: 'custom',
|
|
201
|
+
transform: (body) => {
|
|
202
|
+
// modify body however you want
|
|
203
|
+
return body;
|
|
204
|
+
},
|
|
205
|
+
},
|
|
206
|
+
},
|
|
207
|
+
});
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
| Option | Default | Description |
|
|
211
|
+
|---|---|---|
|
|
212
|
+
| `strategy` | `'auto'` | `'auto'`, `'manual'`, `'custom'`, or `false` |
|
|
213
|
+
| `systemBreakpoints` | `1` | Number of system blocks to cache |
|
|
214
|
+
| `messageBreakpoints` | `1` | Number of messages to cache |
|
|
215
|
+
| `systemPlacement` | `'first'` | Which system blocks: `'first'`, `'last'`, `'all'` |
|
|
216
|
+
| `messagePlacement` | `'last'` | Which messages: `'first'`, `'last'`, `'all'` |
|
|
217
|
+
| `cacheType` | `'ephemeral'` | The `cache_control.type` value |
|
|
218
|
+
|
|
219
|
+
### Setu Server-Side Caching
|
|
220
|
+
|
|
221
|
+
Provider-agnostic caching at the Setu proxy layer:
|
|
222
|
+
|
|
223
|
+
```ts
|
|
224
|
+
createSetu({
|
|
225
|
+
auth,
|
|
226
|
+
cache: {
|
|
227
|
+
promptCacheKey: 'my-session-123',
|
|
228
|
+
promptCacheRetention: 'in_memory', // or '24h'
|
|
229
|
+
},
|
|
230
|
+
});
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
### OpenAI / Google
|
|
234
|
+
|
|
235
|
+
- **OpenAI**: Automatic server-side prefix caching — no configuration needed
|
|
236
|
+
- **Google**: Requires pre-uploaded `cachedContent` at the application level
|
|
237
|
+
|
|
238
|
+
## Balance
|
|
239
|
+
|
|
240
|
+
```ts
|
|
241
|
+
// Setu account balance
|
|
242
|
+
const balance = await setu.balance();
|
|
243
|
+
// { walletAddress, balance, totalSpent, totalTopups, requestCount }
|
|
244
|
+
|
|
245
|
+
// On-chain USDC balance
|
|
246
|
+
const wallet = await setu.walletBalance('mainnet');
|
|
247
|
+
// { walletAddress, usdcBalance, network }
|
|
248
|
+
|
|
249
|
+
// Wallet address
|
|
250
|
+
console.log(setu.walletAddress);
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
## Custom Providers
|
|
254
|
+
|
|
255
|
+
Register providers at init or runtime:
|
|
256
|
+
|
|
257
|
+
```ts
|
|
258
|
+
// At init
|
|
259
|
+
const setu = createSetu({
|
|
260
|
+
auth,
|
|
261
|
+
providers: [
|
|
262
|
+
{ id: 'my-provider', apiFormat: 'openai-chat', modelPrefix: 'myp-' },
|
|
263
|
+
],
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
// At runtime
|
|
267
|
+
setu.registry.register({
|
|
268
|
+
id: 'another-provider',
|
|
269
|
+
apiFormat: 'anthropic-messages',
|
|
270
|
+
models: ['specific-model-id'],
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
// Map a specific model to a provider
|
|
274
|
+
setu.registry.mapModel('some-model', 'openai');
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
### API Formats
|
|
278
|
+
|
|
279
|
+
| Format | Description | Used by |
|
|
280
|
+
|---|---|---|
|
|
281
|
+
| `openai-responses` | OpenAI Responses API | OpenAI |
|
|
282
|
+
| `anthropic-messages` | Anthropic Messages API | Anthropic, MiniMax |
|
|
283
|
+
| `openai-chat` | OpenAI Chat Completions (compatible) | Moonshot, Z.AI |
|
|
284
|
+
| `google-native` | Google GenerativeAI native | Google |
|
|
285
|
+
|
|
286
|
+
## Low-Level: Custom Fetch
|
|
287
|
+
|
|
288
|
+
Use the x402-aware fetch wrapper directly:
|
|
289
|
+
|
|
290
|
+
```ts
|
|
291
|
+
const customFetch = setu.fetch();
|
|
292
|
+
|
|
293
|
+
const response = await customFetch('https://api.setu.ottocode.io/v1/messages', {
|
|
294
|
+
method: 'POST',
|
|
295
|
+
headers: { 'Content-Type': 'application/json' },
|
|
296
|
+
body: JSON.stringify({ model: 'claude-sonnet-4-20250514', messages: [...] }),
|
|
297
|
+
});
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
## Standalone Utilities
|
|
301
|
+
|
|
302
|
+
```ts
|
|
303
|
+
import {
|
|
304
|
+
fetchBalance,
|
|
305
|
+
fetchWalletUsdcBalance,
|
|
306
|
+
getPublicKeyFromPrivate,
|
|
307
|
+
addAnthropicCacheControl,
|
|
308
|
+
createSetuFetch,
|
|
309
|
+
} from '@ottocode/ai-sdk';
|
|
310
|
+
|
|
311
|
+
// Get wallet address from private key
|
|
312
|
+
const address = getPublicKeyFromPrivate(privateKey);
|
|
313
|
+
|
|
314
|
+
// Fetch balance without creating a full Setu instance
|
|
315
|
+
const balance = await fetchBalance({ privateKey });
|
|
316
|
+
|
|
317
|
+
// Fetch on-chain USDC
|
|
318
|
+
const usdc = await fetchWalletUsdcBalance({ privateKey }, 'mainnet');
|
|
319
|
+
|
|
320
|
+
// Create a standalone x402-aware fetch
|
|
321
|
+
const setuFetch = createSetuFetch({
|
|
322
|
+
wallet: createWalletContext({ privateKey }),
|
|
323
|
+
baseURL: 'https://api.setu.ottocode.io',
|
|
324
|
+
});
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
## How It Works
|
|
328
|
+
|
|
329
|
+
1. You call `setu.model('claude-sonnet-4-20250514')` — the SDK resolves this to Anthropic
|
|
330
|
+
2. It creates an ai-sdk provider (`@ai-sdk/anthropic`) pointed at the Setu proxy
|
|
331
|
+
3. A custom fetch wrapper intercepts all requests to:
|
|
332
|
+
- Inject wallet auth headers (address, nonce, signature)
|
|
333
|
+
- Inject Anthropic cache control (if enabled)
|
|
334
|
+
- Handle 402 responses by signing USDC payments via x402
|
|
335
|
+
- Sniff balance/cost info from SSE stream comments
|
|
336
|
+
4. The Setu proxy verifies the wallet, checks balance, forwards to the real provider, tracks usage
|
|
337
|
+
|
|
338
|
+
## Requirements
|
|
339
|
+
|
|
340
|
+
- Solana wallet with USDC (for payments)
|
|
341
|
+
- `ai` SDK v6+ as a peer dependency
|
|
342
|
+
- Node.js 18+ or Bun
|
|
343
|
+
|
|
344
|
+
## License
|
|
345
|
+
|
|
346
|
+
MIT
|
package/package.json
CHANGED
package/src/providers/factory.ts
CHANGED
|
@@ -2,6 +2,8 @@ import { createOpenAI } from '@ai-sdk/openai';
|
|
|
2
2
|
import { createAnthropic } from '@ai-sdk/anthropic';
|
|
3
3
|
import { createGoogleGenerativeAI } from '@ai-sdk/google';
|
|
4
4
|
import { createOpenAICompatible } from '@ai-sdk/openai-compatible';
|
|
5
|
+
import { wrapLanguageModel } from 'ai';
|
|
6
|
+
import type { LanguageModelV3Middleware } from '@ai-sdk/provider';
|
|
5
7
|
import type { ProviderApiFormat, ProviderId, FetchFunction } from '../types.ts';
|
|
6
8
|
|
|
7
9
|
export function createModel(
|
|
@@ -10,9 +12,12 @@ export function createModel(
|
|
|
10
12
|
providerId: ProviderId,
|
|
11
13
|
baseURL: string,
|
|
12
14
|
customFetch: FetchFunction,
|
|
15
|
+
middleware?: LanguageModelV3Middleware | LanguageModelV3Middleware[],
|
|
13
16
|
) {
|
|
14
17
|
const fetchFn = customFetch as unknown as typeof globalThis.fetch;
|
|
15
18
|
|
|
19
|
+
let model;
|
|
20
|
+
|
|
16
21
|
switch (apiFormat) {
|
|
17
22
|
case 'anthropic-messages': {
|
|
18
23
|
const provider = createAnthropic({
|
|
@@ -20,7 +25,8 @@ export function createModel(
|
|
|
20
25
|
apiKey: 'setu-wallet-auth',
|
|
21
26
|
fetch: fetchFn,
|
|
22
27
|
});
|
|
23
|
-
|
|
28
|
+
model = provider(modelId);
|
|
29
|
+
break;
|
|
24
30
|
}
|
|
25
31
|
|
|
26
32
|
case 'google-native': {
|
|
@@ -29,7 +35,8 @@ export function createModel(
|
|
|
29
35
|
apiKey: 'setu-wallet-auth',
|
|
30
36
|
fetch: fetchFn,
|
|
31
37
|
});
|
|
32
|
-
|
|
38
|
+
model = provider(modelId);
|
|
39
|
+
break;
|
|
33
40
|
}
|
|
34
41
|
|
|
35
42
|
case 'openai-chat': {
|
|
@@ -39,7 +46,8 @@ export function createModel(
|
|
|
39
46
|
headers: { Authorization: 'Bearer setu-wallet-auth' },
|
|
40
47
|
fetch: fetchFn,
|
|
41
48
|
});
|
|
42
|
-
|
|
49
|
+
model = provider(modelId);
|
|
50
|
+
break;
|
|
43
51
|
}
|
|
44
52
|
|
|
45
53
|
case 'openai-responses':
|
|
@@ -49,7 +57,14 @@ export function createModel(
|
|
|
49
57
|
apiKey: 'setu-wallet-auth',
|
|
50
58
|
fetch: fetchFn,
|
|
51
59
|
});
|
|
52
|
-
|
|
60
|
+
model = provider.responses(modelId);
|
|
61
|
+
break;
|
|
53
62
|
}
|
|
54
63
|
}
|
|
64
|
+
|
|
65
|
+
if (middleware) {
|
|
66
|
+
return wrapLanguageModel({ model, middleware });
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return model;
|
|
55
70
|
}
|
package/src/setu.ts
CHANGED
|
@@ -55,6 +55,7 @@ export function createSetu(config: SetuConfig): SetuInstance {
|
|
|
55
55
|
resolved.providerId,
|
|
56
56
|
modelBaseURL,
|
|
57
57
|
setuFetch,
|
|
58
|
+
config.middleware,
|
|
58
59
|
);
|
|
59
60
|
},
|
|
60
61
|
|
|
@@ -63,7 +64,7 @@ export function createSetu(config: SetuConfig): SetuInstance {
|
|
|
63
64
|
model(modelId: string) {
|
|
64
65
|
const resolved = registry.resolve(modelId);
|
|
65
66
|
const format = apiFormat ?? resolved?.apiFormat ?? 'openai-chat';
|
|
66
|
-
return createModel(modelId, format, providerId, modelBaseURL, setuFetch);
|
|
67
|
+
return createModel(modelId, format, providerId, modelBaseURL, setuFetch, config.middleware);
|
|
67
68
|
},
|
|
68
69
|
};
|
|
69
70
|
},
|
package/src/types.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { LanguageModelV3Middleware } from '@ai-sdk/provider';
|
|
2
|
+
|
|
1
3
|
export type ProviderId =
|
|
2
4
|
| 'openai'
|
|
3
5
|
| 'anthropic'
|
|
@@ -81,6 +83,7 @@ export interface SetuConfig {
|
|
|
81
83
|
callbacks?: PaymentCallbacks;
|
|
82
84
|
cache?: CacheOptions;
|
|
83
85
|
payment?: PaymentOptions;
|
|
86
|
+
middleware?: LanguageModelV3Middleware | LanguageModelV3Middleware[];
|
|
84
87
|
}
|
|
85
88
|
|
|
86
89
|
export interface ExactPaymentRequirement {
|