@blockrun/franklin 3.25.4 → 3.26.1

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.
@@ -161,6 +161,7 @@ export declare class ModelClient {
161
161
  stopReason: string;
162
162
  }>;
163
163
  private signPayment;
164
+ private recordSettledPayment;
164
165
  private signBasePayment;
165
166
  private signSolanaPayment;
166
167
  private extractPaymentReq;
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 paymentHeader = await this.signPayment(response, request.model);
595
- if (!paymentHeader) {
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, ...paymentHeader },
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 paymentHeader = await this.signPayment(response, request.model);
657
- if (!paymentHeader) {
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, ...paymentHeader },
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
- this.lastPaidUsd = paymentAmountToUsd(details.amount);
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 { 'PAYMENT-SIGNATURE': payload };
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
- this.lastPaidUsd = paymentAmountToUsd(details.amount);
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 { 'PAYMENT-SIGNATURE': payload };
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.2, output: 1.5 },
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.50, output: 1.00 },
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 },
@@ -86,10 +88,10 @@ export const MODEL_PRICING = {
86
88
  'moonshot/kimi-k2.5': { input: 0.6, output: 3.0 },
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
- 'zai/glm-5': { input: 0, output: 0, perCall: 0.001 },
90
- 'zai/glm-5.1': { input: 0, output: 0, perCall: 0.001 },
91
- 'zai/glm-5-turbo': { input: 0, output: 0, perCall: 0.001 },
92
- 'zai/glm-5.1-turbo': { input: 0, output: 0, perCall: 0.001 }, // client alias for zai/glm-5-turbo
91
+ 'zai/glm-5': { input: 0.6, output: 1.92 }, // flat promo ended 2026-06-06 — per-token now
92
+ 'zai/glm-5.1': { input: 1.40, output: 4.40 }, // launch promo ended 2026-06-05 — per-token now
93
+ 'zai/glm-5-turbo': { input: 1.2, output: 4.0 }, // flat promo ended 2026-06-06 — per-token now
94
+ 'zai/glm-5.1-turbo': { input: 1.2, output: 4.0 }, // client alias for zai/glm-5-turbo
93
95
  };
94
96
  /** Opus pricing for savings calculations — tracks the current flagship. */
95
97
  export const OPUS_PRICING = MODEL_PRICING['anthropic/claude-opus-4.8'];
