@agi-cli/sdk 0.1.99 → 0.1.101
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 +5 -1
- package/src/config/src/index.ts +1 -0
- package/src/config/src/manager.ts +8 -2
- package/src/core/src/providers/resolver.ts +24 -2
- package/src/core/src/terminals/manager.ts +0 -2
- package/src/core/src/tools/builtin/terminal.ts +2 -1
- package/src/index.ts +8 -0
- package/src/prompts/src/providers.ts +5 -2
- package/src/providers/src/catalog-manual.ts +93 -0
- package/src/providers/src/catalog-merged.ts +9 -0
- package/src/providers/src/catalog.ts +1107 -59
- package/src/providers/src/env.ts +1 -0
- package/src/providers/src/index.ts +9 -1
- package/src/providers/src/pricing.ts +1 -1
- package/src/providers/src/solforge-client.ts +305 -0
- package/src/providers/src/utils.ts +1 -1
- package/src/providers/src/validate.ts +1 -1
- package/src/types/src/auth.ts +6 -1
- package/src/types/src/provider.ts +2 -1
package/src/providers/src/env.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { isProviderAuthorized, ensureProviderEnv } from './authorization.ts';
|
|
2
|
-
export { catalog } from './catalog.ts';
|
|
2
|
+
export { catalog } from './catalog-merged.ts';
|
|
3
3
|
export type {
|
|
4
4
|
ProviderId,
|
|
5
5
|
ModelInfo,
|
|
@@ -15,3 +15,11 @@ export {
|
|
|
15
15
|
export { validateProviderModel } from './validate.ts';
|
|
16
16
|
export { estimateModelCostUsd } from './pricing.ts';
|
|
17
17
|
export { providerEnvVar, readEnvKey, setEnvKey } from './env.ts';
|
|
18
|
+
export {
|
|
19
|
+
createSolforgeFetch,
|
|
20
|
+
createSolforgeModel,
|
|
21
|
+
} from './solforge-client.ts';
|
|
22
|
+
export type {
|
|
23
|
+
SolforgeAuth,
|
|
24
|
+
SolforgeProviderOptions,
|
|
25
|
+
} from './solforge-client.ts';
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
import { Buffer } from 'node:buffer';
|
|
2
|
+
import { Keypair } from '@solana/web3.js';
|
|
3
|
+
import bs58 from 'bs58';
|
|
4
|
+
import { createPaymentHeader } from 'x402/client';
|
|
5
|
+
import type { PaymentRequirements } from 'x402/types';
|
|
6
|
+
import { svm } from 'x402/shared';
|
|
7
|
+
import nacl from 'tweetnacl';
|
|
8
|
+
import { createOpenAICompatible } from '@ai-sdk/openai-compatible';
|
|
9
|
+
|
|
10
|
+
const DEFAULT_BASE_URL = 'https://ai.solforge.sh';
|
|
11
|
+
const DEFAULT_RPC_URL = 'https://api.mainnet-beta.solana.com';
|
|
12
|
+
const DEFAULT_TOPUP_AMOUNT = '100000'; // $0.10
|
|
13
|
+
const DEFAULT_MAX_ATTEMPTS = 3;
|
|
14
|
+
const DEFAULT_MAX_PAYMENT_ATTEMPTS = 20;
|
|
15
|
+
|
|
16
|
+
export type SolforgeProviderOptions = {
|
|
17
|
+
baseURL?: string;
|
|
18
|
+
rpcURL?: string;
|
|
19
|
+
network?: string;
|
|
20
|
+
topupAmountMicroUsdc?: string;
|
|
21
|
+
maxRequestAttempts?: number;
|
|
22
|
+
maxPaymentAttempts?: number;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export type SolforgeAuth = {
|
|
26
|
+
privateKey: string;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
type ExactPaymentRequirement = {
|
|
30
|
+
scheme: 'exact';
|
|
31
|
+
network: string;
|
|
32
|
+
maxAmountRequired: string;
|
|
33
|
+
asset: string;
|
|
34
|
+
payTo: string;
|
|
35
|
+
description?: string;
|
|
36
|
+
resource?: string;
|
|
37
|
+
extra?: Record<string, unknown>;
|
|
38
|
+
maxTimeoutSeconds?: number;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
type PaymentPayload = {
|
|
42
|
+
x402Version: 1;
|
|
43
|
+
scheme: 'exact';
|
|
44
|
+
network: string;
|
|
45
|
+
payload: { transaction: string };
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
type PaymentResponse = {
|
|
49
|
+
amount_usd: number;
|
|
50
|
+
new_balance: number;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
export function createSolforgeFetch(
|
|
54
|
+
auth: SolforgeAuth,
|
|
55
|
+
options: SolforgeProviderOptions = {},
|
|
56
|
+
): typeof fetch {
|
|
57
|
+
const privateKeyBytes = bs58.decode(auth.privateKey);
|
|
58
|
+
const keypair = Keypair.fromSecretKey(privateKeyBytes);
|
|
59
|
+
const walletAddress = keypair.publicKey.toBase58();
|
|
60
|
+
const baseURL = trimTrailingSlash(options.baseURL ?? DEFAULT_BASE_URL);
|
|
61
|
+
const rpcURL = options.rpcURL ?? DEFAULT_RPC_URL;
|
|
62
|
+
const targetTopup = options.topupAmountMicroUsdc ?? DEFAULT_TOPUP_AMOUNT;
|
|
63
|
+
const maxAttempts = options.maxRequestAttempts ?? DEFAULT_MAX_ATTEMPTS;
|
|
64
|
+
const maxPaymentAttempts =
|
|
65
|
+
options.maxPaymentAttempts ?? DEFAULT_MAX_PAYMENT_ATTEMPTS;
|
|
66
|
+
|
|
67
|
+
const baseFetch = globalThis.fetch.bind(globalThis);
|
|
68
|
+
let paymentAttempts = 0;
|
|
69
|
+
|
|
70
|
+
const buildWalletHeaders = () => {
|
|
71
|
+
const nonce = Date.now().toString();
|
|
72
|
+
const signature = signNonce(nonce, privateKeyBytes);
|
|
73
|
+
return {
|
|
74
|
+
'x-wallet-address': walletAddress,
|
|
75
|
+
'x-wallet-nonce': nonce,
|
|
76
|
+
'x-wallet-signature': signature,
|
|
77
|
+
};
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
return async (
|
|
81
|
+
input: Parameters<typeof fetch>[0],
|
|
82
|
+
init?: Parameters<typeof fetch>[1],
|
|
83
|
+
) => {
|
|
84
|
+
let attempt = 0;
|
|
85
|
+
|
|
86
|
+
while (attempt < maxAttempts) {
|
|
87
|
+
attempt++;
|
|
88
|
+
const headers = new Headers(init?.headers);
|
|
89
|
+
const walletHeaders = buildWalletHeaders();
|
|
90
|
+
headers.set('x-wallet-address', walletHeaders['x-wallet-address']);
|
|
91
|
+
headers.set('x-wallet-nonce', walletHeaders['x-wallet-nonce']);
|
|
92
|
+
headers.set('x-wallet-signature', walletHeaders['x-wallet-signature']);
|
|
93
|
+
const response = await baseFetch(input, { ...init, headers });
|
|
94
|
+
|
|
95
|
+
if (response.status !== 402) {
|
|
96
|
+
return response;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const payload = await response.json().catch(() => ({}));
|
|
100
|
+
const requirement = pickPaymentRequirement(payload, targetTopup);
|
|
101
|
+
if (!requirement) {
|
|
102
|
+
throw new Error('Solforge: unsupported payment requirement');
|
|
103
|
+
}
|
|
104
|
+
if (attempt >= maxAttempts) {
|
|
105
|
+
throw new Error('Solforge: payment failed after multiple attempts');
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const remainingPayments = maxPaymentAttempts - paymentAttempts;
|
|
109
|
+
if (remainingPayments <= 0) {
|
|
110
|
+
throw new Error(
|
|
111
|
+
'Solforge: payment failed after maximum payment attempts.',
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const outcome = await handlePayment({
|
|
116
|
+
requirement,
|
|
117
|
+
keypair,
|
|
118
|
+
rpcURL,
|
|
119
|
+
baseURL,
|
|
120
|
+
baseFetch,
|
|
121
|
+
buildWalletHeaders,
|
|
122
|
+
maxAttempts: remainingPayments,
|
|
123
|
+
});
|
|
124
|
+
paymentAttempts += outcome.attemptsUsed;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
throw new Error('Solforge: max attempts exceeded');
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export function createSolforgeModel(
|
|
132
|
+
model: string,
|
|
133
|
+
auth: SolforgeAuth,
|
|
134
|
+
options: SolforgeProviderOptions = {},
|
|
135
|
+
) {
|
|
136
|
+
const baseURL = `${trimTrailingSlash(
|
|
137
|
+
options.baseURL ?? DEFAULT_BASE_URL,
|
|
138
|
+
)}/v1`;
|
|
139
|
+
const fetch = createSolforgeFetch(auth, options);
|
|
140
|
+
const provider = createOpenAICompatible({
|
|
141
|
+
name: 'solforge',
|
|
142
|
+
baseURL,
|
|
143
|
+
headers: { 'Content-Type': 'application/json' },
|
|
144
|
+
fetch,
|
|
145
|
+
});
|
|
146
|
+
return provider(model);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function trimTrailingSlash(url: string) {
|
|
150
|
+
return url.endsWith('/') ? url.slice(0, -1) : url;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function signNonce(nonce: string, secretKey: Uint8Array): string {
|
|
154
|
+
const data = new TextEncoder().encode(nonce);
|
|
155
|
+
const signature = nacl.sign.detached(data, secretKey);
|
|
156
|
+
return bs58.encode(signature);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
type PaymentRequirementResponse = {
|
|
160
|
+
accepts?: unknown;
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
function pickPaymentRequirement(
|
|
164
|
+
payload: unknown,
|
|
165
|
+
targetAmount: string,
|
|
166
|
+
): ExactPaymentRequirement | null {
|
|
167
|
+
const acceptsValue =
|
|
168
|
+
typeof payload === 'object' && payload !== null
|
|
169
|
+
? (payload as PaymentRequirementResponse).accepts
|
|
170
|
+
: undefined;
|
|
171
|
+
const accepts = Array.isArray(acceptsValue)
|
|
172
|
+
? (acceptsValue as ExactPaymentRequirement[])
|
|
173
|
+
: [];
|
|
174
|
+
const exactMatch = accepts.find(
|
|
175
|
+
(option) =>
|
|
176
|
+
option &&
|
|
177
|
+
option.scheme === 'exact' &&
|
|
178
|
+
option.maxAmountRequired === targetAmount,
|
|
179
|
+
);
|
|
180
|
+
if (exactMatch) return exactMatch;
|
|
181
|
+
const fallback = accepts.find(
|
|
182
|
+
(option) => option && option.scheme === 'exact',
|
|
183
|
+
);
|
|
184
|
+
return fallback ?? null;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
async function handlePayment(args: {
|
|
188
|
+
requirement: ExactPaymentRequirement;
|
|
189
|
+
keypair: Keypair;
|
|
190
|
+
rpcURL: string;
|
|
191
|
+
baseURL: string;
|
|
192
|
+
baseFetch: typeof fetch;
|
|
193
|
+
buildWalletHeaders: () => Record<string, string>;
|
|
194
|
+
maxAttempts: number;
|
|
195
|
+
}): Promise<{ attemptsUsed: number }> {
|
|
196
|
+
let attempts = 0;
|
|
197
|
+
while (attempts < args.maxAttempts) {
|
|
198
|
+
const result = await processSinglePayment(args);
|
|
199
|
+
attempts += result.attempts;
|
|
200
|
+
const balanceValue =
|
|
201
|
+
typeof result.balance === 'number'
|
|
202
|
+
? result.balance
|
|
203
|
+
: result.balance != null
|
|
204
|
+
? Number(result.balance)
|
|
205
|
+
: undefined;
|
|
206
|
+
if (
|
|
207
|
+
balanceValue == null ||
|
|
208
|
+
Number.isNaN(balanceValue) ||
|
|
209
|
+
balanceValue >= 0
|
|
210
|
+
) {
|
|
211
|
+
return { attemptsUsed: attempts };
|
|
212
|
+
}
|
|
213
|
+
console.log(
|
|
214
|
+
`Solforge balance still negative (${balanceValue.toFixed(8)}). Sending another top-up...`,
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
throw new Error(
|
|
218
|
+
`Solforge: payment failed after ${attempts} additional top-ups.`,
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
async function processSinglePayment(args: {
|
|
223
|
+
requirement: ExactPaymentRequirement;
|
|
224
|
+
keypair: Keypair;
|
|
225
|
+
rpcURL: string;
|
|
226
|
+
baseURL: string;
|
|
227
|
+
baseFetch: typeof fetch;
|
|
228
|
+
buildWalletHeaders: () => Record<string, string>;
|
|
229
|
+
}): Promise<{ attempts: number; balance?: number | string }> {
|
|
230
|
+
const paymentPayload = await createPaymentPayload(args);
|
|
231
|
+
const walletHeaders = args.buildWalletHeaders();
|
|
232
|
+
const headers = {
|
|
233
|
+
'Content-Type': 'application/json',
|
|
234
|
+
...walletHeaders,
|
|
235
|
+
};
|
|
236
|
+
const response = await args.baseFetch(`${args.baseURL}/v1/topup`, {
|
|
237
|
+
method: 'POST',
|
|
238
|
+
headers,
|
|
239
|
+
body: JSON.stringify({
|
|
240
|
+
paymentPayload,
|
|
241
|
+
paymentRequirement: args.requirement,
|
|
242
|
+
}),
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
const rawBody = await response.text().catch(() => '');
|
|
246
|
+
if (!response.ok) {
|
|
247
|
+
if (
|
|
248
|
+
response.status === 400 &&
|
|
249
|
+
rawBody.toLowerCase().includes('already processed')
|
|
250
|
+
) {
|
|
251
|
+
console.log('Solforge payment already processed; continuing.');
|
|
252
|
+
return { attempts: 1 };
|
|
253
|
+
}
|
|
254
|
+
throw new Error(`Solforge topup failed (${response.status}): ${rawBody}`);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
let parsed: PaymentResponse | undefined;
|
|
258
|
+
try {
|
|
259
|
+
parsed = rawBody ? (JSON.parse(rawBody) as PaymentResponse) : undefined;
|
|
260
|
+
} catch {
|
|
261
|
+
parsed = undefined;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
if (parsed) {
|
|
265
|
+
console.log(
|
|
266
|
+
`Solforge payment complete: +$${parsed.amount_usd ?? 0} (balance: $${parsed.new_balance ?? 0})`,
|
|
267
|
+
);
|
|
268
|
+
return { attempts: 1, balance: parsed.new_balance };
|
|
269
|
+
}
|
|
270
|
+
console.log('Solforge payment complete.');
|
|
271
|
+
return { attempts: 1 };
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
async function createPaymentPayload(args: {
|
|
275
|
+
requirement: ExactPaymentRequirement;
|
|
276
|
+
keypair: Keypair;
|
|
277
|
+
rpcURL: string;
|
|
278
|
+
}) {
|
|
279
|
+
const privateKeyBase58 = bs58.encode(args.keypair.secretKey);
|
|
280
|
+
const signer = await svm.createSignerFromBase58(privateKeyBase58);
|
|
281
|
+
const header = await createPaymentHeader(
|
|
282
|
+
signer,
|
|
283
|
+
1,
|
|
284
|
+
args.requirement as PaymentRequirements,
|
|
285
|
+
{
|
|
286
|
+
svmConfig: {
|
|
287
|
+
rpcUrl: args.rpcURL,
|
|
288
|
+
},
|
|
289
|
+
},
|
|
290
|
+
);
|
|
291
|
+
const decoded = JSON.parse(
|
|
292
|
+
Buffer.from(header, 'base64').toString('utf-8'),
|
|
293
|
+
) as {
|
|
294
|
+
payload: { transaction: string };
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
return {
|
|
298
|
+
x402Version: 1,
|
|
299
|
+
scheme: 'exact',
|
|
300
|
+
network: args.requirement.network,
|
|
301
|
+
payload: {
|
|
302
|
+
transaction: decoded.payload.transaction,
|
|
303
|
+
},
|
|
304
|
+
} as PaymentPayload;
|
|
305
|
+
}
|
package/src/types/src/auth.ts
CHANGED
|
@@ -5,6 +5,11 @@ import type { ProviderId } from './provider';
|
|
|
5
5
|
*/
|
|
6
6
|
export type ApiAuth = { type: 'api'; key: string };
|
|
7
7
|
|
|
8
|
+
/**
|
|
9
|
+
* Wallet/private-key authentication
|
|
10
|
+
*/
|
|
11
|
+
export type WalletAuth = { type: 'wallet'; secret: string };
|
|
12
|
+
|
|
8
13
|
/**
|
|
9
14
|
* OAuth authentication tokens
|
|
10
15
|
*/
|
|
@@ -18,7 +23,7 @@ export type OAuth = {
|
|
|
18
23
|
/**
|
|
19
24
|
* Union of all auth types
|
|
20
25
|
*/
|
|
21
|
-
export type AuthInfo = ApiAuth | OAuth;
|
|
26
|
+
export type AuthInfo = ApiAuth | OAuth | WalletAuth;
|
|
22
27
|
|
|
23
28
|
/**
|
|
24
29
|
* Collection of auth credentials per provider
|