@agi-cli/sdk 0.1.132 → 0.1.133

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": "@agi-cli/sdk",
3
- "version": "0.1.132",
3
+ "version": "0.1.133",
4
4
  "description": "AI agent SDK for building intelligent assistants - tree-shakable and comprehensive",
5
5
  "author": "ntishxyz",
6
6
  "license": "MIT",
package/src/index.ts CHANGED
@@ -59,11 +59,13 @@ export {
59
59
  createSolforgeModel,
60
60
  fetchSolforgeBalance,
61
61
  getPublicKeyFromPrivate,
62
+ fetchSolanaUsdcBalance,
62
63
  } from './providers/src/index.ts';
63
64
  export type {
64
65
  SolforgeAuth,
65
66
  SolforgeProviderOptions,
66
67
  SolforgeBalanceResponse,
68
+ SolanaUsdcBalanceResponse,
67
69
  } from './providers/src/index.ts';
68
70
  export {
69
71
  createOpenAIOAuthFetch,
@@ -22,12 +22,14 @@ export {
22
22
  createSolforgeModel,
23
23
  fetchSolforgeBalance,
24
24
  getPublicKeyFromPrivate,
25
+ fetchSolanaUsdcBalance,
25
26
  } from './solforge-client.ts';
26
27
  export type {
27
28
  SolforgeAuth,
28
29
  SolforgeProviderOptions,
29
30
  SolforgePaymentCallbacks,
30
31
  SolforgeBalanceResponse,
32
+ SolanaUsdcBalanceResponse,
31
33
  } from './solforge-client.ts';
32
34
  export {
33
35
  createOpenAIOAuthFetch,
@@ -67,6 +67,42 @@ type PaymentResponse = {
67
67
  transaction?: string;
68
68
  };
69
69
 
70
+ type PaymentQueueEntry = {
71
+ promise: Promise<void>;
72
+ resolve: () => void;
73
+ };
74
+
75
+ const paymentQueues = new Map<string, PaymentQueueEntry>();
76
+ const globalPaymentAttempts = new Map<string, number>();
77
+
78
+ async function acquirePaymentLock(walletAddress: string): Promise<() => void> {
79
+ const existing = paymentQueues.get(walletAddress);
80
+
81
+ let resolveFunc: () => void = () => {};
82
+ const newPromise = new Promise<void>((resolve) => {
83
+ resolveFunc = resolve;
84
+ });
85
+
86
+ const entry: PaymentQueueEntry = {
87
+ promise: newPromise,
88
+ resolve: resolveFunc,
89
+ };
90
+
91
+ paymentQueues.set(walletAddress, entry);
92
+
93
+ if (existing) {
94
+ console.log('[Solforge] Waiting for pending payment to complete...');
95
+ await existing.promise;
96
+ }
97
+
98
+ return () => {
99
+ if (paymentQueues.get(walletAddress) === entry) {
100
+ paymentQueues.delete(walletAddress);
101
+ }
102
+ resolveFunc();
103
+ };
104
+ }
105
+
70
106
  export function createSolforgeFetch(
71
107
  auth: SolforgeAuth,
72
108
  options: SolforgeProviderOptions = {},
@@ -84,7 +120,6 @@ export function createSolforgeFetch(
84
120
  const promptCacheRetention = options.promptCacheRetention;
85
121
 
86
122
  const baseFetch = globalThis.fetch.bind(globalThis);
87
- let paymentAttempts = 0;
88
123
 
89
124
  const buildWalletHeaders = () => {
90
125
  const nonce = Date.now().toString();
@@ -105,19 +140,95 @@ export function createSolforgeFetch(
105
140
  while (attempt < maxAttempts) {
106
141
  attempt++;
107
142
  let body = init?.body;
108
- if (
109
- body &&
110
- typeof body === 'string' &&
111
- (promptCacheKey || promptCacheRetention)
112
- ) {
143
+ if (body && typeof body === 'string') {
113
144
  try {
114
145
  const parsed = JSON.parse(body);
115
- if (promptCacheKey) {
116
- parsed.prompt_cache_key = promptCacheKey;
117
- }
118
- if (promptCacheRetention) {
146
+
147
+ if (promptCacheKey) parsed.prompt_cache_key = promptCacheKey;
148
+ if (promptCacheRetention)
119
149
  parsed.prompt_cache_retention = promptCacheRetention;
150
+
151
+ const MAX_SYSTEM_CACHE = 1;
152
+ const MAX_MESSAGE_CACHE = 1;
153
+ let systemCacheUsed = 0;
154
+ let messageCacheUsed = 0;
155
+
156
+ if (parsed.system && Array.isArray(parsed.system)) {
157
+ parsed.system = parsed.system.map(
158
+ (
159
+ block: { type: string; cache_control?: unknown },
160
+ index: number,
161
+ ) => {
162
+ if (block.cache_control) return block;
163
+ if (
164
+ systemCacheUsed < MAX_SYSTEM_CACHE &&
165
+ index === 0 &&
166
+ block.type === 'text'
167
+ ) {
168
+ systemCacheUsed++;
169
+ return { ...block, cache_control: { type: 'ephemeral' } };
170
+ }
171
+ return block;
172
+ },
173
+ );
174
+ }
175
+
176
+ if (parsed.messages && Array.isArray(parsed.messages)) {
177
+ const messageCount = parsed.messages.length;
178
+ parsed.messages = parsed.messages.map(
179
+ (
180
+ msg: {
181
+ role: string;
182
+ content: unknown;
183
+ [key: string]: unknown;
184
+ },
185
+ msgIndex: number,
186
+ ) => {
187
+ const isLast = msgIndex === messageCount - 1;
188
+
189
+ if (Array.isArray(msg.content)) {
190
+ const blocks = msg.content as {
191
+ type: string;
192
+ cache_control?: unknown;
193
+ }[];
194
+ const content = blocks.map((block, blockIndex) => {
195
+ if (block.cache_control) return block;
196
+ if (
197
+ isLast &&
198
+ messageCacheUsed < MAX_MESSAGE_CACHE &&
199
+ blockIndex === blocks.length - 1
200
+ ) {
201
+ messageCacheUsed++;
202
+ return { ...block, cache_control: { type: 'ephemeral' } };
203
+ }
204
+ return block;
205
+ });
206
+ return { ...msg, content };
207
+ }
208
+
209
+ if (
210
+ isLast &&
211
+ messageCacheUsed < MAX_MESSAGE_CACHE &&
212
+ typeof msg.content === 'string'
213
+ ) {
214
+ messageCacheUsed++;
215
+ return {
216
+ ...msg,
217
+ content: [
218
+ {
219
+ type: 'text',
220
+ text: msg.content,
221
+ cache_control: { type: 'ephemeral' },
222
+ },
223
+ ],
224
+ };
225
+ }
226
+
227
+ return msg;
228
+ },
229
+ );
120
230
  }
231
+
121
232
  body = JSON.stringify(parsed);
122
233
  } catch {}
123
234
  }
@@ -143,7 +254,8 @@ export function createSolforgeFetch(
143
254
  throw new Error('Solforge: payment failed after multiple attempts');
144
255
  }
145
256
 
146
- const remainingPayments = maxPaymentAttempts - paymentAttempts;
257
+ const currentAttempts = globalPaymentAttempts.get(walletAddress) ?? 0;
258
+ const remainingPayments = maxPaymentAttempts - currentAttempts;
147
259
  if (remainingPayments <= 0) {
148
260
  callbacks.onPaymentError?.('Maximum payment attempts exceeded');
149
261
  throw new Error(
@@ -151,20 +263,29 @@ export function createSolforgeFetch(
151
263
  );
152
264
  }
153
265
 
154
- const amountUsd = parseInt(requirement.maxAmountRequired, 10) / 1_000_000;
155
- callbacks.onPaymentRequired?.(amountUsd);
156
-
157
- const outcome = await handlePayment({
158
- requirement,
159
- keypair,
160
- rpcURL,
161
- baseURL,
162
- baseFetch,
163
- buildWalletHeaders,
164
- maxAttempts: remainingPayments,
165
- callbacks,
166
- });
167
- paymentAttempts += outcome.attemptsUsed;
266
+ const releaseLock = await acquirePaymentLock(walletAddress);
267
+
268
+ try {
269
+ const amountUsd =
270
+ parseInt(requirement.maxAmountRequired, 10) / 1_000_000;
271
+ callbacks.onPaymentRequired?.(amountUsd);
272
+
273
+ const outcome = await handlePayment({
274
+ requirement,
275
+ keypair,
276
+ rpcURL,
277
+ baseURL,
278
+ baseFetch,
279
+ buildWalletHeaders,
280
+ maxAttempts: remainingPayments,
281
+ callbacks,
282
+ });
283
+
284
+ const newTotal = currentAttempts + outcome.attemptsUsed;
285
+ globalPaymentAttempts.set(walletAddress, newTotal);
286
+ } finally {
287
+ releaseLock();
288
+ }
168
289
  }
169
290
 
170
291
  throw new Error('Solforge: max attempts exceeded');
@@ -442,3 +563,79 @@ export function getPublicKeyFromPrivate(privateKey: string): string | null {
442
563
  return null;
443
564
  }
444
565
  }
566
+
567
+ const USDC_MINT_MAINNET = 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v';
568
+ const USDC_MINT_DEVNET = '4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU';
569
+
570
+ export type SolanaUsdcBalanceResponse = {
571
+ walletAddress: string;
572
+ usdcBalance: number;
573
+ network: 'mainnet' | 'devnet';
574
+ };
575
+
576
+ export async function fetchSolanaUsdcBalance(
577
+ auth: SolforgeAuth,
578
+ network: 'mainnet' | 'devnet' = 'mainnet',
579
+ ): Promise<SolanaUsdcBalanceResponse | null> {
580
+ try {
581
+ const privateKeyBytes = bs58.decode(auth.privateKey);
582
+ const keypair = Keypair.fromSecretKey(privateKeyBytes);
583
+ const walletAddress = keypair.publicKey.toBase58();
584
+
585
+ const rpcUrl =
586
+ network === 'devnet' ? 'https://api.devnet.solana.com' : DEFAULT_RPC_URL;
587
+
588
+ const usdcMint =
589
+ network === 'devnet' ? USDC_MINT_DEVNET : USDC_MINT_MAINNET;
590
+
591
+ const response = await fetch(rpcUrl, {
592
+ method: 'POST',
593
+ headers: { 'Content-Type': 'application/json' },
594
+ body: JSON.stringify({
595
+ jsonrpc: '2.0',
596
+ id: 1,
597
+ method: 'getTokenAccountsByOwner',
598
+ params: [walletAddress, { mint: usdcMint }, { encoding: 'jsonParsed' }],
599
+ }),
600
+ });
601
+
602
+ if (!response.ok) {
603
+ return null;
604
+ }
605
+
606
+ const data = (await response.json()) as {
607
+ result?: {
608
+ value?: Array<{
609
+ account: {
610
+ data: {
611
+ parsed: {
612
+ info: {
613
+ tokenAmount: {
614
+ uiAmount: number;
615
+ };
616
+ };
617
+ };
618
+ };
619
+ };
620
+ }>;
621
+ };
622
+ };
623
+
624
+ const accounts = data.result?.value ?? [];
625
+ let totalUsdcBalance = 0;
626
+
627
+ for (const account of accounts) {
628
+ const uiAmount =
629
+ account.account.data.parsed.info.tokenAmount.uiAmount ?? 0;
630
+ totalUsdcBalance += uiAmount;
631
+ }
632
+
633
+ return {
634
+ walletAddress,
635
+ usdcBalance: totalUsdcBalance,
636
+ network,
637
+ };
638
+ } catch {
639
+ return null;
640
+ }
641
+ }