@blockrun/franklin 3.25.4 → 3.26.0
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/dist/agent/llm.d.ts +1 -0
- package/dist/agent/llm.js +41 -27
- package/dist/pricing.js +5 -3
- package/dist/proxy/server.js +12 -4
- package/dist/router/index.js +2 -2
- package/dist/ui/model-picker.js +17 -9
- package/package.json +1 -1
package/dist/agent/llm.d.ts
CHANGED
package/dist/agent/llm.js
CHANGED
|
@@ -591,17 +591,18 @@ export class ModelClient {
|
|
|
591
591
|
if (response.status === 402) {
|
|
592
592
|
if (this.debug)
|
|
593
593
|
console.error('[franklin] Payment required — signing...');
|
|
594
|
-
const
|
|
595
|
-
if (!
|
|
594
|
+
const signedPayment = await this.signPayment(response, request.model);
|
|
595
|
+
if (!signedPayment) {
|
|
596
596
|
yield { kind: 'error', payload: { message: 'Payment signing failed' } };
|
|
597
597
|
return;
|
|
598
598
|
}
|
|
599
599
|
response = await withAbortableTimeout(() => fetchWithUnwrappedCause(endpoint, {
|
|
600
600
|
method: 'POST',
|
|
601
|
-
headers: { ...headers, ...
|
|
601
|
+
headers: { ...headers, ...signedPayment.headers },
|
|
602
602
|
body,
|
|
603
603
|
signal: requestController.signal,
|
|
604
604
|
}), requestController, createModelTimeoutError('request', request.model, requestTimeoutMs), requestTimeoutMs);
|
|
605
|
+
this.recordSettledPayment(signedPayment, response);
|
|
605
606
|
}
|
|
606
607
|
if (!response.ok) {
|
|
607
608
|
const errorBody = await response.text().catch(() => 'unknown error');
|
|
@@ -653,17 +654,18 @@ export class ModelClient {
|
|
|
653
654
|
signal: requestController.signal,
|
|
654
655
|
}), requestController, createModelTimeoutError('request', request.model, requestTimeoutMs), requestTimeoutMs);
|
|
655
656
|
if (response.status === 402) {
|
|
656
|
-
const
|
|
657
|
-
if (!
|
|
657
|
+
const signedPayment = await this.signPayment(response, request.model);
|
|
658
|
+
if (!signedPayment) {
|
|
658
659
|
yield { kind: 'error', payload: { message: 'Payment signing failed' } };
|
|
659
660
|
return;
|
|
660
661
|
}
|
|
661
662
|
response = await withAbortableTimeout(() => fetchWithUnwrappedCause(endpoint, {
|
|
662
663
|
method: 'POST',
|
|
663
|
-
headers: { ...headers, ...
|
|
664
|
+
headers: { ...headers, ...signedPayment.headers },
|
|
664
665
|
body: retryBody,
|
|
665
666
|
signal: requestController.signal,
|
|
666
667
|
}), requestController, createModelTimeoutError('request', request.model, requestTimeoutMs), requestTimeoutMs);
|
|
668
|
+
this.recordSettledPayment(signedPayment, response);
|
|
667
669
|
}
|
|
668
670
|
if (!response.ok) {
|
|
669
671
|
const retryBodyText = await response.text().catch(() => 'unknown error');
|
|
@@ -1101,6 +1103,15 @@ export class ModelClient {
|
|
|
1101
1103
|
return null;
|
|
1102
1104
|
}
|
|
1103
1105
|
}
|
|
1106
|
+
recordSettledPayment(payment, response) {
|
|
1107
|
+
// A post-signature 402 means the gateway rejected the payment rather than
|
|
1108
|
+
// settling it. Keep both the session cost and cost_log anchored to calls
|
|
1109
|
+
// the gateway accepted after the paid retry returned.
|
|
1110
|
+
if (response.status === 402)
|
|
1111
|
+
return;
|
|
1112
|
+
this.lastPaidUsd += payment.amountUsd;
|
|
1113
|
+
appendSettlementRow(payment.endpoint, payment.amountUsd, payment.meta);
|
|
1114
|
+
}
|
|
1104
1115
|
async signBasePayment(response, model) {
|
|
1105
1116
|
// Refresh wallet cache after TTL to pick up balance/key changes
|
|
1106
1117
|
if (!this.cachedBaseWallet || (Date.now() - this.walletCacheTime > ModelClient.WALLET_CACHE_TTL)) {
|
|
@@ -1116,25 +1127,24 @@ export class ModelClient {
|
|
|
1116
1127
|
throw new Error('No payment requirements in 402 response');
|
|
1117
1128
|
const paymentRequired = parsePaymentRequired(paymentHeader);
|
|
1118
1129
|
const details = extractPaymentDetails(paymentRequired);
|
|
1119
|
-
|
|
1120
|
-
// Mirror the SDK's appendCostLog write so cost_log.jsonl becomes a
|
|
1121
|
-
// true wallet-truth ledger covering both SDK helper traffic AND the
|
|
1122
|
-
// agent's main LLM stream (which uses this signer, not the SDK).
|
|
1123
|
-
// Match SDK schema (model/wallet/network/client_kind) so every row
|
|
1124
|
-
// is independently queryable.
|
|
1125
|
-
appendSettlementRow('/v1/messages', this.lastPaidUsd, {
|
|
1126
|
-
model,
|
|
1127
|
-
wallet: wallet.address,
|
|
1128
|
-
network: details.network || 'base-mainnet',
|
|
1129
|
-
client_kind: 'AgentClient',
|
|
1130
|
-
});
|
|
1130
|
+
const amountUsd = paymentAmountToUsd(details.amount);
|
|
1131
1131
|
const payload = await createPaymentPayload(wallet.privateKey, wallet.address, details.recipient, details.amount, details.network || 'eip155:8453', {
|
|
1132
1132
|
resourceUrl: details.resource?.url || this.apiUrl,
|
|
1133
1133
|
resourceDescription: details.resource?.description || 'BlockRun AI API call',
|
|
1134
1134
|
maxTimeoutSeconds: details.maxTimeoutSeconds || 300,
|
|
1135
1135
|
extra: details.extra,
|
|
1136
1136
|
});
|
|
1137
|
-
return {
|
|
1137
|
+
return {
|
|
1138
|
+
headers: { 'PAYMENT-SIGNATURE': payload },
|
|
1139
|
+
endpoint: '/v1/messages',
|
|
1140
|
+
amountUsd,
|
|
1141
|
+
meta: {
|
|
1142
|
+
model,
|
|
1143
|
+
wallet: wallet.address,
|
|
1144
|
+
network: details.network || 'base-mainnet',
|
|
1145
|
+
client_kind: 'AgentClient',
|
|
1146
|
+
},
|
|
1147
|
+
};
|
|
1138
1148
|
}
|
|
1139
1149
|
async signSolanaPayment(response, model) {
|
|
1140
1150
|
if (!this.cachedSolanaWallet || (Date.now() - this.walletCacheTime > ModelClient.WALLET_CACHE_TTL)) {
|
|
@@ -1149,13 +1159,7 @@ export class ModelClient {
|
|
|
1149
1159
|
throw new Error('No payment requirements in 402 response');
|
|
1150
1160
|
const paymentRequired = parsePaymentRequired(paymentHeader);
|
|
1151
1161
|
const details = extractPaymentDetails(paymentRequired, SOLANA_NETWORK);
|
|
1152
|
-
|
|
1153
|
-
appendSettlementRow('/v1/messages', this.lastPaidUsd, {
|
|
1154
|
-
model,
|
|
1155
|
-
wallet: wallet.address,
|
|
1156
|
-
network: details.network || 'solana-mainnet',
|
|
1157
|
-
client_kind: 'AgentClient',
|
|
1158
|
-
});
|
|
1162
|
+
const amountUsd = paymentAmountToUsd(details.amount);
|
|
1159
1163
|
const secretBytes = await solanaKeyToBytes(wallet.privateKey);
|
|
1160
1164
|
const feePayer = details.extra?.feePayer || details.recipient;
|
|
1161
1165
|
const payload = await createSolanaPaymentPayload(secretBytes, wallet.address, details.recipient, details.amount, feePayer, {
|
|
@@ -1164,7 +1168,17 @@ export class ModelClient {
|
|
|
1164
1168
|
maxTimeoutSeconds: details.maxTimeoutSeconds || 300,
|
|
1165
1169
|
extra: details.extra,
|
|
1166
1170
|
});
|
|
1167
|
-
return {
|
|
1171
|
+
return {
|
|
1172
|
+
headers: { 'PAYMENT-SIGNATURE': payload },
|
|
1173
|
+
endpoint: '/v1/messages',
|
|
1174
|
+
amountUsd,
|
|
1175
|
+
meta: {
|
|
1176
|
+
model,
|
|
1177
|
+
wallet: wallet.address,
|
|
1178
|
+
network: details.network || 'solana-mainnet',
|
|
1179
|
+
client_kind: 'AgentClient',
|
|
1180
|
+
},
|
|
1181
|
+
};
|
|
1168
1182
|
}
|
|
1169
1183
|
async extractPaymentReq(response) {
|
|
1170
1184
|
let header = response.headers.get('payment-required');
|
package/dist/pricing.js
CHANGED
|
@@ -64,17 +64,19 @@ export const MODEL_PRICING = {
|
|
|
64
64
|
'xai/grok-4-fast-reasoning': { input: 0.2, output: 0.5 },
|
|
65
65
|
'xai/grok-4-1-fast': { input: 0.2, output: 0.5 },
|
|
66
66
|
'xai/grok-4-1-fast-reasoning': { input: 0.2, output: 0.5 },
|
|
67
|
-
'xai/grok-4-0709': { input: 0
|
|
67
|
+
'xai/grok-4-0709': { input: 3.0, output: 15.0 }, // gateway lists $3/$15 (was mispriced here at $0.2/$1.5)
|
|
68
68
|
'xai/grok-3-mini': { input: 0.3, output: 0.5 },
|
|
69
69
|
'xai/grok-2-vision': { input: 2.0, output: 10.0 },
|
|
70
70
|
'xai/grok-3': { input: 3.0, output: 15.0 },
|
|
71
|
+
'xai/grok-4.3': { input: 1.5, output: 4.0 }, // public flagship 2026-06-04, OpenRouter resale
|
|
72
|
+
'xai/grok-build-0.1': { input: 1.5, output: 3.0 }, // agentic coding, OpenRouter resale
|
|
71
73
|
// DeepSeek (gateway re-aliased these to V4 Flash on 2026-05-03; price
|
|
72
74
|
// dropped from $0.28/$0.42 to $0.20/$0.40, context bumped 128K→1M).
|
|
73
75
|
'deepseek/deepseek-chat': { input: 0.20, output: 0.40 },
|
|
74
76
|
'deepseek/deepseek-reasoner': { input: 0.20, output: 0.40 },
|
|
75
77
|
// V4 Pro (1.6T MoE / 49B active, 1M ctx, 65K out). 75% launch promo
|
|
76
78
|
// through 2026-05-31 — list is $2.00/$4.00, promo is $0.50/$1.00.
|
|
77
|
-
'deepseek/deepseek-v4-pro': { input: 0.
|
|
79
|
+
'deepseek/deepseek-v4-pro': { input: 0.435, output: 0.87 }, // 75% promo became permanent list after 2026-05-31
|
|
78
80
|
// Minimax
|
|
79
81
|
'minimax/minimax-m3': { input: 0.3, output: 1.2 },
|
|
80
82
|
'minimax/minimax-m2.7': { input: 0.3, output: 1.2 },
|
|
@@ -87,7 +89,7 @@ export const MODEL_PRICING = {
|
|
|
87
89
|
'nvidia/kimi-k2.5': { input: 0.55, output: 2.5 },
|
|
88
90
|
// PROMOTION (active ~2026-04): flat $0.001/call for all GLM models
|
|
89
91
|
'zai/glm-5': { input: 0, output: 0, perCall: 0.001 },
|
|
90
|
-
'zai/glm-5.1': { input:
|
|
92
|
+
'zai/glm-5.1': { input: 1.40, output: 4.40 }, // launch promo ended 2026-06-05 — per-token now
|
|
91
93
|
'zai/glm-5-turbo': { input: 0, output: 0, perCall: 0.001 },
|
|
92
94
|
'zai/glm-5.1-turbo': { input: 0, output: 0, perCall: 0.001 }, // client alias for zai/glm-5-turbo
|
|
93
95
|
};
|
package/dist/proxy/server.js
CHANGED
|
@@ -767,12 +767,13 @@ async function handleBasePayment(response, url, method, headers, body, privateKe
|
|
|
767
767
|
const paymentRequired = parsePaymentRequired(paymentHeader);
|
|
768
768
|
const details = extractPaymentDetails(paymentRequired);
|
|
769
769
|
const paidUsd = paymentAmountToUsd(details.amount);
|
|
770
|
-
|
|
770
|
+
const endpoint = extractEndpointPath(url);
|
|
771
|
+
const settlementMeta = {
|
|
771
772
|
model,
|
|
772
773
|
wallet: fromAddress,
|
|
773
774
|
network: details.network || 'base-mainnet',
|
|
774
775
|
client_kind: 'ProxyClient',
|
|
775
|
-
}
|
|
776
|
+
};
|
|
776
777
|
const paymentPayload = await createPaymentPayload(privateKey, fromAddress, details.recipient, details.amount, details.network || 'eip155:8453', {
|
|
777
778
|
resourceUrl: details.resource?.url || url,
|
|
778
779
|
resourceDescription: details.resource?.description || 'BlockRun AI API call',
|
|
@@ -787,6 +788,9 @@ async function handleBasePayment(response, url, method, headers, body, privateKe
|
|
|
787
788
|
},
|
|
788
789
|
body: body || undefined,
|
|
789
790
|
}, timeoutMs, `Paid proxy request for ${model}`);
|
|
791
|
+
if (paid.status === 402)
|
|
792
|
+
return { response: paid, paidUsd: 0 };
|
|
793
|
+
appendSettlementRow(endpoint, paidUsd, settlementMeta);
|
|
790
794
|
return { response: paid, paidUsd };
|
|
791
795
|
}
|
|
792
796
|
// ======================================================================
|
|
@@ -800,12 +804,13 @@ async function handleSolanaPayment(response, url, method, headers, body, private
|
|
|
800
804
|
const paymentRequired = parsePaymentRequired(paymentHeader);
|
|
801
805
|
const details = extractPaymentDetails(paymentRequired, SOLANA_NETWORK);
|
|
802
806
|
const paidUsd = paymentAmountToUsd(details.amount);
|
|
803
|
-
|
|
807
|
+
const endpoint = extractEndpointPath(url);
|
|
808
|
+
const settlementMeta = {
|
|
804
809
|
model,
|
|
805
810
|
wallet: fromAddress,
|
|
806
811
|
network: details.network || 'solana-mainnet',
|
|
807
812
|
client_kind: 'ProxyClient',
|
|
808
|
-
}
|
|
813
|
+
};
|
|
809
814
|
const secretKey = await solanaKeyToBytes(privateKey);
|
|
810
815
|
const feePayer = details.extra?.feePayer || details.recipient;
|
|
811
816
|
const paymentPayload = await createSolanaPaymentPayload(secretKey, fromAddress, details.recipient, details.amount, feePayer, {
|
|
@@ -822,6 +827,9 @@ async function handleSolanaPayment(response, url, method, headers, body, private
|
|
|
822
827
|
},
|
|
823
828
|
body: body || undefined,
|
|
824
829
|
}, timeoutMs, `Paid proxy request for ${model}`);
|
|
830
|
+
if (paid.status === 402)
|
|
831
|
+
return { response: paid, paidUsd: 0 };
|
|
832
|
+
appendSettlementRow(endpoint, paidUsd, settlementMeta);
|
|
825
833
|
return { response: paid, paidUsd };
|
|
826
834
|
}
|
|
827
835
|
/**
|
package/dist/router/index.js
CHANGED
|
@@ -36,7 +36,7 @@ function loadLearnedWeights() {
|
|
|
36
36
|
}
|
|
37
37
|
// ─── Tier Model Configs ───
|
|
38
38
|
// Auto-routing strategy (post-DeepSeek-V4-Pro launch promo, 2026-05-03):
|
|
39
|
-
// V4 Pro at $0.
|
|
39
|
+
// V4 Pro at $0.435/$0.87 with 1M context is the new sweet spot for SIMPLE +
|
|
40
40
|
// MEDIUM agent work — Sonnet-quality reasoning at ~1/6 the price. Reserve
|
|
41
41
|
// Opus only for genuinely complex multi-file/multi-decision tasks where
|
|
42
42
|
// the model's wider context handling and tighter tool-use discipline still
|
|
@@ -78,7 +78,7 @@ const AUTO_TIERS = {
|
|
|
78
78
|
* if none of them have vision, escalates to COMPLEX (Opus is always vision).
|
|
79
79
|
*
|
|
80
80
|
* Note: only applied when the caller signals needsVision=true. Without that
|
|
81
|
-
* hint the classic per-tier defaults still rule — V4 Pro's $0.
|
|
81
|
+
* hint the classic per-tier defaults still rule — V4 Pro's $0.435/$0.87 price
|
|
82
82
|
* is the right SIMPLE/MEDIUM pick for text-only turns and we don't want to
|
|
83
83
|
* blanket-upgrade everyone to a vision model.
|
|
84
84
|
*/
|
package/dist/ui/model-picker.js
CHANGED
|
@@ -50,8 +50,11 @@ export const MODEL_SHORTCUTS = {
|
|
|
50
50
|
flash: 'google/gemini-2.5-flash',
|
|
51
51
|
'gemini-3': 'google/gemini-3.1-pro',
|
|
52
52
|
'gemini-3.1': 'google/gemini-3.1-pro',
|
|
53
|
-
// xAI
|
|
54
|
-
|
|
53
|
+
// xAI — grok-4.3 is the public flagship since 2026-06-04 (grok-3 and the
|
|
54
|
+
// fast families are hidden on the gateway; explicit IDs still resolve).
|
|
55
|
+
grok: 'xai/grok-4.3',
|
|
56
|
+
'grok-4.3': 'xai/grok-4.3',
|
|
57
|
+
'grok-build': 'xai/grok-build-0.1',
|
|
55
58
|
'grok-3': 'xai/grok-3',
|
|
56
59
|
'grok-4': 'xai/grok-4-0709',
|
|
57
60
|
'grok-fast': 'xai/grok-4-1-fast-reasoning',
|
|
@@ -95,6 +98,7 @@ export const MODEL_SHORTCUTS = {
|
|
|
95
98
|
'm3': 'minimax/minimax-m3',
|
|
96
99
|
'm2.7': 'minimax/minimax-m2.7',
|
|
97
100
|
glm: 'zai/glm-5.1',
|
|
101
|
+
'glm-5': 'zai/glm-5',
|
|
98
102
|
'glm-turbo': 'zai/glm-5-turbo',
|
|
99
103
|
'glm5': 'zai/glm-5.1',
|
|
100
104
|
kimi: 'moonshot/kimi-k2.6',
|
|
@@ -123,9 +127,12 @@ export function resolveModel(input) {
|
|
|
123
127
|
*/
|
|
124
128
|
export const PICKER_CATEGORIES = [
|
|
125
129
|
{
|
|
126
|
-
|
|
130
|
+
// glm-5.1's launch promo ended 2026-06-05 (per-token $1.40/$4.40 now;
|
|
131
|
+
// shortcut `glm` still resolves to it). glm-5 / glm-5-turbo are permanent
|
|
132
|
+
// flat-rate SKUs (gateway billingMode: "flat"), not a promo.
|
|
133
|
+
category: '🔥 Flat rate ($0.001/call)',
|
|
127
134
|
models: [
|
|
128
|
-
{ id: 'zai/glm-5
|
|
135
|
+
{ id: 'zai/glm-5', shortcut: 'glm-5', label: 'GLM-5', price: '$0.001/call', highlight: true },
|
|
129
136
|
{ id: 'zai/glm-5-turbo', shortcut: 'glm-turbo', label: 'GLM-5 Turbo', price: '$0.001/call', highlight: true },
|
|
130
137
|
],
|
|
131
138
|
},
|
|
@@ -136,7 +143,7 @@ export const PICKER_CATEGORIES = [
|
|
|
136
143
|
// Premium are kept as shortcut aliases (`eco`, `premium`) and resolve
|
|
137
144
|
// through the router for back-compat with older configs/sessions, but
|
|
138
145
|
// they're hidden from new users — Auto already covers the cheap end
|
|
139
|
-
// (V4 Pro at $0.
|
|
146
|
+
// (V4 Pro at $0.435/$0.87 for SIMPLE/MEDIUM) and the quality end (Opus
|
|
140
147
|
// for COMPLEX), so a separate Eco/Premium picker entry just adds
|
|
141
148
|
// choice paralysis without distinct value.
|
|
142
149
|
{ id: 'blockrun/auto', shortcut: 'auto', label: 'Auto', price: 'routed' },
|
|
@@ -156,7 +163,7 @@ export const PICKER_CATEGORIES = [
|
|
|
156
163
|
{ id: 'openai/gpt-5.5', shortcut: 'gpt', label: 'GPT-5.5', price: '$5/$30', highlight: true },
|
|
157
164
|
{ id: 'google/gemini-3.1-pro', shortcut: 'gemini-3', label: 'Gemini 3.1 Pro', price: '$2/$12' },
|
|
158
165
|
{ id: 'google/gemini-2.5-pro', shortcut: 'gemini', label: 'Gemini 2.5 Pro', price: '$1.25/$10' },
|
|
159
|
-
{ id: 'xai/grok-4
|
|
166
|
+
{ id: 'xai/grok-4.3', shortcut: 'grok', label: 'Grok 4.3', price: '$1.5/$4' },
|
|
160
167
|
],
|
|
161
168
|
},
|
|
162
169
|
{
|
|
@@ -164,9 +171,10 @@ export const PICKER_CATEGORIES = [
|
|
|
164
171
|
models: [
|
|
165
172
|
{ id: 'openai/o3', shortcut: 'o3', label: 'O3', price: '$2/$8' },
|
|
166
173
|
{ id: 'openai/gpt-5.3-codex', shortcut: 'codex', label: 'GPT-5.3 Codex', price: '$1.75/$14' },
|
|
167
|
-
// V4 Pro
|
|
168
|
-
// 1.6T MoE → punches up to GPT-5.5/Opus
|
|
169
|
-
|
|
174
|
+
// V4 Pro: the 75% launch promo became DeepSeek's permanent list price
|
|
175
|
+
// after 2026-05-31. 1M context, 1.6T MoE → punches up to GPT-5.5/Opus
|
|
176
|
+
// on hard tasks at <1/10 the price.
|
|
177
|
+
{ id: 'deepseek/deepseek-v4-pro', shortcut: 'deepseek-v4-pro', label: 'DeepSeek V4 Pro', price: '$0.435/$0.87', highlight: true },
|
|
170
178
|
{ id: 'deepseek/deepseek-reasoner', shortcut: 'r1', label: 'DeepSeek V4 Flash R.', price: '$0.2/$0.4' },
|
|
171
179
|
{ id: 'xai/grok-4-1-fast-reasoning', shortcut: 'grok-fast', label: 'Grok 4.1 Fast R.', price: '$0.2/$0.5' },
|
|
172
180
|
],
|
package/package.json
CHANGED