@blockrun/clawrouter 0.9.7 → 0.9.9

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/dist/index.d.ts CHANGED
@@ -281,6 +281,91 @@ type RouterOptions = {
281
281
  */
282
282
  declare function route(prompt: string, systemPrompt: string | undefined, maxOutputTokens: number, options: RouterOptions): RoutingDecision;
283
283
 
284
+ /**
285
+ * Response Cache for LLM Completions
286
+ *
287
+ * Caches LLM responses by request hash (model + messages + params).
288
+ * Inspired by LiteLLM's caching system. Returns cached responses for
289
+ * identical requests, saving both cost and latency.
290
+ *
291
+ * Features:
292
+ * - TTL-based expiration (default 10 minutes)
293
+ * - LRU eviction when cache is full
294
+ * - Size limits per item (1MB max)
295
+ * - Heap-based expiration tracking for efficient pruning
296
+ */
297
+ type CachedLLMResponse = {
298
+ body: Buffer;
299
+ status: number;
300
+ headers: Record<string, string>;
301
+ model: string;
302
+ cachedAt: number;
303
+ expiresAt: number;
304
+ };
305
+ type ResponseCacheConfig = {
306
+ /** Maximum number of cached responses. Default: 200 */
307
+ maxSize?: number;
308
+ /** Default TTL in seconds. Default: 600 (10 minutes) */
309
+ defaultTTL?: number;
310
+ /** Maximum size per cached item in bytes. Default: 1MB */
311
+ maxItemSize?: number;
312
+ /** Enable/disable cache. Default: true */
313
+ enabled?: boolean;
314
+ };
315
+ declare class ResponseCache {
316
+ private cache;
317
+ private expirationHeap;
318
+ private config;
319
+ private stats;
320
+ constructor(config?: ResponseCacheConfig);
321
+ /**
322
+ * Generate cache key from request body.
323
+ * Hashes: model + messages + temperature + max_tokens + other params
324
+ */
325
+ static generateKey(body: Buffer | string): string;
326
+ /**
327
+ * Check if caching is enabled for this request.
328
+ * Respects cache control headers and request params.
329
+ */
330
+ shouldCache(body: Buffer | string, headers?: Record<string, string>): boolean;
331
+ /**
332
+ * Get cached response if available and not expired.
333
+ */
334
+ get(key: string): CachedLLMResponse | undefined;
335
+ /**
336
+ * Cache a response with optional custom TTL.
337
+ */
338
+ set(key: string, response: {
339
+ body: Buffer;
340
+ status: number;
341
+ headers: Record<string, string>;
342
+ model: string;
343
+ }, ttlSeconds?: number): void;
344
+ /**
345
+ * Evict expired and oldest entries to make room.
346
+ */
347
+ private evict;
348
+ /**
349
+ * Get cache statistics.
350
+ */
351
+ getStats(): {
352
+ size: number;
353
+ maxSize: number;
354
+ hits: number;
355
+ misses: number;
356
+ evictions: number;
357
+ hitRate: string;
358
+ };
359
+ /**
360
+ * Clear all cached entries.
361
+ */
362
+ clear(): void;
363
+ /**
364
+ * Check if cache is enabled.
365
+ */
366
+ isEnabled(): boolean;
367
+ }
368
+
284
369
  /**
285
370
  * Balance Monitor for ClawRouter
286
371
  *
@@ -521,6 +606,12 @@ type ProxyOptions = {
521
606
  * Set to 0 to compress all requests.
522
607
  */
523
608
  compressionThresholdKB?: number;
609
+ /**
610
+ * Response caching config. When enabled, identical requests return
611
+ * cached responses instead of making new API calls.
612
+ * Default: enabled with 10 minute TTL, 200 max entries.
613
+ */
614
+ cacheConfig?: ResponseCacheConfig;
524
615
  onReady?: (port: number) => void;
525
616
  onError?: (error: Error) => void;
526
617
  onPayment?: (info: {
@@ -917,4 +1008,4 @@ declare function formatStatsAscii(stats: AggregatedStats): string;
917
1008
 
918
1009
  declare const plugin: OpenClawPluginDefinition;
919
1010
 
920
- export { type AggregatedStats, BALANCE_THRESHOLDS, BLOCKRUN_MODELS, type BalanceInfo, BalanceMonitor, type CachedPaymentParams, type CachedResponse, DEFAULT_RETRY_CONFIG, DEFAULT_ROUTING_CONFIG, DEFAULT_SESSION_CONFIG, type DailyStats, EmptyWalletError, InsufficientFundsError, type InsufficientFundsInfo, type LowBalanceInfo, MODEL_ALIASES, OPENCLAW_MODELS, PaymentCache, type PaymentFetchResult, type PreAuthParams, type ProxyHandle, type ProxyOptions, RequestDeduplicator, type RetryConfig, type RoutingConfig, type RoutingDecision, RpcError, type SessionConfig, type SessionEntry, SessionStore, type SufficiencyResult, type Tier, type UsageEntry, blockrunProvider, buildProviderModels, calculateModelCost, createPaymentFetch, plugin as default, fetchWithRetry, formatStatsAscii, getAgenticModels, getFallbackChain, getFallbackChainFiltered, getModelContextWindow, getProxyPort, getSessionId, getStats, isAgenticModel, isBalanceError, isEmptyWalletError, isInsufficientFundsError, isRetryable, isRpcError, logUsage, resolveModelAlias, route, startProxy };
1011
+ export { type AggregatedStats, BALANCE_THRESHOLDS, BLOCKRUN_MODELS, type BalanceInfo, BalanceMonitor, type CachedLLMResponse, type CachedPaymentParams, type CachedResponse, DEFAULT_RETRY_CONFIG, DEFAULT_ROUTING_CONFIG, DEFAULT_SESSION_CONFIG, type DailyStats, EmptyWalletError, InsufficientFundsError, type InsufficientFundsInfo, type LowBalanceInfo, MODEL_ALIASES, OPENCLAW_MODELS, PaymentCache, type PaymentFetchResult, type PreAuthParams, type ProxyHandle, type ProxyOptions, RequestDeduplicator, ResponseCache, type ResponseCacheConfig, type RetryConfig, type RoutingConfig, type RoutingDecision, RpcError, type SessionConfig, type SessionEntry, SessionStore, type SufficiencyResult, type Tier, type UsageEntry, blockrunProvider, buildProviderModels, calculateModelCost, createPaymentFetch, plugin as default, fetchWithRetry, formatStatsAscii, getAgenticModels, getFallbackChain, getFallbackChainFiltered, getModelContextWindow, getProxyPort, getSessionId, getStats, isAgenticModel, isBalanceError, isEmptyWalletError, isInsufficientFundsError, isRetryable, isRpcError, logUsage, resolveModelAlias, route, startProxy };
package/dist/index.js CHANGED
@@ -3,12 +3,16 @@ var MODEL_ALIASES = {
3
3
  // Claude
4
4
  claude: "anthropic/claude-sonnet-4",
5
5
  sonnet: "anthropic/claude-sonnet-4",
6
- opus: "anthropic/claude-opus-4",
6
+ opus: "anthropic/claude-opus-4.6",
7
+ // Updated to latest Opus 4.6
8
+ "opus-46": "anthropic/claude-opus-4.6",
9
+ "opus-45": "anthropic/claude-opus-4.5",
7
10
  haiku: "anthropic/claude-haiku-4.5",
8
11
  // OpenAI
9
12
  gpt: "openai/gpt-4o",
10
13
  gpt4: "openai/gpt-4o",
11
14
  gpt5: "openai/gpt-5.2",
15
+ codex: "openai/gpt-5.2-codex",
12
16
  mini: "openai/gpt-4o-mini",
13
17
  o3: "openai/o3",
14
18
  // DeepSeek
@@ -113,6 +117,16 @@ var BLOCKRUN_MODELS = [
113
117
  maxOutput: 128e3,
114
118
  reasoning: true
115
119
  },
120
+ // OpenAI Codex Family
121
+ {
122
+ id: "openai/gpt-5.2-codex",
123
+ name: "GPT-5.2 Codex",
124
+ inputPrice: 2.5,
125
+ outputPrice: 12,
126
+ contextWindow: 128e3,
127
+ maxOutput: 32e3,
128
+ agentic: true
129
+ },
116
130
  // OpenAI GPT-4 Family
117
131
  {
118
132
  id: "openai/gpt-4.1",
@@ -218,6 +232,17 @@ var BLOCKRUN_MODELS = [
218
232
  reasoning: true,
219
233
  agentic: true
220
234
  },
235
+ {
236
+ id: "anthropic/claude-opus-4.6",
237
+ name: "Claude Opus 4.6",
238
+ inputPrice: 5,
239
+ outputPrice: 25,
240
+ contextWindow: 2e5,
241
+ maxOutput: 64e3,
242
+ reasoning: true,
243
+ vision: true,
244
+ agentic: true
245
+ },
221
246
  // Google
222
247
  {
223
248
  id: "google/gemini-3-pro-preview",
@@ -1645,37 +1670,42 @@ var DEFAULT_ROUTING_CONFIG = {
1645
1670
  }
1646
1671
  },
1647
1672
  // Premium tier configs - best quality (blockrun/premium)
1673
+ // codex=complex coding, kimi=simple coding, sonnet=reasoning/instructions, opus=architecture/PM/audits
1648
1674
  premiumTiers: {
1649
1675
  SIMPLE: {
1650
- primary: "google/gemini-2.5-flash",
1651
- // $0.075/$0.30
1652
- fallback: ["openai/gpt-4o-mini", "anthropic/claude-haiku-4.5", "moonshot/kimi-k2.5"]
1676
+ primary: "moonshot/kimi-k2.5",
1677
+ // $0.50/$2.40 - good for simple coding
1678
+ fallback: ["anthropic/claude-haiku-4.5", "google/gemini-2.5-flash", "xai/grok-code-fast-1"]
1653
1679
  },
1654
1680
  MEDIUM: {
1655
- primary: "openai/gpt-4o",
1656
- // $2.50/$10
1657
- fallback: ["google/gemini-2.5-pro", "anthropic/claude-sonnet-4", "xai/grok-4-0709"]
1681
+ primary: "anthropic/claude-sonnet-4",
1682
+ // $3/$15 - reasoning/instructions
1683
+ fallback: [
1684
+ "openai/gpt-5.2-codex",
1685
+ "moonshot/kimi-k2.5",
1686
+ "google/gemini-2.5-pro",
1687
+ "xai/grok-4-0709"
1688
+ ]
1658
1689
  },
1659
1690
  COMPLEX: {
1660
- primary: "anthropic/claude-opus-4.5",
1661
- // $5/$25 - Latest Opus
1691
+ primary: "openai/gpt-5.2-codex",
1692
+ // $2.50/$10 - complex coding (78% cost savings vs Opus)
1662
1693
  fallback: [
1663
- "openai/gpt-5.2-pro",
1664
- // $21/$168 - Latest GPT pro
1694
+ "anthropic/claude-opus-4.6",
1695
+ "anthropic/claude-opus-4.5",
1696
+ "anthropic/claude-sonnet-4",
1665
1697
  "google/gemini-3-pro-preview",
1666
- // Latest Gemini
1667
- "openai/gpt-5.2",
1668
- "anthropic/claude-sonnet-4"
1698
+ "moonshot/kimi-k2.5"
1669
1699
  ]
1670
1700
  },
1671
1701
  REASONING: {
1672
- primary: "openai/o3",
1673
- // $2/$8 - Best value reasoning
1702
+ primary: "anthropic/claude-sonnet-4",
1703
+ // $3/$15 - best for reasoning/instructions
1674
1704
  fallback: [
1675
- "openai/o4-mini",
1676
- // Latest o-series
1705
+ "anthropic/claude-opus-4.6",
1677
1706
  "anthropic/claude-opus-4.5",
1678
- "google/gemini-3-pro-preview"
1707
+ "openai/o3",
1708
+ "xai/grok-4-1-fast-reasoning"
1679
1709
  ]
1680
1710
  }
1681
1711
  },
@@ -1698,7 +1728,7 @@ var DEFAULT_ROUTING_CONFIG = {
1698
1728
  COMPLEX: {
1699
1729
  primary: "anthropic/claude-sonnet-4",
1700
1730
  fallback: [
1701
- "anthropic/claude-opus-4.5",
1731
+ "anthropic/claude-opus-4.6",
1702
1732
  // Latest Opus - best agentic
1703
1733
  "openai/gpt-5.2",
1704
1734
  "google/gemini-3-pro-preview",
@@ -1709,7 +1739,7 @@ var DEFAULT_ROUTING_CONFIG = {
1709
1739
  primary: "anthropic/claude-sonnet-4",
1710
1740
  // Strong tool use + reasoning for agentic tasks
1711
1741
  fallback: [
1712
- "anthropic/claude-opus-4.5",
1742
+ "anthropic/claude-opus-4.6",
1713
1743
  "xai/grok-4-fast-reasoning",
1714
1744
  "moonshot/kimi-k2.5",
1715
1745
  "deepseek/deepseek-reasoner"
@@ -2139,6 +2169,203 @@ var RequestDeduplicator = class {
2139
2169
  }
2140
2170
  };
2141
2171
 
2172
+ // src/response-cache.ts
2173
+ import { createHash as createHash2 } from "crypto";
2174
+ var DEFAULT_CONFIG = {
2175
+ maxSize: 200,
2176
+ defaultTTL: 600,
2177
+ maxItemSize: 1048576,
2178
+ // 1MB
2179
+ enabled: true
2180
+ };
2181
+ function canonicalize2(obj) {
2182
+ if (obj === null || typeof obj !== "object") {
2183
+ return obj;
2184
+ }
2185
+ if (Array.isArray(obj)) {
2186
+ return obj.map(canonicalize2);
2187
+ }
2188
+ const sorted = {};
2189
+ for (const key of Object.keys(obj).sort()) {
2190
+ sorted[key] = canonicalize2(obj[key]);
2191
+ }
2192
+ return sorted;
2193
+ }
2194
+ var TIMESTAMP_PATTERN2 = /^\[\w{3}\s+\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}\s+\w+\]\s*/;
2195
+ function normalizeForCache(obj) {
2196
+ const result = {};
2197
+ for (const [key, value] of Object.entries(obj)) {
2198
+ if (["stream", "user", "request_id", "x-request-id"].includes(key)) {
2199
+ continue;
2200
+ }
2201
+ if (key === "messages" && Array.isArray(value)) {
2202
+ result[key] = value.map((msg) => {
2203
+ if (typeof msg === "object" && msg !== null) {
2204
+ const m = msg;
2205
+ if (typeof m.content === "string") {
2206
+ return { ...m, content: m.content.replace(TIMESTAMP_PATTERN2, "") };
2207
+ }
2208
+ }
2209
+ return msg;
2210
+ });
2211
+ } else {
2212
+ result[key] = value;
2213
+ }
2214
+ }
2215
+ return result;
2216
+ }
2217
+ var ResponseCache = class {
2218
+ cache = /* @__PURE__ */ new Map();
2219
+ expirationHeap = [];
2220
+ config;
2221
+ // Stats for monitoring
2222
+ stats = {
2223
+ hits: 0,
2224
+ misses: 0,
2225
+ evictions: 0
2226
+ };
2227
+ constructor(config = {}) {
2228
+ const filtered = Object.fromEntries(
2229
+ Object.entries(config).filter(([, v]) => v !== void 0)
2230
+ );
2231
+ this.config = { ...DEFAULT_CONFIG, ...filtered };
2232
+ }
2233
+ /**
2234
+ * Generate cache key from request body.
2235
+ * Hashes: model + messages + temperature + max_tokens + other params
2236
+ */
2237
+ static generateKey(body) {
2238
+ try {
2239
+ const parsed = JSON.parse(typeof body === "string" ? body : body.toString());
2240
+ const normalized = normalizeForCache(parsed);
2241
+ const canonical = canonicalize2(normalized);
2242
+ const keyContent = JSON.stringify(canonical);
2243
+ return createHash2("sha256").update(keyContent).digest("hex").slice(0, 32);
2244
+ } catch {
2245
+ const content = typeof body === "string" ? body : body.toString();
2246
+ return createHash2("sha256").update(content).digest("hex").slice(0, 32);
2247
+ }
2248
+ }
2249
+ /**
2250
+ * Check if caching is enabled for this request.
2251
+ * Respects cache control headers and request params.
2252
+ */
2253
+ shouldCache(body, headers) {
2254
+ if (!this.config.enabled) return false;
2255
+ if (headers?.["cache-control"]?.includes("no-cache")) {
2256
+ return false;
2257
+ }
2258
+ try {
2259
+ const parsed = JSON.parse(typeof body === "string" ? body : body.toString());
2260
+ if (parsed.cache === false || parsed.no_cache === true) {
2261
+ return false;
2262
+ }
2263
+ } catch {
2264
+ }
2265
+ return true;
2266
+ }
2267
+ /**
2268
+ * Get cached response if available and not expired.
2269
+ */
2270
+ get(key) {
2271
+ const entry = this.cache.get(key);
2272
+ if (!entry) {
2273
+ this.stats.misses++;
2274
+ return void 0;
2275
+ }
2276
+ if (Date.now() > entry.expiresAt) {
2277
+ this.cache.delete(key);
2278
+ this.stats.misses++;
2279
+ return void 0;
2280
+ }
2281
+ this.stats.hits++;
2282
+ return entry;
2283
+ }
2284
+ /**
2285
+ * Cache a response with optional custom TTL.
2286
+ */
2287
+ set(key, response, ttlSeconds) {
2288
+ if (!this.config.enabled || this.config.maxSize <= 0) return;
2289
+ if (response.body.length > this.config.maxItemSize) {
2290
+ console.log(`[ResponseCache] Skipping cache - item too large: ${response.body.length} bytes`);
2291
+ return;
2292
+ }
2293
+ if (response.status >= 400) {
2294
+ return;
2295
+ }
2296
+ if (this.cache.size >= this.config.maxSize) {
2297
+ this.evict();
2298
+ }
2299
+ const now = Date.now();
2300
+ const ttl = ttlSeconds ?? this.config.defaultTTL;
2301
+ const expiresAt = now + ttl * 1e3;
2302
+ const entry = {
2303
+ ...response,
2304
+ cachedAt: now,
2305
+ expiresAt
2306
+ };
2307
+ this.cache.set(key, entry);
2308
+ this.expirationHeap.push({ expiresAt, key });
2309
+ }
2310
+ /**
2311
+ * Evict expired and oldest entries to make room.
2312
+ */
2313
+ evict() {
2314
+ const now = Date.now();
2315
+ this.expirationHeap.sort((a, b) => a.expiresAt - b.expiresAt);
2316
+ while (this.expirationHeap.length > 0) {
2317
+ const oldest = this.expirationHeap[0];
2318
+ const entry = this.cache.get(oldest.key);
2319
+ if (!entry || entry.expiresAt !== oldest.expiresAt) {
2320
+ this.expirationHeap.shift();
2321
+ continue;
2322
+ }
2323
+ if (oldest.expiresAt <= now) {
2324
+ this.cache.delete(oldest.key);
2325
+ this.expirationHeap.shift();
2326
+ this.stats.evictions++;
2327
+ } else {
2328
+ break;
2329
+ }
2330
+ }
2331
+ while (this.cache.size >= this.config.maxSize && this.expirationHeap.length > 0) {
2332
+ const oldest = this.expirationHeap.shift();
2333
+ if (this.cache.has(oldest.key)) {
2334
+ this.cache.delete(oldest.key);
2335
+ this.stats.evictions++;
2336
+ }
2337
+ }
2338
+ }
2339
+ /**
2340
+ * Get cache statistics.
2341
+ */
2342
+ getStats() {
2343
+ const total = this.stats.hits + this.stats.misses;
2344
+ const hitRate = total > 0 ? (this.stats.hits / total * 100).toFixed(1) + "%" : "0%";
2345
+ return {
2346
+ size: this.cache.size,
2347
+ maxSize: this.config.maxSize,
2348
+ hits: this.stats.hits,
2349
+ misses: this.stats.misses,
2350
+ evictions: this.stats.evictions,
2351
+ hitRate
2352
+ };
2353
+ }
2354
+ /**
2355
+ * Clear all cached entries.
2356
+ */
2357
+ clear() {
2358
+ this.cache.clear();
2359
+ this.expirationHeap = [];
2360
+ }
2361
+ /**
2362
+ * Check if cache is enabled.
2363
+ */
2364
+ isEnabled() {
2365
+ return this.config.enabled;
2366
+ }
2367
+ };
2368
+
2142
2369
  // src/balance.ts
2143
2370
  import { createPublicClient, http, erc20Abi } from "viem";
2144
2371
  import { base } from "viem/chains";
@@ -3642,6 +3869,7 @@ async function startProxy(options) {
3642
3869
  modelPricing
3643
3870
  };
3644
3871
  const deduplicator = new RequestDeduplicator();
3872
+ const responseCache = new ResponseCache(options.cacheConfig);
3645
3873
  const sessionStore = new SessionStore(options.sessionConfig);
3646
3874
  const connections = /* @__PURE__ */ new Set();
3647
3875
  const server = createServer(async (req, res) => {
@@ -3682,6 +3910,15 @@ async function startProxy(options) {
3682
3910
  res.end(JSON.stringify(response));
3683
3911
  return;
3684
3912
  }
3913
+ if (req.url === "/cache" || req.url?.startsWith("/cache?")) {
3914
+ const stats = responseCache.getStats();
3915
+ res.writeHead(200, {
3916
+ "Content-Type": "application/json",
3917
+ "Cache-Control": "no-cache"
3918
+ });
3919
+ res.end(JSON.stringify(stats, null, 2));
3920
+ return;
3921
+ }
3685
3922
  if (req.url === "/stats" || req.url?.startsWith("/stats?")) {
3686
3923
  try {
3687
3924
  const url = new URL(req.url, "http://localhost");
@@ -3728,7 +3965,8 @@ async function startProxy(options) {
3728
3965
  routerOpts,
3729
3966
  deduplicator,
3730
3967
  balanceMonitor,
3731
- sessionStore
3968
+ sessionStore,
3969
+ responseCache
3732
3970
  );
3733
3971
  } catch (err) {
3734
3972
  const error = err instanceof Error ? err : new Error(String(err));
@@ -3929,7 +4167,7 @@ async function tryModelRequest(upstreamUrl, method, headers, body, modelId, maxT
3929
4167
  };
3930
4168
  }
3931
4169
  }
3932
- async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, deduplicator, balanceMonitor, sessionStore) {
4170
+ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, deduplicator, balanceMonitor, sessionStore, responseCache) {
3933
4171
  const startTime = Date.now();
3934
4172
  const upstreamUrl = `${apiBase}${req.url}`;
3935
4173
  const bodyChunks = [];
@@ -4097,6 +4335,20 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
4097
4335
  );
4098
4336
  }
4099
4337
  }
4338
+ const cacheKey = ResponseCache.generateKey(body);
4339
+ const reqHeaders = {};
4340
+ for (const [key, value] of Object.entries(req.headers)) {
4341
+ if (typeof value === "string") reqHeaders[key] = value;
4342
+ }
4343
+ if (responseCache.shouldCache(body, reqHeaders)) {
4344
+ const cachedResponse = responseCache.get(cacheKey);
4345
+ if (cachedResponse) {
4346
+ console.log(`[ClawRouter] Cache HIT for ${cachedResponse.model} (saved API call)`);
4347
+ res.writeHead(cachedResponse.status, cachedResponse.headers);
4348
+ res.end(cachedResponse.body);
4349
+ return;
4350
+ }
4351
+ }
4100
4352
  const dedupKey = RequestDeduplicator.hash(body);
4101
4353
  const cached = deduplicator.getCached(dedupKey);
4102
4354
  if (cached) {
@@ -4449,12 +4701,22 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
4449
4701
  }
4450
4702
  }
4451
4703
  res.end();
4704
+ const responseBody = Buffer.concat(responseChunks);
4452
4705
  deduplicator.complete(dedupKey, {
4453
4706
  status: upstream.status,
4454
4707
  headers: responseHeaders,
4455
- body: Buffer.concat(responseChunks),
4708
+ body: responseBody,
4456
4709
  completedAt: Date.now()
4457
4710
  });
4711
+ if (upstream.status === 200 && responseCache.shouldCache(body)) {
4712
+ responseCache.set(cacheKey, {
4713
+ body: responseBody,
4714
+ status: upstream.status,
4715
+ headers: responseHeaders,
4716
+ model: modelId
4717
+ });
4718
+ console.log(`[ClawRouter] Cached response for ${modelId} (${responseBody.length} bytes)`);
4719
+ }
4458
4720
  }
4459
4721
  if (estimatedCostMicros !== void 0) {
4460
4722
  balanceMonitor.deductEstimated(estimatedCostMicros);
@@ -4761,9 +5023,9 @@ function injectModelsConfig(logger) {
4761
5023
  { id: "eco", alias: "eco" },
4762
5024
  { id: "premium", alias: "premium" },
4763
5025
  { id: "free", alias: "free" },
4764
- { id: "sonnet", alias: "sonnet" },
4765
- { id: "opus", alias: "opus" },
4766
- { id: "haiku", alias: "haiku" },
5026
+ { id: "sonnet", alias: "br-sonnet" },
5027
+ { id: "opus", alias: "br-opus" },
5028
+ { id: "haiku", alias: "br-haiku" },
4767
5029
  { id: "gpt5", alias: "gpt5" },
4768
5030
  { id: "mini", alias: "mini" },
4769
5031
  { id: "grok-fast", alias: "grok-fast" },
@@ -4789,9 +5051,13 @@ function injectModelsConfig(logger) {
4789
5051
  }
4790
5052
  for (const m of KEY_MODEL_ALIASES) {
4791
5053
  const fullId = `blockrun/${m.id}`;
4792
- if (!allowlist[fullId]) {
5054
+ const existing = allowlist[fullId];
5055
+ if (!existing) {
4793
5056
  allowlist[fullId] = { alias: m.alias };
4794
5057
  needsWrite = true;
5058
+ } else if (existing.alias !== m.alias) {
5059
+ existing.alias = m.alias;
5060
+ needsWrite = true;
4795
5061
  }
4796
5062
  }
4797
5063
  if (needsWrite) {
@@ -5125,6 +5391,7 @@ export {
5125
5391
  OPENCLAW_MODELS,
5126
5392
  PaymentCache,
5127
5393
  RequestDeduplicator,
5394
+ ResponseCache,
5128
5395
  RpcError,
5129
5396
  SessionStore,
5130
5397
  blockrunProvider,