@@ -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
- appendSettlementRow(extractEndpointPath(url), paidUsd, {
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
- appendSettlementRow(extractEndpointPath(url), paidUsd, {
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
  /**
@@ -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.50/$1.00 with 1M context is the new sweet spot for SIMPLE +
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.50/$1.00 promo
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
  */
@@ -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
- grok: 'xai/grok-3',
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',
@@ -122,13 +126,6 @@ export function resolveModel(input) {
122
126
  * edit this one place.
123
127
  */
124
128
  export const PICKER_CATEGORIES = [
125
- {
126
- category: '🔥 Promo (flat $0.001/call)',
127
- models: [
128
- { id: 'zai/glm-5.1', shortcut: 'glm', label: 'GLM-5.1', price: '$0.001/call', highlight: true },
129
- { id: 'zai/glm-5-turbo', shortcut: 'glm-turbo', label: 'GLM-5 Turbo', price: '$0.001/call', highlight: true },
130
- ],
131
- },
132
129
  {
133
130
  category: '🧠 Smart routing (auto-pick)',
134
131
  models: [
@@ -136,7 +133,7 @@ export const PICKER_CATEGORIES = [
136
133
  // Premium are kept as shortcut aliases (`eco`, `premium`) and resolve
137
134
  // through the router for back-compat with older configs/sessions, but
138
135
  // they're hidden from new users — Auto already covers the cheap end
139
- // (V4 Pro at $0.50/$1.00 for SIMPLE/MEDIUM) and the quality end (Opus
136
+ // (V4 Pro at $0.435/$0.87 for SIMPLE/MEDIUM) and the quality end (Opus
140
137
  // for COMPLEX), so a separate Eco/Premium picker entry just adds
141
138
  // choice paralysis without distinct value.
142
139
  { id: 'blockrun/auto', shortcut: 'auto', label: 'Auto', price: 'routed' },
@@ -156,7 +153,7 @@ export const PICKER_CATEGORIES = [
156
153
  { id: 'openai/gpt-5.5', shortcut: 'gpt', label: 'GPT-5.5', price: '$5/$30', highlight: true },
157
154
  { id: 'google/gemini-3.1-pro', shortcut: 'gemini-3', label: 'Gemini 3.1 Pro', price: '$2/$12' },
158
155
  { id: 'google/gemini-2.5-pro', shortcut: 'gemini', label: 'Gemini 2.5 Pro', price: '$1.25/$10' },
159
- { id: 'xai/grok-4-0709', shortcut: 'grok-4', label: 'Grok 4', price: '$0.2/$1.5' },
156
+ { id: 'xai/grok-4.3', shortcut: 'grok', label: 'Grok 4.3', price: '$1.5/$4' },
160
157
  ],
161
158
  },
162
159
  {
@@ -164,9 +161,10 @@ export const PICKER_CATEGORIES = [
164
161
  models: [
165
162
  { id: 'openai/o3', shortcut: 'o3', label: 'O3', price: '$2/$8' },
166
163
  { id: 'openai/gpt-5.3-codex', shortcut: 'codex', label: 'GPT-5.3 Codex', price: '$1.75/$14' },
167
- // V4 Pro on launch promo (75% off through 2026-05-31). 1M context,
168
- // 1.6T MoE → punches up to GPT-5.5/Opus on hard tasks at <1/10 the price.
169
- { id: 'deepseek/deepseek-v4-pro', shortcut: 'deepseek-v4-pro', label: 'DeepSeek V4 Pro', price: '$0.5/$1 (promo)', highlight: true },
164
+ // V4 Pro: the 75% launch promo became DeepSeek's permanent list price
165
+ // after 2026-05-31. 1M context, 1.6T MoE → punches up to GPT-5.5/Opus
166
+ // on hard tasks at <1/10 the price.
167
+ { id: 'deepseek/deepseek-v4-pro', shortcut: 'deepseek-v4-pro', label: 'DeepSeek V4 Pro', price: '$0.435/$0.87', highlight: true },
170
168
  { id: 'deepseek/deepseek-reasoner', shortcut: 'r1', label: 'DeepSeek V4 Flash R.', price: '$0.2/$0.4' },
171
169
  { id: 'xai/grok-4-1-fast-reasoning', shortcut: 'grok-fast', label: 'Grok 4.1 Fast R.', price: '$0.2/$0.5' },
172
170
  ],
@@ -180,6 +178,9 @@ export const PICKER_CATEGORIES = [
180
178
  // Re-aliased to V4 Flash Chat upstream — context 1M, price 30% lower.
181
179
  { id: 'deepseek/deepseek-chat', shortcut: 'deepseek', label: 'DeepSeek V4 Flash Chat', price: '$0.2/$0.4' },
182
180
  { id: 'moonshot/kimi-k2.6', shortcut: 'kimi', label: 'Kimi K2.6', price: '$0.95/$4' },
181
+ // GLM flat-rate promos fully ended 2026-06-06 — whole family per-token
182
+ // now (glm-5 $0.60/$1.92; `glm` shortcut still pins flagship glm-5.1).
183
+ { id: 'zai/glm-5', shortcut: 'glm-5', label: 'GLM-5', price: '$0.6/$1.92' },
183
184
  // Minimax M2.7 hidden to make room for V4 Pro in Reasoning + V4 Flash
184
185
  // (free) without exceeding the picker's 24-entry cap. Shortcut `minimax`
185
186
  // still resolves to it.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blockrun/franklin",
3
- "version": "3.25.4",
3
+ "version": "3.26.1",
4
4
  "description": "Franklin Agent — The AI agent with a wallet. Spends USDC autonomously to get real work done. Pay per action, no subscriptions.",
5
5
  "type": "module",
6
6
  "exports": {