@agi-cli/sdk 0.1.130 → 0.1.132

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.130",
3
+ "version": "0.1.132",
4
4
  "description": "AI agent SDK for building intelligent assistants - tree-shakable and comprehensive",
5
5
  "author": "ntishxyz",
6
6
  "license": "MIT",
@@ -10,6 +10,23 @@ import {
10
10
  } from '../../../providers/src/index.ts';
11
11
  import type { OAuth } from '../../../types/src/index.ts';
12
12
 
13
+ function needsResponsesApi(model: string): boolean {
14
+ const m = model.toLowerCase();
15
+ if (m.includes('gpt-5')) return true;
16
+ if (m.startsWith('o1')) return true;
17
+ if (m.startsWith('o3')) return true;
18
+ if (m.startsWith('o4')) return true;
19
+ if (m.includes('codex-mini')) return true;
20
+ return false;
21
+ }
22
+
23
+ function resolveOpenAIModel(
24
+ instance: ReturnType<typeof createOpenAI>,
25
+ model: string,
26
+ ) {
27
+ return needsResponsesApi(model) ? instance.responses(model) : instance(model);
28
+ }
29
+
13
30
  export type ProviderName =
14
31
  | 'openai'
15
32
  | 'anthropic'
@@ -45,13 +62,13 @@ export async function resolveModel(
45
62
  apiKey: config.apiKey || 'oauth-token',
46
63
  fetch: config.customFetch,
47
64
  });
48
- return instance(model);
65
+ return resolveOpenAIModel(instance, model);
49
66
  }
50
67
  if (config.apiKey) {
51
68
  const instance = createOpenAI({ apiKey: config.apiKey });
52
- return instance(model);
69
+ return resolveOpenAIModel(instance, model);
53
70
  }
54
- return openai(model);
71
+ return needsResponsesApi(model) ? openai.responses(model) : openai(model);
55
72
  }
56
73
 
57
74
  if (provider === 'anthropic') {
@@ -143,14 +160,12 @@ export async function resolveModel(
143
160
  }
144
161
  const baseURL = config.baseURL || process.env.SOLFORGE_BASE_URL;
145
162
  const rpcURL = process.env.SOLFORGE_SOLANA_RPC_URL;
146
- const topupAmount = process.env.SOLFORGE_TOPUP_MICRO_USDC;
147
163
  return createSolforgeModel(
148
164
  model,
149
165
  { privateKey },
150
166
  {
151
167
  baseURL,
152
168
  rpcURL,
153
- topupAmountMicroUsdc: topupAmount,
154
169
  },
155
170
  );
156
171
  }
