@agi-cli/sdk 0.1.131 → 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 +1 -1
- package/src/core/src/providers/resolver.ts +20 -5
- package/src/index.ts +5 -0
- package/src/providers/src/catalog-manual.ts +21 -2
- package/src/providers/src/catalog.ts +80 -5
- package/src/providers/src/index.ts +5 -0
- package/src/providers/src/openai-oauth-client.ts +46 -3
- package/src/providers/src/solforge-client.ts +314 -35
package/package.json
CHANGED
|
@@ -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
|
|
65
|
+
return resolveOpenAIModel(instance, model);
|
|
49
66
|
}
|
|
50
67
|
if (config.apiKey) {
|
|
51
68
|
const instance = createOpenAI({ apiKey: config.apiKey });
|
|
52
|
-
return instance
|
|
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,15 @@ export {
|
|
|
57
57
|
export {
|
|
58
58
|
createSolforgeFetch,
|
|
59
59
|
createSolforgeModel,
|
|
60
|
+
fetchSolforgeBalance,
|
|
61
|
+
getPublicKeyFromPrivate,
|
|
62
|
+
fetchSolanaUsdcBalance,
|
|
60
63
|
} from './providers/src/index.ts';
|
|
61
64
|
export type {
|
|
62
65
|
SolforgeAuth,
|
|
63
66
|
SolforgeProviderOptions,
|
|
67
|
+
SolforgeBalanceResponse,
|
|
68
|
+
SolanaUsdcBalanceResponse,
|
|
64
69
|
} from './providers/src/index.ts';
|
|
65
70
|
export {
|
|
66
71
|
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
|
|
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 = '
|
|
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.
|
|
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.
|
|
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.
|
|
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,16 @@ export { providerEnvVar, readEnvKey, setEnvKey } from './env.ts';
|
|
|
20
20
|
export {
|
|
21
21
|
createSolforgeFetch,
|
|
22
22
|
createSolforgeModel,
|
|
23
|
+
fetchSolforgeBalance,
|
|
24
|
+
getPublicKeyFromPrivate,
|
|
25
|
+
fetchSolanaUsdcBalance,
|
|
23
26
|
} from './solforge-client.ts';
|
|
24
27
|
export type {
|
|
25
28
|
SolforgeAuth,
|
|
26
29
|
SolforgeProviderOptions,
|
|
27
30
|
SolforgePaymentCallbacks,
|
|
31
|
+
SolforgeBalanceResponse,
|
|
32
|
+
SolanaUsdcBalanceResponse,
|
|
28
33
|
} from './solforge-client.ts';
|
|
29
34
|
export {
|
|
30
35
|
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
|
-
|
|
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 (
|
|
50
|
-
|
|
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: {
|
|
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,8 +64,45 @@ type PaymentResponse = {
|
|
|
60
64
|
new_balance?: number | string;
|
|
61
65
|
amount?: number;
|
|
62
66
|
balance?: number;
|
|
67
|
+
transaction?: string;
|
|
63
68
|
};
|
|
64
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
|
+
|
|
65
106
|
export function createSolforgeFetch(
|
|
66
107
|
auth: SolforgeAuth,
|
|
67
108
|
options: SolforgeProviderOptions = {},
|
|
@@ -71,14 +112,14 @@ export function createSolforgeFetch(
|
|
|
71
112
|
const walletAddress = keypair.publicKey.toBase58();
|
|
72
113
|
const baseURL = trimTrailingSlash(options.baseURL ?? DEFAULT_BASE_URL);
|
|
73
114
|
const rpcURL = options.rpcURL ?? DEFAULT_RPC_URL;
|
|
74
|
-
const targetTopup = options.topupAmountMicroUsdc ?? DEFAULT_TOPUP_AMOUNT;
|
|
75
115
|
const maxAttempts = options.maxRequestAttempts ?? DEFAULT_MAX_ATTEMPTS;
|
|
76
116
|
const maxPaymentAttempts =
|
|
77
117
|
options.maxPaymentAttempts ?? DEFAULT_MAX_PAYMENT_ATTEMPTS;
|
|
78
118
|
const callbacks = options.callbacks ?? {};
|
|
119
|
+
const promptCacheKey = options.promptCacheKey;
|
|
120
|
+
const promptCacheRetention = options.promptCacheRetention;
|
|
79
121
|
|
|
80
122
|
const baseFetch = globalThis.fetch.bind(globalThis);
|
|
81
|
-
let paymentAttempts = 0;
|
|
82
123
|
|
|
83
124
|
const buildWalletHeaders = () => {
|
|
84
125
|
const nonce = Date.now().toString();
|
|
@@ -98,19 +139,112 @@ export function createSolforgeFetch(
|
|
|
98
139
|
|
|
99
140
|
while (attempt < maxAttempts) {
|
|
100
141
|
attempt++;
|
|
142
|
+
let body = init?.body;
|
|
143
|
+
if (body && typeof body === 'string') {
|
|
144
|
+
try {
|
|
145
|
+
const parsed = JSON.parse(body);
|
|
146
|
+
|
|
147
|
+
if (promptCacheKey) parsed.prompt_cache_key = promptCacheKey;
|
|
148
|
+
if (promptCacheRetention)
|
|
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
|
+
);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
body = JSON.stringify(parsed);
|
|
233
|
+
} catch {}
|
|
234
|
+
}
|
|
101
235
|
const headers = new Headers(init?.headers);
|
|
102
236
|
const walletHeaders = buildWalletHeaders();
|
|
103
237
|
headers.set('x-wallet-address', walletHeaders['x-wallet-address']);
|
|
104
238
|
headers.set('x-wallet-nonce', walletHeaders['x-wallet-nonce']);
|
|
105
239
|
headers.set('x-wallet-signature', walletHeaders['x-wallet-signature']);
|
|
106
|
-
const response = await baseFetch(input, { ...init, headers });
|
|
240
|
+
const response = await baseFetch(input, { ...init, body, headers });
|
|
107
241
|
|
|
108
242
|
if (response.status !== 402) {
|
|
109
243
|
return response;
|
|
110
244
|
}
|
|
111
245
|
|
|
112
246
|
const payload = await response.json().catch(() => ({}));
|
|
113
|
-
const requirement = pickPaymentRequirement(payload
|
|
247
|
+
const requirement = pickPaymentRequirement(payload);
|
|
114
248
|
if (!requirement) {
|
|
115
249
|
callbacks.onPaymentError?.('Unsupported payment requirement');
|
|
116
250
|
throw new Error('Solforge: unsupported payment requirement');
|
|
@@ -120,7 +254,8 @@ export function createSolforgeFetch(
|
|
|
120
254
|
throw new Error('Solforge: payment failed after multiple attempts');
|
|
121
255
|
}
|
|
122
256
|
|
|
123
|
-
const
|
|
257
|
+
const currentAttempts = globalPaymentAttempts.get(walletAddress) ?? 0;
|
|
258
|
+
const remainingPayments = maxPaymentAttempts - currentAttempts;
|
|
124
259
|
if (remainingPayments <= 0) {
|
|
125
260
|
callbacks.onPaymentError?.('Maximum payment attempts exceeded');
|
|
126
261
|
throw new Error(
|
|
@@ -128,20 +263,29 @@ export function createSolforgeFetch(
|
|
|
128
263
|
);
|
|
129
264
|
}
|
|
130
265
|
|
|
131
|
-
const
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
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
|
+
}
|
|
145
289
|
}
|
|
146
290
|
|
|
147
291
|
throw new Error('Solforge: max attempts exceeded');
|
|
@@ -183,7 +327,7 @@ export function createSolforgeModel(
|
|
|
183
327
|
apiKey: 'solforge-wallet-auth',
|
|
184
328
|
fetch: customFetch,
|
|
185
329
|
});
|
|
186
|
-
return openai(model);
|
|
330
|
+
return openai.responses(model);
|
|
187
331
|
}
|
|
188
332
|
|
|
189
333
|
function trimTrailingSlash(url: string) {
|
|
@@ -202,7 +346,6 @@ type PaymentRequirementResponse = {
|
|
|
202
346
|
|
|
203
347
|
function pickPaymentRequirement(
|
|
204
348
|
payload: unknown,
|
|
205
|
-
targetAmount: string,
|
|
206
349
|
): ExactPaymentRequirement | null {
|
|
207
350
|
const acceptsValue =
|
|
208
351
|
typeof payload === 'object' && payload !== null
|
|
@@ -211,17 +354,7 @@ function pickPaymentRequirement(
|
|
|
211
354
|
const accepts = Array.isArray(acceptsValue)
|
|
212
355
|
? (acceptsValue as ExactPaymentRequirement[])
|
|
213
356
|
: [];
|
|
214
|
-
|
|
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;
|
|
357
|
+
return accepts.find((option) => option && option.scheme === 'exact') ?? null;
|
|
225
358
|
}
|
|
226
359
|
|
|
227
360
|
async function handlePayment(args: {
|
|
@@ -318,6 +451,7 @@ async function processSinglePayment(args: {
|
|
|
318
451
|
args.callbacks.onPaymentComplete?.({
|
|
319
452
|
amountUsd,
|
|
320
453
|
newBalance,
|
|
454
|
+
transactionId: parsed.transaction,
|
|
321
455
|
});
|
|
322
456
|
console.log(
|
|
323
457
|
`Solforge payment complete: +$${amountUsd} (balance: $${newBalance})`,
|
|
@@ -360,3 +494,148 @@ async function createPaymentPayload(args: {
|
|
|
360
494
|
},
|
|
361
495
|
} as PaymentPayload;
|
|
362
496
|
}
|
|
497
|
+
|
|
498
|
+
export type SolforgeBalanceResponse = {
|
|
499
|
+
walletAddress: string;
|
|
500
|
+
balance: number;
|
|
501
|
+
totalSpent: number;
|
|
502
|
+
totalTopups: number;
|
|
503
|
+
requestCount: number;
|
|
504
|
+
createdAt?: string;
|
|
505
|
+
lastRequest?: string;
|
|
506
|
+
};
|
|
507
|
+
|
|
508
|
+
export async function fetchSolforgeBalance(
|
|
509
|
+
auth: SolforgeAuth,
|
|
510
|
+
baseURL?: string,
|
|
511
|
+
): Promise<SolforgeBalanceResponse | null> {
|
|
512
|
+
try {
|
|
513
|
+
const privateKeyBytes = bs58.decode(auth.privateKey);
|
|
514
|
+
const keypair = Keypair.fromSecretKey(privateKeyBytes);
|
|
515
|
+
const walletAddress = keypair.publicKey.toBase58();
|
|
516
|
+
const url = trimTrailingSlash(baseURL ?? DEFAULT_BASE_URL);
|
|
517
|
+
|
|
518
|
+
const nonce = Date.now().toString();
|
|
519
|
+
const signature = signNonce(nonce, privateKeyBytes);
|
|
520
|
+
|
|
521
|
+
const response = await fetch(`${url}/v1/balance`, {
|
|
522
|
+
headers: {
|
|
523
|
+
'x-wallet-address': walletAddress,
|
|
524
|
+
'x-wallet-nonce': nonce,
|
|
525
|
+
'x-wallet-signature': signature,
|
|
526
|
+
},
|
|
527
|
+
});
|
|
528
|
+
|
|
529
|
+
if (!response.ok) {
|
|
530
|
+
return null;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
const data = (await response.json()) as {
|
|
534
|
+
wallet_address: string;
|
|
535
|
+
balance_usd: number;
|
|
536
|
+
total_spent: number;
|
|
537
|
+
total_topups: number;
|
|
538
|
+
request_count: number;
|
|
539
|
+
created_at?: string;
|
|
540
|
+
last_request?: string;
|
|
541
|
+
};
|
|
542
|
+
|
|
543
|
+
return {
|
|
544
|
+
walletAddress: data.wallet_address,
|
|
545
|
+
balance: data.balance_usd,
|
|
546
|
+
totalSpent: data.total_spent,
|
|
547
|
+
totalTopups: data.total_topups,
|
|
548
|
+
requestCount: data.request_count,
|
|
549
|
+
createdAt: data.created_at,
|
|
550
|
+
lastRequest: data.last_request,
|
|
551
|
+
};
|
|
552
|
+
} catch {
|
|
553
|
+
return null;
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
export function getPublicKeyFromPrivate(privateKey: string): string | null {
|
|
558
|
+
try {
|
|
559
|
+
const privateKeyBytes = bs58.decode(privateKey);
|
|
560
|
+
const keypair = Keypair.fromSecretKey(privateKeyBytes);
|
|
561
|
+
return keypair.publicKey.toBase58();
|
|
562
|
+
} catch {
|
|
563
|
+
return null;
|
|
564
|
+
}
|
|
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
|
+
}
|