@blockrun/franklin 3.21.9 → 3.23.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/README.md +12 -0
- package/dist/agent/context.js +6 -4
- package/dist/agent/llm.js +20 -7
- package/dist/agent/loop.js +29 -0
- package/dist/agent/optimize.js +3 -2
- package/dist/agent/repair/flatten.d.ts +32 -0
- package/dist/agent/repair/flatten.js +77 -0
- package/dist/agent/repair/index.d.ts +66 -0
- package/dist/agent/repair/index.js +77 -0
- package/dist/agent/repair/scavenge.d.ts +12 -0
- package/dist/agent/repair/scavenge.js +193 -0
- package/dist/agent/repair/truncation.d.ts +17 -0
- package/dist/agent/repair/truncation.js +94 -0
- package/dist/agent/tokens.js +2 -1
- package/dist/commands/init.js +1 -1
- package/dist/content/image-pricing.d.ts +7 -1
- package/dist/content/image-pricing.js +40 -17
- package/dist/content/record-image.d.ts +1 -1
- package/dist/content/record-image.js +2 -2
- package/dist/pricing.js +2 -1
- package/dist/proxy/server.js +2 -1
- package/dist/router/index.js +6 -5
- package/dist/router/vision.js +1 -0
- package/dist/tools/blockrun.js +13 -4
- package/dist/tools/imagegen.d.ts +22 -3
- package/dist/tools/imagegen.js +252 -88
- package/dist/tools/index.js +5 -1
- package/dist/tools/realface.d.ts +29 -0
- package/dist/tools/realface.js +263 -0
- package/dist/tools/surf.d.ts +22 -0
- package/dist/tools/surf.js +281 -0
- package/dist/tools/tool-categories.js +7 -0
- package/dist/tools/videogen.js +44 -1
- package/dist/ui/model-picker.js +3 -2
- package/package.json +2 -2
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Truncated-JSON repair — ported from reasonix (MIT). Format-agnostic:
|
|
3
|
+
* works on any raw JSON argument string. Common trigger: model hits
|
|
4
|
+
* max_tokens mid-structure; the last useful argument is half-emitted.
|
|
5
|
+
*
|
|
6
|
+
* Local-only — never makes a continuation call. The agent loop owns
|
|
7
|
+
* budgets; this just patches what we have so the dispatcher can either
|
|
8
|
+
* parse it or report a clean fallback.
|
|
9
|
+
*/
|
|
10
|
+
export function repairTruncatedJson(input) {
|
|
11
|
+
const notes = [];
|
|
12
|
+
if (!input || !input.trim()) {
|
|
13
|
+
return {
|
|
14
|
+
repaired: '{}',
|
|
15
|
+
changed: input !== '{}',
|
|
16
|
+
notes: ['empty input → {}'],
|
|
17
|
+
fallback: false,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
// Fast path: already valid JSON.
|
|
21
|
+
try {
|
|
22
|
+
JSON.parse(input);
|
|
23
|
+
return { repaired: input, changed: false, notes: [], fallback: false };
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
/* fall through to repair */
|
|
27
|
+
}
|
|
28
|
+
const stack = [];
|
|
29
|
+
let escaped = false;
|
|
30
|
+
let inString = false;
|
|
31
|
+
let lastSignificant = -1;
|
|
32
|
+
for (let i = 0; i < input.length; i++) {
|
|
33
|
+
const c = input[i];
|
|
34
|
+
if (!/\s/.test(c))
|
|
35
|
+
lastSignificant = i;
|
|
36
|
+
if (escaped) {
|
|
37
|
+
escaped = false;
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
if (inString) {
|
|
41
|
+
if (c === '\\') {
|
|
42
|
+
escaped = true;
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
if (c === '"') {
|
|
46
|
+
inString = false;
|
|
47
|
+
stack.pop();
|
|
48
|
+
}
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
if (c === '"') {
|
|
52
|
+
inString = true;
|
|
53
|
+
stack.push('"');
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
if (c === '{' || c === '[')
|
|
57
|
+
stack.push(c);
|
|
58
|
+
else if (c === '}' || c === ']')
|
|
59
|
+
stack.pop();
|
|
60
|
+
}
|
|
61
|
+
let s = input.slice(0, lastSignificant + 1);
|
|
62
|
+
if (/,$/.test(s)) {
|
|
63
|
+
s = s.replace(/,$/, '');
|
|
64
|
+
notes.push('trimmed trailing comma');
|
|
65
|
+
}
|
|
66
|
+
if (/"\s*:\s*$/.test(s)) {
|
|
67
|
+
s += ' null';
|
|
68
|
+
notes.push('filled dangling key with null');
|
|
69
|
+
}
|
|
70
|
+
if (inString) {
|
|
71
|
+
s += '"';
|
|
72
|
+
stack.pop();
|
|
73
|
+
notes.push('closed unterminated string');
|
|
74
|
+
}
|
|
75
|
+
while (stack.length > 0) {
|
|
76
|
+
const top = stack.pop();
|
|
77
|
+
if (top === '{')
|
|
78
|
+
s += '}';
|
|
79
|
+
else if (top === '[')
|
|
80
|
+
s += ']';
|
|
81
|
+
else if (top === '"')
|
|
82
|
+
s += '"';
|
|
83
|
+
}
|
|
84
|
+
try {
|
|
85
|
+
JSON.parse(s);
|
|
86
|
+
return { repaired: s, changed: s !== input, notes, fallback: false };
|
|
87
|
+
}
|
|
88
|
+
catch (err) {
|
|
89
|
+
const preview = input.length <= 500 ? input : `${input.slice(0, 500)} …[+${input.length - 500} chars]`;
|
|
90
|
+
notes.push(`fallback to {}: ${err.message}`);
|
|
91
|
+
notes.push(`unrecoverable truncation — original args preview: ${preview}`);
|
|
92
|
+
return { repaired: '{}', changed: true, notes, fallback: true };
|
|
93
|
+
}
|
|
94
|
+
}
|
package/dist/agent/tokens.js
CHANGED
|
@@ -191,10 +191,11 @@ export function estimateHistoryTokens(history) {
|
|
|
191
191
|
*/
|
|
192
192
|
const MODEL_CONTEXT_WINDOWS = {
|
|
193
193
|
// Anthropic. The BlockRun gateway model entry advertises 1M context for
|
|
194
|
-
// Opus 4.7, but the 1M beta header may not be enabled at the gateway
|
|
194
|
+
// Opus 4.8 / 4.7, but the 1M beta header may not be enabled at the gateway
|
|
195
195
|
// edge yet — sending more than 200k without it 413s. Keep 200k as the
|
|
196
196
|
// safe Franklin baseline; bump to 1_000_000 in a separate commit once
|
|
197
197
|
// a real >200k call has been verified end-to-end.
|
|
198
|
+
'anthropic/claude-opus-4.8': 200_000,
|
|
198
199
|
'anthropic/claude-opus-4.7': 200_000,
|
|
199
200
|
'anthropic/claude-opus-4.6': 200_000,
|
|
200
201
|
'anthropic/claude-sonnet-4.6': 200_000,
|
package/dist/commands/init.js
CHANGED
|
@@ -29,7 +29,7 @@ export async function initCommand(options) {
|
|
|
29
29
|
ANTHROPIC_AUTH_TOKEN: 'x402-proxy-handles-auth',
|
|
30
30
|
ANTHROPIC_MODEL: 'blockrun/auto',
|
|
31
31
|
ANTHROPIC_DEFAULT_SONNET_MODEL: 'anthropic/claude-sonnet-4.6',
|
|
32
|
-
ANTHROPIC_DEFAULT_OPUS_MODEL: 'anthropic/claude-opus-4.
|
|
32
|
+
ANTHROPIC_DEFAULT_OPUS_MODEL: 'anthropic/claude-opus-4.8',
|
|
33
33
|
ANTHROPIC_DEFAULT_HAIKU_MODEL: 'anthropic/claude-haiku-4.5-20251001',
|
|
34
34
|
};
|
|
35
35
|
fs.mkdirSync(path.dirname(CLAUDE_SETTINGS_FILE), { recursive: true });
|
|
@@ -11,4 +11,10 @@
|
|
|
11
11
|
* gateway ever exposes the realized payment amount on the response, that
|
|
12
12
|
* should be preferred — fall back to this estimate when it's missing.
|
|
13
13
|
*/
|
|
14
|
-
|
|
14
|
+
/**
|
|
15
|
+
* Estimate the USD cost of `n` images for a model + size. `n` defaults to 1.
|
|
16
|
+
* Unknown models return 0 rather than a guess — a free/custom model should not
|
|
17
|
+
* carry a phantom charge against the Content budget, and surprise overcharging
|
|
18
|
+
* from a wrong guess is worse than under-counting.
|
|
19
|
+
*/
|
|
20
|
+
export declare function estimateImageCostUsd(model: string, size: string, n?: number): number;
|
|
@@ -11,22 +11,45 @@
|
|
|
11
11
|
* gateway ever exposes the realized payment amount on the response, that
|
|
12
12
|
* should be preferred — fall back to this estimate when it's missing.
|
|
13
13
|
*/
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
/**
|
|
15
|
+
* Per-image base price by model + size. Mirrors the gateway's IMAGE_MODELS.sizes
|
|
16
|
+
* (blockrun src/lib/models.ts). These are base prices — the realized x402 charge
|
|
17
|
+
* adds a small markup — but they're close enough for budget tracking. Sizes not
|
|
18
|
+
* listed for a model fall back to that model's 1024x1024 tier.
|
|
19
|
+
*/
|
|
20
|
+
const PRICE_TABLE = {
|
|
21
|
+
'openai/dall-e-3': {
|
|
22
|
+
base: 0.04,
|
|
23
|
+
sizes: { '1024x1024': 0.04, '1792x1024': 0.08, '1024x1792': 0.08 },
|
|
24
|
+
},
|
|
25
|
+
'openai/gpt-image-1': {
|
|
26
|
+
base: 0.02,
|
|
27
|
+
sizes: { '1024x1024': 0.02, '1536x1024': 0.04, '1024x1536': 0.04 },
|
|
28
|
+
},
|
|
29
|
+
'openai/gpt-image-2': {
|
|
30
|
+
base: 0.06,
|
|
31
|
+
sizes: { '1024x1024': 0.06, '1536x1024': 0.12, '1024x1536': 0.12 },
|
|
32
|
+
},
|
|
33
|
+
'google/nano-banana': {
|
|
34
|
+
base: 0.05,
|
|
35
|
+
sizes: { '1024x1024': 0.05 },
|
|
36
|
+
},
|
|
37
|
+
'google/nano-banana-pro': {
|
|
38
|
+
base: 0.1,
|
|
39
|
+
sizes: { '1024x1024': 0.1, '2048x2048': 0.1, '4096x4096': 0.15 },
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* Estimate the USD cost of `n` images for a model + size. `n` defaults to 1.
|
|
44
|
+
* Unknown models return 0 rather than a guess — a free/custom model should not
|
|
45
|
+
* carry a phantom charge against the Content budget, and surprise overcharging
|
|
46
|
+
* from a wrong guess is worse than under-counting.
|
|
47
|
+
*/
|
|
48
|
+
export function estimateImageCostUsd(model, size, n = 1) {
|
|
49
|
+
const entry = PRICE_TABLE[model.toLowerCase()];
|
|
50
|
+
if (!entry)
|
|
51
|
+
return 0;
|
|
16
52
|
const s = size.replace(/\s+/g, '');
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
return 0.08;
|
|
20
|
-
// All other sizes fall back to the standard 1024x1024 tier.
|
|
21
|
-
return 0.04;
|
|
22
|
-
}
|
|
23
|
-
if (m === 'openai/gpt-image-1') {
|
|
24
|
-
// gpt-image-1 standard tier; larger sizes would tier up but Franklin
|
|
25
|
-
// sends 1024x1024 as default.
|
|
26
|
-
return 0.042;
|
|
27
|
-
}
|
|
28
|
-
// Unknown model: return 0 rather than a guess. A free/custom model should
|
|
29
|
-
// not have a phantom charge against the Content budget, and surprise
|
|
30
|
-
// overcharging from a wrong guess is worse than under-counting.
|
|
31
|
-
return 0;
|
|
53
|
+
const perImage = entry.sizes[s] ?? entry.base;
|
|
54
|
+
return perImage * Math.max(1, n);
|
|
32
55
|
}
|
|
@@ -34,7 +34,7 @@ export type RecordImageDecision = {
|
|
|
34
34
|
* estimated cost fits; `{ ok: false, reason }` when it doesn't or the
|
|
35
35
|
* content doesn't exist. Non-mutating.
|
|
36
36
|
*/
|
|
37
|
-
export declare function checkImageBudget(library: ContentLibrary, contentId: string, model: string, size: string): {
|
|
37
|
+
export declare function checkImageBudget(library: ContentLibrary, contentId: string, model: string, size: string, count?: number): {
|
|
38
38
|
ok: true;
|
|
39
39
|
} | {
|
|
40
40
|
ok: false;
|
|
@@ -20,12 +20,12 @@ import { estimateImageCostUsd } from './image-pricing.js';
|
|
|
20
20
|
* estimated cost fits; `{ ok: false, reason }` when it doesn't or the
|
|
21
21
|
* content doesn't exist. Non-mutating.
|
|
22
22
|
*/
|
|
23
|
-
export function checkImageBudget(library, contentId, model, size) {
|
|
23
|
+
export function checkImageBudget(library, contentId, model, size, count = 1) {
|
|
24
24
|
const content = library.get(contentId);
|
|
25
25
|
if (!content) {
|
|
26
26
|
return { ok: false, reason: `Content ${contentId} not found` };
|
|
27
27
|
}
|
|
28
|
-
const cost = estimateImageCostUsd(model, size);
|
|
28
|
+
const cost = estimateImageCostUsd(model, size, count);
|
|
29
29
|
if (content.spentUsd + cost > content.budgetUsd + 1e-9) {
|
|
30
30
|
return {
|
|
31
31
|
ok: false,
|
package/dist/pricing.js
CHANGED
|
@@ -27,6 +27,7 @@ export const MODEL_PRICING = {
|
|
|
27
27
|
'nvidia/mistral-large-3-675b': { input: 0, output: 0 },
|
|
28
28
|
// Anthropic
|
|
29
29
|
'anthropic/claude-sonnet-4.6': { input: 3.0, output: 15.0 },
|
|
30
|
+
'anthropic/claude-opus-4.8': { input: 5.0, output: 25.0 },
|
|
30
31
|
'anthropic/claude-opus-4.7': { input: 5.0, output: 25.0 },
|
|
31
32
|
'anthropic/claude-opus-4.6': { input: 5.0, output: 25.0 },
|
|
32
33
|
'anthropic/claude-haiku-4.5': { input: 1.0, output: 5.0 },
|
|
@@ -90,7 +91,7 @@ export const MODEL_PRICING = {
|
|
|
90
91
|
'zai/glm-5.1-turbo': { input: 0, output: 0, perCall: 0.001 }, // client alias for zai/glm-5-turbo
|
|
91
92
|
};
|
|
92
93
|
/** Opus pricing for savings calculations — tracks the current flagship. */
|
|
93
|
-
export const OPUS_PRICING = MODEL_PRICING['anthropic/claude-opus-4.
|
|
94
|
+
export const OPUS_PRICING = MODEL_PRICING['anthropic/claude-opus-4.8'];
|
|
94
95
|
/**
|
|
95
96
|
* Estimate cost in USD for a request.
|
|
96
97
|
* Falls back to $2/$10 per 1M for unknown models.
|
package/dist/proxy/server.js
CHANGED
|
@@ -100,7 +100,8 @@ const MODEL_SHORTCUTS = {
|
|
|
100
100
|
sonnet: 'anthropic/claude-sonnet-4.6',
|
|
101
101
|
claude: 'anthropic/claude-sonnet-4.6',
|
|
102
102
|
'sonnet-4.6': 'anthropic/claude-sonnet-4.6',
|
|
103
|
-
opus: 'anthropic/claude-opus-4.
|
|
103
|
+
opus: 'anthropic/claude-opus-4.8',
|
|
104
|
+
'opus-4.8': 'anthropic/claude-opus-4.8',
|
|
104
105
|
'opus-4.7': 'anthropic/claude-opus-4.7',
|
|
105
106
|
'opus-4.6': 'anthropic/claude-opus-4.6',
|
|
106
107
|
haiku: 'anthropic/claude-haiku-4.5-20251001',
|
package/dist/router/index.js
CHANGED
|
@@ -55,14 +55,15 @@ const AUTO_TIERS = {
|
|
|
55
55
|
// Hard tasks — multi-file refactors, ambiguous specs, dense reasoning
|
|
56
56
|
// chains — still go to Opus. V4 Pro is great but not a Sonnet/Opus
|
|
57
57
|
// replacement at the high end of difficulty per recent agent-bench runs.
|
|
58
|
-
primary: 'anthropic/claude-opus-4.
|
|
59
|
-
fallback: ['openai/gpt-5.5', 'anthropic/claude-sonnet-4.6', 'deepseek/deepseek-v4-pro'],
|
|
58
|
+
primary: 'anthropic/claude-opus-4.8',
|
|
59
|
+
fallback: ['anthropic/claude-opus-4.7', 'openai/gpt-5.5', 'anthropic/claude-sonnet-4.6', 'deepseek/deepseek-v4-pro'],
|
|
60
60
|
},
|
|
61
61
|
REASONING: {
|
|
62
|
-
// Opus 4.
|
|
63
|
-
//
|
|
64
|
-
primary: 'anthropic/claude-opus-4.
|
|
62
|
+
// Opus 4.8: latest flagship, most capable for agentic coding. 4.7 and 4.6
|
|
63
|
+
// stay in the fallback chain in case of rollout delays.
|
|
64
|
+
primary: 'anthropic/claude-opus-4.8',
|
|
65
65
|
fallback: [
|
|
66
|
+
'anthropic/claude-opus-4.7',
|
|
66
67
|
'anthropic/claude-opus-4.6',
|
|
67
68
|
'openai/o3',
|
|
68
69
|
'deepseek/deepseek-v4-pro',
|
package/dist/router/vision.js
CHANGED
package/dist/tools/blockrun.js
CHANGED
|
@@ -237,12 +237,21 @@ export const blockrunCapability = {
|
|
|
237
237
|
}
|
|
238
238
|
catch { /* best-effort */ }
|
|
239
239
|
if (!result.ok) {
|
|
240
|
-
const
|
|
241
|
-
|
|
242
|
-
: `HTTP ${result.status}`;
|
|
240
|
+
const b = result.body;
|
|
241
|
+
const detail = typeof b?.error === 'string' ? b.error : `HTTP ${result.status}`;
|
|
243
242
|
const fullOutput = result.raw || JSON.stringify(result.body, null, 2);
|
|
243
|
+
// Surface the gateway's self-correction hints into the model-visible
|
|
244
|
+
// output. On a wrong path the Surf route returns `available: [...all
|
|
245
|
+
// valid paths]` (and often a `message`); without this the model only
|
|
246
|
+
// saw "Not Found" and kept guessing until the tool-failure circuit
|
|
247
|
+
// breaker tripped. The list comes straight from the live registry, so
|
|
248
|
+
// it's always complete and in sync — no drift.
|
|
249
|
+
const hint = typeof b?.message === 'string' ? `\n${b.message}` : '';
|
|
250
|
+
const avail = Array.isArray(b?.available)
|
|
251
|
+
? `\nValid endpoints: ${b.available.filter((x) => typeof x === 'string').join(', ')}`
|
|
252
|
+
: '';
|
|
244
253
|
return {
|
|
245
|
-
output: `BlockRun ${method} ${path} failed: ${detail} (status ${result.status}). No charge if status is 4xx pre-payment
|
|
254
|
+
output: `BlockRun ${method} ${path} failed: ${detail} (status ${result.status}). No charge if status is 4xx pre-payment.${hint}${avail}`,
|
|
246
255
|
fullOutput,
|
|
247
256
|
isError: true,
|
|
248
257
|
};
|
package/dist/tools/imagegen.d.ts
CHANGED
|
@@ -5,11 +5,28 @@
|
|
|
5
5
|
import type { CapabilityHandler } from '../agent/types.js';
|
|
6
6
|
import type { ContentLibrary } from '../content/library.js';
|
|
7
7
|
/**
|
|
8
|
-
* Models that accept a reference image via /v1/images/image2image.
|
|
9
|
-
*
|
|
10
|
-
*
|
|
8
|
+
* Models that accept a reference image via /v1/images/image2image. Mirrors the
|
|
9
|
+
* gateway's EDIT_SUPPORTED_MODELS (src/app/api/v1/images/image2image/route.ts):
|
|
10
|
+
* both OpenAI gpt-image-* and Google Nano Banana support image-to-image edits.
|
|
11
11
|
*/
|
|
12
12
|
export declare const EDIT_SUPPORTED_MODELS: Set<string>;
|
|
13
|
+
/**
|
|
14
|
+
* Mask-based inpainting is OpenAI-only. Gemini (Nano Banana) does prompt-based
|
|
15
|
+
* edits with no mask concept. Mirrors the gateway's MASK_SUPPORTED_MODELS.
|
|
16
|
+
*/
|
|
17
|
+
export declare const MASK_SUPPORTED_MODELS: Set<string>;
|
|
18
|
+
/**
|
|
19
|
+
* Output-image count ceiling. The gateway has no hard max but price scales with
|
|
20
|
+
* n, so cap client-side to keep a typo from draining the wallet.
|
|
21
|
+
*/
|
|
22
|
+
export declare const MAX_OUTPUT_IMAGES = 4;
|
|
23
|
+
/**
|
|
24
|
+
* Valid sizes per known image model, mirroring the gateway's IMAGE_MODELS.sizes
|
|
25
|
+
* (src/lib/models.ts). Used to fail cheaply before paying when a caller or the
|
|
26
|
+
* media router picks a size the model rejects. Models absent from this table
|
|
27
|
+
* (custom / future gateway models) skip validation and let the gateway decide.
|
|
28
|
+
*/
|
|
29
|
+
export declare const IMAGE_MODEL_SIZES: Record<string, string[]>;
|
|
13
30
|
export declare const REFERENCE_IMAGE_MAX_BYTES = 4000000;
|
|
14
31
|
/**
|
|
15
32
|
* Normalize a reference image into a base64 data URI for the gateway. The
|
|
@@ -24,6 +41,8 @@ export interface ImageGenDeps {
|
|
|
24
41
|
/** Invoked after successful content-linked generation; lets callers persist. */
|
|
25
42
|
onContentChange?: () => void | Promise<void>;
|
|
26
43
|
}
|
|
44
|
+
/** Insert a `-{idx}` suffix before the file extension: a.png → a-2.png. */
|
|
45
|
+
export declare function withIndexSuffix(p: string, idx: number): string;
|
|
27
46
|
/**
|
|
28
47
|
* Build the ImageGen capability. Passing `deps.library` enables the
|
|
29
48
|
* contentId flow: pre-flight budget check + post-generation asset
|