package/src/index.ts CHANGED
@@ -57,10 +57,13 @@ export {
57
57
  export {
58
58
  createSolforgeFetch,
59
59
  createSolforgeModel,
60
+ fetchSolforgeBalance,
61
+ getPublicKeyFromPrivate,
60
62
  } from './providers/src/index.ts';
61
63
  export type {
62
64
  SolforgeAuth,
63
65
  SolforgeProviderOptions,
66
+ SolforgeBalanceResponse,
64
67
  } from './providers/src/index.ts';
65
68
  export {
66
69
  createOpenAIOAuthFetch,
@@ -7,6 +7,20 @@ import type {
7
7
  type CatalogMap = Partial<Record<ProviderId, ProviderCatalogEntry>>;
8
8
 
9
9
  const SOLFORGE_ID: ProviderId = 'solforge';
10
+
11
+ const isAllowedOpenAIModel = (id: string): boolean => {
12
+ if (id === 'codex-mini-latest') return true;
13
+ if (id.startsWith('gpt-5')) return true;
14
+ if (id.includes('codex')) return true;
15
+ return false;
16
+ };
17
+
18
+ const isAllowedAnthropicModel = (id: string): boolean => {
19
+ if (id.includes('-4-') || id.includes('-4.')) return true;
20
+ if (id.match(/claude-(haiku|sonnet|opus)-4/)) return true;
21
+ return false;
22
+ };
23
+
10
24
  const SOLFORGE_SOURCES: Array<{ id: ProviderId; npm: string }> = [
11
25
  {
12
26
  id: 'openai',
@@ -39,7 +53,12 @@ function cloneModel(model: ModelInfo): ModelInfo {
39
53
 
40
54
  function buildSolforgeEntry(base: CatalogMap): ProviderCatalogEntry | null {
41
55
  const solforgeModels = SOLFORGE_SOURCES.flatMap(({ id, npm }) => {
42
- const sourceModels = base[id]?.models ?? [];
56
+ const allModels = base[id]?.models ?? [];
57
+ const sourceModels = allModels.filter((model) => {
58
+ if (id === 'openai') return isAllowedOpenAIModel(model.id);
59
+ if (id === 'anthropic') return isAllowedAnthropicModel(model.id);
60
+ return true;
61
+ });
43
62
  return sourceModels.map((model) => {
44
63
  const cloned = cloneModel(model);
45
64
  cloned.provider = { ...(cloned.provider ?? {}), npm };
@@ -61,7 +80,7 @@ function buildSolforgeEntry(base: CatalogMap): ProviderCatalogEntry | null {
61
80
  return providerA.localeCompare(providerB);
62
81
  });
63
82
 
64
- const defaultModelId = 'gpt-4o-mini';
83
+ const defaultModelId = 'codex-mini-latest';
65
84
  const defaultIdx = solforgeModels.findIndex((m) => m.id === defaultModelId);
66
85
  if (defaultIdx > 0) {
67
86
  const [picked] = solforgeModels.splice(defaultIdx, 1);
@@ -324,7 +324,7 @@ export const catalog: Partial<Record<ProviderId, ProviderCatalogEntry>> = {
324
324
  cost: {
325
325
  input: 1.25,
326
326
  output: 10,
327
- cacheRead: 0.13,
327
+ cacheRead: 0.125,
328
328
  },
329
329
  limit: {
330
330
  context: 400000,
@@ -398,7 +398,7 @@ export const catalog: Partial<Record<ProviderId, ProviderCatalogEntry>> = {
398
398
  cost: {
399
399
  input: 0.25,
400
400
  output: 2,
401
- cacheRead: 0.03,
401
+ cacheRead: 0.025,
402
402
  },
403
403
  limit: {
404
404
  context: 400000,
@@ -423,7 +423,7 @@ export const catalog: Partial<Record<ProviderId, ProviderCatalogEntry>> = {
423
423
  cost: {
424
424
  input: 0.05,
425
425
  output: 0.4,
426
- cacheRead: 0.01,
426
+ cacheRead: 0.005,
427
427
  },
428
428
  limit: {
429
429
  context: 400000,
@@ -633,7 +633,7 @@ export const catalog: Partial<Record<ProviderId, ProviderCatalogEntry>> = {
633
633
  id: 'gpt-5.2-codex',
634
634
  label: 'GPT-5.2 Codex',
635
635
  modalities: {
636
- input: ['text', 'image'],
636
+ input: ['text', 'image', 'pdf'],
637
637
  output: ['text'],
638
638
  },
639
639
  toolCall: true,
@@ -4278,6 +4278,31 @@ export const catalog: Partial<Record<ProviderId, ProviderCatalogEntry>> = {
4278
4278
  output: 128000,
4279
4279
  },
4280
4280
  },
4281
+ {
4282
+ id: 'openai/gpt-5.1-codex-max',
4283
+ label: 'GPT-5.1-Codex-Max',
4284
+ modalities: {
4285
+ input: ['text', 'image'],
4286
+ output: ['text'],
4287
+ },
4288
+ toolCall: true,
4289
+ reasoning: true,
4290
+ attachment: true,
4291
+ temperature: true,
4292
+ knowledge: '2024-09-30',
4293
+ releaseDate: '2025-11-13',
4294
+ lastUpdated: '2025-11-13',
4295
+ openWeights: false,
4296
+ cost: {
4297
+ input: 1.1,
4298
+ output: 9,
4299
+ cacheRead: 0.11,
4300
+ },
4301
+ limit: {
4302
+ context: 400000,
4303
+ output: 128000,
4304
+ },
4305
+ },
4281
4306
  {
4282
4307
  id: 'openai/gpt-5.1-codex-mini',
4283
4308
  label: 'GPT-5.1-Codex-Mini',
@@ -5925,6 +5950,31 @@ export const catalog: Partial<Record<ProviderId, ProviderCatalogEntry>> = {
5925
5950
  output: 131072,
5926
5951
  },
5927
5952
  },
5953
+ {
5954
+ id: 'glm-4.7',
5955
+ label: 'GLM-4.7',
5956
+ modalities: {
5957
+ input: ['text'],
5958
+ output: ['text'],
5959
+ },
5960
+ toolCall: true,
5961
+ reasoning: true,
5962
+ attachment: false,
5963
+ temperature: true,
5964
+ knowledge: '2025-04',
5965
+ releaseDate: '2025-12-22',
5966
+ lastUpdated: '2025-12-22',
5967
+ openWeights: true,
5968
+ cost: {
5969
+ input: 0.6,
5970
+ output: 2.2,
5971
+ cacheRead: 0.1,
5972
+ },
5973
+ limit: {
5974
+ context: 204800,
5975
+ output: 131072,
5976
+ },
5977
+ },
5928
5978
  {
5929
5979
  id: 'glm-4.7-free',
5930
5980
  label: 'GLM-4.7',
@@ -6178,7 +6228,7 @@ export const catalog: Partial<Record<ProviderId, ProviderCatalogEntry>> = {
6178
6228
  id: 'gpt-5.2-codex',
6179
6229
  label: 'GPT-5.2 Codex',
6180
6230
  modalities: {
6181
- input: ['text', 'image'],
6231
+ input: ['text', 'image', 'pdf'],
6182
6232
  output: ['text'],
6183
6233
  },
6184
6234
  toolCall: true,
@@ -6694,6 +6744,31 @@ export const catalog: Partial<Record<ProviderId, ProviderCatalogEntry>> = {
6694
6744
  output: 131072,
6695
6745
  },
6696
6746
  },
6747
+ {
6748
+ id: 'glm-4.7-flash',
6749
+ label: 'GLM-4.7-Flash',
6750
+ modalities: {
6751
+ input: ['text'],
6752
+ output: ['text'],
6753
+ },
6754
+ toolCall: true,
6755
+ reasoning: true,
6756
+ attachment: false,
6757
+ temperature: true,
6758
+ knowledge: '2025-04',
6759
+ releaseDate: '2026-01-19',
6760
+ lastUpdated: '2026-01-19',
6761
+ openWeights: true,
6762
+ cost: {
6763
+ input: 0,
6764
+ output: 0,
6765
+ cacheRead: 0,
6766
+ },
6767
+ limit: {
6768
+ context: 200000,
6769
+ output: 131072,
6770
+ },
6771
+ },
6697
6772
  ],
6698
6773
  label: 'Z.AI Coding Plan',
6699
6774
  env: ['ZHIPU_API_KEY'],
@@ -20,11 +20,14 @@ export { providerEnvVar, readEnvKey, setEnvKey } from './env.ts';
20
20
  export {
21
21
  createSolforgeFetch,
22
22
  createSolforgeModel,
23
+ fetchSolforgeBalance,
24
+ getPublicKeyFromPrivate,
23
25
  } from './solforge-client.ts';
24
26
  export type {
25
27
  SolforgeAuth,
26
28
  SolforgeProviderOptions,
27
29
  SolforgePaymentCallbacks,
30
+ SolforgeBalanceResponse,
28
31
  } from './solforge-client.ts';
29
32
  export {
30
33
  createOpenAIOAuthFetch,
@@ -14,6 +14,8 @@ export type OpenAIOAuthConfig = {
14
14
  instructions?: string;
15
15
  reasoningEffort?: 'none' | 'low' | 'medium' | 'high' | 'xhigh';
16
16
  reasoningSummary?: 'auto' | 'detailed';
17
+ promptCacheKey?: string;
18
+ promptCacheRetention?: 'in_memory' | '24h';
17
19
  };
18
20
 
19
21
  async function ensureValidToken(
@@ -44,10 +46,38 @@ async function ensureValidToken(
44
46
 
45
47
  function stripIdsFromInput(input: unknown): unknown {
46
48
  if (Array.isArray(input)) {
47
- return input
49
+ const filtered = input.filter((item) => {
50
+ if (item && typeof item === 'object' && 'type' in item) {
51
+ if (item.type === 'item_reference') return false;
52
+ }
53
+ return true;
54
+ });
55
+
56
+ const validCallIds = new Set<string>();
57
+ for (const item of filtered) {
58
+ if (
59
+ item &&
60
+ typeof item === 'object' &&
61
+ 'type' in item &&
62
+ item.type === 'function_call' &&
63
+ 'call_id' in item &&
64
+ typeof item.call_id === 'string'
65
+ ) {
66
+ validCallIds.add(item.call_id);
67
+ }
68
+ }
69
+
70
+ return filtered
48
71
  .filter((item) => {
49
- if (item && typeof item === 'object' && 'type' in item) {
50
- if (item.type === 'item_reference') return false;
72
+ if (
73
+ item &&
74
+ typeof item === 'object' &&
75
+ 'type' in item &&
76
+ item.type === 'function_call_output' &&
77
+ 'call_id' in item &&
78
+ typeof item.call_id === 'string'
79
+ ) {
80
+ return validCallIds.has(item.call_id);
51
81
  }
52
82
  return true;
53
83
  })
@@ -155,6 +185,19 @@ export function createOpenAIOAuthFetch(config: OpenAIOAuthConfig) {
155
185
  }
156
186
  }
157
187
 
188
+ const cacheKey =
189
+ parsed.providerOptions?.openai?.promptCacheKey ||
190
+ config.promptCacheKey;
191
+ const cacheRetention =
192
+ parsed.providerOptions?.openai?.promptCacheRetention ||
193
+ config.promptCacheRetention;
194
+ if (cacheKey) {
195
+ parsed.prompt_cache_key = cacheKey;
196
+ }
197
+ if (cacheRetention) {
198
+ parsed.prompt_cache_retention = cacheRetention;
199
+ }
200
+
158
201
  delete parsed.max_output_tokens;
159
202
  delete parsed.max_completion_tokens;
160
203
 
@@ -10,14 +10,17 @@ import { createAnthropic } from '@ai-sdk/anthropic';
10
10
 
11
11
  const DEFAULT_BASE_URL = 'https://router.solforge.sh';
12
12
  const DEFAULT_RPC_URL = 'https://api.mainnet-beta.solana.com';
13
- const DEFAULT_TOPUP_AMOUNT = '5000000'; // $5.00
14
13
  const DEFAULT_MAX_ATTEMPTS = 3;
15
14
  const DEFAULT_MAX_PAYMENT_ATTEMPTS = 20;
16
15
 
17
16
  export type SolforgePaymentCallbacks = {
18
17
  onPaymentRequired?: (amountUsd: number) => void;
19
18
  onPaymentSigning?: () => void;
20
- onPaymentComplete?: (data: { amountUsd: number; newBalance: number }) => void;
19
+ onPaymentComplete?: (data: {
20
+ amountUsd: number;
21
+ newBalance: number;
22
+ transactionId?: string;
23
+ }) => void;
21
24
  onPaymentError?: (error: string) => void;
22
25
  };
23
26
 
@@ -25,11 +28,12 @@ export type SolforgeProviderOptions = {
25
28
  baseURL?: string;
26
29
  rpcURL?: string;
27
30
  network?: string;
28
- topupAmountMicroUsdc?: string;
29
31
  maxRequestAttempts?: number;
30
32
  maxPaymentAttempts?: number;
31
33
  callbacks?: SolforgePaymentCallbacks;
32
34
  providerNpm?: string;
35
+ promptCacheKey?: string;
36
+ promptCacheRetention?: 'in_memory' | '24h';
33
37
  };
34
38
 
35
39
  export type SolforgeAuth = {
@@ -60,6 +64,7 @@ type PaymentResponse = {
60
64
  new_balance?: number | string;
61
65
  amount?: number;
62
66
  balance?: number;
67
+ transaction?: string;
63
68
  };
64
69
 
65
70
  export function createSolforgeFetch(
@@ -71,11 +76,12 @@ export function createSolforgeFetch(
71
76
  const walletAddress = keypair.publicKey.toBase58();
72
77
  const baseURL = trimTrailingSlash(options.baseURL ?? DEFAULT_BASE_URL);
73
78
  const rpcURL = options.rpcURL ?? DEFAULT_RPC_URL;
74
- const targetTopup = options.topupAmountMicroUsdc ?? DEFAULT_TOPUP_AMOUNT;
75
79
  const maxAttempts = options.maxRequestAttempts ?? DEFAULT_MAX_ATTEMPTS;
76
80
  const maxPaymentAttempts =
77
81
  options.maxPaymentAttempts ?? DEFAULT_MAX_PAYMENT_ATTEMPTS;
78
82
  const callbacks = options.callbacks ?? {};
83
+ const promptCacheKey = options.promptCacheKey;
84
+ const promptCacheRetention = options.promptCacheRetention;
79
85
 
80
86
  const baseFetch = globalThis.fetch.bind(globalThis);
81
87
  let paymentAttempts = 0;
@@ -98,19 +104,36 @@ export function createSolforgeFetch(
98
104
 
99
105
  while (attempt < maxAttempts) {
100
106
  attempt++;
107
+ let body = init?.body;
108
+ if (
109
+ body &&
110
+ typeof body === 'string' &&
111
+ (promptCacheKey || promptCacheRetention)
112
+ ) {
113
+ try {
114
+ const parsed = JSON.parse(body);
115
+ if (promptCacheKey) {
116
+ parsed.prompt_cache_key = promptCacheKey;
117
+ }
118
+ if (promptCacheRetention) {
119
+ parsed.prompt_cache_retention = promptCacheRetention;
120
+ }
121
+ body = JSON.stringify(parsed);
122
+ } catch {}
123
+ }
101
124
  const headers = new Headers(init?.headers);
102
125
  const walletHeaders = buildWalletHeaders();
103
126
  headers.set('x-wallet-address', walletHeaders['x-wallet-address']);
104
127
  headers.set('x-wallet-nonce', walletHeaders['x-wallet-nonce']);
105
128
  headers.set('x-wallet-signature', walletHeaders['x-wallet-signature']);
106
- const response = await baseFetch(input, { ...init, headers });
129
+ const response = await baseFetch(input, { ...init, body, headers });
107
130
 
108
131
  if (response.status !== 402) {
109
132
  return response;
110
133
  }
111
134
 
112
135
  const payload = await response.json().catch(() => ({}));
113
- const requirement = pickPaymentRequirement(payload, targetTopup);
136
+ const requirement = pickPaymentRequirement(payload);
114
137
  if (!requirement) {
115
138
  callbacks.onPaymentError?.('Unsupported payment requirement');
116
139
  throw new Error('Solforge: unsupported payment requirement');
@@ -183,7 +206,7 @@ export function createSolforgeModel(
183
206
  apiKey: 'solforge-wallet-auth',
184
207
  fetch: customFetch,
185
208
  });
186
- return openai(model);
209
+ return openai.responses(model);
187
210
  }
188
211
 
189
212
  function trimTrailingSlash(url: string) {
@@ -202,7 +225,6 @@ type PaymentRequirementResponse = {
202
225
 
203
226
  function pickPaymentRequirement(
204
227
  payload: unknown,
205
- targetAmount: string,
206
228
  ): ExactPaymentRequirement | null {
207
229
  const acceptsValue =
208
230
  typeof payload === 'object' && payload !== null
@@ -211,17 +233,7 @@ function pickPaymentRequirement(
211
233
  const accepts = Array.isArray(acceptsValue)
212
234
  ? (acceptsValue as ExactPaymentRequirement[])
213
235
  : [];
214
- const exactMatch = accepts.find(
215
- (option) =>
216
- option &&
217
- option.scheme === 'exact' &&
218
- option.maxAmountRequired === targetAmount,
219
- );
220
- if (exactMatch) return exactMatch;
221
- const fallback = accepts.find(
222
- (option) => option && option.scheme === 'exact',
223
- );
224
- return fallback ?? null;
236
+ return accepts.find((option) => option && option.scheme === 'exact') ?? null;
225
237
  }
226
238
 
227
239
  async function handlePayment(args: {
@@ -318,6 +330,7 @@ async function processSinglePayment(args: {
318
330
  args.callbacks.onPaymentComplete?.({
319
331
  amountUsd,
320
332
  newBalance,
333
+ transactionId: parsed.transaction,
321
334
  });
322
335
  console.log(
323
336
  `Solforge payment complete: +$${amountUsd} (balance: $${newBalance})`,
@@ -360,3 +373,72 @@ async function createPaymentPayload(args: {
360
373
  },
361
374
  } as PaymentPayload;
362
375
  }
376
+
377
+ export type SolforgeBalanceResponse = {
378
+ walletAddress: string;
379
+ balance: number;
380
+ totalSpent: number;
381
+ totalTopups: number;
382
+ requestCount: number;
383
+ createdAt?: string;
384
+ lastRequest?: string;
385
+ };
386
+
387
+ export async function fetchSolforgeBalance(
388
+ auth: SolforgeAuth,
389
+ baseURL?: string,
390
+ ): Promise<SolforgeBalanceResponse | null> {
391
+ try {
392
+ const privateKeyBytes = bs58.decode(auth.privateKey);
393
+ const keypair = Keypair.fromSecretKey(privateKeyBytes);
394
+ const walletAddress = keypair.publicKey.toBase58();
395
+ const url = trimTrailingSlash(baseURL ?? DEFAULT_BASE_URL);
396
+
397
+ const nonce = Date.now().toString();
398
+ const signature = signNonce(nonce, privateKeyBytes);
399
+
400
+ const response = await fetch(`${url}/v1/balance`, {
401
+ headers: {
402
+ 'x-wallet-address': walletAddress,
403
+ 'x-wallet-nonce': nonce,
404
+ 'x-wallet-signature': signature,
405
+ },
406
+ });
407
+
408
+ if (!response.ok) {
409
+ return null;
410
+ }
411
+
412
+ const data = (await response.json()) as {
413
+ wallet_address: string;
414
+ balance_usd: number;
415
+ total_spent: number;
416
+ total_topups: number;
417
+ request_count: number;
418
+ created_at?: string;
419
+ last_request?: string;
420
+ };
421
+
422
+ return {
423
+ walletAddress: data.wallet_address,
424
+ balance: data.balance_usd,
425
+ totalSpent: data.total_spent,
426
+ totalTopups: data.total_topups,
427
+ requestCount: data.request_count,
428
+ createdAt: data.created_at,
429
+ lastRequest: data.last_request,
430
+ };
431
+ } catch {
432
+ return null;
433
+ }
434
+ }
435
+
436
+ export function getPublicKeyFromPrivate(privateKey: string): string | null {
437
+ try {
438
+ const privateKeyBytes = bs58.decode(privateKey);
439
+ const keypair = Keypair.fromSecretKey(privateKeyBytes);
440
+ return keypair.publicKey.toBase58();
441
+ } catch {
442
+ return null;
443
+ }
444
+ }