@blockrun/franklin 3.8.35 → 3.8.37
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/README.md +1 -1
- package/dist/agent/commands.js +1 -1
- package/dist/agent/compact.js +1 -1
- package/dist/agent/evaluator.d.ts +3 -1
- package/dist/agent/evaluator.js +44 -8
- package/dist/agent/llm.js +2 -2
- package/dist/agent/loop.js +19 -0
- package/dist/agent/optimize.js +1 -0
- package/dist/agent/permissions.js +10 -1
- package/dist/agent/tokens.js +4 -0
- package/dist/agent/types.d.ts +22 -1
- package/dist/commands/balance.js +1 -1
- package/dist/commands/daemon.js +23 -16
- package/dist/commands/plugin.d.ts +1 -1
- package/dist/commands/plugin.js +10 -10
- package/dist/commands/stats.d.ts +1 -1
- package/dist/commands/stats.js +2 -2
- package/dist/index.js +2 -2
- package/dist/panel/server.js +7 -6
- package/dist/plugin-sdk/index.d.ts +2 -2
- package/dist/plugin-sdk/index.js +2 -2
- package/dist/plugin-sdk/plugin.d.ts +4 -4
- package/dist/plugins/registry.d.ts +3 -3
- package/dist/plugins/registry.js +6 -6
- package/dist/pricing.js +1 -0
- package/dist/proxy/server.js +148 -26
- package/dist/router/index.js +3 -3
- package/dist/session/storage.js +2 -2
- package/dist/tools/imagegen.d.ts +14 -0
- package/dist/tools/imagegen.js +154 -22
- package/dist/tools/read.js +29 -2
- package/dist/tools/videogen.d.ts +14 -3
- package/dist/tools/videogen.js +161 -28
- package/dist/tools/webhook.js +2 -1
- package/dist/trading/providers/coingecko/client.js +2 -1
- package/dist/ui/app.js +12 -12
- package/dist/ui/model-picker.js +7 -4
- package/dist/wallet/index.d.ts +17 -0
- package/dist/wallet/index.js +22 -0
- package/package.json +7 -5
package/dist/tools/videogen.d.ts
CHANGED
|
@@ -3,9 +3,20 @@
|
|
|
3
3
|
* /v1/videos/generations endpoint. Uses x402 payment (Base or Solana).
|
|
4
4
|
*
|
|
5
5
|
* Default model `xai/grok-imagine-video` returns an 8-second clip for ~$0.42.
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
6
|
+
* Seedance 2.0 (bytedance/seedance-2.0 and -fast) runs longer — up to a few
|
|
7
|
+
* minutes for a 10s clip.
|
|
8
|
+
*
|
|
9
|
+
* Flow (async since blockrun@654cd35):
|
|
10
|
+
* 1. POST /v1/videos/generations with signed x-payment header. The server
|
|
11
|
+
* verifies payment (does NOT settle), submits the upstream job, and
|
|
12
|
+
* returns 202 { id, poll_url, status: "queued" }.
|
|
13
|
+
* 2. GET the poll_url with the SAME x-payment header every ~5s until
|
|
14
|
+
* status=completed. On the first completed poll the server backs up
|
|
15
|
+
* the MP4 to GCS, settles payment, and returns the video URL.
|
|
16
|
+
* 3. Download the MP4 and write it locally.
|
|
17
|
+
*
|
|
18
|
+
* If the upstream job fails, the server returns status=failed and no USDC
|
|
19
|
+
* is ever transferred. If the client never polls, no charge either.
|
|
9
20
|
*/
|
|
10
21
|
import type { CapabilityHandler } from '../agent/types.js';
|
|
11
22
|
import type { ContentLibrary } from '../content/library.js';
|
package/dist/tools/videogen.js
CHANGED
|
@@ -3,9 +3,20 @@
|
|
|
3
3
|
* /v1/videos/generations endpoint. Uses x402 payment (Base or Solana).
|
|
4
4
|
*
|
|
5
5
|
* Default model `xai/grok-imagine-video` returns an 8-second clip for ~$0.42.
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
6
|
+
* Seedance 2.0 (bytedance/seedance-2.0 and -fast) runs longer — up to a few
|
|
7
|
+
* minutes for a 10s clip.
|
|
8
|
+
*
|
|
9
|
+
* Flow (async since blockrun@654cd35):
|
|
10
|
+
* 1. POST /v1/videos/generations with signed x-payment header. The server
|
|
11
|
+
* verifies payment (does NOT settle), submits the upstream job, and
|
|
12
|
+
* returns 202 { id, poll_url, status: "queued" }.
|
|
13
|
+
* 2. GET the poll_url with the SAME x-payment header every ~5s until
|
|
14
|
+
* status=completed. On the first completed poll the server backs up
|
|
15
|
+
* the MP4 to GCS, settles payment, and returns the video URL.
|
|
16
|
+
* 3. Download the MP4 and write it locally.
|
|
17
|
+
*
|
|
18
|
+
* If the upstream job fails, the server returns status=failed and no USDC
|
|
19
|
+
* is ever transferred. If the client never polls, no charge either.
|
|
9
20
|
*/
|
|
10
21
|
import fs from 'node:fs';
|
|
11
22
|
import path from 'node:path';
|
|
@@ -13,12 +24,17 @@ import { getOrCreateWallet, getOrCreateSolanaWallet, createPaymentPayload, creat
|
|
|
13
24
|
import { loadChain, API_URLS, VERSION } from '../config.js';
|
|
14
25
|
import { ModelClient } from '../agent/llm.js';
|
|
15
26
|
import { analyzeMediaRequest, renderProposalForAskUser } from '../agent/media-router.js';
|
|
27
|
+
import { recordUsage } from '../stats/tracker.js';
|
|
28
|
+
import { findModel, estimateCostUsd } from '../gateway-models.js';
|
|
16
29
|
const DEFAULT_MODEL = 'xai/grok-imagine-video';
|
|
17
30
|
const DEFAULT_DURATION = 8;
|
|
18
31
|
const PRICE_PER_SECOND_USD = 0.05;
|
|
19
|
-
//
|
|
20
|
-
//
|
|
21
|
-
|
|
32
|
+
// POST submit is fast (~3-20s). Generation is async upstream (60-300s for
|
|
33
|
+
// Seedance, 20-90s for Grok). We poll until completed, then download. The
|
|
34
|
+
// server signs authorizations for 600s — keep the overall budget below that.
|
|
35
|
+
const SUBMIT_TIMEOUT_MS = 30_000;
|
|
36
|
+
const POLL_INTERVAL_MS = 5_000;
|
|
37
|
+
const POLL_MAX_WAIT_MS = 480_000; // 8 min — covers Seedance worst case
|
|
22
38
|
const DOWNLOAD_TIMEOUT_MS = 60_000;
|
|
23
39
|
function estimateVideoCostUsd(durationSeconds = DEFAULT_DURATION) {
|
|
24
40
|
return Math.max(1, durationSeconds) * PRICE_PER_SECOND_USD;
|
|
@@ -121,26 +137,32 @@ function buildExecute(deps) {
|
|
|
121
137
|
'Content-Type': 'application/json',
|
|
122
138
|
'User-Agent': `franklin/${VERSION}`,
|
|
123
139
|
};
|
|
124
|
-
const
|
|
125
|
-
|
|
126
|
-
//
|
|
127
|
-
|
|
128
|
-
|
|
140
|
+
const onAbort = (ctrl) => () => ctrl.abort();
|
|
141
|
+
// Phase 1: submit the job. First POST triggers a 402; we sign and retry.
|
|
142
|
+
// The signed paymentHeaders must be reused on every GET poll — the server
|
|
143
|
+
// uses the authorization to verify identity on each poll and settles on
|
|
144
|
+
// the first completed response.
|
|
145
|
+
const submitCtrl = new AbortController();
|
|
146
|
+
const submitTimeout = setTimeout(() => submitCtrl.abort(), SUBMIT_TIMEOUT_MS);
|
|
147
|
+
const submitAbort = onAbort(submitCtrl);
|
|
148
|
+
ctx.abortSignal.addEventListener('abort', submitAbort, { once: true });
|
|
149
|
+
let paymentHeaders = null;
|
|
150
|
+
let submitResult;
|
|
129
151
|
try {
|
|
130
152
|
let response = await fetch(endpoint, {
|
|
131
153
|
method: 'POST',
|
|
132
|
-
signal:
|
|
154
|
+
signal: submitCtrl.signal,
|
|
133
155
|
headers,
|
|
134
156
|
body,
|
|
135
157
|
});
|
|
136
158
|
if (response.status === 402) {
|
|
137
|
-
|
|
159
|
+
paymentHeaders = await signPayment(response, chain, endpoint);
|
|
138
160
|
if (!paymentHeaders) {
|
|
139
161
|
return { output: 'Payment failed. Check wallet balance with: franklin balance', isError: true };
|
|
140
162
|
}
|
|
141
163
|
response = await fetch(endpoint, {
|
|
142
164
|
method: 'POST',
|
|
143
|
-
signal:
|
|
165
|
+
signal: submitCtrl.signal,
|
|
144
166
|
headers: { ...headers, ...paymentHeaders },
|
|
145
167
|
body,
|
|
146
168
|
});
|
|
@@ -148,22 +170,71 @@ function buildExecute(deps) {
|
|
|
148
170
|
if (!response.ok) {
|
|
149
171
|
const errText = await response.text().catch(() => '');
|
|
150
172
|
return {
|
|
151
|
-
output: `Video
|
|
173
|
+
output: `Video submit failed (${response.status}): ${errText.slice(0, 300)}`,
|
|
152
174
|
isError: true,
|
|
153
175
|
};
|
|
154
176
|
}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
177
|
+
submitResult = await response.json();
|
|
178
|
+
}
|
|
179
|
+
catch (err) {
|
|
180
|
+
const msg = err.message || '';
|
|
181
|
+
if (msg.includes('abort')) {
|
|
182
|
+
return {
|
|
183
|
+
output: `Video submit timed out or was aborted after ${Math.round(SUBMIT_TIMEOUT_MS / 1000)}s.`,
|
|
184
|
+
isError: true,
|
|
185
|
+
};
|
|
159
186
|
}
|
|
187
|
+
return { output: `Error submitting video job: ${msg}`, isError: true };
|
|
188
|
+
}
|
|
189
|
+
finally {
|
|
190
|
+
clearTimeout(submitTimeout);
|
|
191
|
+
ctx.abortSignal.removeEventListener('abort', submitAbort);
|
|
192
|
+
}
|
|
193
|
+
if (!submitResult.poll_url || !paymentHeaders) {
|
|
194
|
+
return { output: 'API did not return a poll_url for the video job', isError: true };
|
|
195
|
+
}
|
|
196
|
+
// Phase 2: poll GET /v1/videos/generations/{id} with the SAME signed
|
|
197
|
+
// x-payment header until the job completes. Server settles on the first
|
|
198
|
+
// completed poll and returns the backed-up video URL.
|
|
199
|
+
const origin = new URL(apiUrl).origin;
|
|
200
|
+
const pollEndpoint = submitResult.poll_url.startsWith('http')
|
|
201
|
+
? submitResult.poll_url
|
|
202
|
+
: `${origin}${submitResult.poll_url}`;
|
|
203
|
+
const outcome = await pollUntilReady(pollEndpoint, { ...headers, ...paymentHeaders }, ctx.abortSignal);
|
|
204
|
+
if (outcome.kind === 'timed_out') {
|
|
205
|
+
return {
|
|
206
|
+
output: `Video generation did not complete within ${Math.round(POLL_MAX_WAIT_MS / 1000)}s. ` +
|
|
207
|
+
`No USDC was charged (settlement only fires on completion).`,
|
|
208
|
+
isError: true,
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
if (outcome.kind === 'failed') {
|
|
212
|
+
return {
|
|
213
|
+
output: `Video generation failed upstream: ${outcome.error ?? 'unknown error'}. No USDC was charged.`,
|
|
214
|
+
isError: true,
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
const videoData = outcome.data;
|
|
218
|
+
const videoUrl = videoData.url;
|
|
219
|
+
if (!videoUrl) {
|
|
220
|
+
return { output: 'No video URL returned from API', isError: true };
|
|
221
|
+
}
|
|
222
|
+
try {
|
|
160
223
|
// Download the MP4
|
|
161
224
|
const dlCtrl = new AbortController();
|
|
162
225
|
const dlTimeout = setTimeout(() => dlCtrl.abort(), DOWNLOAD_TIMEOUT_MS);
|
|
163
|
-
const
|
|
164
|
-
|
|
226
|
+
const dlAbort = onAbort(dlCtrl);
|
|
227
|
+
ctx.abortSignal.addEventListener('abort', dlAbort, { once: true });
|
|
228
|
+
let vidResp;
|
|
229
|
+
try {
|
|
230
|
+
vidResp = await fetch(videoUrl, { signal: dlCtrl.signal });
|
|
231
|
+
}
|
|
232
|
+
finally {
|
|
233
|
+
clearTimeout(dlTimeout);
|
|
234
|
+
ctx.abortSignal.removeEventListener('abort', dlAbort);
|
|
235
|
+
}
|
|
165
236
|
if (!vidResp.ok) {
|
|
166
|
-
return { output: `Video fetched URL but download failed (${vidResp.status}): ${
|
|
237
|
+
return { output: `Video fetched URL but download failed (${vidResp.status}): ${videoUrl}`, isError: true };
|
|
167
238
|
}
|
|
168
239
|
const buffer = Buffer.from(await vidResp.arrayBuffer());
|
|
169
240
|
fs.mkdirSync(path.dirname(outPath), { recursive: true });
|
|
@@ -171,6 +242,21 @@ function buildExecute(deps) {
|
|
|
171
242
|
const fileSize = fs.statSync(outPath).size;
|
|
172
243
|
const sizeMB = (fileSize / 1_048_576).toFixed(1);
|
|
173
244
|
const dur = videoData.duration_seconds ?? duration;
|
|
245
|
+
// Stats: record this generation so it shows up in `franklin insights`
|
|
246
|
+
// alongside chat spend. Before this, media generations bypassed
|
|
247
|
+
// recordUsage entirely, so the insights panel under-reported total
|
|
248
|
+
// spend and never surfaced video models in its "top models" list.
|
|
249
|
+
// Prefer the live gateway price when the model is in the catalog;
|
|
250
|
+
// fall back to the legacy $0.05/s estimate otherwise. Fire-and-
|
|
251
|
+
// forget — stats write must not fail a user-visible generation.
|
|
252
|
+
void (async () => {
|
|
253
|
+
try {
|
|
254
|
+
const m = await findModel(videoModel);
|
|
255
|
+
const estCost = m ? estimateCostUsd(m, { duration_seconds: dur }) : estimateVideoCostUsd(dur);
|
|
256
|
+
recordUsage(videoModel, 0, 0, estCost, 0);
|
|
257
|
+
}
|
|
258
|
+
catch { /* ignore stats errors */ }
|
|
259
|
+
})();
|
|
174
260
|
let contentSummary = '';
|
|
175
261
|
if (contentId && deps.library) {
|
|
176
262
|
const rec = deps.library.addAsset(contentId, {
|
|
@@ -208,18 +294,63 @@ function buildExecute(deps) {
|
|
|
208
294
|
const msg = err.message || '';
|
|
209
295
|
if (msg.includes('abort')) {
|
|
210
296
|
return {
|
|
211
|
-
output: `Video
|
|
297
|
+
output: `Video download timed out or was aborted after ${Math.round(DOWNLOAD_TIMEOUT_MS / 1000)}s.`,
|
|
212
298
|
isError: true,
|
|
213
299
|
};
|
|
214
300
|
}
|
|
215
301
|
return { output: `Error: ${msg}`, isError: true };
|
|
216
302
|
}
|
|
217
|
-
finally {
|
|
218
|
-
clearTimeout(timeout);
|
|
219
|
-
ctx.abortSignal.removeEventListener('abort', onAbort);
|
|
220
|
-
}
|
|
221
303
|
};
|
|
222
304
|
}
|
|
305
|
+
/**
|
|
306
|
+
* Poll the GET /v1/videos/generations/{id} endpoint until the job reaches a
|
|
307
|
+
* terminal state. Reuses the caller's signed x-payment header verbatim on
|
|
308
|
+
* every request — the server verifies the same authorization each poll and
|
|
309
|
+
* settles on the first completed response.
|
|
310
|
+
*/
|
|
311
|
+
async function pollUntilReady(pollEndpoint, headers, userAbort) {
|
|
312
|
+
const deadline = Date.now() + POLL_MAX_WAIT_MS;
|
|
313
|
+
while (Date.now() < deadline) {
|
|
314
|
+
if (userAbort.aborted)
|
|
315
|
+
throw new Error('aborted');
|
|
316
|
+
const resp = await fetch(pollEndpoint, { method: 'GET', headers, signal: userAbort });
|
|
317
|
+
// 202 = still queued/in_progress; 200 = completed or failed.
|
|
318
|
+
if (resp.status === 202 || resp.status === 200) {
|
|
319
|
+
const body = (await resp.json().catch(() => ({})));
|
|
320
|
+
if (body.status === 'completed' && body.data?.[0]?.url) {
|
|
321
|
+
return { kind: 'completed', data: body.data[0] };
|
|
322
|
+
}
|
|
323
|
+
if (body.status === 'failed') {
|
|
324
|
+
return { kind: 'failed', error: body.error };
|
|
325
|
+
}
|
|
326
|
+
// queued / in_progress — sleep and try again.
|
|
327
|
+
}
|
|
328
|
+
else if (resp.status === 429 || resp.status >= 500) {
|
|
329
|
+
// Transient — back off briefly. Fall through to the sleep below.
|
|
330
|
+
}
|
|
331
|
+
else {
|
|
332
|
+
const text = await resp.text().catch(() => '');
|
|
333
|
+
throw new Error(`Poll failed (${resp.status}): ${text.slice(0, 300)}`);
|
|
334
|
+
}
|
|
335
|
+
await sleep(POLL_INTERVAL_MS, userAbort);
|
|
336
|
+
}
|
|
337
|
+
return { kind: 'timed_out' };
|
|
338
|
+
}
|
|
339
|
+
function sleep(ms, signal) {
|
|
340
|
+
return new Promise((resolve, reject) => {
|
|
341
|
+
if (signal.aborted)
|
|
342
|
+
return reject(new Error('aborted'));
|
|
343
|
+
const t = setTimeout(() => {
|
|
344
|
+
signal.removeEventListener('abort', onAbort);
|
|
345
|
+
resolve();
|
|
346
|
+
}, ms);
|
|
347
|
+
const onAbort = () => {
|
|
348
|
+
clearTimeout(t);
|
|
349
|
+
reject(new Error('aborted'));
|
|
350
|
+
};
|
|
351
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
352
|
+
});
|
|
353
|
+
}
|
|
223
354
|
// ─── Payment ───────────────────────────────────────────────────────────────
|
|
224
355
|
async function signPayment(response, chain, endpoint) {
|
|
225
356
|
try {
|
|
@@ -235,7 +366,9 @@ async function signPayment(response, chain, endpoint) {
|
|
|
235
366
|
const payload = await createSolanaPaymentPayload(secretBytes, wallet.address, details.recipient, details.amount, feePayer, {
|
|
236
367
|
resourceUrl: details.resource?.url || endpoint,
|
|
237
368
|
resourceDescription: details.resource?.description || 'Franklin video generation',
|
|
238
|
-
|
|
369
|
+
// Video poll can take up to 8 min; honor the server's advertised
|
|
370
|
+
// value (blockrun sends 600s) and fall back to 600 not 300.
|
|
371
|
+
maxTimeoutSeconds: details.maxTimeoutSeconds || 600,
|
|
239
372
|
extra: details.extra,
|
|
240
373
|
});
|
|
241
374
|
return { 'PAYMENT-SIGNATURE': payload };
|
package/dist/tools/webhook.js
CHANGED
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
* session.
|
|
16
16
|
*/
|
|
17
17
|
import { isIP } from 'node:net';
|
|
18
|
+
import { VERSION } from '../config.js';
|
|
18
19
|
const DEFAULT_TIMEOUT_MS = 15_000;
|
|
19
20
|
const MAX_BODY_BYTES = 512 * 1024; // 512 KB is generous for a chat push.
|
|
20
21
|
function isPrivateHost(hostname) {
|
|
@@ -101,7 +102,7 @@ async function execute(input, ctx) {
|
|
|
101
102
|
}
|
|
102
103
|
const finalHeaders = {
|
|
103
104
|
'Content-Type': contentType,
|
|
104
|
-
'User-Agent':
|
|
105
|
+
'User-Agent': `franklin/${VERSION} (webhook)`,
|
|
105
106
|
...(headers ?? {}),
|
|
106
107
|
};
|
|
107
108
|
const ctrl = new AbortController();
|
|
@@ -6,8 +6,9 @@
|
|
|
6
6
|
* cooldown, user-agent, timeout, and in-memory cache.
|
|
7
7
|
*/
|
|
8
8
|
import { recordFetch } from '../telemetry.js';
|
|
9
|
+
import { VERSION } from '../../../config.js';
|
|
9
10
|
const BASE = 'https://api.coingecko.com/api/v3';
|
|
10
|
-
const UA =
|
|
11
|
+
const UA = `franklin/${VERSION} (trading)`;
|
|
11
12
|
const TIMEOUT_MS = 10_000;
|
|
12
13
|
// Ticker → CoinGecko slug. Not exhaustive; unknown tickers fall through to
|
|
13
14
|
// lowercase and let CoinGecko either accept the slug or 404.
|
package/dist/ui/app.js
CHANGED
|
@@ -509,7 +509,7 @@ function RunCodeApp({ initialModel, workDir, walletAddress, walletBalance, chain
|
|
|
509
509
|
}, []);
|
|
510
510
|
// Expose event handler, balance updater, and permission bridge
|
|
511
511
|
useEffect(() => {
|
|
512
|
-
globalThis.
|
|
512
|
+
globalThis.__franklin_ui = {
|
|
513
513
|
updateModel: (model) => { setCurrentModel(model); },
|
|
514
514
|
updateBalance: (bal) => {
|
|
515
515
|
setBalance(bal);
|
|
@@ -703,7 +703,7 @@ function RunCodeApp({ initialModel, workDir, walletAddress, walletBalance, chain
|
|
|
703
703
|
setQueuedInputs((prev) => prev.slice(1));
|
|
704
704
|
// Small delay so React can flush the ready=true state first
|
|
705
705
|
setTimeout(() => {
|
|
706
|
-
const fn = globalThis.
|
|
706
|
+
const fn = globalThis.__franklin_submit;
|
|
707
707
|
if (typeof fn === 'function')
|
|
708
708
|
fn(queued);
|
|
709
709
|
}, 50);
|
|
@@ -713,12 +713,12 @@ function RunCodeApp({ initialModel, workDir, walletAddress, walletBalance, chain
|
|
|
713
713
|
}
|
|
714
714
|
},
|
|
715
715
|
};
|
|
716
|
-
globalThis.
|
|
716
|
+
globalThis.__franklin_submit = (msg) => {
|
|
717
717
|
handleSubmit(msg);
|
|
718
718
|
};
|
|
719
719
|
return () => {
|
|
720
|
-
delete globalThis.
|
|
721
|
-
delete globalThis.
|
|
720
|
+
delete globalThis.__franklin_ui;
|
|
721
|
+
delete globalThis.__franklin_submit;
|
|
722
722
|
};
|
|
723
723
|
}, [handleSubmit, commitResponse, showStatus]);
|
|
724
724
|
// ── Render ──
|
|
@@ -781,7 +781,7 @@ export function launchInkUI(opts) {
|
|
|
781
781
|
let pendingInput = null; // Queue for inputs that arrive before waitForInput
|
|
782
782
|
let exiting = false;
|
|
783
783
|
let abortCallback = null;
|
|
784
|
-
const instance = render(_jsx(RunCodeApp, { initialModel: opts.model, workDir: opts.workDir, walletAddress: opts.walletAddress || 'not set — run:
|
|
784
|
+
const instance = render(_jsx(RunCodeApp, { initialModel: opts.model, workDir: opts.workDir, walletAddress: opts.walletAddress || 'not set — run: franklin setup', walletBalance: opts.walletBalance || 'unknown', chain: opts.chain || 'base', startWithPicker: opts.showPicker, onSubmit: (value) => {
|
|
785
785
|
if (resolveInput) {
|
|
786
786
|
resolveInput(value);
|
|
787
787
|
resolveInput = null;
|
|
@@ -799,19 +799,19 @@ export function launchInkUI(opts) {
|
|
|
799
799
|
} }));
|
|
800
800
|
return {
|
|
801
801
|
handleEvent: (event) => {
|
|
802
|
-
const ui = globalThis.
|
|
802
|
+
const ui = globalThis.__franklin_ui;
|
|
803
803
|
ui?.handleEvent(event);
|
|
804
804
|
},
|
|
805
805
|
updateModel: (model) => {
|
|
806
|
-
const ui = globalThis.
|
|
806
|
+
const ui = globalThis.__franklin_ui;
|
|
807
807
|
ui?.updateModel(model);
|
|
808
808
|
},
|
|
809
809
|
updateBalance: (bal) => {
|
|
810
|
-
const ui = globalThis.
|
|
810
|
+
const ui = globalThis.__franklin_ui;
|
|
811
811
|
ui?.updateBalance(bal);
|
|
812
812
|
},
|
|
813
813
|
onTurnDone: (cb) => {
|
|
814
|
-
const ui = globalThis.
|
|
814
|
+
const ui = globalThis.__franklin_ui;
|
|
815
815
|
ui?.onTurnDone(cb);
|
|
816
816
|
},
|
|
817
817
|
waitForInput: () => {
|
|
@@ -828,11 +828,11 @@ export function launchInkUI(opts) {
|
|
|
828
828
|
onAbort: (cb) => { abortCallback = cb; },
|
|
829
829
|
cleanup: () => { mouse.disable(); instance.unmount(); },
|
|
830
830
|
requestPermission: (toolName, description) => {
|
|
831
|
-
const ui = globalThis.
|
|
831
|
+
const ui = globalThis.__franklin_ui;
|
|
832
832
|
return ui?.requestPermission(toolName, description) ?? Promise.resolve('no');
|
|
833
833
|
},
|
|
834
834
|
requestAskUser: (question, options) => {
|
|
835
|
-
const ui = globalThis.
|
|
835
|
+
const ui = globalThis.__franklin_ui;
|
|
836
836
|
return ui?.requestAskUser(question, options) ?? Promise.resolve('(no response)');
|
|
837
837
|
},
|
|
838
838
|
};
|
package/dist/ui/model-picker.js
CHANGED
|
@@ -19,9 +19,11 @@ export const MODEL_SHORTCUTS = {
|
|
|
19
19
|
'opus-4.6': 'anthropic/claude-opus-4.6',
|
|
20
20
|
haiku: 'anthropic/claude-haiku-4.5-20251001',
|
|
21
21
|
// OpenAI
|
|
22
|
-
gpt
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
// `gpt` / `gpt5` / `gpt-5` follow the gateway's flagship — currently 5.5.
|
|
23
|
+
gpt: 'openai/gpt-5.5',
|
|
24
|
+
gpt5: 'openai/gpt-5.5',
|
|
25
|
+
'gpt-5': 'openai/gpt-5.5',
|
|
26
|
+
'gpt-5.5': 'openai/gpt-5.5',
|
|
25
27
|
'gpt-5.4': 'openai/gpt-5.4',
|
|
26
28
|
'gpt-5.4-pro': 'openai/gpt-5.4-pro',
|
|
27
29
|
'gpt-5.3': 'openai/gpt-5.3',
|
|
@@ -107,7 +109,8 @@ export const PICKER_CATEGORIES = [
|
|
|
107
109
|
{ id: 'anthropic/claude-opus-4.7', shortcut: 'opus', label: 'Claude Opus 4.7', price: '$5/$25', highlight: true },
|
|
108
110
|
{ id: 'anthropic/claude-sonnet-4.6', shortcut: 'sonnet', label: 'Claude Sonnet 4.6', price: '$3/$15' },
|
|
109
111
|
{ id: 'anthropic/claude-opus-4.6', shortcut: 'opus-4.6', label: 'Claude Opus 4.6', price: '$5/$25' },
|
|
110
|
-
{ id: 'openai/gpt-5.
|
|
112
|
+
{ id: 'openai/gpt-5.5', shortcut: 'gpt', label: 'GPT-5.5', price: '$5/$30', highlight: true },
|
|
113
|
+
{ id: 'openai/gpt-5.4', shortcut: 'gpt-5.4', label: 'GPT-5.4', price: '$2.5/$15' },
|
|
111
114
|
{ id: 'openai/gpt-5.4-pro', shortcut: 'gpt-5.4-pro', label: 'GPT-5.4 Pro', price: '$30/$180' },
|
|
112
115
|
{ id: 'google/gemini-2.5-pro', shortcut: 'gemini', label: 'Gemini 2.5 Pro', price: '$1.25/$10' },
|
|
113
116
|
{ id: 'google/gemini-3.1-pro', shortcut: 'gemini-3', label: 'Gemini 3.1 Pro', price: '$2/$12' },
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public wallet surface for `@blockrun/franklin/wallet`.
|
|
3
|
+
*
|
|
4
|
+
* Thin pass-through over Franklin's wallet/manager helpers plus the
|
|
5
|
+
* lower-level primitives from `@blockrun/llm` that downstream code
|
|
6
|
+
* typically needs (setup, address, load/save, funding messages, types).
|
|
7
|
+
*
|
|
8
|
+
* Modelled after the flat wallet surface in `@blockrun/clawrouter` —
|
|
9
|
+
* but exposed under a subpath because Franklin's `.` entry is a CLI
|
|
10
|
+
* (side-effectful shebang) and can't double as a library.
|
|
11
|
+
*/
|
|
12
|
+
export { walletExists, setupWallet, setupSolanaWallet, getAddress, } from './manager.js';
|
|
13
|
+
export { getOrCreateWallet, getOrCreateSolanaWallet, setupAgentWallet, setupAgentSolanaWallet, createWallet, createSolanaWallet, } from '@blockrun/llm';
|
|
14
|
+
export { getWalletAddress, scanWallets, scanSolanaWallets, loadWallet, loadSolanaWallet, saveWallet, saveSolanaWallet, } from '@blockrun/llm';
|
|
15
|
+
export { formatWalletCreatedMessage, formatNeedsFundingMessage, formatFundingMessageCompact, getEip681Uri, getPaymentLinks, } from '@blockrun/llm';
|
|
16
|
+
export { WALLET_DIR_PATH, WALLET_FILE_PATH, SOLANA_WALLET_FILE_PATH, } from '@blockrun/llm';
|
|
17
|
+
export type { WalletInfo, SolanaWalletInfo, PaymentLinks, } from '@blockrun/llm';
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public wallet surface for `@blockrun/franklin/wallet`.
|
|
3
|
+
*
|
|
4
|
+
* Thin pass-through over Franklin's wallet/manager helpers plus the
|
|
5
|
+
* lower-level primitives from `@blockrun/llm` that downstream code
|
|
6
|
+
* typically needs (setup, address, load/save, funding messages, types).
|
|
7
|
+
*
|
|
8
|
+
* Modelled after the flat wallet surface in `@blockrun/clawrouter` —
|
|
9
|
+
* but exposed under a subpath because Franklin's `.` entry is a CLI
|
|
10
|
+
* (side-effectful shebang) and can't double as a library.
|
|
11
|
+
*/
|
|
12
|
+
export { walletExists, setupWallet, setupSolanaWallet, getAddress, } from './manager.js';
|
|
13
|
+
// ─── Re-exports from @blockrun/llm ────────────────────────────────────────
|
|
14
|
+
// So callers only need one import: `@blockrun/franklin/wallet`.
|
|
15
|
+
// Setup / create
|
|
16
|
+
export { getOrCreateWallet, getOrCreateSolanaWallet, setupAgentWallet, setupAgentSolanaWallet, createWallet, createSolanaWallet, } from '@blockrun/llm';
|
|
17
|
+
// Query / load / save
|
|
18
|
+
export { getWalletAddress, scanWallets, scanSolanaWallets, loadWallet, loadSolanaWallet, saveWallet, saveSolanaWallet, } from '@blockrun/llm';
|
|
19
|
+
// Funding / payment link helpers
|
|
20
|
+
export { formatWalletCreatedMessage, formatNeedsFundingMessage, formatFundingMessageCompact, getEip681Uri, getPaymentLinks, } from '@blockrun/llm';
|
|
21
|
+
// File-system paths (useful for dotfiles / migration scripts)
|
|
22
|
+
export { WALLET_DIR_PATH, WALLET_FILE_PATH, SOLANA_WALLET_FILE_PATH, } from '@blockrun/llm';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blockrun/franklin",
|
|
3
|
-
"version": "3.8.
|
|
3
|
+
"version": "3.8.37",
|
|
4
4
|
"description": "Franklin — 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": {
|
|
@@ -12,11 +12,14 @@
|
|
|
12
12
|
"types": "./dist/plugin-sdk/index.d.ts",
|
|
13
13
|
"default": "./dist/plugin-sdk/index.js"
|
|
14
14
|
},
|
|
15
|
+
"./wallet": {
|
|
16
|
+
"types": "./dist/wallet/index.d.ts",
|
|
17
|
+
"default": "./dist/wallet/index.js"
|
|
18
|
+
},
|
|
15
19
|
"./package.json": "./package.json"
|
|
16
20
|
},
|
|
17
21
|
"bin": {
|
|
18
|
-
"franklin": "dist/index.js"
|
|
19
|
-
"runcode": "dist/index.js"
|
|
22
|
+
"franklin": "dist/index.js"
|
|
20
23
|
},
|
|
21
24
|
"files": [
|
|
22
25
|
"dist",
|
|
@@ -47,8 +50,7 @@
|
|
|
47
50
|
"ai-marketing",
|
|
48
51
|
"ai-trading",
|
|
49
52
|
"crypto-native",
|
|
50
|
-
"blockrun"
|
|
51
|
-
"runcode"
|
|
53
|
+
"blockrun"
|
|
52
54
|
],
|
|
53
55
|
"license": "Apache-2.0",
|
|
54
56
|
"repository": {
|