@ottocode/sdk 0.1.198 → 0.1.199

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ottocode/sdk",
3
- "version": "0.1.198",
3
+ "version": "0.1.199",
4
4
  "description": "AI agent SDK for building intelligent assistants - tree-shakable and comprehensive",
5
5
  "author": "nitishxyz",
6
6
  "license": "MIT",
package/src/index.ts CHANGED
@@ -70,6 +70,8 @@ export {
70
70
  export type {
71
71
  SetuAuth,
72
72
  SetuProviderOptions,
73
+ SetuPaymentCallbacks,
74
+ SetuBalanceUpdate,
73
75
  SetuBalanceResponse,
74
76
  SolanaUsdcBalanceResponse,
75
77
  } from './providers/src/index.ts';
@@ -30,6 +30,12 @@ const isAllowedGoogleModel = (id: string): boolean => {
30
30
  return false;
31
31
  };
32
32
 
33
+ const isAllowedZaiModel = (id: string): boolean => {
34
+ if (id.startsWith('glm-4.7')) return true;
35
+ if (id.startsWith('glm-5')) return true;
36
+ return false;
37
+ };
38
+
33
39
  const SETU_SOURCES: Array<{
34
40
  id: ProviderId;
35
41
  npm: string;
@@ -55,6 +61,11 @@ const SETU_SOURCES: Array<{
55
61
  npm: '@ai-sdk/google',
56
62
  family: 'google',
57
63
  },
64
+ {
65
+ id: 'zai',
66
+ npm: '@ai-sdk/openai-compatible',
67
+ family: 'openai-compatible',
68
+ },
58
69
  ];
59
70
 
60
71
  function cloneModel(model: ModelInfo): ModelInfo {
@@ -83,6 +94,7 @@ function buildSetuEntry(base: CatalogMap): ProviderCatalogEntry | null {
83
94
  if (id === 'openai') return isAllowedOpenAIModel(model.id);
84
95
  if (id === 'anthropic') return isAllowedAnthropicModel(model.id);
85
96
  if (id === 'google') return isAllowedGoogleModel(model.id);
97
+ if (id === 'zai') return isAllowedZaiModel(model.id);
86
98
  return true;
87
99
  });
88
100
  return sourceModels.map((model) => {
@@ -35,6 +35,7 @@ export type {
35
35
  SetuAuth,
36
36
  SetuProviderOptions,
37
37
  SetuPaymentCallbacks,
38
+ SetuBalanceUpdate,
38
39
  SetuBalanceResponse,
39
40
  SolanaUsdcBalanceResponse,
40
41
  } from './setu-client.ts';
@@ -44,6 +44,13 @@ const DEFAULT_RPC_URL = 'https://api.mainnet-beta.solana.com';
44
44
  const DEFAULT_MAX_ATTEMPTS = 3;
45
45
  const DEFAULT_MAX_PAYMENT_ATTEMPTS = 20;
46
46
 
47
+ export type SetuBalanceUpdate = {
48
+ costUsd: number;
49
+ balanceRemaining: number;
50
+ inputTokens?: number;
51
+ outputTokens?: number;
52
+ };
53
+
47
54
  export type SetuPaymentCallbacks = {
48
55
  onPaymentRequired?: (amountUsd: number, currentBalance?: number) => void;
49
56
  onPaymentSigning?: () => void;
@@ -57,6 +64,7 @@ export type SetuPaymentCallbacks = {
57
64
  amountUsd: number;
58
65
  currentBalance: number;
59
66
  }) => Promise<'crypto' | 'fiat' | 'cancel'>;
67
+ onBalanceUpdate?: (update: SetuBalanceUpdate) => void;
60
68
  };
61
69
 
62
70
  export type SetuProviderOptions = {
@@ -197,7 +205,7 @@ export function createSetuFetch(
197
205
  const response = await baseFetch(input, { ...init, body, headers });
198
206
 
199
207
  if (response.status !== 402) {
200
- return response;
208
+ return wrapResponseWithBalanceSniffing(response, callbacks);
201
209
  }
202
210
 
203
211
  const payload = await response.json().catch(() => ({}));
@@ -266,6 +274,70 @@ export function createSetuFetch(
266
274
  };
267
275
  }
268
276
 
277
+ function tryParseSetuComment(
278
+ line: string,
279
+ onBalanceUpdate: (update: SetuBalanceUpdate) => void,
280
+ ) {
281
+ const trimmed = line.replace(/\r$/, '');
282
+ if (!trimmed.startsWith(': setu ')) return;
283
+ try {
284
+ const data = JSON.parse(trimmed.slice(7));
285
+ onBalanceUpdate({
286
+ costUsd: parseFloat(data.cost_usd ?? '0'),
287
+ balanceRemaining: parseFloat(data.balance_remaining ?? '0'),
288
+ inputTokens: data.input_tokens ? Number(data.input_tokens) : undefined,
289
+ outputTokens: data.output_tokens ? Number(data.output_tokens) : undefined,
290
+ });
291
+ } catch {}
292
+ }
293
+
294
+ function wrapResponseWithBalanceSniffing(
295
+ response: Response,
296
+ callbacks: SetuPaymentCallbacks,
297
+ ): Response {
298
+ if (!callbacks.onBalanceUpdate) return response;
299
+
300
+ const balanceHeader = response.headers.get('x-balance-remaining');
301
+ const costHeader = response.headers.get('x-cost-usd');
302
+ if (balanceHeader && costHeader) {
303
+ callbacks.onBalanceUpdate({
304
+ costUsd: parseFloat(costHeader),
305
+ balanceRemaining: parseFloat(balanceHeader),
306
+ });
307
+ return response;
308
+ }
309
+
310
+ if (!response.body) return response;
311
+
312
+ const onBalanceUpdate = callbacks.onBalanceUpdate;
313
+ let partial = '';
314
+ const decoder = new TextDecoder();
315
+ const transform = new TransformStream<Uint8Array, Uint8Array>({
316
+ transform(chunk, controller) {
317
+ controller.enqueue(chunk);
318
+ partial += decoder.decode(chunk, { stream: true });
319
+ let nlIndex = partial.indexOf('\n');
320
+ while (nlIndex !== -1) {
321
+ const line = partial.slice(0, nlIndex);
322
+ partial = partial.slice(nlIndex + 1);
323
+ tryParseSetuComment(line, onBalanceUpdate);
324
+ nlIndex = partial.indexOf('\n');
325
+ }
326
+ },
327
+ flush() {
328
+ if (partial.trim()) {
329
+ tryParseSetuComment(partial, onBalanceUpdate);
330
+ }
331
+ },
332
+ });
333
+
334
+ return new Response(response.body.pipeThrough(transform), {
335
+ status: response.status,
336
+ statusText: response.statusText,
337
+ headers: response.headers,
338
+ });
339
+ }
340
+
269
341
  /**
270
342
  * Create a Setu-backed AI model.
271
343
  *