@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 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
- const IMAGE_MODEL_ALIASES = {
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
- const imageResult = await imageResponse.json();
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 (!imageResponse.ok || imageResult.error) {
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 content = endsWithToolCalls || toolCalls && toolCalls.length > 0 ? "" : stripThinkingTokens(rawContent);
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
- if (message.content !== "") {
92520
- message.content = "";
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
- sizes: { "1024x1024": 0.045, "2048x1024": 0.045, "2048x2048": 0.09 }
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: "8 image models (DALL-E, Flux, Grok, ...)",
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/dall-e-3, black-forest/flux-1.1-pro, xai/grok-imagine-image, xai/grok-imagine-image-pro, zai/cogview-4. Returns a local http://localhost:8402/images/<file>.png URL.",
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/dall-e-3'). Default: google/nano-banana.",
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/dall-e-3",
226130
- "black-forest/flux-1.1-pro",
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
- "1216x832",
226246
+ "1024x1536",
226247
+ "1280x720",
226152
226248
  "1344x768",
226153
226249
  "1440x1440",
226154
226250
  "1536x1024",
226155
- "1024x1536",
226156
- "1792x1024",
226157
- "1024x1792",
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
- const IMAGE_MODEL_ALIASES = {
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
- const imageResult = await imageResponse.json();
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 (!imageResponse.ok || imageResult.error) {
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 content = endsWithToolCalls || toolCalls && toolCalls.length > 0 ? "" : stripThinkingTokens(rawContent);
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
- if (message.content !== "") {
92340
- message.content = "";
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
- sizes: { "1024x1024": 0.045, "2048x1024": 0.045, "2048x2048": 0.09 }
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: "8 image models (DALL-E, Flux, Grok, ...)",
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/dall-e-3, black-forest/flux-1.1-pro, xai/grok-imagine-image, xai/grok-imagine-image-pro, zai/cogview-4. Returns a local http://localhost:8402/images/<file>.png URL.",
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/dall-e-3'). Default: google/nano-banana.",
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/dall-e-3",
225883
- "black-forest/flux-1.1-pro",
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
- "1216x832",
225998
+ "1024x1536",
225999
+ "1280x720",
225905
226000
  "1344x768",
225906
226001
  "1440x1440",
225907
226002
  "1536x1024",
225908
- "1024x1536",
225909
- "1792x1024",
225910
- "1024x1792",
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,
@@ -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 | Shorthand | Price | Max Size | Provider |
55
- | --------------------------- | ------------- | ----------- | --------- | ------------------- |
56
- | `google/nano-banana` | `nano-banana` | $0.05/image | 1024×1024 | Google Gemini Flash |
57
- | `google/nano-banana-pro` | `banana-pro` | $0.10/image | 4096×4096 | Google Gemini Pro |
58
- | `openai/dall-e-3` | `dall-e-3` | $0.04/image | 1792×1024 | OpenAI DALL-E 3 |
59
- | `openai/gpt-image-1` | `gpt-image` | $0.02/image | 1536×1024 | OpenAI GPT Image |
60
- | `black-forest/flux-1.1-pro` | `flux` | $0.04/image | 1024×1024 | Black Forest Labs |
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 (dall-e-3 only)
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
- # DALL-E 3 with landscape size ($0.04)
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/dall-e-3",
159
+ "model": "openai/gpt-image-2",
154
160
  "prompt": "a serene Japanese garden in autumn",
155
- "size": "1792x1024",
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: "openai/dall-e-3",
242
+ model: "bytedance/seedream-5-pro",
237
243
  prompt: "a serene Japanese garden in autumn",
238
- size: "1792x1024",
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 dall-e-3 a futuristic city at sunset
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 | Full ID |
367
- | ------------- | --------------------------- |
368
- | `nano-banana` | `google/nano-banana` |
369
- | `banana-pro` | `google/nano-banana-pro` |
370
- | `dall-e-3` | `openai/dall-e-3` |
371
- | `gpt-image` | `openai/gpt-image-1` |
372
- | `flux` | `black-forest/flux-1.1-pro` |
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
- - **No DALL-E content policy bypass** — DALL-E 3 and GPT Image 1 still apply OpenAI's content policy. Use `flux` or `nano-banana` for more flexibility with generation.
381
- - **Size limits** — Requesting a size larger than the model's max will return an error. Check the table above before setting `--size`.
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blockrun/clawrouter",
3
- "version": "0.12.246",
3
+ "version": "0.12.248",
4
4
  "description": "Smart LLM router — save 88% on inference costs. 71 models (5 free), one wallet, x402 micropayments.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -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 dall-e-3 <prompt> # DALL-E 3"
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` | 8 image models — GPT Image 1/2, Nano Banana / Pro, Seedream 5 Pro, Grok Imagine / Pro, CogView-4 | $0.015–$0.15 / image |
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
 
@@ -57,8 +57,9 @@ Display inline: `![generated image](http://localhost:8402/images/abc123.png)`
57
57
 
58
58
  | Alias | Full ID | Price | Sizes | Best for |
59
59
  | ------------------ | ---------------------------- | ------------ | ------------------------------- | ------------------------------------------------------------------------------------------------------------------ |
60
- | `nano-banana` | `google/nano-banana` | $0.05 | 1024×1024, 1216×832, 1024×1792 | Default — fast, cheap, good quality |
61
- | `banana-pro` | `google/nano-banana-pro` | $0.10–$0.15 | up to 4096×4096 | High-res, large format |
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: `![generated image](http://localhost:8402/images/abc123.png)`
79
80
 
80
81
  **Choosing a size:**
81
82
 
82
- - Default: `1024x1024`
83
- - Portrait: `1024x1792`
84
- - Landscape: `1536x1024` (gpt-image / gpt-image-2) or `1216x832` (nano-banana)
85
- - High-res: `2048x2048` or `4096x4096` with `banana-pro` only
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 `banana-pro`, size `1024x1792`.
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".