@blockrun/clawrouter 0.12.246 → 0.12.248
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 -0
- package/dist/cli.js +139 -41
- package/dist/index.d.ts +7 -1
- package/dist/index.js +139 -41
- package/docs/image-generation.md +34 -23
- package/package.json +1 -1
- package/scripts/reinstall.sh +1 -1
- package/skills/clawrouter/SKILL.md +1 -1
- package/skills/imagegen/SKILL.md +9 -7
package/README.md
CHANGED
|
@@ -244,6 +244,7 @@ Generate images directly from chat with `/cr-imagegen`:
|
|
|
244
244
|
| Model | Provider | Price | Max Size |
|
|
245
245
|
| ---------------------------- | -------------------- | ------------ | --------- |
|
|
246
246
|
| `nano-banana` | Google Gemini Flash | $0.05/image | 1024x1024 |
|
|
247
|
+
| `banana-2` | Google Nano Banana 2 | $0.09/image | 1024x1024 |
|
|
247
248
|
| `banana-pro` | Google Gemini Pro | $0.10/image | 4096x4096 |
|
|
248
249
|
| `gpt-image` | OpenAI GPT Image 1 | $0.02/image | 1536x1024 |
|
|
249
250
|
| `gpt-image-2` | OpenAI GPT Image 2 | $0.06/image | 1536x1024 |
|
package/dist/cli.js
CHANGED
|
@@ -89132,6 +89132,9 @@ function stripThinkingTokens(content) {
|
|
|
89132
89132
|
cleaned = cleaned.replace(THINKING_TAG_RE, "");
|
|
89133
89133
|
return cleaned;
|
|
89134
89134
|
}
|
|
89135
|
+
function forwardToolCallProse() {
|
|
89136
|
+
return process.env.CLAWROUTER_TOOL_CALL_PROSE?.trim().toLowerCase() !== "off";
|
|
89137
|
+
}
|
|
89135
89138
|
function buildModelPricing() {
|
|
89136
89139
|
const map = /* @__PURE__ */ new Map();
|
|
89137
89140
|
for (const m of BLOCKRUN_MODELS) {
|
|
@@ -90816,25 +90819,7 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
|
|
|
90816
90819
|
const modelMatch = imageArgs.match(/--model\s+(\S+)/);
|
|
90817
90820
|
if (modelMatch) {
|
|
90818
90821
|
const raw = modelMatch[1];
|
|
90819
|
-
|
|
90820
|
-
// dall-e-3 delisted upstream 2026-05-25 — legacy aliases route to
|
|
90821
|
-
// the OpenAI successor instead of a guaranteed 400.
|
|
90822
|
-
"dall-e-3": "openai/gpt-image-2",
|
|
90823
|
-
dalle3: "openai/gpt-image-2",
|
|
90824
|
-
dalle: "openai/gpt-image-2",
|
|
90825
|
-
"gpt-image": "openai/gpt-image-1",
|
|
90826
|
-
"gpt-image-1": "openai/gpt-image-1",
|
|
90827
|
-
"gpt-image-2": "openai/gpt-image-2",
|
|
90828
|
-
seedream: "bytedance/seedream-5-pro",
|
|
90829
|
-
banana: "google/nano-banana",
|
|
90830
|
-
"nano-banana": "google/nano-banana",
|
|
90831
|
-
"banana-pro": "google/nano-banana-pro",
|
|
90832
|
-
"nano-banana-pro": "google/nano-banana-pro",
|
|
90833
|
-
"grok-imagine": "xai/grok-imagine-image",
|
|
90834
|
-
"grok-imagine-pro": "xai/grok-imagine-image-pro",
|
|
90835
|
-
cogview: "zai/cogview-4"
|
|
90836
|
-
};
|
|
90837
|
-
imageModel = IMAGE_MODEL_ALIASES[raw] ?? raw;
|
|
90822
|
+
imageModel = Object.prototype.hasOwnProperty.call(IMAGE_MODEL_ALIASES, raw) ? IMAGE_MODEL_ALIASES[raw] : raw;
|
|
90838
90823
|
imagePrompt = imagePrompt.replace(/--model\s+\S+/, "").trim();
|
|
90839
90824
|
}
|
|
90840
90825
|
const sizeMatch = imageArgs.match(/--size\s+(\d+x\d+)/);
|
|
@@ -90852,11 +90837,13 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
|
|
|
90852
90837
|
"",
|
|
90853
90838
|
"Models:",
|
|
90854
90839
|
" nano-banana Google Gemini Flash \u2014 $0.05/image",
|
|
90840
|
+
" banana-2 Google Nano Banana 2 \u2014 $0.09/image",
|
|
90855
90841
|
" banana-pro Google Gemini Pro \u2014 $0.10/image (up to 4K)",
|
|
90856
90842
|
" gpt-image OpenAI GPT Image 1 \u2014 $0.02/image",
|
|
90857
90843
|
" gpt-image-2 OpenAI GPT Image 2 \u2014 $0.06/image",
|
|
90858
90844
|
" seedream ByteDance Seedream 5 Pro \u2014 $0.045/image",
|
|
90859
90845
|
" grok-imagine xAI Grok Imagine \u2014 $0.02/image",
|
|
90846
|
+
" grok-imagine-pro xAI Grok Imagine Pro \u2014 $0.07/image",
|
|
90860
90847
|
" cogview Zhipu CogView-4 \u2014 $0.015/image",
|
|
90861
90848
|
"",
|
|
90862
90849
|
"Examples:",
|
|
@@ -90919,14 +90906,68 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
|
|
|
90919
90906
|
size: imageSize,
|
|
90920
90907
|
n: 1
|
|
90921
90908
|
});
|
|
90909
|
+
const imagegenAbort = new AbortController();
|
|
90910
|
+
res.on("close", () => {
|
|
90911
|
+
if (!res.writableEnded) imagegenAbort.abort();
|
|
90912
|
+
});
|
|
90922
90913
|
const imageResponse = await payFetch(imageUpstreamUrl, {
|
|
90923
90914
|
method: "POST",
|
|
90924
90915
|
headers: { "content-type": "application/json", "user-agent": USER_AGENT },
|
|
90925
|
-
body: imageBody
|
|
90916
|
+
body: imageBody,
|
|
90917
|
+
signal: imagegenAbort.signal
|
|
90926
90918
|
});
|
|
90927
|
-
|
|
90919
|
+
let imageResult = await imageResponse.json();
|
|
90920
|
+
let imageFailure;
|
|
90921
|
+
if (imageResponse.ok && imageResult.poll_url && imageResult.id && !imageResult.data?.length) {
|
|
90922
|
+
const apiOrigin = new URL(apiBase).origin;
|
|
90923
|
+
const pollUrl = imageResult.poll_url.startsWith("http") ? imageResult.poll_url : `${apiOrigin}${imageResult.poll_url}`;
|
|
90924
|
+
console.log(
|
|
90925
|
+
`[ClawRouter] /imagegen job submitted (id=${imageResult.id}), polling upstream \u2014 typical 30\u2013120s...`
|
|
90926
|
+
);
|
|
90927
|
+
const pollDeadline = Date.now() + 3e5;
|
|
90928
|
+
await new Promise((r2) => setTimeout(r2, 2e3));
|
|
90929
|
+
let completed2 = false;
|
|
90930
|
+
while (Date.now() < pollDeadline) {
|
|
90931
|
+
if (imagegenAbort.signal.aborted) {
|
|
90932
|
+
console.log(
|
|
90933
|
+
`[ClawRouter] Chat client disconnected \u2014 abandoning image poll (id=${imageResult.id})`
|
|
90934
|
+
);
|
|
90935
|
+
return;
|
|
90936
|
+
}
|
|
90937
|
+
const pollResp = await payFetch(pollUrl, {
|
|
90938
|
+
method: "GET",
|
|
90939
|
+
headers: { "user-agent": USER_AGENT },
|
|
90940
|
+
signal: imagegenAbort.signal
|
|
90941
|
+
});
|
|
90942
|
+
const pollText = await pollResp.text();
|
|
90943
|
+
let pollBody = {};
|
|
90944
|
+
try {
|
|
90945
|
+
pollBody = JSON.parse(pollText);
|
|
90946
|
+
} catch {
|
|
90947
|
+
imageFailure = `Non-JSON poll response (${pollResp.status}): ${pollText.slice(0, 200)}`;
|
|
90948
|
+
break;
|
|
90949
|
+
}
|
|
90950
|
+
if (pollResp.status === 202 || pollBody.status === "queued" || pollBody.status === "in_progress") {
|
|
90951
|
+
await new Promise((r2) => setTimeout(r2, 3e3));
|
|
90952
|
+
continue;
|
|
90953
|
+
}
|
|
90954
|
+
if (pollBody.status === "failed" || !pollResp.ok) {
|
|
90955
|
+
imageFailure = typeof pollBody.error === "string" ? pollBody.error : pollBody.error?.message ?? `Upstream image generation failed (HTTP ${pollResp.status})`;
|
|
90956
|
+
break;
|
|
90957
|
+
}
|
|
90958
|
+
imageResult = pollBody;
|
|
90959
|
+
completed2 = true;
|
|
90960
|
+
break;
|
|
90961
|
+
}
|
|
90962
|
+
if (!completed2 && !imageFailure) {
|
|
90963
|
+
imageFailure = "Image generation timed out after 5 minutes \u2014 the job may still complete upstream.";
|
|
90964
|
+
}
|
|
90965
|
+
}
|
|
90928
90966
|
let responseText;
|
|
90929
|
-
if (
|
|
90967
|
+
if (imageFailure) {
|
|
90968
|
+
responseText = `Image generation failed: ${imageFailure}`;
|
|
90969
|
+
console.log(`[ClawRouter] /imagegen error: ${imageFailure}`);
|
|
90970
|
+
} else if (!imageResponse.ok || imageResult.error) {
|
|
90930
90971
|
const errMsg = typeof imageResult.error === "string" ? imageResult.error : imageResult.error?.message ?? `HTTP ${imageResponse.status}`;
|
|
90931
90972
|
responseText = `Image generation failed: ${errMsg}`;
|
|
90932
90973
|
console.log(`[ClawRouter] /imagegen error: ${errMsg}`);
|
|
@@ -90946,9 +90987,7 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
|
|
|
90946
90987
|
console.error(
|
|
90947
90988
|
`[ClawRouter] /imagegen: failed to upload data URI: ${uploadErr instanceof Error ? uploadErr.message : String(uploadErr)}`
|
|
90948
90989
|
);
|
|
90949
|
-
lines.push(
|
|
90950
|
-
"Image generated but upload failed. Try again or use --model dall-e-3."
|
|
90951
|
-
);
|
|
90990
|
+
lines.push("Image generated but upload failed. Try again.");
|
|
90952
90991
|
}
|
|
90953
90992
|
} else {
|
|
90954
90993
|
lines.push(img.url);
|
|
@@ -92308,13 +92347,16 @@ data: [DONE]
|
|
|
92308
92347
|
const endsWithToolCalls = choice.finish_reason === "tool_calls";
|
|
92309
92348
|
let toolCalls = choice.message?.tool_calls ?? choice.delta?.tool_calls;
|
|
92310
92349
|
const rawContent = choice.message?.content ?? choice.delta?.content ?? "";
|
|
92350
|
+
let proseContent = rawContent;
|
|
92311
92351
|
if (!endsWithToolCalls && (!toolCalls || toolCalls.length === 0) && rawContent) {
|
|
92312
92352
|
const extracted = extractTextualToolCalls(rawContent, { tools: requestTools });
|
|
92313
92353
|
if (extracted.toolCalls.length > 0) {
|
|
92314
92354
|
toolCalls = extracted.toolCalls;
|
|
92355
|
+
proseContent = extracted.cleanedContent;
|
|
92315
92356
|
}
|
|
92316
92357
|
}
|
|
92317
|
-
const
|
|
92358
|
+
const suppressProse = (endsWithToolCalls || toolCalls && toolCalls.length > 0) && !forwardToolCallProse();
|
|
92359
|
+
const content = suppressProse ? "" : stripThinkingTokens(proseContent);
|
|
92318
92360
|
const role = choice.message?.role ?? choice.delta?.role ?? "assistant";
|
|
92319
92361
|
const index2 = choice.index ?? 0;
|
|
92320
92362
|
if (content) {
|
|
@@ -92516,8 +92558,9 @@ data: [DONE]
|
|
|
92516
92558
|
const message = choice.message;
|
|
92517
92559
|
if (!message || typeof message.content !== "string") continue;
|
|
92518
92560
|
if (choice.finish_reason === "tool_calls" || Array.isArray(message.tool_calls) && message.tool_calls.length > 0) {
|
|
92519
|
-
|
|
92520
|
-
|
|
92561
|
+
const next = forwardToolCallProse() ? stripThinkingTokens(message.content) : "";
|
|
92562
|
+
if (message.content !== next) {
|
|
92563
|
+
message.content = next;
|
|
92521
92564
|
changed = true;
|
|
92522
92565
|
}
|
|
92523
92566
|
continue;
|
|
@@ -92525,7 +92568,7 @@ data: [DONE]
|
|
|
92525
92568
|
const extracted = extractTextualToolCalls(message.content, { tools: requestTools });
|
|
92526
92569
|
if (extracted.toolCalls.length > 0) {
|
|
92527
92570
|
message.tool_calls = extracted.toolCalls;
|
|
92528
|
-
message.content = "";
|
|
92571
|
+
message.content = forwardToolCallProse() ? stripThinkingTokens(extracted.cleanedContent) : "";
|
|
92529
92572
|
choice.finish_reason = "tool_calls";
|
|
92530
92573
|
changed = true;
|
|
92531
92574
|
continue;
|
|
@@ -92732,7 +92775,7 @@ data: [DONE]
|
|
|
92732
92775
|
});
|
|
92733
92776
|
}
|
|
92734
92777
|
}
|
|
92735
|
-
var paymentStore, BLOCKRUN_API, BLOCKRUN_SOLANA_API, IMAGE_DIR, AUDIO_DIR, VIDEO_DIR, AUTO_MODEL, ROUTING_PROFILES, FREE_MODELS, FREE_MODEL, MAX_MESSAGES, CONTEXT_LIMIT_KB, HEARTBEAT_INTERVAL_MS, BALANCE_CHECK_TIMEOUT_MS, DEFAULT_REQUEST_TIMEOUT_MS, PER_MODEL_TIMEOUT_MS, REASONING_MODEL_TIMEOUT_MS, REASONING_MODEL_IDS, MAX_FALLBACK_ATTEMPTS, HEALTH_CHECK_TIMEOUT_MS, RATE_LIMIT_COOLDOWN_MS, OVERLOAD_COOLDOWN_MS, PORT_RETRY_ATTEMPTS, PORT_RETRY_DELAY_MS, MODEL_BODY_READ_TIMEOUT_MS, ERROR_BODY_READ_TIMEOUT_MS, rateLimitedModels, overloadedModels, perProviderErrors, BALANCE_CHECK_BUFFER, BALANCE_PREFLIGHT_OUTPUT_TOKEN_CAP, PROVIDER_ERROR_PATTERNS, DEGRADED_RESPONSE_PATTERNS, DEGRADED_LOOP_PATTERNS, VALID_ROLES, ROLE_MAPPINGS, VALID_TOOL_ID_PATTERN, KIMI_BLOCK_RE, KIMI_TOKEN_RE, THINKING_TAG_RE, THINKING_BLOCK_RE, BLOCKRUN_MODEL_BY_ID, IMAGE_PRICING, VIDEO_PRICING, PHONE_PRICING;
|
|
92778
|
+
var paymentStore, BLOCKRUN_API, BLOCKRUN_SOLANA_API, IMAGE_DIR, AUDIO_DIR, VIDEO_DIR, AUTO_MODEL, ROUTING_PROFILES, FREE_MODELS, FREE_MODEL, MAX_MESSAGES, CONTEXT_LIMIT_KB, HEARTBEAT_INTERVAL_MS, BALANCE_CHECK_TIMEOUT_MS, DEFAULT_REQUEST_TIMEOUT_MS, PER_MODEL_TIMEOUT_MS, REASONING_MODEL_TIMEOUT_MS, REASONING_MODEL_IDS, MAX_FALLBACK_ATTEMPTS, HEALTH_CHECK_TIMEOUT_MS, RATE_LIMIT_COOLDOWN_MS, OVERLOAD_COOLDOWN_MS, PORT_RETRY_ATTEMPTS, PORT_RETRY_DELAY_MS, MODEL_BODY_READ_TIMEOUT_MS, ERROR_BODY_READ_TIMEOUT_MS, rateLimitedModels, overloadedModels, perProviderErrors, BALANCE_CHECK_BUFFER, BALANCE_PREFLIGHT_OUTPUT_TOKEN_CAP, PROVIDER_ERROR_PATTERNS, DEGRADED_RESPONSE_PATTERNS, DEGRADED_LOOP_PATTERNS, VALID_ROLES, ROLE_MAPPINGS, VALID_TOOL_ID_PATTERN, KIMI_BLOCK_RE, KIMI_TOKEN_RE, THINKING_TAG_RE, THINKING_BLOCK_RE, BLOCKRUN_MODEL_BY_ID, IMAGE_PRICING, IMAGE_MODEL_IDS, IMAGE_MODEL_SIZES, IMAGE_MODEL_ALIASES, VIDEO_PRICING, PHONE_PRICING;
|
|
92736
92779
|
var init_proxy = __esm({
|
|
92737
92780
|
"src/proxy.ts"() {
|
|
92738
92781
|
"use strict";
|
|
@@ -92877,9 +92920,20 @@ var init_proxy = __esm({
|
|
|
92877
92920
|
},
|
|
92878
92921
|
"bytedance/seedream-5-pro": {
|
|
92879
92922
|
default: 0.045,
|
|
92880
|
-
|
|
92923
|
+
// Full size list live-probed 2026-08-23 (gateway 402 quotes, base price):
|
|
92924
|
+
// the 2K portrait/landscape tiers bill at the 2048x2048 rate.
|
|
92925
|
+
sizes: {
|
|
92926
|
+
"1024x1024": 0.045,
|
|
92927
|
+
"1280x720": 0.045,
|
|
92928
|
+
"2048x1024": 0.045,
|
|
92929
|
+
"2048x2048": 0.09,
|
|
92930
|
+
"2304x1728": 0.09,
|
|
92931
|
+
"1728x2304": 0.09,
|
|
92932
|
+
"2848x1600": 0.09,
|
|
92933
|
+
"1600x2848": 0.09
|
|
92934
|
+
}
|
|
92881
92935
|
},
|
|
92882
|
-
"google/nano-banana": { default: 0.05 },
|
|
92936
|
+
"google/nano-banana": { default: 0.05, sizes: { "1024x1024": 0.05 } },
|
|
92883
92937
|
// Nano Banana 2 (Gemini 3.1 Flash imagegen, blockrun #329): $0.09/image at 1K.
|
|
92884
92938
|
"google/nano-banana-2": { default: 0.09, sizes: { "1024x1024": 0.09 } },
|
|
92885
92939
|
"google/nano-banana-pro": {
|
|
@@ -92900,6 +92954,30 @@ var init_proxy = __esm({
|
|
|
92900
92954
|
}
|
|
92901
92955
|
}
|
|
92902
92956
|
};
|
|
92957
|
+
IMAGE_MODEL_IDS = Object.freeze(Object.keys(IMAGE_PRICING));
|
|
92958
|
+
IMAGE_MODEL_SIZES = Object.freeze(
|
|
92959
|
+
[...new Set(Object.values(IMAGE_PRICING).flatMap((p) => Object.keys(p.sizes ?? {})))].sort()
|
|
92960
|
+
);
|
|
92961
|
+
IMAGE_MODEL_ALIASES = Object.freeze({
|
|
92962
|
+
// dall-e-3 delisted upstream 2026-05-25 — legacy aliases route to
|
|
92963
|
+
// the OpenAI successor instead of a guaranteed 400.
|
|
92964
|
+
"dall-e-3": "openai/gpt-image-2",
|
|
92965
|
+
dalle3: "openai/gpt-image-2",
|
|
92966
|
+
dalle: "openai/gpt-image-2",
|
|
92967
|
+
"gpt-image": "openai/gpt-image-1",
|
|
92968
|
+
"gpt-image-1": "openai/gpt-image-1",
|
|
92969
|
+
"gpt-image-2": "openai/gpt-image-2",
|
|
92970
|
+
seedream: "bytedance/seedream-5-pro",
|
|
92971
|
+
banana: "google/nano-banana",
|
|
92972
|
+
"nano-banana": "google/nano-banana",
|
|
92973
|
+
"banana-2": "google/nano-banana-2",
|
|
92974
|
+
"nano-banana-2": "google/nano-banana-2",
|
|
92975
|
+
"banana-pro": "google/nano-banana-pro",
|
|
92976
|
+
"nano-banana-pro": "google/nano-banana-pro",
|
|
92977
|
+
"grok-imagine": "xai/grok-imagine-image",
|
|
92978
|
+
"grok-imagine-pro": "xai/grok-imagine-image-pro",
|
|
92979
|
+
cogview: "zai/cogview-4"
|
|
92980
|
+
});
|
|
92903
92981
|
VIDEO_PRICING = {
|
|
92904
92982
|
"xai/grok-imagine-video": { pricePerSecond: 0.05, defaultDurationSeconds: 8 },
|
|
92905
92983
|
// Seedance rates re-synced 2026-08-08 from blockrun's models.ts after the
|
|
@@ -93402,8 +93480,8 @@ var init_registry = __esm({
|
|
|
93402
93480
|
name: "Image Generation",
|
|
93403
93481
|
partner: "BlockRun",
|
|
93404
93482
|
category: "Image & Video",
|
|
93405
|
-
shortDescription: "
|
|
93406
|
-
description: "Generate an image from a text prompt. Models available: google/nano-banana (default), google/nano-banana-pro (up to 4K), openai/gpt-image-1, openai/
|
|
93483
|
+
shortDescription: "9 image models (Nano Banana, GPT Image, ...)",
|
|
93484
|
+
description: "Generate an image from a text prompt. Models available: google/nano-banana (default), google/nano-banana-2, google/nano-banana-pro (up to 4K), openai/gpt-image-1, openai/gpt-image-2, bytedance/seedream-5-pro, xai/grok-imagine-image, xai/grok-imagine-image-pro, zai/cogview-4. Returns a local http://localhost:8402/images/<file>.png URL.",
|
|
93407
93485
|
proxyPath: "/images/generations",
|
|
93408
93486
|
method: "POST",
|
|
93409
93487
|
params: [
|
|
@@ -93416,7 +93494,7 @@ var init_registry = __esm({
|
|
|
93416
93494
|
{
|
|
93417
93495
|
name: "model",
|
|
93418
93496
|
type: "string",
|
|
93419
|
-
description: "Full model ID (e.g. 'google/nano-banana', 'openai/
|
|
93497
|
+
description: "Full model ID (e.g. 'google/nano-banana', 'openai/gpt-image-2'). Default: google/nano-banana.",
|
|
93420
93498
|
required: false
|
|
93421
93499
|
},
|
|
93422
93500
|
{
|
|
@@ -225359,6 +225437,7 @@ __export(index_exports, {
|
|
|
225359
225437
|
SpendControl: () => SpendControl,
|
|
225360
225438
|
VISIBLE_OPENCLAW_MODELS: () => VISIBLE_OPENCLAW_MODELS,
|
|
225361
225439
|
blockrunProvider: () => blockrunProvider,
|
|
225440
|
+
buildImageGenerationProvider: () => buildImageGenerationProvider,
|
|
225362
225441
|
buildPartnerTools: () => buildPartnerTools,
|
|
225363
225442
|
buildProviderModels: () => buildProviderModels,
|
|
225364
225443
|
calculateModelCost: () => calculateModelCost,
|
|
@@ -226122,12 +226201,23 @@ function buildImageGenerationProvider() {
|
|
|
226122
226201
|
id: "blockrun",
|
|
226123
226202
|
label: "BlockRun",
|
|
226124
226203
|
defaultModel: "google/nano-banana",
|
|
226204
|
+
// Must stay in sync with IMAGE_PRICING (proxy.ts). OpenClaw sends the
|
|
226205
|
+
// picked id straight to /v1/images/generations, which forwards the body
|
|
226206
|
+
// verbatim with no alias resolution, so a retired id here is a guaranteed
|
|
226207
|
+
// upstream 400. dall-e-3 (delisted 2026-05-25) and flux-1.1-pro (no
|
|
226208
|
+
// gateway entry) were dropped in v0.12.227 and had lingered here.
|
|
226209
|
+
// Kept as a hand-written array (not spread from IMAGE_MODEL_IDS) because
|
|
226210
|
+
// this is the picker's curated display order — default model first, which
|
|
226211
|
+
// IMAGE_PRICING declaration order does not give — and because the
|
|
226212
|
+
// lifecycle tests fully mock ./proxy.js. Set-equality is enforced by
|
|
226213
|
+
// src/index.image-provider.test.ts, which pins the two lists together.
|
|
226125
226214
|
models: [
|
|
226126
226215
|
"google/nano-banana",
|
|
226216
|
+
"google/nano-banana-2",
|
|
226127
226217
|
"google/nano-banana-pro",
|
|
226128
226218
|
"openai/gpt-image-1",
|
|
226129
|
-
"openai/
|
|
226130
|
-
"
|
|
226219
|
+
"openai/gpt-image-2",
|
|
226220
|
+
"bytedance/seedream-5-pro",
|
|
226131
226221
|
"xai/grok-imagine-image",
|
|
226132
226222
|
"xai/grok-imagine-image-pro",
|
|
226133
226223
|
"zai/cogview-4"
|
|
@@ -226142,20 +226232,28 @@ function buildImageGenerationProvider() {
|
|
|
226142
226232
|
// Only openai/gpt-image-1 supports edit server-side; OpenClaw's UI picks a
|
|
226143
226233
|
// compatible model at edit time via /v1/images/image2image.
|
|
226144
226234
|
edit: { enabled: true },
|
|
226235
|
+
// Union of every size the gateway accepts across the models above,
|
|
226236
|
+
// live-probed 2026-08-23 (the gateway validates size per-model BEFORE
|
|
226237
|
+
// payment and 400s unknown ones — 1216x832 / 1792x1024 / 1024x1792 were
|
|
226238
|
+
// accepted by no model and are gone with dall-e-3). Pinned against
|
|
226239
|
+
// IMAGE_MODEL_SIZES (proxy.ts) by src/index.image-provider.test.ts.
|
|
226145
226240
|
geometry: {
|
|
226146
226241
|
sizes: [
|
|
226147
226242
|
"512x512",
|
|
226148
226243
|
"768x768",
|
|
226149
226244
|
"768x1344",
|
|
226150
226245
|
"1024x1024",
|
|
226151
|
-
"
|
|
226246
|
+
"1024x1536",
|
|
226247
|
+
"1280x720",
|
|
226152
226248
|
"1344x768",
|
|
226153
226249
|
"1440x1440",
|
|
226154
226250
|
"1536x1024",
|
|
226155
|
-
"
|
|
226156
|
-
"
|
|
226157
|
-
"
|
|
226251
|
+
"1600x2848",
|
|
226252
|
+
"1728x2304",
|
|
226253
|
+
"2048x1024",
|
|
226158
226254
|
"2048x2048",
|
|
226255
|
+
"2304x1728",
|
|
226256
|
+
"2848x1600",
|
|
226159
226257
|
"4096x4096"
|
|
226160
226258
|
]
|
|
226161
226259
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1594,6 +1594,12 @@ declare function parseCallArgs(raw: string): {
|
|
|
1594
1594
|
from?: string;
|
|
1595
1595
|
language?: string;
|
|
1596
1596
|
};
|
|
1597
|
+
/**
|
|
1598
|
+
* Build the ImageGenerationProvider that registers BlockRun image models
|
|
1599
|
+
* with OpenClaw's native image generation UI.
|
|
1600
|
+
* Delegates to the local proxy (which handles x402 payment).
|
|
1601
|
+
*/
|
|
1602
|
+
declare function buildImageGenerationProvider(): ImageGenerationProviderPlugin;
|
|
1597
1603
|
declare const plugin: OpenClawPluginDefinition;
|
|
1598
1604
|
|
|
1599
|
-
export { type AggregatedStats, BALANCE_THRESHOLDS, BLOCKRUN_MODELS, type BalanceInfo, BalanceMonitor, type CachedLLMResponse, type CachedResponse, type CheckResult, DEFAULT_RETRY_CONFIG, DEFAULT_SESSION_CONFIG, type DailyStats, type DerivedKeys, EmptyWalletError, FileSpendControlStorage, InMemorySpendControlStorage, InsufficientFundsError, type InsufficientFundsInfo, type LowBalanceInfo, MODEL_ALIASES, OPENCLAW_MODELS, PARTNER_SERVICES, type PartnerServiceDefinition, type PartnerToolDefinition, type PaymentChain, type ProxyHandle, type ProxyOptions, RequestDeduplicator, ResponseCache, type ResponseCacheConfig, type RetryConfig, RpcError, type SessionConfig, type SessionEntry, SessionStore, type SolanaBalanceInfo, SolanaBalanceMonitor, SpendControl, type SpendControlOptions, type SpendControlStorage, type SpendLimits, type SpendRecord, type SpendWindow, type SpendingStatus, type SufficiencyResult, type UsageEntry, VISIBLE_OPENCLAW_MODELS, type WalletConfig, type WalletResolution, blockrunProvider, buildPartnerTools, buildProviderModels, clearStats, plugin as default, deriveAllKeys, deriveEvmKey, deriveSolanaKeyBytes, fetchWithRetry, formatDuration, formatStatsAscii, generateWalletMnemonic, getAgenticModels, getModelContextWindow, getPartnerService, getProxyPort, getSessionId, getStats, hashRequestContent, injectAuthProfile, injectModelsConfig, isAgenticModel, isBalanceError, isBlockrunWebSearchDisabled, isEmptyWalletError, isInsufficientFundsError, isRetryable, isRpcError, isValidMnemonic, loadPaymentChain, logUsage, parseCallArgs, resolveModelAlias, resolvePaymentChain, savePaymentChain, setupSolana, startProxy, syncAgentModelCache };
|
|
1605
|
+
export { type AggregatedStats, BALANCE_THRESHOLDS, BLOCKRUN_MODELS, type BalanceInfo, BalanceMonitor, type CachedLLMResponse, type CachedResponse, type CheckResult, DEFAULT_RETRY_CONFIG, DEFAULT_SESSION_CONFIG, type DailyStats, type DerivedKeys, EmptyWalletError, FileSpendControlStorage, InMemorySpendControlStorage, InsufficientFundsError, type InsufficientFundsInfo, type LowBalanceInfo, MODEL_ALIASES, OPENCLAW_MODELS, PARTNER_SERVICES, type PartnerServiceDefinition, type PartnerToolDefinition, type PaymentChain, type ProxyHandle, type ProxyOptions, RequestDeduplicator, ResponseCache, type ResponseCacheConfig, type RetryConfig, RpcError, type SessionConfig, type SessionEntry, SessionStore, type SolanaBalanceInfo, SolanaBalanceMonitor, SpendControl, type SpendControlOptions, type SpendControlStorage, type SpendLimits, type SpendRecord, type SpendWindow, type SpendingStatus, type SufficiencyResult, type UsageEntry, VISIBLE_OPENCLAW_MODELS, type WalletConfig, type WalletResolution, blockrunProvider, buildImageGenerationProvider, buildPartnerTools, buildProviderModels, clearStats, plugin as default, deriveAllKeys, deriveEvmKey, deriveSolanaKeyBytes, fetchWithRetry, formatDuration, formatStatsAscii, generateWalletMnemonic, getAgenticModels, getModelContextWindow, getPartnerService, getProxyPort, getSessionId, getStats, hashRequestContent, injectAuthProfile, injectModelsConfig, isAgenticModel, isBalanceError, isBlockrunWebSearchDisabled, isEmptyWalletError, isInsufficientFundsError, isRetryable, isRpcError, isValidMnemonic, loadPaymentChain, logUsage, parseCallArgs, resolveModelAlias, resolvePaymentChain, savePaymentChain, setupSolana, startProxy, syncAgentModelCache };
|
package/dist/index.js
CHANGED
|
@@ -88952,6 +88952,9 @@ function stripThinkingTokens(content) {
|
|
|
88952
88952
|
cleaned = cleaned.replace(THINKING_TAG_RE, "");
|
|
88953
88953
|
return cleaned;
|
|
88954
88954
|
}
|
|
88955
|
+
function forwardToolCallProse() {
|
|
88956
|
+
return process.env.CLAWROUTER_TOOL_CALL_PROSE?.trim().toLowerCase() !== "off";
|
|
88957
|
+
}
|
|
88955
88958
|
function buildModelPricing() {
|
|
88956
88959
|
const map = /* @__PURE__ */ new Map();
|
|
88957
88960
|
for (const m of BLOCKRUN_MODELS) {
|
|
@@ -90636,25 +90639,7 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
|
|
|
90636
90639
|
const modelMatch = imageArgs.match(/--model\s+(\S+)/);
|
|
90637
90640
|
if (modelMatch) {
|
|
90638
90641
|
const raw = modelMatch[1];
|
|
90639
|
-
|
|
90640
|
-
// dall-e-3 delisted upstream 2026-05-25 — legacy aliases route to
|
|
90641
|
-
// the OpenAI successor instead of a guaranteed 400.
|
|
90642
|
-
"dall-e-3": "openai/gpt-image-2",
|
|
90643
|
-
dalle3: "openai/gpt-image-2",
|
|
90644
|
-
dalle: "openai/gpt-image-2",
|
|
90645
|
-
"gpt-image": "openai/gpt-image-1",
|
|
90646
|
-
"gpt-image-1": "openai/gpt-image-1",
|
|
90647
|
-
"gpt-image-2": "openai/gpt-image-2",
|
|
90648
|
-
seedream: "bytedance/seedream-5-pro",
|
|
90649
|
-
banana: "google/nano-banana",
|
|
90650
|
-
"nano-banana": "google/nano-banana",
|
|
90651
|
-
"banana-pro": "google/nano-banana-pro",
|
|
90652
|
-
"nano-banana-pro": "google/nano-banana-pro",
|
|
90653
|
-
"grok-imagine": "xai/grok-imagine-image",
|
|
90654
|
-
"grok-imagine-pro": "xai/grok-imagine-image-pro",
|
|
90655
|
-
cogview: "zai/cogview-4"
|
|
90656
|
-
};
|
|
90657
|
-
imageModel = IMAGE_MODEL_ALIASES[raw] ?? raw;
|
|
90642
|
+
imageModel = Object.prototype.hasOwnProperty.call(IMAGE_MODEL_ALIASES, raw) ? IMAGE_MODEL_ALIASES[raw] : raw;
|
|
90658
90643
|
imagePrompt = imagePrompt.replace(/--model\s+\S+/, "").trim();
|
|
90659
90644
|
}
|
|
90660
90645
|
const sizeMatch = imageArgs.match(/--size\s+(\d+x\d+)/);
|
|
@@ -90672,11 +90657,13 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
|
|
|
90672
90657
|
"",
|
|
90673
90658
|
"Models:",
|
|
90674
90659
|
" nano-banana Google Gemini Flash \u2014 $0.05/image",
|
|
90660
|
+
" banana-2 Google Nano Banana 2 \u2014 $0.09/image",
|
|
90675
90661
|
" banana-pro Google Gemini Pro \u2014 $0.10/image (up to 4K)",
|
|
90676
90662
|
" gpt-image OpenAI GPT Image 1 \u2014 $0.02/image",
|
|
90677
90663
|
" gpt-image-2 OpenAI GPT Image 2 \u2014 $0.06/image",
|
|
90678
90664
|
" seedream ByteDance Seedream 5 Pro \u2014 $0.045/image",
|
|
90679
90665
|
" grok-imagine xAI Grok Imagine \u2014 $0.02/image",
|
|
90666
|
+
" grok-imagine-pro xAI Grok Imagine Pro \u2014 $0.07/image",
|
|
90680
90667
|
" cogview Zhipu CogView-4 \u2014 $0.015/image",
|
|
90681
90668
|
"",
|
|
90682
90669
|
"Examples:",
|
|
@@ -90739,14 +90726,68 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
|
|
|
90739
90726
|
size: imageSize,
|
|
90740
90727
|
n: 1
|
|
90741
90728
|
});
|
|
90729
|
+
const imagegenAbort = new AbortController();
|
|
90730
|
+
res.on("close", () => {
|
|
90731
|
+
if (!res.writableEnded) imagegenAbort.abort();
|
|
90732
|
+
});
|
|
90742
90733
|
const imageResponse = await payFetch(imageUpstreamUrl, {
|
|
90743
90734
|
method: "POST",
|
|
90744
90735
|
headers: { "content-type": "application/json", "user-agent": USER_AGENT },
|
|
90745
|
-
body: imageBody
|
|
90736
|
+
body: imageBody,
|
|
90737
|
+
signal: imagegenAbort.signal
|
|
90746
90738
|
});
|
|
90747
|
-
|
|
90739
|
+
let imageResult = await imageResponse.json();
|
|
90740
|
+
let imageFailure;
|
|
90741
|
+
if (imageResponse.ok && imageResult.poll_url && imageResult.id && !imageResult.data?.length) {
|
|
90742
|
+
const apiOrigin = new URL(apiBase).origin;
|
|
90743
|
+
const pollUrl = imageResult.poll_url.startsWith("http") ? imageResult.poll_url : `${apiOrigin}${imageResult.poll_url}`;
|
|
90744
|
+
console.log(
|
|
90745
|
+
`[ClawRouter] /imagegen job submitted (id=${imageResult.id}), polling upstream \u2014 typical 30\u2013120s...`
|
|
90746
|
+
);
|
|
90747
|
+
const pollDeadline = Date.now() + 3e5;
|
|
90748
|
+
await new Promise((r2) => setTimeout(r2, 2e3));
|
|
90749
|
+
let completed2 = false;
|
|
90750
|
+
while (Date.now() < pollDeadline) {
|
|
90751
|
+
if (imagegenAbort.signal.aborted) {
|
|
90752
|
+
console.log(
|
|
90753
|
+
`[ClawRouter] Chat client disconnected \u2014 abandoning image poll (id=${imageResult.id})`
|
|
90754
|
+
);
|
|
90755
|
+
return;
|
|
90756
|
+
}
|
|
90757
|
+
const pollResp = await payFetch(pollUrl, {
|
|
90758
|
+
method: "GET",
|
|
90759
|
+
headers: { "user-agent": USER_AGENT },
|
|
90760
|
+
signal: imagegenAbort.signal
|
|
90761
|
+
});
|
|
90762
|
+
const pollText = await pollResp.text();
|
|
90763
|
+
let pollBody = {};
|
|
90764
|
+
try {
|
|
90765
|
+
pollBody = JSON.parse(pollText);
|
|
90766
|
+
} catch {
|
|
90767
|
+
imageFailure = `Non-JSON poll response (${pollResp.status}): ${pollText.slice(0, 200)}`;
|
|
90768
|
+
break;
|
|
90769
|
+
}
|
|
90770
|
+
if (pollResp.status === 202 || pollBody.status === "queued" || pollBody.status === "in_progress") {
|
|
90771
|
+
await new Promise((r2) => setTimeout(r2, 3e3));
|
|
90772
|
+
continue;
|
|
90773
|
+
}
|
|
90774
|
+
if (pollBody.status === "failed" || !pollResp.ok) {
|
|
90775
|
+
imageFailure = typeof pollBody.error === "string" ? pollBody.error : pollBody.error?.message ?? `Upstream image generation failed (HTTP ${pollResp.status})`;
|
|
90776
|
+
break;
|
|
90777
|
+
}
|
|
90778
|
+
imageResult = pollBody;
|
|
90779
|
+
completed2 = true;
|
|
90780
|
+
break;
|
|
90781
|
+
}
|
|
90782
|
+
if (!completed2 && !imageFailure) {
|
|
90783
|
+
imageFailure = "Image generation timed out after 5 minutes \u2014 the job may still complete upstream.";
|
|
90784
|
+
}
|
|
90785
|
+
}
|
|
90748
90786
|
let responseText;
|
|
90749
|
-
if (
|
|
90787
|
+
if (imageFailure) {
|
|
90788
|
+
responseText = `Image generation failed: ${imageFailure}`;
|
|
90789
|
+
console.log(`[ClawRouter] /imagegen error: ${imageFailure}`);
|
|
90790
|
+
} else if (!imageResponse.ok || imageResult.error) {
|
|
90750
90791
|
const errMsg = typeof imageResult.error === "string" ? imageResult.error : imageResult.error?.message ?? `HTTP ${imageResponse.status}`;
|
|
90751
90792
|
responseText = `Image generation failed: ${errMsg}`;
|
|
90752
90793
|
console.log(`[ClawRouter] /imagegen error: ${errMsg}`);
|
|
@@ -90766,9 +90807,7 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
|
|
|
90766
90807
|
console.error(
|
|
90767
90808
|
`[ClawRouter] /imagegen: failed to upload data URI: ${uploadErr instanceof Error ? uploadErr.message : String(uploadErr)}`
|
|
90768
90809
|
);
|
|
90769
|
-
lines.push(
|
|
90770
|
-
"Image generated but upload failed. Try again or use --model dall-e-3."
|
|
90771
|
-
);
|
|
90810
|
+
lines.push("Image generated but upload failed. Try again.");
|
|
90772
90811
|
}
|
|
90773
90812
|
} else {
|
|
90774
90813
|
lines.push(img.url);
|
|
@@ -92128,13 +92167,16 @@ data: [DONE]
|
|
|
92128
92167
|
const endsWithToolCalls = choice.finish_reason === "tool_calls";
|
|
92129
92168
|
let toolCalls = choice.message?.tool_calls ?? choice.delta?.tool_calls;
|
|
92130
92169
|
const rawContent = choice.message?.content ?? choice.delta?.content ?? "";
|
|
92170
|
+
let proseContent = rawContent;
|
|
92131
92171
|
if (!endsWithToolCalls && (!toolCalls || toolCalls.length === 0) && rawContent) {
|
|
92132
92172
|
const extracted = extractTextualToolCalls(rawContent, { tools: requestTools });
|
|
92133
92173
|
if (extracted.toolCalls.length > 0) {
|
|
92134
92174
|
toolCalls = extracted.toolCalls;
|
|
92175
|
+
proseContent = extracted.cleanedContent;
|
|
92135
92176
|
}
|
|
92136
92177
|
}
|
|
92137
|
-
const
|
|
92178
|
+
const suppressProse = (endsWithToolCalls || toolCalls && toolCalls.length > 0) && !forwardToolCallProse();
|
|
92179
|
+
const content = suppressProse ? "" : stripThinkingTokens(proseContent);
|
|
92138
92180
|
const role = choice.message?.role ?? choice.delta?.role ?? "assistant";
|
|
92139
92181
|
const index2 = choice.index ?? 0;
|
|
92140
92182
|
if (content) {
|
|
@@ -92336,8 +92378,9 @@ data: [DONE]
|
|
|
92336
92378
|
const message = choice.message;
|
|
92337
92379
|
if (!message || typeof message.content !== "string") continue;
|
|
92338
92380
|
if (choice.finish_reason === "tool_calls" || Array.isArray(message.tool_calls) && message.tool_calls.length > 0) {
|
|
92339
|
-
|
|
92340
|
-
|
|
92381
|
+
const next = forwardToolCallProse() ? stripThinkingTokens(message.content) : "";
|
|
92382
|
+
if (message.content !== next) {
|
|
92383
|
+
message.content = next;
|
|
92341
92384
|
changed = true;
|
|
92342
92385
|
}
|
|
92343
92386
|
continue;
|
|
@@ -92345,7 +92388,7 @@ data: [DONE]
|
|
|
92345
92388
|
const extracted = extractTextualToolCalls(message.content, { tools: requestTools });
|
|
92346
92389
|
if (extracted.toolCalls.length > 0) {
|
|
92347
92390
|
message.tool_calls = extracted.toolCalls;
|
|
92348
|
-
message.content = "";
|
|
92391
|
+
message.content = forwardToolCallProse() ? stripThinkingTokens(extracted.cleanedContent) : "";
|
|
92349
92392
|
choice.finish_reason = "tool_calls";
|
|
92350
92393
|
changed = true;
|
|
92351
92394
|
continue;
|
|
@@ -92552,7 +92595,7 @@ data: [DONE]
|
|
|
92552
92595
|
});
|
|
92553
92596
|
}
|
|
92554
92597
|
}
|
|
92555
|
-
var paymentStore, BLOCKRUN_API, BLOCKRUN_SOLANA_API, IMAGE_DIR, AUDIO_DIR, VIDEO_DIR, AUTO_MODEL, ROUTING_PROFILES, FREE_MODELS, FREE_MODEL, MAX_MESSAGES, CONTEXT_LIMIT_KB, HEARTBEAT_INTERVAL_MS, BALANCE_CHECK_TIMEOUT_MS, DEFAULT_REQUEST_TIMEOUT_MS, PER_MODEL_TIMEOUT_MS, REASONING_MODEL_TIMEOUT_MS, REASONING_MODEL_IDS, MAX_FALLBACK_ATTEMPTS, HEALTH_CHECK_TIMEOUT_MS, RATE_LIMIT_COOLDOWN_MS, OVERLOAD_COOLDOWN_MS, PORT_RETRY_ATTEMPTS, PORT_RETRY_DELAY_MS, MODEL_BODY_READ_TIMEOUT_MS, ERROR_BODY_READ_TIMEOUT_MS, rateLimitedModels, overloadedModels, perProviderErrors, BALANCE_CHECK_BUFFER, BALANCE_PREFLIGHT_OUTPUT_TOKEN_CAP, PROVIDER_ERROR_PATTERNS, DEGRADED_RESPONSE_PATTERNS, DEGRADED_LOOP_PATTERNS, VALID_ROLES, ROLE_MAPPINGS, VALID_TOOL_ID_PATTERN, KIMI_BLOCK_RE, KIMI_TOKEN_RE, THINKING_TAG_RE, THINKING_BLOCK_RE, BLOCKRUN_MODEL_BY_ID, IMAGE_PRICING, VIDEO_PRICING, PHONE_PRICING;
|
|
92598
|
+
var paymentStore, BLOCKRUN_API, BLOCKRUN_SOLANA_API, IMAGE_DIR, AUDIO_DIR, VIDEO_DIR, AUTO_MODEL, ROUTING_PROFILES, FREE_MODELS, FREE_MODEL, MAX_MESSAGES, CONTEXT_LIMIT_KB, HEARTBEAT_INTERVAL_MS, BALANCE_CHECK_TIMEOUT_MS, DEFAULT_REQUEST_TIMEOUT_MS, PER_MODEL_TIMEOUT_MS, REASONING_MODEL_TIMEOUT_MS, REASONING_MODEL_IDS, MAX_FALLBACK_ATTEMPTS, HEALTH_CHECK_TIMEOUT_MS, RATE_LIMIT_COOLDOWN_MS, OVERLOAD_COOLDOWN_MS, PORT_RETRY_ATTEMPTS, PORT_RETRY_DELAY_MS, MODEL_BODY_READ_TIMEOUT_MS, ERROR_BODY_READ_TIMEOUT_MS, rateLimitedModels, overloadedModels, perProviderErrors, BALANCE_CHECK_BUFFER, BALANCE_PREFLIGHT_OUTPUT_TOKEN_CAP, PROVIDER_ERROR_PATTERNS, DEGRADED_RESPONSE_PATTERNS, DEGRADED_LOOP_PATTERNS, VALID_ROLES, ROLE_MAPPINGS, VALID_TOOL_ID_PATTERN, KIMI_BLOCK_RE, KIMI_TOKEN_RE, THINKING_TAG_RE, THINKING_BLOCK_RE, BLOCKRUN_MODEL_BY_ID, IMAGE_PRICING, IMAGE_MODEL_IDS, IMAGE_MODEL_SIZES, IMAGE_MODEL_ALIASES, VIDEO_PRICING, PHONE_PRICING;
|
|
92556
92599
|
var init_proxy = __esm({
|
|
92557
92600
|
"src/proxy.ts"() {
|
|
92558
92601
|
"use strict";
|
|
@@ -92697,9 +92740,20 @@ var init_proxy = __esm({
|
|
|
92697
92740
|
},
|
|
92698
92741
|
"bytedance/seedream-5-pro": {
|
|
92699
92742
|
default: 0.045,
|
|
92700
|
-
|
|
92743
|
+
// Full size list live-probed 2026-08-23 (gateway 402 quotes, base price):
|
|
92744
|
+
// the 2K portrait/landscape tiers bill at the 2048x2048 rate.
|
|
92745
|
+
sizes: {
|
|
92746
|
+
"1024x1024": 0.045,
|
|
92747
|
+
"1280x720": 0.045,
|
|
92748
|
+
"2048x1024": 0.045,
|
|
92749
|
+
"2048x2048": 0.09,
|
|
92750
|
+
"2304x1728": 0.09,
|
|
92751
|
+
"1728x2304": 0.09,
|
|
92752
|
+
"2848x1600": 0.09,
|
|
92753
|
+
"1600x2848": 0.09
|
|
92754
|
+
}
|
|
92701
92755
|
},
|
|
92702
|
-
"google/nano-banana": { default: 0.05 },
|
|
92756
|
+
"google/nano-banana": { default: 0.05, sizes: { "1024x1024": 0.05 } },
|
|
92703
92757
|
// Nano Banana 2 (Gemini 3.1 Flash imagegen, blockrun #329): $0.09/image at 1K.
|
|
92704
92758
|
"google/nano-banana-2": { default: 0.09, sizes: { "1024x1024": 0.09 } },
|
|
92705
92759
|
"google/nano-banana-pro": {
|
|
@@ -92720,6 +92774,30 @@ var init_proxy = __esm({
|
|
|
92720
92774
|
}
|
|
92721
92775
|
}
|
|
92722
92776
|
};
|
|
92777
|
+
IMAGE_MODEL_IDS = Object.freeze(Object.keys(IMAGE_PRICING));
|
|
92778
|
+
IMAGE_MODEL_SIZES = Object.freeze(
|
|
92779
|
+
[...new Set(Object.values(IMAGE_PRICING).flatMap((p) => Object.keys(p.sizes ?? {})))].sort()
|
|
92780
|
+
);
|
|
92781
|
+
IMAGE_MODEL_ALIASES = Object.freeze({
|
|
92782
|
+
// dall-e-3 delisted upstream 2026-05-25 — legacy aliases route to
|
|
92783
|
+
// the OpenAI successor instead of a guaranteed 400.
|
|
92784
|
+
"dall-e-3": "openai/gpt-image-2",
|
|
92785
|
+
dalle3: "openai/gpt-image-2",
|
|
92786
|
+
dalle: "openai/gpt-image-2",
|
|
92787
|
+
"gpt-image": "openai/gpt-image-1",
|
|
92788
|
+
"gpt-image-1": "openai/gpt-image-1",
|
|
92789
|
+
"gpt-image-2": "openai/gpt-image-2",
|
|
92790
|
+
seedream: "bytedance/seedream-5-pro",
|
|
92791
|
+
banana: "google/nano-banana",
|
|
92792
|
+
"nano-banana": "google/nano-banana",
|
|
92793
|
+
"banana-2": "google/nano-banana-2",
|
|
92794
|
+
"nano-banana-2": "google/nano-banana-2",
|
|
92795
|
+
"banana-pro": "google/nano-banana-pro",
|
|
92796
|
+
"nano-banana-pro": "google/nano-banana-pro",
|
|
92797
|
+
"grok-imagine": "xai/grok-imagine-image",
|
|
92798
|
+
"grok-imagine-pro": "xai/grok-imagine-image-pro",
|
|
92799
|
+
cogview: "zai/cogview-4"
|
|
92800
|
+
});
|
|
92723
92801
|
VIDEO_PRICING = {
|
|
92724
92802
|
"xai/grok-imagine-video": { pricePerSecond: 0.05, defaultDurationSeconds: 8 },
|
|
92725
92803
|
// Seedance rates re-synced 2026-08-08 from blockrun's models.ts after the
|
|
@@ -93493,8 +93571,8 @@ var init_registry = __esm({
|
|
|
93493
93571
|
name: "Image Generation",
|
|
93494
93572
|
partner: "BlockRun",
|
|
93495
93573
|
category: "Image & Video",
|
|
93496
|
-
shortDescription: "
|
|
93497
|
-
description: "Generate an image from a text prompt. Models available: google/nano-banana (default), google/nano-banana-pro (up to 4K), openai/gpt-image-1, openai/
|
|
93574
|
+
shortDescription: "9 image models (Nano Banana, GPT Image, ...)",
|
|
93575
|
+
description: "Generate an image from a text prompt. Models available: google/nano-banana (default), google/nano-banana-2, google/nano-banana-pro (up to 4K), openai/gpt-image-1, openai/gpt-image-2, bytedance/seedream-5-pro, xai/grok-imagine-image, xai/grok-imagine-image-pro, zai/cogview-4. Returns a local http://localhost:8402/images/<file>.png URL.",
|
|
93498
93576
|
proxyPath: "/images/generations",
|
|
93499
93577
|
method: "POST",
|
|
93500
93578
|
params: [
|
|
@@ -93507,7 +93585,7 @@ var init_registry = __esm({
|
|
|
93507
93585
|
{
|
|
93508
93586
|
name: "model",
|
|
93509
93587
|
type: "string",
|
|
93510
|
-
description: "Full model ID (e.g. 'google/nano-banana', 'openai/
|
|
93588
|
+
description: "Full model ID (e.g. 'google/nano-banana', 'openai/gpt-image-2'). Default: google/nano-banana.",
|
|
93511
93589
|
required: false
|
|
93512
93590
|
},
|
|
93513
93591
|
{
|
|
@@ -225875,12 +225953,23 @@ function buildImageGenerationProvider() {
|
|
|
225875
225953
|
id: "blockrun",
|
|
225876
225954
|
label: "BlockRun",
|
|
225877
225955
|
defaultModel: "google/nano-banana",
|
|
225956
|
+
// Must stay in sync with IMAGE_PRICING (proxy.ts). OpenClaw sends the
|
|
225957
|
+
// picked id straight to /v1/images/generations, which forwards the body
|
|
225958
|
+
// verbatim with no alias resolution, so a retired id here is a guaranteed
|
|
225959
|
+
// upstream 400. dall-e-3 (delisted 2026-05-25) and flux-1.1-pro (no
|
|
225960
|
+
// gateway entry) were dropped in v0.12.227 and had lingered here.
|
|
225961
|
+
// Kept as a hand-written array (not spread from IMAGE_MODEL_IDS) because
|
|
225962
|
+
// this is the picker's curated display order — default model first, which
|
|
225963
|
+
// IMAGE_PRICING declaration order does not give — and because the
|
|
225964
|
+
// lifecycle tests fully mock ./proxy.js. Set-equality is enforced by
|
|
225965
|
+
// src/index.image-provider.test.ts, which pins the two lists together.
|
|
225878
225966
|
models: [
|
|
225879
225967
|
"google/nano-banana",
|
|
225968
|
+
"google/nano-banana-2",
|
|
225880
225969
|
"google/nano-banana-pro",
|
|
225881
225970
|
"openai/gpt-image-1",
|
|
225882
|
-
"openai/
|
|
225883
|
-
"
|
|
225971
|
+
"openai/gpt-image-2",
|
|
225972
|
+
"bytedance/seedream-5-pro",
|
|
225884
225973
|
"xai/grok-imagine-image",
|
|
225885
225974
|
"xai/grok-imagine-image-pro",
|
|
225886
225975
|
"zai/cogview-4"
|
|
@@ -225895,20 +225984,28 @@ function buildImageGenerationProvider() {
|
|
|
225895
225984
|
// Only openai/gpt-image-1 supports edit server-side; OpenClaw's UI picks a
|
|
225896
225985
|
// compatible model at edit time via /v1/images/image2image.
|
|
225897
225986
|
edit: { enabled: true },
|
|
225987
|
+
// Union of every size the gateway accepts across the models above,
|
|
225988
|
+
// live-probed 2026-08-23 (the gateway validates size per-model BEFORE
|
|
225989
|
+
// payment and 400s unknown ones — 1216x832 / 1792x1024 / 1024x1792 were
|
|
225990
|
+
// accepted by no model and are gone with dall-e-3). Pinned against
|
|
225991
|
+
// IMAGE_MODEL_SIZES (proxy.ts) by src/index.image-provider.test.ts.
|
|
225898
225992
|
geometry: {
|
|
225899
225993
|
sizes: [
|
|
225900
225994
|
"512x512",
|
|
225901
225995
|
"768x768",
|
|
225902
225996
|
"768x1344",
|
|
225903
225997
|
"1024x1024",
|
|
225904
|
-
"
|
|
225998
|
+
"1024x1536",
|
|
225999
|
+
"1280x720",
|
|
225905
226000
|
"1344x768",
|
|
225906
226001
|
"1440x1440",
|
|
225907
226002
|
"1536x1024",
|
|
225908
|
-
"
|
|
225909
|
-
"
|
|
225910
|
-
"
|
|
226003
|
+
"1600x2848",
|
|
226004
|
+
"1728x2304",
|
|
226005
|
+
"2048x1024",
|
|
225911
226006
|
"2048x2048",
|
|
226007
|
+
"2304x1728",
|
|
226008
|
+
"2848x1600",
|
|
225912
226009
|
"4096x4096"
|
|
225913
226010
|
]
|
|
225914
226011
|
}
|
|
@@ -226874,6 +226971,7 @@ export {
|
|
|
226874
226971
|
SpendControl,
|
|
226875
226972
|
VISIBLE_OPENCLAW_MODELS,
|
|
226876
226973
|
blockrunProvider,
|
|
226974
|
+
buildImageGenerationProvider,
|
|
226877
226975
|
buildPartnerTools,
|
|
226878
226976
|
buildProviderModels,
|
|
226879
226977
|
calculateModelCost,
|
package/docs/image-generation.md
CHANGED
|
@@ -51,16 +51,22 @@ The returned URL is a publicly hosted image, ready to use in Telegram, Discord,
|
|
|
51
51
|
|
|
52
52
|
## Models & Pricing
|
|
53
53
|
|
|
54
|
-
| Model ID
|
|
55
|
-
|
|
|
56
|
-
| `google/nano-banana`
|
|
57
|
-
| `google/nano-banana-
|
|
58
|
-
| `
|
|
59
|
-
| `openai/gpt-image-1`
|
|
60
|
-
| `
|
|
54
|
+
| Model ID | Shorthand | Price | Sizes | Provider |
|
|
55
|
+
| ---------------------------- | ------------------ | ------------------- | ---------------------------------------------------------------------------------- | --------------------- |
|
|
56
|
+
| `google/nano-banana` | `nano-banana` | $0.05/image | 1024×1024 | Google Gemini Flash |
|
|
57
|
+
| `google/nano-banana-2` | `banana-2` | $0.09/image | 1024×1024 | Google Nano Banana 2 |
|
|
58
|
+
| `google/nano-banana-pro` | `banana-pro` | $0.10–$0.15/image | 1024×1024, 2048×2048, 4096×4096 | Google Gemini Pro |
|
|
59
|
+
| `openai/gpt-image-1` | `gpt-image` | $0.02–$0.04/image | 1024×1024, 1536×1024, 1024×1536 | OpenAI GPT Image 1 |
|
|
60
|
+
| `openai/gpt-image-2` | `gpt-image-2` | $0.06–$0.12/image | 1024×1024, 1536×1024, 1024×1536 | OpenAI GPT Image 2 |
|
|
61
|
+
| `bytedance/seedream-5-pro` | `seedream` | $0.045–$0.09/image | 1024×1024, 1280×720, 2048×1024, 2048×2048, 2304×1728, 1728×2304, 2848×1600, 1600×2848 | ByteDance Seedream 5 |
|
|
62
|
+
| `xai/grok-imagine-image` | `grok-imagine` | $0.02/image | 1024×1024 | xAI Grok Imagine |
|
|
63
|
+
| `xai/grok-imagine-image-pro` | `grok-imagine-pro` | $0.07/image | 1024×1024 | xAI Grok Imagine Pro |
|
|
64
|
+
| `zai/cogview-4` | `cogview` | $0.015–$0.02/image | 512×512, 768×768, 1024×1024, 768×1344, 1344×768, 1440×1440 | Zhipu CogView-4 |
|
|
61
65
|
|
|
62
66
|
Default model: `google/nano-banana`.
|
|
63
67
|
|
|
68
|
+
> `openai/dall-e-3` was delisted upstream on 2026-05-25 and `black-forest/flux-1.1-pro` is no longer served; the legacy `dall-e-3` / `dalle` shorthands now route to `openai/gpt-image-2`. The gateway validates `size` per model **before** payment — sizes outside the lists above return an error and cost nothing. Slow models (notably `gpt-image-2`) can take longer than 30s; the proxy transparently polls the job for up to 5 minutes and still returns a single blocking response.
|
|
69
|
+
|
|
64
70
|
---
|
|
65
71
|
|
|
66
72
|
## API Reference
|
|
@@ -85,7 +91,7 @@ OpenAI-compatible endpoint. Route via ClawRouter proxy (`http://localhost:8402`)
|
|
|
85
91
|
created: number; // Unix timestamp
|
|
86
92
|
data: Array<{
|
|
87
93
|
url: string; // Publicly hosted image URL
|
|
88
|
-
revised_prompt?: string; // Model's rewritten prompt (
|
|
94
|
+
revised_prompt?: string; // Model's rewritten prompt (when the model provides one)
|
|
89
95
|
}>;
|
|
90
96
|
}
|
|
91
97
|
```
|
|
@@ -146,13 +152,13 @@ curl -X POST http://localhost:8402/v1/images/generations \
|
|
|
146
152
|
"n": 1
|
|
147
153
|
}'
|
|
148
154
|
|
|
149
|
-
#
|
|
155
|
+
# GPT Image 2 with landscape size ($0.12 — slow model, the proxy polls for you)
|
|
150
156
|
curl -X POST http://localhost:8402/v1/images/generations \
|
|
151
157
|
-H "Content-Type: application/json" \
|
|
152
158
|
-d '{
|
|
153
|
-
"model": "openai/
|
|
159
|
+
"model": "openai/gpt-image-2",
|
|
154
160
|
"prompt": "a serene Japanese garden in autumn",
|
|
155
|
-
"size": "
|
|
161
|
+
"size": "1536x1024",
|
|
156
162
|
"n": 1
|
|
157
163
|
}'
|
|
158
164
|
```
|
|
@@ -233,9 +239,9 @@ const response = await fetch(`${proxy.baseUrl}/v1/images/generations`, {
|
|
|
233
239
|
method: "POST",
|
|
234
240
|
headers: { "Content-Type": "application/json" },
|
|
235
241
|
body: JSON.stringify({
|
|
236
|
-
model: "
|
|
242
|
+
model: "bytedance/seedream-5-pro",
|
|
237
243
|
prompt: "a serene Japanese garden in autumn",
|
|
238
|
-
size: "
|
|
244
|
+
size: "2048x1024",
|
|
239
245
|
n: 1,
|
|
240
246
|
}),
|
|
241
247
|
});
|
|
@@ -335,7 +341,7 @@ When using ClawRouter with OpenClaw, generate and edit images directly from any
|
|
|
335
341
|
|
|
336
342
|
```
|
|
337
343
|
/cr-imagegen a dog dancing on the beach
|
|
338
|
-
/cr-imagegen --model
|
|
344
|
+
/cr-imagegen --model gpt-image-2 a futuristic city at sunset
|
|
339
345
|
/cr-imagegen --model banana-pro --size 2048x2048 mountain landscape
|
|
340
346
|
```
|
|
341
347
|
|
|
@@ -363,13 +369,18 @@ When using ClawRouter with OpenClaw, generate and edit images directly from any
|
|
|
363
369
|
|
|
364
370
|
### Model shorthands
|
|
365
371
|
|
|
366
|
-
| Shorthand
|
|
367
|
-
|
|
|
368
|
-
| `nano-banana`
|
|
369
|
-
| `banana-
|
|
370
|
-
| `
|
|
371
|
-
| `gpt-image`
|
|
372
|
-
| `
|
|
372
|
+
| Shorthand | Full ID |
|
|
373
|
+
| ------------------------------------ | ---------------------------- |
|
|
374
|
+
| `nano-banana`, `banana` | `google/nano-banana` |
|
|
375
|
+
| `banana-2`, `nano-banana-2` | `google/nano-banana-2` |
|
|
376
|
+
| `banana-pro`, `nano-banana-pro` | `google/nano-banana-pro` |
|
|
377
|
+
| `gpt-image`, `gpt-image-1` | `openai/gpt-image-1` |
|
|
378
|
+
| `gpt-image-2` | `openai/gpt-image-2` |
|
|
379
|
+
| `seedream` | `bytedance/seedream-5-pro` |
|
|
380
|
+
| `grok-imagine` | `xai/grok-imagine-image` |
|
|
381
|
+
| `grok-imagine-pro` | `xai/grok-imagine-image-pro` |
|
|
382
|
+
| `cogview` | `zai/cogview-4` |
|
|
383
|
+
| `dall-e-3`, `dalle3`, `dalle` (legacy) | `openai/gpt-image-2` |
|
|
373
384
|
|
|
374
385
|
---
|
|
375
386
|
|
|
@@ -377,6 +388,6 @@ When using ClawRouter with OpenClaw, generate and edit images directly from any
|
|
|
377
388
|
|
|
378
389
|
- **Local image caching** — All images (generated and edited) are cached locally at `~/.openclaw/blockrun/images/` and served via `http://localhost:8402/images/`. Both base64 data URIs and HTTP URLs from upstream are downloaded and replaced with localhost URLs.
|
|
379
390
|
- **Payment** — Each image costs the listed price in USDC, deducted from your wallet via x402. Make sure your wallet is funded before generating or editing.
|
|
380
|
-
- **
|
|
381
|
-
- **Size limits** —
|
|
391
|
+
- **Content policy** — OpenAI image models (GPT Image 1/2) apply OpenAI's content policy. Use `nano-banana` or `grok-imagine` for more flexibility with generation.
|
|
392
|
+
- **Size limits** — The gateway validates `size` per model before payment; a size outside the model's list returns an error and costs nothing. Check the table above before setting `--size`.
|
|
382
393
|
- **Image editing** — The `/v1/images/image2image` endpoint currently supports `openai/gpt-image-1` (default). The `image` and `mask` fields accept local file paths (`~/photo.png`, `/abs/path.png`), HTTP/HTTPS URLs, or base64 data URIs. ClawRouter handles file reading and URL downloading automatically. Supported formats: PNG, JPG/JPEG, WebP.
|
package/package.json
CHANGED
package/scripts/reinstall.sh
CHANGED
|
@@ -899,7 +899,7 @@ echo " /exclude add <model> → block a model from routing"
|
|
|
899
899
|
echo ""
|
|
900
900
|
echo "Image generation:"
|
|
901
901
|
echo " /imagegen <prompt> # default: nano-banana"
|
|
902
|
-
echo " /imagegen --model
|
|
902
|
+
echo " /imagegen --model gpt-image-2 <prompt> # GPT Image 2"
|
|
903
903
|
echo " /imagegen --model gpt-image <prompt> # GPT Image 1"
|
|
904
904
|
echo ""
|
|
905
905
|
echo "CLI commands:"
|
|
@@ -164,7 +164,7 @@ Realtime prices and historical OHLC across every asset class. The agent should c
|
|
|
164
164
|
|
|
165
165
|
| Tool | Purpose | Price |
|
|
166
166
|
| --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------- |
|
|
167
|
-
| `blockrun_image_generation` |
|
|
167
|
+
| `blockrun_image_generation` | 9 image models — GPT Image 1/2, Nano Banana / 2 / Pro, Seedream 5 Pro, Grok Imagine / Pro, CogView-4 | $0.015–$0.15 / image |
|
|
168
168
|
| `blockrun_image_edit` | Edit / inpaint existing image (openai/gpt-image-1) | $0.02–$0.04 / image |
|
|
169
169
|
| `blockrun_video_generation` | Grok Imagine + ByteDance Seedance (1.5-pro / 2.0-fast / 2.0) at 720p with synced audio (t2v default), 5–10s, + OpenAI Sora 2 via Azure (`azure/sora-2`, 4/8/12s, t2v + i2v, rejects human faces in reference images). Seedance is token-priced upstream (~20,256 tokens/sec at the 720p+audio default — 2× the prior 480p rate); `image_url` (image-to-video) costs the same as t2v since 2026-06-01. Seedance 2.0 variants accept optional `real_face_asset_id` (`ta_…`) for BytePlus RealFace character-consistency — mutually exclusive with `image_url`. | $0.05/s (Grok); $0.10/s (Sora 2); Seedance ~$0.46–$1.49 per 5s clip |
|
|
170
170
|
|
package/skills/imagegen/SKILL.md
CHANGED
|
@@ -57,8 +57,9 @@ Display inline: ``
|
|
|
57
57
|
|
|
58
58
|
| Alias | Full ID | Price | Sizes | Best for |
|
|
59
59
|
| ------------------ | ---------------------------- | ------------ | ------------------------------- | ------------------------------------------------------------------------------------------------------------------ |
|
|
60
|
-
| `nano-banana` | `google/nano-banana` | $0.05 | 1024×1024
|
|
61
|
-
| `banana-
|
|
60
|
+
| `nano-banana` | `google/nano-banana` | $0.05 | 1024×1024 | Default — fast, cheap, good quality |
|
|
61
|
+
| `banana-2` | `google/nano-banana-2` | $0.09 | 1024×1024 | Gemini 3.1 Flash imagegen — sharper than nano-banana at 1K |
|
|
62
|
+
| `banana-pro` | `google/nano-banana-pro` | $0.10–$0.15 | 1024×1024, 2048×2048, 4096×4096 | High-res, large format |
|
|
62
63
|
| `gpt-image` | `openai/gpt-image-1` | $0.02–$0.04 | 1024×1024, 1536×1024, 1024×1536 | Budget option; supports editing |
|
|
63
64
|
| `gpt-image-2` | `openai/gpt-image-2` | $0.06–$0.12 | 1024×1024, 1536×1024, 1024×1536 | Photorealistic, reasoning-driven, text rendering (slow — proxy polls up to 5min); legacy `dalle` alias routes here |
|
|
64
65
|
| `seedream` | `bytedance/seedream-5-pro` | $0.045–$0.09 | up to 2848×1600 / 2304×1728 | Flagship quality, reference-image support |
|
|
@@ -79,10 +80,11 @@ Display inline: ``
|
|
|
79
80
|
|
|
80
81
|
**Choosing a size:**
|
|
81
82
|
|
|
82
|
-
- Default: `1024x1024`
|
|
83
|
-
- Portrait: `
|
|
84
|
-
- Landscape: `1536x1024` (gpt-image / gpt-image-2) or `
|
|
85
|
-
- High-res: `2048x2048`
|
|
83
|
+
- Default: `1024x1024` (the only size every model accepts)
|
|
84
|
+
- Portrait: `1024x1536` (gpt-image / gpt-image-2) or `1728x2304` (seedream)
|
|
85
|
+
- Landscape: `1536x1024` (gpt-image / gpt-image-2), `1344x768` (cogview), or `2048x1024` / `1280x720` (seedream)
|
|
86
|
+
- High-res: `2048x2048` / `4096x4096` with `banana-pro`; `2848x1600` with `seedream`
|
|
87
|
+
- The gateway validates size per model BEFORE payment and rejects unknown ones — do not invent sizes outside each model's list above
|
|
86
88
|
|
|
87
89
|
---
|
|
88
90
|
|
|
@@ -123,7 +125,7 @@ Response is identical to generation:
|
|
|
123
125
|
→ POST to `/v1/images/generations`, model `nano-banana`, prompt as given.
|
|
124
126
|
|
|
125
127
|
**User:** Generate a high-res portrait of a samurai
|
|
126
|
-
→ POST to `/v1/images/generations`, model `
|
|
128
|
+
→ POST to `/v1/images/generations`, model `seedream`, size `1728x2304`.
|
|
127
129
|
|
|
128
130
|
**User:** Edit this photo to add a sunset background: https://example.com/portrait.jpg
|
|
129
131
|
→ POST to `/v1/images/image2image`, model `gpt-image`, image = the URL, prompt = "add a warm sunset background".
|