From cd5721a9595f6ca02c933bfb822ceaab6fabaacb Mon Sep 17 00:00:00 2001 From: 1bcMax Date: Mon, 9 Mar 2026 13:06:23 -0400 Subject: [PATCH] fix: parse Solana payment errors from blockrun-sol and bump to v0.12.27 transformPaymentError() now handles blockrun-sol's error format (code=PAYMENT_INVALID + debug field) in addition to the existing Base/EVM format (details field). Solana users now see actionable messages for insufficient_funds, simulation failures, and signature errors instead of the raw server response. --- dist-e2e/e2e.js | 1671 ++++++++++++++++++++++--------------------- dist-e2e/e2e.js.map | 2 +- package-lock.json | 4 +- package.json | 3 +- src/proxy.ts | 64 ++ 5 files changed, 927 insertions(+), 817 deletions(-) diff --git a/dist-e2e/e2e.js b/dist-e2e/e2e.js index dbc2a7e..799e692 100644 --- a/dist-e2e/e2e.js +++ b/dist-e2e/e2e.js @@ -14,14 +14,14 @@ function scoreKeywordMatch(text, keywords, name, signalLabel, thresholds, scores return { name, score: scores.high, - signal: `${signalLabel} (${matches.slice(0, 3).join(", ")})`, + signal: `${signalLabel} (${matches.slice(0, 3).join(", ")})` }; } if (matches.length >= thresholds.low) { return { name, score: scores.low, - signal: `${signalLabel} (${matches.slice(0, 3).join(", ")})`, + signal: `${signalLabel} (${matches.slice(0, 3).join(", ")})` }; } return { name, score: scores.none, signal: null }; @@ -57,32 +57,32 @@ function scoreAgenticTask(text, keywords) { dimensionScore: { name: "agenticTask", score: 1, - signal: `agentic (${signals.join(", ")})`, + signal: `agentic (${signals.join(", ")})` }, - agenticScore: 1, + agenticScore: 1 }; } else if (matchCount >= 3) { return { dimensionScore: { name: "agenticTask", score: 0.6, - signal: `agentic (${signals.join(", ")})`, + signal: `agentic (${signals.join(", ")})` }, - agenticScore: 0.6, + agenticScore: 0.6 }; } else if (matchCount >= 1) { return { dimensionScore: { name: "agenticTask", score: 0.2, - signal: `agentic-light (${signals.join(", ")})`, + signal: `agentic-light (${signals.join(", ")})` }, - agenticScore: 0.2, + agenticScore: 0.2 }; } return { dimensionScore: { name: "agenticTask", score: 0, signal: null }, - agenticScore: 0, + agenticScore: 0 }; } function classifyByRules(prompt, systemPrompt, estimatedTokens, config2) { @@ -96,7 +96,7 @@ function classifyByRules(prompt, systemPrompt, estimatedTokens, config2) { "codePresence", "code", { low: 1, high: 2 }, - { none: 0, low: 0.5, high: 1 }, + { none: 0, low: 0.5, high: 1 } ), scoreKeywordMatch( userText, @@ -104,7 +104,7 @@ function classifyByRules(prompt, systemPrompt, estimatedTokens, config2) { "reasoningMarkers", "reasoning", { low: 1, high: 2 }, - { none: 0, low: 0.7, high: 1 }, + { none: 0, low: 0.7, high: 1 } ), scoreKeywordMatch( userText, @@ -112,7 +112,7 @@ function classifyByRules(prompt, systemPrompt, estimatedTokens, config2) { "technicalTerms", "technical", { low: 2, high: 4 }, - { none: 0, low: 0.5, high: 1 }, + { none: 0, low: 0.5, high: 1 } ), scoreKeywordMatch( userText, @@ -120,7 +120,7 @@ function classifyByRules(prompt, systemPrompt, estimatedTokens, config2) { "creativeMarkers", "creative", { low: 1, high: 2 }, - { none: 0, low: 0.5, high: 0.7 }, + { none: 0, low: 0.5, high: 0.7 } ), scoreKeywordMatch( userText, @@ -128,7 +128,7 @@ function classifyByRules(prompt, systemPrompt, estimatedTokens, config2) { "simpleIndicators", "simple", { low: 1, high: 2 }, - { none: 0, low: -1, high: -1 }, + { none: 0, low: -1, high: -1 } ), scoreMultiStep(userText), scoreQuestionComplexity(prompt), @@ -139,7 +139,7 @@ function classifyByRules(prompt, systemPrompt, estimatedTokens, config2) { "imperativeVerbs", "imperative", { low: 1, high: 2 }, - { none: 0, low: 0.3, high: 0.5 }, + { none: 0, low: 0.3, high: 0.5 } ), scoreKeywordMatch( userText, @@ -147,7 +147,7 @@ function classifyByRules(prompt, systemPrompt, estimatedTokens, config2) { "constraintCount", "constraints", { low: 1, high: 3 }, - { none: 0, low: 0.3, high: 0.7 }, + { none: 0, low: 0.3, high: 0.7 } ), scoreKeywordMatch( userText, @@ -155,7 +155,7 @@ function classifyByRules(prompt, systemPrompt, estimatedTokens, config2) { "outputFormat", "format", { low: 1, high: 2 }, - { none: 0, low: 0.4, high: 0.7 }, + { none: 0, low: 0.4, high: 0.7 } ), scoreKeywordMatch( userText, @@ -163,7 +163,7 @@ function classifyByRules(prompt, systemPrompt, estimatedTokens, config2) { "referenceComplexity", "references", { low: 1, high: 2 }, - { none: 0, low: 0.3, high: 0.5 }, + { none: 0, low: 0.3, high: 0.5 } ), scoreKeywordMatch( userText, @@ -171,7 +171,7 @@ function classifyByRules(prompt, systemPrompt, estimatedTokens, config2) { "negationComplexity", "negation", { low: 2, high: 3 }, - { none: 0, low: 0.3, high: 0.5 }, + { none: 0, low: 0.3, high: 0.5 } ), scoreKeywordMatch( userText, @@ -179,8 +179,8 @@ function classifyByRules(prompt, systemPrompt, estimatedTokens, config2) { "domainSpecificity", "domain-specific", { low: 1, high: 2 }, - { none: 0, low: 0.5, high: 0.8 }, - ), + { none: 0, low: 0.5, high: 0.8 } + ) ]; const agenticResult = scoreAgenticTask(userText, config2.agenticTaskKeywords); dimensions.push(agenticResult.dimensionScore); @@ -192,14 +192,14 @@ function classifyByRules(prompt, systemPrompt, estimatedTokens, config2) { const w = weights[d.name] ?? 0; weightedScore += d.score * w; } - const reasoningMatches = config2.reasoningKeywords.filter((kw) => - userText.includes(kw.toLowerCase()), + const reasoningMatches = config2.reasoningKeywords.filter( + (kw) => userText.includes(kw.toLowerCase()) ); if (reasoningMatches.length >= 2) { const confidence2 = calibrateConfidence( Math.max(weightedScore, 0.3), // ensure positive for confidence calc - config2.confidenceSteepness, + config2.confidenceSteepness ); return { score: weightedScore, @@ -207,7 +207,7 @@ function classifyByRules(prompt, systemPrompt, estimatedTokens, config2) { confidence: Math.max(confidence2, 0.85), signals, agenticScore, - dimensions, + dimensions }; } const { simpleMedium, mediumComplex, complexReasoning } = config2.tierBoundaries; @@ -223,7 +223,7 @@ function classifyByRules(prompt, systemPrompt, estimatedTokens, config2) { tier = "COMPLEX"; distanceFromBoundary = Math.min( weightedScore - mediumComplex, - complexReasoning - weightedScore, + complexReasoning - weightedScore ); } else { tier = "REASONING"; @@ -243,38 +243,22 @@ function calibrateConfidence(distance, steepness) { var BASELINE_MODEL_ID = "anthropic/claude-opus-4.6"; var BASELINE_INPUT_PRICE = 5; var BASELINE_OUTPUT_PRICE = 25; -function selectModel( - tier, - confidence, - method, - reasoning, - tierConfigs, - modelPricing2, - estimatedInputTokens, - maxOutputTokens, - routingProfile, - agenticScore, -) { +function selectModel(tier, confidence, method, reasoning, tierConfigs, modelPricing2, estimatedInputTokens, maxOutputTokens, routingProfile, agenticScore) { const tierConfig = tierConfigs[tier]; const model = tierConfig.primary; const pricing = modelPricing2.get(model); const inputPrice = pricing?.inputPrice ?? 0; const outputPrice = pricing?.outputPrice ?? 0; - const inputCost = (estimatedInputTokens / 1e6) * inputPrice; - const outputCost = (maxOutputTokens / 1e6) * outputPrice; + const inputCost = estimatedInputTokens / 1e6 * inputPrice; + const outputCost = maxOutputTokens / 1e6 * outputPrice; const costEstimate = inputCost + outputCost; const opusPricing = modelPricing2.get(BASELINE_MODEL_ID); const opusInputPrice = opusPricing?.inputPrice ?? BASELINE_INPUT_PRICE; const opusOutputPrice = opusPricing?.outputPrice ?? BASELINE_OUTPUT_PRICE; - const baselineInput = (estimatedInputTokens / 1e6) * opusInputPrice; - const baselineOutput = (maxOutputTokens / 1e6) * opusOutputPrice; + const baselineInput = estimatedInputTokens / 1e6 * opusInputPrice; + const baselineOutput = maxOutputTokens / 1e6 * opusOutputPrice; const baselineCost = baselineInput + baselineOutput; - const savings = - routingProfile === "premium" - ? 0 - : baselineCost > 0 - ? Math.max(0, (baselineCost - costEstimate) / baselineCost) - : 0; + const savings = routingProfile === "premium" ? 0 : baselineCost > 0 ? Math.max(0, (baselineCost - costEstimate) / baselineCost) : 0; return { model, tier, @@ -284,38 +268,27 @@ function selectModel( costEstimate, baselineCost, savings, - ...(agenticScore !== void 0 && { agenticScore }), + ...agenticScore !== void 0 && { agenticScore } }; } function getFallbackChain(tier, tierConfigs) { const config2 = tierConfigs[tier]; return [config2.primary, ...config2.fallback]; } -function calculateModelCost( - model, - modelPricing2, - estimatedInputTokens, - maxOutputTokens, - routingProfile, -) { +function calculateModelCost(model, modelPricing2, estimatedInputTokens, maxOutputTokens, routingProfile) { const pricing = modelPricing2.get(model); const inputPrice = pricing?.inputPrice ?? 0; const outputPrice = pricing?.outputPrice ?? 0; - const inputCost = (estimatedInputTokens / 1e6) * inputPrice; - const outputCost = (maxOutputTokens / 1e6) * outputPrice; + const inputCost = estimatedInputTokens / 1e6 * inputPrice; + const outputCost = maxOutputTokens / 1e6 * outputPrice; const costEstimate = inputCost + outputCost; const opusPricing = modelPricing2.get(BASELINE_MODEL_ID); const opusInputPrice = opusPricing?.inputPrice ?? BASELINE_INPUT_PRICE; const opusOutputPrice = opusPricing?.outputPrice ?? BASELINE_OUTPUT_PRICE; - const baselineInput = (estimatedInputTokens / 1e6) * opusInputPrice; - const baselineOutput = (maxOutputTokens / 1e6) * opusOutputPrice; + const baselineInput = estimatedInputTokens / 1e6 * opusInputPrice; + const baselineOutput = maxOutputTokens / 1e6 * opusOutputPrice; const baselineCost = baselineInput + baselineOutput; - const savings = - routingProfile === "premium" - ? 0 - : baselineCost > 0 - ? Math.max(0, (baselineCost - costEstimate) / baselineCost) - : 0; + const savings = routingProfile === "premium" ? 0 : baselineCost > 0 ? Math.max(0, (baselineCost - costEstimate) / baselineCost) : 0; return { costEstimate, baselineCost, savings }; } function filterByToolCalling(models, hasTools, supportsToolCalling2) { @@ -351,7 +324,7 @@ var DEFAULT_ROUTING_CONFIG = { llmMaxTokens: 10, llmTemperature: 0, promptTruncationChars: 500, - cacheTtlMs: 36e5, + cacheTtlMs: 36e5 // 1 hour }, scoring: { @@ -454,7 +427,7 @@ var DEFAULT_ROUTING_CONFIG = { "\u0627\u0646\u062A\u0638\u0627\u0631", "\u062B\u0627\u0628\u062A", "\u0645\u062A\u063A\u064A\u0631", - "\u0625\u0631\u062C\u0627\u0639", + "\u0625\u0631\u062C\u0627\u0639" ], reasoningKeywords: [ // English @@ -544,7 +517,7 @@ var DEFAULT_ROUTING_CONFIG = { "\u0631\u0633\u0645\u064A\u0627\u064B", "\u0631\u064A\u0627\u0636\u064A", "\u0628\u0631\u0647\u0627\u0646", - "\u0645\u0646\u0637\u0642\u064A\u0627\u064B", + "\u0645\u0646\u0637\u0642\u064A\u0627\u064B" ], simpleKeywords: [ // English @@ -635,7 +608,7 @@ var DEFAULT_ROUTING_CONFIG = { "\u0646\u0639\u0645 \u0623\u0648 \u0644\u0627", "\u0639\u0627\u0635\u0645\u0629", "\u0645\u0646 \u0647\u0648", - "\u0645\u062A\u0649", + "\u0645\u062A\u0649" ], technicalKeywords: [ // English @@ -712,7 +685,7 @@ var DEFAULT_ROUTING_CONFIG = { "\u0645\u0648\u0632\u0639", "\u062E\u062F\u0645\u0629 \u0645\u0635\u063A\u0631\u0629", "\u0642\u0627\u0639\u062F\u0629 \u0628\u064A\u0627\u0646\u0627\u062A", - "\u0628\u0646\u064A\u0629 \u062A\u062D\u062A\u064A\u0629", + "\u0628\u0646\u064A\u0629 \u062A\u062D\u062A\u064A\u0629" ], creativeKeywords: [ // English @@ -788,7 +761,7 @@ var DEFAULT_ROUTING_CONFIG = { "\u0639\u0635\u0641 \u0630\u0647\u0646\u064A", "\u0625\u0628\u062F\u0627\u0639\u064A", "\u062A\u062E\u064A\u0644", - "\u0627\u0643\u062A\u0628", + "\u0627\u0643\u062A\u0628" ], // New dimension keyword lists (multilingual) imperativeVerbs: [ @@ -884,7 +857,7 @@ var DEFAULT_ROUTING_CONFIG = { "\u062A\u0637\u0648\u064A\u0631", "\u062A\u0648\u0644\u064A\u062F", "\u0646\u0634\u0631", - "\u0625\u0639\u062F\u0627\u062F", + "\u0625\u0639\u062F\u0627\u062F" ], constraintIndicators: [ // English @@ -964,7 +937,7 @@ var DEFAULT_ROUTING_CONFIG = { "\u0623\u0642\u0635\u0649", "\u0623\u062F\u0646\u0649", "\u062D\u062F", - "\u0645\u064A\u0632\u0627\u0646\u064A\u0629", + "\u0645\u064A\u0632\u0627\u0646\u064A\u0629" ], outputFormatKeywords: [ // English @@ -1008,7 +981,7 @@ var DEFAULT_ROUTING_CONFIG = { // Arabic "\u062C\u062F\u0648\u0644", "\u062A\u0646\u0633\u064A\u0642", - "\u0645\u0646\u0638\u0645", + "\u0645\u0646\u0638\u0645" ], referenceKeywords: [ // English @@ -1085,7 +1058,7 @@ var DEFAULT_ROUTING_CONFIG = { "\u0627\u0644\u062A\u0627\u0644\u064A", "\u0627\u0644\u0648\u062B\u0627\u0626\u0642", "\u0627\u0644\u0643\u0648\u062F", - "\u0645\u0631\u0641\u0642", + "\u0645\u0631\u0641\u0642" ], negationKeywords: [ // English @@ -1154,7 +1127,7 @@ var DEFAULT_ROUTING_CONFIG = { "\u0623\u0628\u062F\u0627\u064B", "\u0628\u062F\u0648\u0646", "\u0628\u0627\u0633\u062A\u062B\u0646\u0627\u0621", - "\u0627\u0633\u062A\u0628\u0639\u0627\u062F", + "\u0627\u0633\u062A\u0628\u0639\u0627\u062F" ], domainSpecificKeywords: [ // English @@ -1227,7 +1200,7 @@ var DEFAULT_ROUTING_CONFIG = { "\u0636\u0648\u0626\u064A\u0627\u062A", "\u062C\u064A\u0646\u0648\u0645\u064A\u0627\u062A", "\u0637\u0648\u0628\u0648\u0644\u0648\u062C\u064A", - "\u062A\u0645\u0627\u062B\u0644\u064A", + "\u062A\u0645\u0627\u062B\u0644\u064A" ], // Agentic task keywords - file ops, execution, multi-step, iterative work // Pruned: removed overly common words like "then", "first", "run", "test", "build" @@ -1334,7 +1307,7 @@ var DEFAULT_ROUTING_CONFIG = { "\u0627\u0644\u062E\u0637\u0648\u0629 2", "\u0625\u0635\u0644\u0627\u062D", "\u062A\u0635\u062D\u064A\u062D", - "\u062A\u062D\u0642\u0642", + "\u062A\u062D\u0642\u0642" ], // Dimension weights (sum to 1.0) dimensionWeights: { @@ -1353,7 +1326,7 @@ var DEFAULT_ROUTING_CONFIG = { referenceComplexity: 0.02, negationComplexity: 0.01, domainSpecificity: 0.02, - agenticTask: 0.04, + agenticTask: 0.04 // Reduced - agentic signals influence tier selection, not dominate it }, // Tier boundaries on weighted score axis @@ -1361,13 +1334,13 @@ var DEFAULT_ROUTING_CONFIG = { simpleMedium: 0, mediumComplex: 0.3, // Raised from 0.18 - prevent simple tasks from reaching expensive COMPLEX tier - complexReasoning: 0.5, + complexReasoning: 0.5 // Raised from 0.4 - reserve for true reasoning tasks }, // Sigmoid steepness for confidence calibration confidenceSteepness: 12, // Below this confidence → ambiguous (null tier) - confidenceThreshold: 0.7, + confidenceThreshold: 0.7 }, // Auto (balanced) tier configs - current default smart routing tiers: { @@ -1379,8 +1352,8 @@ var DEFAULT_ROUTING_CONFIG = { // 1M context, ultra cheap ($0.10/$0.40) "nvidia/gpt-oss-120b", // FREE fallback - "deepseek/deepseek-chat", - ], + "deepseek/deepseek-chat" + ] }, MEDIUM: { primary: "moonshot/kimi-k2.5", @@ -1389,9 +1362,9 @@ var DEFAULT_ROUTING_CONFIG = { "deepseek/deepseek-chat", "google/gemini-2.5-flash-lite", // 1M context, ultra cheap ($0.10/$0.40) - "xai/grok-4-1-fast-non-reasoning", + "xai/grok-4-1-fast-non-reasoning" // Upgraded Grok 4.1 - ], + ] }, COMPLEX: { primary: "google/gemini-3.1-pro", @@ -1404,11 +1377,11 @@ var DEFAULT_ROUTING_CONFIG = { "google/gemini-2.5-pro", "deepseek/deepseek-chat", "xai/grok-4-0709", - "openai/gpt-5.2", - // Newer and cheaper input than gpt-4o + "openai/gpt-5.4", + // Newest flagship, same price as 4o "openai/gpt-4o", - "anthropic/claude-sonnet-4.6", - ], + "anthropic/claude-sonnet-4.6" + ] }, REASONING: { primary: "xai/grok-4-1-fast-reasoning", @@ -1418,32 +1391,32 @@ var DEFAULT_ROUTING_CONFIG = { // Cheap reasoning model "openai/o4-mini", // Newer and cheaper than o3 ($1.10 vs $2.00) - "openai/o3", - ], - }, + "openai/o3" + ] + } }, // Eco tier configs - absolute cheapest (blockrun/eco) ecoTiers: { SIMPLE: { primary: "nvidia/gpt-oss-120b", // FREE! $0.00/$0.00 - fallback: ["google/gemini-2.5-flash-lite", "deepseek/deepseek-chat"], + fallback: ["google/gemini-2.5-flash-lite", "deepseek/deepseek-chat"] }, MEDIUM: { primary: "google/gemini-2.5-flash-lite", // $0.10/$0.40 - cheapest capable with 1M context - fallback: ["deepseek/deepseek-chat", "nvidia/gpt-oss-120b"], + fallback: ["deepseek/deepseek-chat", "nvidia/gpt-oss-120b"] }, COMPLEX: { primary: "google/gemini-2.5-flash-lite", // $0.10/$0.40 - 1M context handles complexity - fallback: ["google/gemini-2.5-flash", "deepseek/deepseek-chat", "xai/grok-4-0709"], + fallback: ["google/gemini-2.5-flash", "deepseek/deepseek-chat", "xai/grok-4-0709"] }, REASONING: { primary: "xai/grok-4-1-fast-reasoning", // $0.20/$0.50 - fallback: ["deepseek/deepseek-reasoner"], - }, + fallback: ["deepseek/deepseek-reasoner"] + } }, // Premium tier configs - best quality (blockrun/premium) // codex=complex coding, kimi=simple coding, sonnet=reasoning/instructions, opus=architecture/PM/audits @@ -1454,8 +1427,8 @@ var DEFAULT_ROUTING_CONFIG = { fallback: [ "anthropic/claude-haiku-4.5", "google/gemini-2.5-flash-lite", - "deepseek/deepseek-chat", - ], + "deepseek/deepseek-chat" + ] }, MEDIUM: { primary: "openai/gpt-5.2-codex", @@ -1464,21 +1437,23 @@ var DEFAULT_ROUTING_CONFIG = { "moonshot/kimi-k2.5", "google/gemini-2.5-pro", "xai/grok-4-0709", - "anthropic/claude-sonnet-4.6", - ], + "anthropic/claude-sonnet-4.6" + ] }, COMPLEX: { primary: "anthropic/claude-opus-4.6", // Best quality for complex tasks fallback: [ + "openai/gpt-5.4", + // Newest flagship "openai/gpt-5.2-codex", "anthropic/claude-opus-4.6", "anthropic/claude-sonnet-4.6", "google/gemini-3.1-pro", // Newest Gemini "google/gemini-3-pro-preview", - "moonshot/kimi-k2.5", - ], + "moonshot/kimi-k2.5" + ] }, REASONING: { primary: "anthropic/claude-sonnet-4.6", @@ -1489,9 +1464,9 @@ var DEFAULT_ROUTING_CONFIG = { "openai/o4-mini", // Newer and cheaper than o3 ($1.10 vs $2.00) "openai/o3", - "xai/grok-4-1-fast-reasoning", - ], - }, + "xai/grok-4-1-fast-reasoning" + ] + } }, // Agentic tier configs - models that excel at multi-step autonomous tasks agenticTiers: { @@ -1501,8 +1476,8 @@ var DEFAULT_ROUTING_CONFIG = { fallback: [ "anthropic/claude-haiku-4.5", "xai/grok-4-1-fast-non-reasoning", - "openai/gpt-4o-mini", - ], + "openai/gpt-4o-mini" + ] }, MEDIUM: { primary: "moonshot/kimi-k2.5", @@ -1510,20 +1485,21 @@ var DEFAULT_ROUTING_CONFIG = { fallback: [ "anthropic/claude-haiku-4.5", "deepseek/deepseek-chat", - "xai/grok-4-1-fast-non-reasoning", - ], + "xai/grok-4-1-fast-non-reasoning" + ] }, COMPLEX: { primary: "anthropic/claude-sonnet-4.6", fallback: [ "anthropic/claude-opus-4.6", // Latest Opus - best agentic - "openai/gpt-5.2", + "openai/gpt-5.4", + // Newest flagship "google/gemini-3.1-pro", // Newest Gemini "google/gemini-3-pro-preview", - "xai/grok-4-0709", - ], + "xai/grok-4-0709" + ] }, REASONING: { primary: "anthropic/claude-sonnet-4.6", @@ -1531,16 +1507,16 @@ var DEFAULT_ROUTING_CONFIG = { fallback: [ "anthropic/claude-opus-4.6", "xai/grok-4-1-fast-reasoning", - "deepseek/deepseek-reasoner", - ], - }, + "deepseek/deepseek-reasoner" + ] + } }, overrides: { maxTokensForceComplex: 1e5, structuredOutputMinTier: "MEDIUM", ambiguousDefaultTier: "MEDIUM", - agenticMode: false, - }, + agenticMode: false + } }; // src/router/index.ts @@ -1562,9 +1538,10 @@ function route(prompt, systemPrompt, maxOutputTokens, options) { const agenticScore = ruleResult.agenticScore ?? 0; const isAutoAgentic = agenticScore >= 0.5; const isExplicitAgentic = config2.overrides.agenticMode ?? false; - const useAgenticTiers = (isAutoAgentic || isExplicitAgentic) && config2.agenticTiers != null; + const hasToolsInRequest = options.hasTools ?? false; + const useAgenticTiers = (hasToolsInRequest || isAutoAgentic || isExplicitAgentic) && config2.agenticTiers != null; tierConfigs = useAgenticTiers ? config2.agenticTiers : config2.tiers; - profileSuffix = useAgenticTiers ? " | agentic" : ""; + profileSuffix = useAgenticTiers ? ` | agentic${hasToolsInRequest ? " (tools)" : ""}` : ""; } const agenticScoreValue = ruleResult.agenticScore; if (estimatedTokens > config2.overrides.maxTokensForceComplex) { @@ -1578,7 +1555,7 @@ function route(prompt, systemPrompt, maxOutputTokens, options) { estimatedTokens, maxOutputTokens, routingProfile, - agenticScoreValue, + agenticScoreValue ); } const hasStructuredOutput = systemPrompt ? /json|structured|schema/i.test(systemPrompt) : false; @@ -1613,7 +1590,7 @@ function route(prompt, systemPrompt, maxOutputTokens, options) { estimatedTokens, maxOutputTokens, routingProfile, - agenticScoreValue, + agenticScoreValue ); } @@ -1646,7 +1623,9 @@ var MODEL_ALIASES = { // OpenAI gpt: "openai/gpt-4o", gpt4: "openai/gpt-4o", - gpt5: "openai/gpt-5.2", + gpt5: "openai/gpt-5.4", + "gpt-5.4": "openai/gpt-5.4", + "gpt-5.4-pro": "openai/gpt-5.4-pro", codex: "openai/gpt-5.2-codex", mini: "openai/gpt-4o-mini", o1: "openai/o1", @@ -1674,7 +1653,7 @@ var MODEL_ALIASES = { minimax: "minimax/minimax-m2.5", // Routing profile aliases (common variations) "auto-router": "auto", - router: "auto", + router: "auto" // Note: auto, free, eco, premium are virtual routing profiles registered in BLOCKRUN_MODELS // They don't need aliases since they're already top-level model IDs }; @@ -1688,6 +1667,13 @@ function resolveModelAlias(model) { if (resolvedWithoutPrefix) return resolvedWithoutPrefix; return withoutPrefix; } + if (normalized.startsWith("openai/")) { + const withoutPrefix = normalized.slice("openai/".length); + const resolvedWithoutPrefix = MODEL_ALIASES[withoutPrefix]; + if (resolvedWithoutPrefix) return resolvedWithoutPrefix; + const isVirtualProfile = BLOCKRUN_MODELS.some((m) => m.id === withoutPrefix); + if (isVirtualProfile) return withoutPrefix; + } return model; } var BLOCKRUN_MODELS = [ @@ -1699,7 +1685,7 @@ var BLOCKRUN_MODELS = [ inputPrice: 0, outputPrice: 0, contextWindow: 105e4, - maxOutput: 128e3, + maxOutput: 128e3 }, { id: "free", @@ -1707,7 +1693,7 @@ var BLOCKRUN_MODELS = [ inputPrice: 0, outputPrice: 0, contextWindow: 128e3, - maxOutput: 4096, + maxOutput: 4096 }, { id: "eco", @@ -1715,7 +1701,7 @@ var BLOCKRUN_MODELS = [ inputPrice: 0, outputPrice: 0, contextWindow: 105e4, - maxOutput: 128e3, + maxOutput: 128e3 }, { id: "premium", @@ -1723,7 +1709,7 @@ var BLOCKRUN_MODELS = [ inputPrice: 0, outputPrice: 0, contextWindow: 2e6, - maxOutput: 2e5, + maxOutput: 2e5 }, // OpenAI GPT-5 Family { @@ -1737,7 +1723,7 @@ var BLOCKRUN_MODELS = [ reasoning: true, vision: true, agentic: true, - toolCalling: true, + toolCalling: true }, { id: "openai/gpt-5-mini", @@ -1747,7 +1733,7 @@ var BLOCKRUN_MODELS = [ outputPrice: 2, contextWindow: 2e5, maxOutput: 65536, - toolCalling: true, + toolCalling: true }, { id: "openai/gpt-5-nano", @@ -1757,7 +1743,7 @@ var BLOCKRUN_MODELS = [ outputPrice: 0.4, contextWindow: 128e3, maxOutput: 32768, - toolCalling: true, + toolCalling: true }, { id: "openai/gpt-5.2-pro", @@ -1768,7 +1754,32 @@ var BLOCKRUN_MODELS = [ contextWindow: 4e5, maxOutput: 128e3, reasoning: true, - toolCalling: true, + toolCalling: true + }, + // GPT-5.4 — newest flagship, same input price as 4o but much more capable + { + id: "openai/gpt-5.4", + name: "GPT-5.4", + version: "5.4", + inputPrice: 2.5, + outputPrice: 15, + contextWindow: 4e5, + maxOutput: 128e3, + reasoning: true, + vision: true, + agentic: true, + toolCalling: true + }, + { + id: "openai/gpt-5.4-pro", + name: "GPT-5.4 Pro", + version: "5.4", + inputPrice: 30, + outputPrice: 180, + contextWindow: 4e5, + maxOutput: 128e3, + reasoning: true, + toolCalling: true }, // OpenAI Codex Family { @@ -1780,7 +1791,7 @@ var BLOCKRUN_MODELS = [ contextWindow: 128e3, maxOutput: 32e3, agentic: true, - toolCalling: true, + toolCalling: true }, // OpenAI GPT-4 Family { @@ -1792,7 +1803,7 @@ var BLOCKRUN_MODELS = [ contextWindow: 128e3, maxOutput: 16384, vision: true, - toolCalling: true, + toolCalling: true }, { id: "openai/gpt-4.1-mini", @@ -1802,7 +1813,7 @@ var BLOCKRUN_MODELS = [ outputPrice: 1.6, contextWindow: 128e3, maxOutput: 16384, - toolCalling: true, + toolCalling: true }, { id: "openai/gpt-4.1-nano", @@ -1812,7 +1823,7 @@ var BLOCKRUN_MODELS = [ outputPrice: 0.4, contextWindow: 128e3, maxOutput: 16384, - toolCalling: true, + toolCalling: true }, { id: "openai/gpt-4o", @@ -1824,7 +1835,7 @@ var BLOCKRUN_MODELS = [ maxOutput: 16384, vision: true, agentic: true, - toolCalling: true, + toolCalling: true }, { id: "openai/gpt-4o-mini", @@ -1834,7 +1845,7 @@ var BLOCKRUN_MODELS = [ outputPrice: 0.6, contextWindow: 128e3, maxOutput: 16384, - toolCalling: true, + toolCalling: true }, // OpenAI O-series (Reasoning) { @@ -1846,7 +1857,7 @@ var BLOCKRUN_MODELS = [ contextWindow: 2e5, maxOutput: 1e5, reasoning: true, - toolCalling: true, + toolCalling: true }, { id: "openai/o1-mini", @@ -1857,7 +1868,7 @@ var BLOCKRUN_MODELS = [ contextWindow: 128e3, maxOutput: 65536, reasoning: true, - toolCalling: true, + toolCalling: true }, { id: "openai/o3", @@ -1868,7 +1879,7 @@ var BLOCKRUN_MODELS = [ contextWindow: 2e5, maxOutput: 1e5, reasoning: true, - toolCalling: true, + toolCalling: true }, { id: "openai/o3-mini", @@ -1879,7 +1890,7 @@ var BLOCKRUN_MODELS = [ contextWindow: 128e3, maxOutput: 65536, reasoning: true, - toolCalling: true, + toolCalling: true }, { id: "openai/o4-mini", @@ -1890,7 +1901,7 @@ var BLOCKRUN_MODELS = [ contextWindow: 128e3, maxOutput: 65536, reasoning: true, - toolCalling: true, + toolCalling: true }, // Anthropic - all Claude models excel at agentic workflows // Use newest versions (4.6) with full provider prefix @@ -1904,7 +1915,7 @@ var BLOCKRUN_MODELS = [ maxOutput: 8192, vision: true, agentic: true, - toolCalling: true, + toolCalling: true }, { id: "anthropic/claude-sonnet-4.6", @@ -1917,7 +1928,7 @@ var BLOCKRUN_MODELS = [ reasoning: true, vision: true, agentic: true, - toolCalling: true, + toolCalling: true }, { id: "anthropic/claude-opus-4.6", @@ -1930,7 +1941,7 @@ var BLOCKRUN_MODELS = [ reasoning: true, vision: true, agentic: true, - toolCalling: true, + toolCalling: true }, // Google { @@ -1943,7 +1954,7 @@ var BLOCKRUN_MODELS = [ maxOutput: 65536, reasoning: true, vision: true, - toolCalling: true, + toolCalling: true }, { id: "google/gemini-3-pro-preview", @@ -1955,7 +1966,7 @@ var BLOCKRUN_MODELS = [ maxOutput: 65536, reasoning: true, vision: true, - toolCalling: true, + toolCalling: true }, { id: "google/gemini-3-flash-preview", @@ -1965,8 +1976,7 @@ var BLOCKRUN_MODELS = [ outputPrice: 3, contextWindow: 1e6, maxOutput: 65536, - vision: true, - toolCalling: true, + vision: true }, { id: "google/gemini-2.5-pro", @@ -1978,7 +1988,7 @@ var BLOCKRUN_MODELS = [ maxOutput: 65536, reasoning: true, vision: true, - toolCalling: true, + toolCalling: true }, { id: "google/gemini-2.5-flash", @@ -1989,7 +1999,7 @@ var BLOCKRUN_MODELS = [ contextWindow: 1e6, maxOutput: 65536, vision: true, - toolCalling: true, + toolCalling: true }, { id: "google/gemini-2.5-flash-lite", @@ -1999,7 +2009,7 @@ var BLOCKRUN_MODELS = [ outputPrice: 0.4, contextWindow: 1e6, maxOutput: 65536, - toolCalling: true, + toolCalling: true }, // DeepSeek { @@ -2010,7 +2020,7 @@ var BLOCKRUN_MODELS = [ outputPrice: 0.42, contextWindow: 128e3, maxOutput: 8192, - toolCalling: true, + toolCalling: true }, { id: "deepseek/deepseek-reasoner", @@ -2021,7 +2031,7 @@ var BLOCKRUN_MODELS = [ contextWindow: 128e3, maxOutput: 8192, reasoning: true, - toolCalling: true, + toolCalling: true }, // Moonshot / Kimi - optimized for agentic workflows { @@ -2035,7 +2045,7 @@ var BLOCKRUN_MODELS = [ reasoning: true, vision: true, agentic: true, - toolCalling: true, + toolCalling: true }, // xAI / Grok { @@ -2047,7 +2057,7 @@ var BLOCKRUN_MODELS = [ contextWindow: 131072, maxOutput: 16384, reasoning: true, - toolCalling: true, + toolCalling: true }, // grok-3-fast removed - too expensive ($5/$25), use grok-4-fast instead { @@ -2058,7 +2068,7 @@ var BLOCKRUN_MODELS = [ outputPrice: 0.5, contextWindow: 131072, maxOutput: 16384, - toolCalling: true, + toolCalling: true }, // xAI Grok 4 Family - Ultra-cheap fast models { @@ -2070,7 +2080,7 @@ var BLOCKRUN_MODELS = [ contextWindow: 131072, maxOutput: 16384, reasoning: true, - toolCalling: true, + toolCalling: true }, { id: "xai/grok-4-fast-non-reasoning", @@ -2080,7 +2090,7 @@ var BLOCKRUN_MODELS = [ outputPrice: 0.5, contextWindow: 131072, maxOutput: 16384, - toolCalling: true, + toolCalling: true }, { id: "xai/grok-4-1-fast-reasoning", @@ -2091,7 +2101,7 @@ var BLOCKRUN_MODELS = [ contextWindow: 131072, maxOutput: 16384, reasoning: true, - toolCalling: true, + toolCalling: true }, { id: "xai/grok-4-1-fast-non-reasoning", @@ -2101,7 +2111,7 @@ var BLOCKRUN_MODELS = [ outputPrice: 0.5, contextWindow: 131072, maxOutput: 16384, - toolCalling: true, + toolCalling: true }, { id: "xai/grok-code-fast-1", @@ -2110,7 +2120,7 @@ var BLOCKRUN_MODELS = [ inputPrice: 0.2, outputPrice: 1.5, contextWindow: 131072, - maxOutput: 16384, + maxOutput: 16384 // toolCalling intentionally omitted: outputs tool calls as plain text JSON, // not OpenAI-compatible structured function calls. Will be skipped when // request has tools to prevent the "talking to itself" bug. @@ -2124,7 +2134,7 @@ var BLOCKRUN_MODELS = [ contextWindow: 131072, maxOutput: 16384, reasoning: true, - toolCalling: true, + toolCalling: true }, { id: "xai/grok-2-vision", @@ -2135,7 +2145,7 @@ var BLOCKRUN_MODELS = [ contextWindow: 131072, maxOutput: 16384, vision: true, - toolCalling: true, + toolCalling: true }, // MiniMax { @@ -2148,7 +2158,7 @@ var BLOCKRUN_MODELS = [ maxOutput: 16384, reasoning: true, agentic: true, - toolCalling: true, + toolCalling: true }, // NVIDIA - Free/cheap models { @@ -2158,7 +2168,7 @@ var BLOCKRUN_MODELS = [ inputPrice: 0, outputPrice: 0, contextWindow: 128e3, - maxOutput: 16384, + maxOutput: 16384 // toolCalling intentionally omitted: free model, structured function // calling support unverified. Excluded from tool-heavy routing paths. }, @@ -2170,8 +2180,8 @@ var BLOCKRUN_MODELS = [ outputPrice: 2.5, contextWindow: 262144, maxOutput: 16384, - toolCalling: true, - }, + toolCalling: true + } ]; function toOpenClawModel(m) { return { @@ -2184,20 +2194,21 @@ function toOpenClawModel(m) { input: m.inputPrice, output: m.outputPrice, cacheRead: 0, - cacheWrite: 0, + cacheWrite: 0 }, contextWindow: m.contextWindow, - maxTokens: m.maxOutput, + maxTokens: m.maxOutput }; } -var ALIAS_MODELS = Object.entries(MODEL_ALIASES) - .map(([alias, targetId]) => { - const target = BLOCKRUN_MODELS.find((m) => m.id === targetId); - if (!target) return null; - return toOpenClawModel({ ...target, id: alias, name: `${alias} \u2192 ${target.name}` }); - }) - .filter((m) => m !== null); -var OPENCLAW_MODELS = [...BLOCKRUN_MODELS.map(toOpenClawModel), ...ALIAS_MODELS]; +var ALIAS_MODELS = Object.entries(MODEL_ALIASES).map(([alias, targetId]) => { + const target = BLOCKRUN_MODELS.find((m) => m.id === targetId); + if (!target) return null; + return toOpenClawModel({ ...target, id: alias, name: `${alias} \u2192 ${target.name}` }); +}).filter((m) => m !== null); +var OPENCLAW_MODELS = [ + ...BLOCKRUN_MODELS.map(toOpenClawModel), + ...ALIAS_MODELS +]; function supportsToolCalling(modelId) { const normalized = modelId.replace("blockrun/", ""); const model = BLOCKRUN_MODELS.find((m) => m.id === normalized); @@ -2222,6 +2233,9 @@ function isReasoningModel(modelId) { // src/proxy.ts import { createServer } from "http"; import { finished } from "stream"; +import { homedir as homedir4 } from "os"; +import { join as join5 } from "path"; +import { mkdir as mkdir3, writeFile as writeFile2, readFile, stat as fsStat } from "fs/promises"; import { createPublicClient as createPublicClient2, http as http2 } from "viem"; import { base as base2 } from "viem/chains"; import { privateKeyToAccount as privateKeyToAccount3 } from "viem/accounts"; @@ -2264,14 +2278,21 @@ function createPayFetchWithPreAuth(baseFetch, client, ttlMs = DEFAULT_TTL_MS, op const getHeader = (name) => response.headers.get(name); let body; try { - const responseText = await response.text(); + const responseText = await Promise.race([ + response.text(), + new Promise( + (_, reject) => setTimeout(() => reject(new Error("Body read timeout")), 3e4) + ) + ]); if (responseText) body = JSON.parse(responseText); - } catch {} + } catch { + } paymentRequired = httpClient.getPaymentRequiredResponse(getHeader, body); cache.set(urlPath, { paymentRequired, cachedAt: Date.now() }); } catch (error) { throw new Error( `Failed to parse payment requirements: ${error instanceof Error ? error.message : "Unknown error"}`, + { cause: error } ); } const payload = await client.createPaymentPayload(paymentRequired); @@ -2304,11 +2325,12 @@ async function logUsage(entry) { const date = entry.timestamp.slice(0, 10); const file = join(LOG_DIR, `usage-${date}.jsonl`); await appendFile(file, JSON.stringify(entry) + "\n"); - } catch {} + } catch { + } } // src/stats.ts -import { readdir } from "fs/promises"; +import { readdir, unlink } from "fs/promises"; // src/fs-read.ts import { open } from "fs/promises"; @@ -2316,9 +2338,15 @@ import { openSync, readSync, closeSync, fstatSync } from "fs"; async function readTextFile(filePath) { const fh = await open(filePath, "r"); try { - const buf = Buffer.alloc((await fh.stat()).size); - await fh.read(buf, 0, buf.length, 0); - return buf.toString("utf-8"); + const size = (await fh.stat()).size; + const buf = Buffer.alloc(size); + let offset = 0; + while (offset < size) { + const { bytesRead } = await fh.read(buf, offset, size - offset, offset); + if (bytesRead === 0) break; + offset += bytesRead; + } + return buf.subarray(0, offset).toString("utf-8"); } finally { await fh.close(); } @@ -2345,18 +2373,23 @@ async function parseLogFile(filePath) { try { const content = await readTextFile(filePath); const lines = content.trim().split("\n").filter(Boolean); - return lines.map((line) => { - const entry = JSON.parse(line); - return { - timestamp: entry.timestamp || /* @__PURE__ */ new Date().toISOString(), - model: entry.model || "unknown", - tier: entry.tier || "UNKNOWN", - cost: entry.cost || 0, - baselineCost: entry.baselineCost || entry.cost || 0, - savings: entry.savings || 0, - latencyMs: entry.latencyMs || 0, - }; - }); + const entries = []; + for (const line of lines) { + try { + const entry = JSON.parse(line); + entries.push({ + timestamp: entry.timestamp || (/* @__PURE__ */ new Date()).toISOString(), + model: entry.model || "unknown", + tier: entry.tier || "UNKNOWN", + cost: entry.cost || 0, + baselineCost: entry.baselineCost || entry.cost || 0, + savings: entry.savings || 0, + latencyMs: entry.latencyMs || 0 + }); + } catch { + } + } + return entries; } catch { return []; } @@ -2364,10 +2397,7 @@ async function parseLogFile(filePath) { async function getLogFiles() { try { const files = await readdir(LOG_DIR2); - return files - .filter((f) => f.startsWith("usage-") && f.endsWith(".jsonl")) - .sort() - .reverse(); + return files.filter((f) => f.startsWith("usage-") && f.endsWith(".jsonl")).sort().reverse(); } catch { return []; } @@ -2395,7 +2425,7 @@ function aggregateDay(date, entries) { totalSavings: totalBaselineCost - totalCost, avgLatencyMs: entries.length > 0 ? totalLatency / entries.length : 0, byTier, - byModel, + byModel }; } async function getStats(days = 7) { @@ -2434,18 +2464,18 @@ async function getStats(days = 7) { for (const [tier, stats] of Object.entries(allByTier)) { byTierWithPercentage[tier] = { ...stats, - percentage: totalRequests > 0 ? (stats.count / totalRequests) * 100 : 0, + percentage: totalRequests > 0 ? stats.count / totalRequests * 100 : 0 }; } const byModelWithPercentage = {}; for (const [model, stats] of Object.entries(allByModel)) { byModelWithPercentage[model] = { ...stats, - percentage: totalRequests > 0 ? (stats.count / totalRequests) * 100 : 0, + percentage: totalRequests > 0 ? stats.count / totalRequests * 100 : 0 }; } const totalSavings = totalBaselineCost - totalCost; - const savingsPercentage = totalBaselineCost > 0 ? (totalSavings / totalBaselineCost) * 100 : 0; + const savingsPercentage = totalBaselineCost > 0 ? totalSavings / totalBaselineCost * 100 : 0; let entriesWithBaseline = 0; for (const day of dailyBreakdown) { if (day.totalBaselineCost !== day.totalCost) { @@ -2465,10 +2495,20 @@ async function getStats(days = 7) { byModel: byModelWithPercentage, dailyBreakdown: dailyBreakdown.reverse(), // Oldest first for charts - entriesWithBaseline, + entriesWithBaseline // How many entries have valid baseline tracking }; } +async function clearStats() { + try { + const files = await readdir(LOG_DIR2); + const logFiles = files.filter((f) => f.startsWith("usage-") && f.endsWith(".jsonl")); + await Promise.all(logFiles.map((f) => unlink(join3(LOG_DIR2, f)))); + return { deletedFiles: logFiles.length }; + } catch { + return { deletedFiles: 0 }; + } +} // src/dedup.ts import { createHash } from "crypto"; @@ -2520,7 +2560,8 @@ var RequestDeduplicator = class { const stripped = stripTimestamps(parsed); const canonical = canonicalize(stripped); content = Buffer.from(JSON.stringify(canonical)); - } catch {} + } catch { + } return createHash("sha256").update(content).digest("hex").slice(0, 16); } /** Check if a response is cached for this key. */ @@ -2544,7 +2585,7 @@ var RequestDeduplicator = class { /** Mark a request as in-flight. */ markInflight(key) { this.inflight.set(key, { - resolvers: [], + resolvers: [] }); } /** Complete an in-flight request — cache result and notify waiters. */ @@ -2568,15 +2609,15 @@ var RequestDeduplicator = class { if (entry) { const errorBody = Buffer.from( JSON.stringify({ - error: { message: "Original request failed, please retry", type: "dedup_origin_failed" }, - }), + error: { message: "Original request failed, please retry", type: "dedup_origin_failed" } + }) ); for (const resolve of entry.resolvers) { resolve({ status: 503, headers: { "content-type": "application/json" }, body: errorBody, - completedAt: Date.now(), + completedAt: Date.now() }); } this.inflight.delete(key); @@ -2600,7 +2641,7 @@ var DEFAULT_CONFIG = { defaultTTL: 600, maxItemSize: 1048576, // 1MB - enabled: true, + enabled: true }; function canonicalize2(obj) { if (obj === null || typeof obj !== "object") { @@ -2646,10 +2687,12 @@ var ResponseCache = class { stats = { hits: 0, misses: 0, - evictions: 0, + evictions: 0 }; constructor(config2 = {}) { - const filtered = Object.fromEntries(Object.entries(config2).filter(([, v]) => v !== void 0)); + const filtered = Object.fromEntries( + Object.entries(config2).filter(([, v]) => v !== void 0) + ); this.config = { ...DEFAULT_CONFIG, ...filtered }; } /** @@ -2682,7 +2725,8 @@ var ResponseCache = class { if (parsed.cache === false || parsed.no_cache === true) { return false; } - } catch {} + } catch { + } return true; } /** @@ -2723,7 +2767,7 @@ var ResponseCache = class { const entry = { ...response, cachedAt: now, - expiresAt, + expiresAt }; this.cache.set(key, entry); this.expirationHeap.push({ expiresAt, key }); @@ -2762,14 +2806,14 @@ var ResponseCache = class { */ getStats() { const total = this.stats.hits + this.stats.misses; - const hitRate = total > 0 ? ((this.stats.hits / total) * 100).toFixed(1) + "%" : "0%"; + const hitRate = total > 0 ? (this.stats.hits / total * 100).toFixed(1) + "%" : "0%"; return { size: this.cache.size, maxSize: this.config.maxSize, hits: this.stats.hits, misses: this.stats.misses, evictions: this.stats.evictions, - hitRate, + hitRate }; } /** @@ -2809,7 +2853,7 @@ var BALANCE_THRESHOLDS = { /** Low balance warning threshold: $1.00 */ LOW_BALANCE_MICROS: 1000000n, /** Effectively zero threshold: $0.0001 (covers dust/rounding) */ - ZERO_THRESHOLD: 100n, + ZERO_THRESHOLD: 100n }; var BalanceMonitor = class { client; @@ -2823,9 +2867,9 @@ var BalanceMonitor = class { this.client = createPublicClient({ chain: base, transport: http(void 0, { - timeout: 1e4, + timeout: 1e4 // 10 second timeout to prevent hanging on slow RPC - }), + }) }); } /** @@ -2834,12 +2878,14 @@ var BalanceMonitor = class { */ async checkBalance() { const now = Date.now(); - if (this.cachedBalance !== null && now - this.cachedAt < CACHE_TTL_MS) { + if (this.cachedBalance !== null && this.cachedBalance > 0n && now - this.cachedAt < CACHE_TTL_MS) { return this.buildInfo(this.cachedBalance); } const balance = await this.fetchBalance(); - this.cachedBalance = balance; - this.cachedAt = now; + if (balance > 0n) { + this.cachedBalance = balance; + this.cachedAt = now; + } return this.buildInfo(balance); } /** @@ -2856,7 +2902,7 @@ var BalanceMonitor = class { return { sufficient: false, info, - shortfall: this.formatUSDC(shortfall), + shortfall: this.formatUSDC(shortfall) }; } /** @@ -2905,7 +2951,7 @@ var BalanceMonitor = class { address: USDC_BASE, abi: erc20Abi, functionName: "balanceOf", - args: [this.walletAddress], + args: [this.walletAddress] }); return balance; } catch (error) { @@ -2919,7 +2965,7 @@ var BalanceMonitor = class { balanceUSD: this.formatUSDC(balance), isLow: balance < BALANCE_THRESHOLDS.LOW_BALANCE_MICROS, isEmpty: balance < BALANCE_THRESHOLDS.ZERO_THRESHOLD, - walletAddress: this.walletAddress, + walletAddress: this.walletAddress }; } }; @@ -2942,12 +2988,14 @@ var SolanaBalanceMonitor = class { } async checkBalance() { const now = Date.now(); - if (this.cachedBalance !== null && now - this.cachedAt < CACHE_TTL_MS2) { + if (this.cachedBalance !== null && this.cachedBalance > 0n && now - this.cachedAt < CACHE_TTL_MS2) { return this.buildInfo(this.cachedBalance); } const balance = await this.fetchBalance(); - this.cachedBalance = balance; - this.cachedAt = now; + if (balance > 0n) { + this.cachedBalance = balance; + this.cachedAt = now; + } return this.buildInfo(balance); } deductEstimated(amountMicros) { @@ -2975,7 +3023,7 @@ var SolanaBalanceMonitor = class { return { sufficient: false, info, - shortfall: this.formatUSDC(shortfall), + shortfall: this.formatUSDC(shortfall) }; } /** @@ -2991,12 +3039,18 @@ var SolanaBalanceMonitor = class { async fetchBalance() { const owner = solAddress(this.walletAddress); const mint = solAddress(SOLANA_USDC_MINT); + for (let attempt = 0; attempt < 2; attempt++) { + const result = await this.fetchBalanceOnce(owner, mint); + if (result > 0n || attempt === 1) return result; + await new Promise((r) => setTimeout(r, 1e3)); + } + return 0n; + } + async fetchBalanceOnce(owner, mint) { const controller = new AbortController(); const timer = setTimeout(() => controller.abort(), BALANCE_TIMEOUT_MS); try { - const response = await this.rpc - .getTokenAccountsByOwner(owner, { mint }, { encoding: "jsonParsed" }) - .send({ abortSignal: controller.signal }); + const response = await this.rpc.getTokenAccountsByOwner(owner, { mint }, { encoding: "jsonParsed" }).send({ abortSignal: controller.signal }); if (response.value.length === 0) return 0n; let total = 0n; for (const account of response.value) { @@ -3007,6 +3061,7 @@ var SolanaBalanceMonitor = class { } catch (err) { throw new Error( `Failed to fetch Solana USDC balance: ${err instanceof Error ? err.message : String(err)}`, + { cause: err } ); } finally { clearTimeout(timer); @@ -3019,7 +3074,7 @@ var SolanaBalanceMonitor = class { balanceUSD: `$${dollars.toFixed(2)}`, isLow: balance < 1000000n, isEmpty: balance < 100n, - walletAddress: this.walletAddress, + walletAddress: this.walletAddress }; } }; @@ -3035,6 +3090,7 @@ import { HDKey } from "@scure/bip32"; import { generateMnemonic, mnemonicToSeedSync, validateMnemonic } from "@scure/bip39"; import { wordlist as english } from "@scure/bip39/wordlists/english"; import { privateKeyToAccount } from "viem/accounts"; +var SOLANA_HARDENED_INDICES = [44 + 2147483648, 501 + 2147483648, 0 + 2147483648, 0 + 2147483648]; // src/auth.ts var WALLET_DIR = join4(homedir3(), ".openclaw", "blockrun"); @@ -3073,15 +3129,15 @@ var DEFAULT_COMPRESSION_CONFIG = { // Safe: just removes JSON whitespace observation: false, // DISABLED: may lose important context - dynamicCodebook: false, + dynamicCodebook: false // DISABLED: requires model to understand codes }, dictionary: { maxEntries: 50, minPhraseLength: 15, - includeCodebookHeader: false, + includeCodebookHeader: false // No codebook header needed - }, + } }; // src/compression/layers/deduplication.ts @@ -3099,9 +3155,9 @@ function hashMessage(message) { JSON.stringify( message.tool_calls.map((tc) => ({ name: tc.function.name, - args: tc.function.arguments, - })), - ), + args: tc.function.arguments + })) + ) ); } const content = parts.join("|"); @@ -3131,8 +3187,8 @@ function deduplicateMessages(messages) { continue; } if (message.role === "assistant" && message.tool_calls) { - const hasReferencedToolCall = message.tool_calls.some((tc) => - referencedToolCallIds.has(tc.id), + const hasReferencedToolCall = message.tool_calls.some( + (tc) => referencedToolCallIds.has(tc.id) ); if (hasReferencedToolCall) { result.push(message); @@ -3150,22 +3206,14 @@ function deduplicateMessages(messages) { return { messages: result, duplicatesRemoved, - originalCount: messages.length, + originalCount: messages.length }; } // src/compression/layers/whitespace.ts function normalizeWhitespace(content) { if (!content || typeof content !== "string") return content; - return content - .replace(/\r\n/g, "\n") - .replace(/\r/g, "\n") - .replace(/\n{3,}/g, "\n\n") - .replace(/[ \t]+$/gm, "") - .replace(/([^\n]) {2,}/g, "$1 ") - .replace(/^[ ]{8,}/gm, (match) => " ".repeat(Math.ceil(match.length / 4))) - .replace(/\t/g, " ") - .trim(); + return content.replace(/\r\n/g, "\n").replace(/\r/g, "\n").replace(/\n{3,}/g, "\n\n").replace(/[ \t]+$/gm, "").replace(/([^\n]) {2,}/g, "$1 ").replace(/^[ ]{8,}/gm, (match) => " ".repeat(Math.ceil(match.length / 4))).replace(/\t/g, " ").trim(); } function normalizeMessagesWhitespace(messages) { let charsSaved = 0; @@ -3176,12 +3224,12 @@ function normalizeMessagesWhitespace(messages) { charsSaved += originalLength - normalizedContent.length; return { ...message, - content: normalizedContent, + content: normalizedContent }; }); return { messages: result, - charsSaved, + charsSaved }; } @@ -3247,7 +3295,7 @@ var STATIC_CODEBOOK = { $M02: "openai/", $M03: "anthropic/", $M04: "google/", - $M05: "xai/", + $M05: "xai/" }; function getInverseCodebook() { const inverse = {}; @@ -3262,15 +3310,11 @@ function generateCodebookHeader(usedCodes, pathMap = {}) { } const parts = []; if (usedCodes.size > 0) { - const codeEntries = Array.from(usedCodes) - .map((code) => `${code}=${STATIC_CODEBOOK[code]}`) - .join(", "); + const codeEntries = Array.from(usedCodes).map((code) => `${code}=${STATIC_CODEBOOK[code]}`).join(", "); parts.push(`[Dict: ${codeEntries}]`); } if (Object.keys(pathMap).length > 0) { - const pathEntries = Object.entries(pathMap) - .map(([code, path]) => `${code}=${path}`) - .join(", "); + const pathEntries = Object.entries(pathMap).map(([code, path]) => `${code}=${path}`).join(", "); parts.push(`[Paths: ${pathEntries}]`); } return parts.join("\n"); @@ -3311,21 +3355,21 @@ function encodeMessages(messages) { if (!message.content || typeof message.content !== "string") return message; const { encoded, substitutions, codes, charsSaved } = encodeContent( message.content, - inverseCodebook, + inverseCodebook ); totalSubstitutions += substitutions; totalCharsSaved += charsSaved; codes.forEach((code) => allUsedCodes.add(code)); return { ...message, - content: encoded, + content: encoded }; }); return { messages: result, substitutionCount: totalSubstitutions, usedCodes: allUsedCodes, - charsSaved: totalCharsSaved, + charsSaved: totalCharsSaved }; } @@ -3351,11 +3395,7 @@ function findFrequentPrefixes(paths) { prefixCounts.set(prefix, (prefixCounts.get(prefix) || 0) + 1); } } - return Array.from(prefixCounts.entries()) - .filter(([, count]) => count >= 3) - .sort((a, b) => b[0].length - a[0].length) - .slice(0, 5) - .map(([prefix]) => prefix); + return Array.from(prefixCounts.entries()).filter(([, count]) => count >= 3).sort((a, b) => b[0].length - a[0].length).slice(0, 5).map(([prefix]) => prefix); } function shortenPaths(messages) { const allPaths = extractPaths(messages); @@ -3363,7 +3403,7 @@ function shortenPaths(messages) { return { messages, pathMap: {}, - charsSaved: 0, + charsSaved: 0 }; } const prefixes = findFrequentPrefixes(allPaths); @@ -3371,7 +3411,7 @@ function shortenPaths(messages) { return { messages, pathMap: {}, - charsSaved: 0, + charsSaved: 0 }; } const pathMap = {}; @@ -3389,13 +3429,13 @@ function shortenPaths(messages) { charsSaved += originalLength - content.length; return { ...message, - content, + content }; }); return { messages: result, pathMap, - charsSaved, + charsSaved }; } @@ -3410,18 +3450,15 @@ function compactJson(jsonString) { } function looksLikeJson(str) { const trimmed = str.trim(); - return ( - (trimmed.startsWith("{") && trimmed.endsWith("}")) || - (trimmed.startsWith("[") && trimmed.endsWith("]")) - ); + return trimmed.startsWith("{") && trimmed.endsWith("}") || trimmed.startsWith("[") && trimmed.endsWith("]"); } function compactToolCalls(toolCalls) { return toolCalls.map((tc) => ({ ...tc, function: { ...tc.function, - arguments: compactJson(tc.function.arguments), - }, + arguments: compactJson(tc.function.arguments) + } })); } function compactMessagesJson(messages) { @@ -3434,12 +3471,7 @@ function compactMessagesJson(messages) { const newLength = JSON.stringify(newMessage.tool_calls).length; charsSaved += originalLength - newLength; } - if ( - message.role === "tool" && - message.content && - typeof message.content === "string" && - looksLikeJson(message.content) - ) { + if (message.role === "tool" && message.content && typeof message.content === "string" && looksLikeJson(message.content)) { const originalLength = message.content.length; const compacted = compactJson(message.content); charsSaved += originalLength - compacted.length; @@ -3449,7 +3481,7 @@ function compactMessagesJson(messages) { }); return { messages: result, - charsSaved, + charsSaved }; } @@ -3460,16 +3492,12 @@ function compressToolResult(content) { if (!content || content.length <= TOOL_RESULT_THRESHOLD) { return content; } - const lines = content - .split("\n") - .map((l) => l.trim()) - .filter(Boolean); + const lines = content.split("\n").map((l) => l.trim()).filter(Boolean); const errorLines = lines.filter( - (l) => /error|exception|failed|denied|refused|timeout|invalid/i.test(l) && l.length < 200, + (l) => /error|exception|failed|denied|refused|timeout|invalid/i.test(l) && l.length < 200 ); const statusLines = lines.filter( - (l) => - /success|complete|created|updated|found|result|status|total|count/i.test(l) && l.length < 150, + (l) => /success|complete|created|updated|found|result|status|total|count/i.test(l) && l.length < 150 ); const jsonMatches = []; const jsonPattern = /"(id|name|status|error|message|count|total|url|path)":\s*"?([^",}\n]+)"?/gi; @@ -3550,7 +3578,7 @@ function compressObservations(messages) { return { messages: result, charsSaved, - observationsCompressed, + observationsCompressed }; } @@ -3616,7 +3644,7 @@ function applyDynamicCodebook(messages) { messages, charsSaved: 0, dynamicCodes: {}, - substitutions: 0, + substitutions: 0 }; } const phraseToCode = {}; @@ -3645,18 +3673,15 @@ function applyDynamicCodebook(messages) { messages: result, charsSaved, dynamicCodes: codebook, - substitutions, + substitutions }; } function generateDynamicCodebookHeader(codebook) { if (Object.keys(codebook).length === 0) return ""; - const entries = Object.entries(codebook) - .slice(0, 20) - .map(([code, phrase]) => { - const displayPhrase = phrase.length > 40 ? phrase.slice(0, 37) + "..." : phrase; - return `${code}=${displayPhrase}`; - }) - .join(", "); + const entries = Object.entries(codebook).slice(0, 20).map(([code, phrase]) => { + const displayPhrase = phrase.length > 40 ? phrase.slice(0, 37) + "..." : phrase; + return `${code}=${displayPhrase}`; + }).join(", "); return `[DynDict: ${entries}]`; } @@ -3692,7 +3717,7 @@ function prependCodebookHeader(messages, usedCodes, pathMap) { ...msg, content: `${header} -${msg.content}`, +${msg.content}` }; } } @@ -3705,12 +3730,12 @@ async function compressContext(messages, config2 = {}) { ...config2, layers: { ...DEFAULT_COMPRESSION_CONFIG.layers, - ...config2.layers, + ...config2.layers }, dictionary: { ...DEFAULT_COMPRESSION_CONFIG.dictionary, - ...config2.dictionary, - }, + ...config2.dictionary + } }; if (!fullConfig.enabled) { const originalChars2 = calculateTotalChars(messages); @@ -3729,11 +3754,11 @@ async function compressContext(messages, config2 = {}) { observationsCompressed: 0, observationCharsSaved: 0, dynamicSubstitutions: 0, - dynamicCharsSaved: 0, + dynamicCharsSaved: 0 }, codebook: {}, pathMap: {}, - dynamicCodes: {}, + dynamicCodes: {} }; } const originalMessages = fullConfig.preserveRaw ? cloneMessages(messages) : messages; @@ -3747,7 +3772,7 @@ async function compressContext(messages, config2 = {}) { observationsCompressed: 0, observationCharsSaved: 0, dynamicSubstitutions: 0, - dynamicCharsSaved: 0, + dynamicCharsSaved: 0 }; let result = cloneMessages(messages); let usedCodes = /* @__PURE__ */ new Set(); @@ -3793,10 +3818,7 @@ async function compressContext(messages, config2 = {}) { stats.dynamicCharsSaved = dynResult.charsSaved; dynamicCodes = dynResult.dynamicCodes; } - if ( - fullConfig.dictionary.includeCodebookHeader && - (usedCodes.size > 0 || Object.keys(pathMap).length > 0 || Object.keys(dynamicCodes).length > 0) - ) { + if (fullConfig.dictionary.includeCodebookHeader && (usedCodes.size > 0 || Object.keys(pathMap).length > 0 || Object.keys(dynamicCodes).length > 0)) { result = prependCodebookHeader(result, usedCodes, pathMap); if (Object.keys(dynamicCodes).length > 0) { const dynHeader = generateDynamicCodebookHeader(dynamicCodes); @@ -3806,7 +3828,7 @@ async function compressContext(messages, config2 = {}) { result[systemIndex] = { ...result[systemIndex], content: `${dynHeader} -${result[systemIndex].content}`, +${result[systemIndex].content}` }; } } @@ -3827,7 +3849,7 @@ ${result[systemIndex].content}`, stats, codebook: usedCodebook, pathMap, - dynamicCodes, + dynamicCodes }; } function shouldCompress(messages) { @@ -3841,7 +3863,7 @@ var DEFAULT_SESSION_CONFIG = { enabled: true, timeoutMs: 30 * 60 * 1e3, // 30 minutes - headerName: "x-session-id", + headerName: "x-session-id" }; var SessionStore = class { sessions = /* @__PURE__ */ new Map(); @@ -3896,7 +3918,7 @@ var SessionStore = class { requestCount: 1, recentHashes: [], strikes: 0, - escalated: false, + escalated: false }); } } @@ -3933,7 +3955,7 @@ var SessionStore = class { const sessions = Array.from(this.sessions.entries()).map(([id, entry]) => ({ id: id.slice(0, 8) + "...", model: entry.model, - age: Math.round((now - entry.createdAt) / 1e3), + age: Math.round((now - entry.createdAt) / 1e3) })); return { count: this.sessions.size, sessions }; } @@ -4008,22 +4030,17 @@ function getSessionId(headers, headerName = DEFAULT_SESSION_CONFIG.headerName) { function deriveSessionId(messages) { const firstUser = messages.find((m) => m.role === "user"); if (!firstUser) return void 0; - const content = - typeof firstUser.content === "string" ? firstUser.content : JSON.stringify(firstUser.content); + const content = typeof firstUser.content === "string" ? firstUser.content : JSON.stringify(firstUser.content); return createHash3("sha256").update(content).digest("hex").slice(0, 8); } function hashRequestContent(lastUserContent, toolCallNames) { const normalized = lastUserContent.replace(/\s+/g, " ").trim().slice(0, 500); const toolSuffix = toolCallNames?.length ? `|tools:${toolCallNames.sort().join(",")}` : ""; - return createHash3("sha256") - .update(normalized + toolSuffix) - .digest("hex") - .slice(0, 12); + return createHash3("sha256").update(normalized + toolSuffix).digest("hex").slice(0, 12); } // src/updater.ts var NPM_REGISTRY = "https://registry.npmjs.org/@blockrun/clawrouter/latest"; -var UPDATE_URL = "https://blockrun.ai/ClawRouter-update"; var CHECK_TIMEOUT_MS = 5e3; function compareSemver(a, b) { const pa = a.split(".").map(Number); @@ -4040,7 +4057,7 @@ async function checkForUpdates() { const timeout = setTimeout(() => controller.abort(), CHECK_TIMEOUT_MS); const res = await fetch(NPM_REGISTRY, { signal: controller.signal, - headers: { Accept: "application/json" }, + headers: { Accept: "application/json" } }); clearTimeout(timeout); if (!res.ok) return; @@ -4049,13 +4066,12 @@ async function checkForUpdates() { if (!latest) return; if (compareSemver(latest, VERSION) > 0) { console.log(""); - console.log( - `\x1B[33m\u2B06\uFE0F ClawRouter ${latest} available (you have ${VERSION})\x1B[0m`, - ); - console.log(` Run: \x1B[36mcurl -fsSL ${UPDATE_URL} | bash\x1B[0m`); + console.log(`\x1B[33m\u2B06\uFE0F ClawRouter ${latest} available (you have ${VERSION})\x1B[0m`); + console.log(` Run: \x1B[36mnpx @blockrun/clawrouter@latest\x1B[0m`); console.log(""); } - } catch {} + } catch { + } } // src/config.ts @@ -4076,7 +4092,7 @@ var DEFAULT_CONFIG2 = { maxEntries: 100, maxAgeMs: 24 * 60 * 60 * 1e3, // 24 hours - maxEventsPerResponse: 5, + maxEventsPerResponse: 5 }; var SessionJournal = class { journals = /* @__PURE__ */ new Map(); @@ -4106,7 +4122,7 @@ var SessionJournal = class { // Success patterns /Successfully ([^.!?\n]{10,150})/gi, // Tool usage patterns (when agent uses tools) - /I (?:also |then |have |)?(?:ran|executed|called|invoked) ([^.!?\n]{10,100})/gi, + /I (?:also |then |have |)?(?:ran|executed|called|invoked) ([^.!?\n]{10,100})/gi ]; for (const pattern of patterns) { pattern.lastIndex = 0; @@ -4144,7 +4160,7 @@ var SessionJournal = class { journal.push({ timestamp: now, action, - model, + model }); } const cutoff = now - this.config.maxAgeMs; @@ -4182,7 +4198,7 @@ var SessionJournal = class { "your progress", "accomplished", "achievements", - "completed tasks", + "completed tasks" ]; return triggers.some((t) => lower.includes(t)); } @@ -4199,7 +4215,7 @@ var SessionJournal = class { const time = new Date(e.timestamp).toLocaleTimeString("en-US", { hour: "2-digit", minute: "2-digit", - hour12: true, + hour12: true }); return `- ${time}: ${e.action}`; }); @@ -4234,7 +4250,7 @@ ${lines.join("\n")}`; } return { sessions: this.journals.size, - totalEntries, + totalEntries }; } }; @@ -4242,6 +4258,7 @@ ${lines.join("\n")}`; // src/proxy.ts var BLOCKRUN_API = "https://blockrun.ai/api"; var BLOCKRUN_SOLANA_API = "https://sol.blockrun.ai/api"; +var IMAGE_DIR = join5(homedir4(), ".openclaw", "blockrun", "images"); var AUTO_MODEL = "blockrun/auto"; var ROUTING_PROFILES = /* @__PURE__ */ new Set([ "blockrun/free", @@ -4251,7 +4268,7 @@ var ROUTING_PROFILES = /* @__PURE__ */ new Set([ "blockrun/auto", "auto", "blockrun/premium", - "premium", + "premium" ]); var FREE_MODEL = "nvidia/gpt-oss-120b"; var MAX_MESSAGES = 200; @@ -4263,6 +4280,31 @@ var HEALTH_CHECK_TIMEOUT_MS = 2e3; var RATE_LIMIT_COOLDOWN_MS = 6e4; var PORT_RETRY_ATTEMPTS = 5; var PORT_RETRY_DELAY_MS = 1e3; +var MODEL_BODY_READ_TIMEOUT_MS = 3e5; +var ERROR_BODY_READ_TIMEOUT_MS = 3e4; +async function readBodyWithTimeout(body, timeoutMs = MODEL_BODY_READ_TIMEOUT_MS) { + if (!body) return []; + const reader = body.getReader(); + const chunks = []; + let timer; + try { + while (true) { + const result = await Promise.race([ + reader.read(), + new Promise((_, reject) => { + timer = setTimeout(() => reject(new Error("Body read timeout")), timeoutMs); + }) + ]); + clearTimeout(timer); + if (result.done) break; + chunks.push(result.value); + } + } finally { + clearTimeout(timer); + reader.releaseLock(); + } + return chunks; +} function transformPaymentError(errorBody) { try { const parsed = JSON.parse(errorBody); @@ -4272,7 +4314,7 @@ function transformPaymentError(errorBody) { const innerJson = JSON.parse(match[1]); if (innerJson.invalidReason === "insufficient_funds" && innerJson.invalidMessage) { const balanceMatch = innerJson.invalidMessage.match( - /insufficient balance:\s*(\d+)\s*<\s*(\d+)/i, + /insufficient balance:\s*(\d+)\s*<\s*(\d+)/i ); if (balanceMatch) { const currentMicros = parseInt(balanceMatch[1], 10); @@ -4280,8 +4322,7 @@ function transformPaymentError(errorBody) { const currentUSD = (currentMicros / 1e6).toFixed(6); const requiredUSD = (requiredMicros / 1e6).toFixed(6); const wallet = innerJson.payer || "unknown"; - const shortWallet = - wallet.length > 12 ? `${wallet.slice(0, 6)}...${wallet.slice(-4)}` : wallet; + const shortWallet = wallet.length > 12 ? `${wallet.slice(0, 6)}...${wallet.slice(-4)}` : wallet; return JSON.stringify({ error: { message: `Insufficient USDC balance. Current: $${currentUSD}, Required: ~$${requiredUSD}`, @@ -4289,8 +4330,8 @@ function transformPaymentError(errorBody) { wallet, current_balance_usd: currentUSD, required_usd: requiredUSD, - help: `Fund wallet ${shortWallet} with USDC on Base, or use free model: /model free`, - }, + help: `Fund wallet ${shortWallet} with USDC on Base, or use free model: /model free` + } }); } } @@ -4299,26 +4340,37 @@ function transformPaymentError(errorBody) { error: { message: "Payment signature invalid. This may be a temporary issue.", type: "invalid_payload", - help: "Try again. If this persists, reinstall ClawRouter: curl -fsSL https://blockrun.ai/ClawRouter-update | bash", - }, + help: "Try again. If this persists, reinstall ClawRouter: curl -fsSL https://blockrun.ai/ClawRouter-update | bash" + } + }); + } + if (innerJson.invalidReason === "transaction_simulation_failed") { + console.error( + `[ClawRouter] Solana transaction simulation failed: ${innerJson.invalidMessage || "unknown"}` + ); + return JSON.stringify({ + error: { + message: "Solana payment simulation failed. Retrying with a different model.", + type: "transaction_simulation_failed", + help: "This is usually temporary. If it persists, check your Solana USDC balance or try: /model free" + } }); } } } - if (parsed.error === "Settlement failed" || parsed.details?.includes("Settlement failed")) { + if (parsed.error === "Settlement failed" || parsed.error === "Payment settlement failed" || parsed.details?.includes("Settlement failed") || parsed.details?.includes("transaction_simulation_failed")) { const details = parsed.details || ""; const gasError = details.includes("unable to estimate gas"); return JSON.stringify({ error: { - message: gasError - ? "Payment failed: network congestion or gas issue. Try again." - : "Payment settlement failed. Try again in a moment.", + message: gasError ? "Payment failed: network congestion or gas issue. Try again." : "Payment settlement failed. Try again in a moment.", type: "settlement_failed", - help: "This is usually temporary. If it persists, try: /model free", - }, + help: "This is usually temporary. If it persists, try: /model free" + } }); } - } catch {} + } catch { + } return errorBody; } var rateLimitedModels = /* @__PURE__ */ new Map(); @@ -4349,16 +4401,12 @@ function prioritizeNonRateLimited(models) { return [...available, ...rateLimited]; } function canWrite(res) { - return ( - !res.writableEnded && - !res.destroyed && - res.socket !== null && - !res.socket.destroyed && - res.socket.writable - ); + return !res.writableEnded && !res.destroyed && res.socket !== null && !res.socket.destroyed && res.socket.writable; } function safeWrite(res, data) { if (!canWrite(res)) { + const bytes = typeof data === "string" ? Buffer.byteLength(data) : data.length; + console.warn(`[ClawRouter] safeWrite: socket not writable, dropping ${bytes} bytes`); return false; } return res.write(data); @@ -4372,7 +4420,7 @@ async function checkExistingProxy(port) { const timeoutId = setTimeout(() => controller.abort(), HEALTH_CHECK_TIMEOUT_MS); try { const response = await fetch(`http://127.0.0.1:${port}/health`, { - signal: controller.signal, + signal: controller.signal }); clearTimeout(timeoutId); if (response.ok) { @@ -4406,17 +4454,17 @@ var PROVIDER_ERROR_PATTERNS = [ /payload too large/i, /payment.*verification.*failed/i, /model.*not.*allowed/i, - /unknown.*model/i, + /unknown.*model/i ]; var DEGRADED_RESPONSE_PATTERNS = [ /the ai service is temporarily overloaded/i, /service is temporarily overloaded/i, - /please try again in a moment/i, + /please try again in a moment/i ]; var DEGRADED_LOOP_PATTERNS = [ /the boxed is the response\./i, /the response is the text\./i, - /the final answer is the boxed\./i, + /the final answer is the boxed\./i ]; function extractAssistantContent(payload) { if (!payload || typeof payload !== "object") return void 0; @@ -4433,14 +4481,11 @@ function extractAssistantContent(payload) { } function hasKnownLoopSignature(text) { const matchCount = DEGRADED_LOOP_PATTERNS.reduce( - (count, pattern) => (pattern.test(text) ? count + 1 : count), - 0, + (count, pattern) => pattern.test(text) ? count + 1 : count, + 0 ); if (matchCount >= 2) return true; - const lines = text - .split(/\r?\n/) - .map((line) => line.trim()) - .filter(Boolean); + const lines = text.split(/\r?\n/).map((line) => line.trim()).filter(Boolean); if (lines.length < 8) return false; const counts = /* @__PURE__ */ new Map(); for (const line of lines) { @@ -4470,10 +4515,8 @@ function detectDegradedSuccessResponse(body) { errorText = [ typeof errObj.message === "string" ? errObj.message : "", typeof errObj.type === "string" ? errObj.type : "", - typeof errObj.code === "string" ? errObj.code : "", - ] - .filter(Boolean) - .join(" "); + typeof errObj.code === "string" ? errObj.code : "" + ].filter(Boolean).join(" "); } if (errorText && PROVIDER_ERROR_PATTERNS.some((pattern) => pattern.test(errorText))) { return `degraded response: ${errorText.slice(0, 120)}`; @@ -4486,7 +4529,8 @@ function detectDegradedSuccessResponse(body) { if (hasKnownLoopSignature(assistantContent)) { return "degraded response: repetitive assistant loop"; } - } catch {} + } catch { + } return void 0; } var FALLBACK_STATUS_CODES = [ @@ -4508,7 +4552,7 @@ var FALLBACK_STATUS_CODES = [ // Bad gateway 503, // Service unavailable - 504, + 504 // Gateway timeout ]; function isProviderError(status, body) { @@ -4524,7 +4568,7 @@ var VALID_ROLES = /* @__PURE__ */ new Set(["system", "user", "assistant", "tool" var ROLE_MAPPINGS = { developer: "system", // OpenAI's newer API uses "developer" for system messages - model: "assistant", + model: "assistant" // Some APIs use "model" instead of "assistant" }; var VALID_TOOL_ID_PATTERN = /^[a-zA-Z0-9_-]+$/; @@ -4574,11 +4618,7 @@ function sanitizeToolIds(messages) { newBlock = { ...newBlock, id: sanitized2 }; } } - if ( - block.type === "tool_result" && - block.tool_use_id && - typeof block.tool_use_id === "string" - ) { + if (block.type === "tool_result" && block.tool_use_id && typeof block.tool_use_id === "string") { const sanitized2 = sanitizeToolId(block.tool_use_id); if (sanitized2 !== block.tool_use_id) { blockChanged = true; @@ -4634,7 +4674,7 @@ function normalizeMessagesForGoogle(messages) { const normalized = [...messages]; normalized.splice(firstNonSystemIdx, 0, { role: "user", - content: "(continuing conversation)", + content: "(continuing conversation)" }); return normalized; } @@ -4650,10 +4690,8 @@ function normalizeMessagesForThinking(messages) { if (msg.role !== "assistant" || msg.reasoning_content !== void 0) { return msg; } - const hasOpenAIToolCalls = - msg.tool_calls && Array.isArray(msg.tool_calls) && msg.tool_calls.length > 0; - const hasAnthropicToolUse = - Array.isArray(msg.content) && msg.content.some((block) => block?.type === "tool_use"); + const hasOpenAIToolCalls = msg.tool_calls && Array.isArray(msg.tool_calls) && msg.tool_calls.length > 0; + const hasAnthropicToolUse = Array.isArray(msg.content) && msg.content.some((block) => block?.type === "tool_use"); if (hasOpenAIToolCalls || hasAnthropicToolUse) { hasChanges = true; return { ...msg, reasoning_content: "" }; @@ -4668,7 +4706,7 @@ function truncateMessages(messages) { messages, wasTruncated: false, originalCount: messages?.length ?? 0, - truncatedCount: messages?.length ?? 0, + truncatedCount: messages?.length ?? 0 }; } const systemMsgs = messages.filter((m) => m.role === "system"); @@ -4677,20 +4715,19 @@ function truncateMessages(messages) { const truncatedConversation = conversationMsgs.slice(-maxConversation); const result = [...systemMsgs, ...truncatedConversation]; console.log( - `[ClawRouter] Truncated messages: ${messages.length} \u2192 ${result.length} (kept ${systemMsgs.length} system + ${truncatedConversation.length} recent)`, + `[ClawRouter] Truncated messages: ${messages.length} \u2192 ${result.length} (kept ${systemMsgs.length} system + ${truncatedConversation.length} recent)` ); return { messages: result, wasTruncated: true, originalCount: messages.length, - truncatedCount: result.length, + truncatedCount: result.length }; } var KIMI_BLOCK_RE = /<[||][^<>]*begin[^<>]*[||]>[\s\S]*?<[||][^<>]*end[^<>]*[||]>/gi; var KIMI_TOKEN_RE = /<[||][^<>]*[||]>/g; var THINKING_TAG_RE = /<\s*\/?\s*(?:think(?:ing)?|thought|antthinking)\b[^>]*>/gi; -var THINKING_BLOCK_RE = - /<\s*(?:think(?:ing)?|thought|antthinking)\b[^>]*>[\s\S]*?<\s*\/\s*(?:think(?:ing)?|thought|antthinking)\s*>/gi; +var THINKING_BLOCK_RE = /<\s*(?:think(?:ing)?|thought|antthinking)\b[^>]*>[\s\S]*?<\s*\/\s*(?:think(?:ing)?|thought|antthinking)\s*>/gi; function stripThinkingTokens(content) { if (!content) return content; let cleaned = content.replace(KIMI_BLOCK_RE, ""); @@ -4717,7 +4754,7 @@ function buildProxyModelList(createdAt = Math.floor(Date.now() / 1e3)) { id: model.id, object: "model", created: createdAt, - owned_by: model.id.includes("/") ? (model.id.split("/")[0] ?? "blockrun") : "blockrun", + owned_by: model.id.includes("/") ? model.id.split("/")[0] ?? "blockrun" : "blockrun" })); } function mergeRoutingConfig(overrides) { @@ -4728,7 +4765,7 @@ function mergeRoutingConfig(overrides) { classifier: { ...DEFAULT_ROUTING_CONFIG.classifier, ...overrides.classifier }, scoring: { ...DEFAULT_ROUTING_CONFIG.scoring, ...overrides.scoring }, tiers: { ...DEFAULT_ROUTING_CONFIG.tiers, ...overrides.tiers }, - overrides: { ...DEFAULT_ROUTING_CONFIG.overrides, ...overrides.overrides }, + overrides: { ...DEFAULT_ROUTING_CONFIG.overrides, ...overrides.overrides } }; } function estimateAmount(modelId, bodyLength, maxTokens) { @@ -4736,9 +4773,7 @@ function estimateAmount(modelId, bodyLength, maxTokens) { if (!model) return void 0; const estimatedInputTokens = Math.ceil(bodyLength / 4); const estimatedOutputTokens = maxTokens || model.maxOutput || 4096; - const costUsd = - (estimatedInputTokens / 1e6) * model.inputPrice + - (estimatedOutputTokens / 1e6) * model.outputPrice; + const costUsd = estimatedInputTokens / 1e6 * model.inputPrice + estimatedOutputTokens / 1e6 * model.outputPrice; const amountMicros = Math.max(1e3, Math.ceil(costUsd * 1.2 * 1e6)); return amountMicros.toString(); } @@ -4752,12 +4787,7 @@ async function proxyPartnerRequest(req, res, apiBase, payFetch) { const body = Buffer.concat(bodyChunks); const headers = {}; for (const [key, value] of Object.entries(req.headers)) { - if ( - key === "host" || - key === "connection" || - key === "transfer-encoding" || - key === "content-length" - ) + if (key === "host" || key === "connection" || key === "transfer-encoding" || key === "content-length") continue; if (typeof value === "string") headers[key] = value; } @@ -4767,7 +4797,7 @@ async function proxyPartnerRequest(req, res, apiBase, payFetch) { const upstream = await payFetch(upstreamUrl, { method: req.method ?? "POST", headers, - body: body.length > 0 ? new Uint8Array(body) : void 0, + body: body.length > 0 ? new Uint8Array(body) : void 0 }); const responseHeaders = {}; upstream.headers.forEach((value, key) => { @@ -4776,22 +4806,16 @@ async function proxyPartnerRequest(req, res, apiBase, payFetch) { }); res.writeHead(upstream.status, responseHeaders); if (upstream.body) { - const reader = upstream.body.getReader(); - try { - while (true) { - const { done, value } = await reader.read(); - if (done) break; - safeWrite(res, Buffer.from(value)); - } - } finally { - reader.releaseLock(); + const chunks = await readBodyWithTimeout(upstream.body, ERROR_BODY_READ_TIMEOUT_MS); + for (const chunk of chunks) { + safeWrite(res, Buffer.from(chunk)); } } res.end(); const latencyMs = Date.now() - startTime; console.log(`[ClawRouter] Partner response: ${upstream.status} (${latencyMs}ms)`); logUsage({ - timestamp: /* @__PURE__ */ new Date().toISOString(), + timestamp: (/* @__PURE__ */ new Date()).toISOString(), model: "partner", tier: "PARTNER", cost: 0, @@ -4799,44 +4823,52 @@ async function proxyPartnerRequest(req, res, apiBase, payFetch) { baselineCost: 0, savings: 0, latencyMs, - partnerId: - (req.url?.split("?")[0] ?? "").replace(/^\/v1\//, "").replace(/\//g, "_") || "unknown", - service: "partner", - }).catch(() => {}); + partnerId: (req.url?.split("?")[0] ?? "").replace(/^\/v1\//, "").replace(/\//g, "_") || "unknown", + service: "partner" + }).catch(() => { + }); } async function uploadDataUriToHost(dataUri) { const match = dataUri.match(/^data:(image\/\w+);base64,(.+)$/); if (!match) throw new Error("Invalid data URI format"); const [, mimeType, b64Data] = match; - const ext = mimeType === "image/jpeg" ? "jpg" : (mimeType.split("/")[1] ?? "png"); + const ext = mimeType === "image/jpeg" ? "jpg" : mimeType.split("/")[1] ?? "png"; const buffer = Buffer.from(b64Data, "base64"); const blob = new Blob([buffer], { type: mimeType }); const form = new FormData(); form.append("reqtype", "fileupload"); form.append("fileToUpload", blob, `image.${ext}`); - const resp = await fetch("https://catbox.moe/user/api.php", { - method: "POST", - body: form, - }); - if (!resp.ok) throw new Error(`catbox.moe upload failed: HTTP ${resp.status}`); - const result = await resp.text(); - if (result.startsWith("https://")) { - return result.trim(); + const uploadController = new AbortController(); + const uploadTimeout = setTimeout(() => uploadController.abort(), 3e4); + try { + const resp = await fetch("https://catbox.moe/user/api.php", { + method: "POST", + body: form, + signal: uploadController.signal + }); + if (!resp.ok) throw new Error(`catbox.moe upload failed: HTTP ${resp.status}`); + const result = await resp.text(); + if (result.startsWith("https://")) { + return result.trim(); + } + throw new Error(`catbox.moe upload failed: ${result}`); + } finally { + clearTimeout(uploadTimeout); } - throw new Error(`catbox.moe upload failed: ${result}`); } async function startProxy(options) { const walletKey2 = typeof options.wallet === "string" ? options.wallet : options.wallet.key; - const solanaPrivateKeyBytes = - typeof options.wallet === "string" ? void 0 : options.wallet.solanaPrivateKeyBytes; - const paymentChain = options.paymentChain ?? (await resolvePaymentChain()); - const apiBase = - options.apiBase ?? - (paymentChain === "solana" && solanaPrivateKeyBytes ? BLOCKRUN_SOLANA_API : BLOCKRUN_API); + const solanaPrivateKeyBytes = typeof options.wallet === "string" ? void 0 : options.wallet.solanaPrivateKeyBytes; + const paymentChain = options.paymentChain ?? await resolvePaymentChain(); + const apiBase = options.apiBase ?? (paymentChain === "solana" && solanaPrivateKeyBytes ? BLOCKRUN_SOLANA_API : BLOCKRUN_API); if (paymentChain === "solana" && !solanaPrivateKeyBytes) { console.warn( - `[ClawRouter] Payment chain is Solana but no Solana keys provided. Using Base (EVM).`, + `[ClawRouter] \u26A0 Payment chain is Solana but no mnemonic found \u2014 falling back to Base (EVM).` ); + console.warn( + `[ClawRouter] To fix: run "npx @blockrun/clawrouter wallet recover" if your mnemonic exists,` + ); + console.warn(`[ClawRouter] or run "npx @blockrun/clawrouter chain base" to switch to EVM.`); } else if (paymentChain === "solana") { console.log(`[ClawRouter] Payment chain: Solana (${BLOCKRUN_SOLANA_API})`); } @@ -4847,21 +4879,21 @@ async function startProxy(options) { const baseUrl2 = `http://127.0.0.1:${listenPort}`; if (existingProxy.wallet !== account2.address) { console.warn( - `[ClawRouter] Existing proxy on port ${listenPort} uses wallet ${existingProxy.wallet}, but current config uses ${account2.address}. Reusing existing proxy.`, + `[ClawRouter] Existing proxy on port ${listenPort} uses wallet ${existingProxy.wallet}, but current config uses ${account2.address}. Reusing existing proxy.` ); } if (existingProxy.paymentChain) { if (existingProxy.paymentChain !== paymentChain) { throw new Error( - `Existing proxy on port ${listenPort} is using ${existingProxy.paymentChain} but ${paymentChain} was requested. Stop the existing proxy first or use a different port.`, + `Existing proxy on port ${listenPort} is using ${existingProxy.paymentChain} but ${paymentChain} was requested. Stop the existing proxy first or use a different port.` ); } } else if (paymentChain !== "base") { console.warn( - `[ClawRouter] Existing proxy on port ${listenPort} does not report paymentChain (pre-v0.11 instance). Assuming Base.`, + `[ClawRouter] Existing proxy on port ${listenPort} does not report paymentChain (pre-v0.11 instance). Assuming Base.` ); throw new Error( - `Existing proxy on port ${listenPort} is a pre-v0.11 instance (assumed Base) but ${paymentChain} was requested. Stop the existing proxy first or use a different port.`, + `Existing proxy on port ${listenPort} is a pre-v0.11 instance (assumed Base) but ${paymentChain} was requested. Stop the existing proxy first or use a different port.` ); } let reuseSolanaAddress; @@ -4870,10 +4902,7 @@ async function startProxy(options) { const solanaSigner = await createKeyPairSignerFromPrivateKeyBytes(solanaPrivateKeyBytes); reuseSolanaAddress = solanaSigner.address; } - const balanceMonitor2 = - paymentChain === "solana" && reuseSolanaAddress - ? new SolanaBalanceMonitor(reuseSolanaAddress) - : new BalanceMonitor(account2.address); + const balanceMonitor2 = paymentChain === "solana" && reuseSolanaAddress ? new SolanaBalanceMonitor(reuseSolanaAddress) : new BalanceMonitor(account2.address); options.onReady?.(listenPort); return { port: listenPort, @@ -4881,7 +4910,8 @@ async function startProxy(options) { walletAddress: existingProxy.wallet, solanaAddress: reuseSolanaAddress, balanceMonitor: balanceMonitor2, - close: async () => {}, + close: async () => { + } }; } const account = privateKeyToAccount3(walletKey2); @@ -4900,25 +4930,18 @@ async function startProxy(options) { } x402.onAfterPaymentCreation(async (context) => { const network = context.selectedRequirements.network; - const chain = network.startsWith("eip155") - ? "Base (EVM)" - : network.startsWith("solana") - ? "Solana" - : network; + const chain = network.startsWith("eip155") ? "Base (EVM)" : network.startsWith("solana") ? "Solana" : network; console.log(`[ClawRouter] Payment signed on ${chain} (${network})`); }); const payFetch = createPayFetchWithPreAuth(fetch, x402, void 0, { - skipPreAuth: paymentChain === "solana", + skipPreAuth: paymentChain === "solana" }); - const balanceMonitor = - paymentChain === "solana" && solanaAddress - ? new SolanaBalanceMonitor(solanaAddress) - : new BalanceMonitor(account.address); + const balanceMonitor = paymentChain === "solana" && solanaAddress ? new SolanaBalanceMonitor(solanaAddress) : new BalanceMonitor(account.address); const routingConfig = mergeRoutingConfig(options.routingConfig); const modelPricing2 = buildModelPricing(); const routerOpts2 = { config: routingConfig, - modelPricing: modelPricing2, + modelPricing: modelPricing2 }; const deduplicator = new RequestDeduplicator(); const responseCache = new ResponseCache(options.cacheConfig); @@ -4948,7 +4971,7 @@ async function startProxy(options) { const response = { status: "ok", wallet: account.address, - paymentChain, + paymentChain }; if (solanaAddress) { response.solana = solanaAddress; @@ -4971,11 +4994,26 @@ async function startProxy(options) { const stats = responseCache.getStats(); res.writeHead(200, { "Content-Type": "application/json", - "Cache-Control": "no-cache", + "Cache-Control": "no-cache" }); res.end(JSON.stringify(stats, null, 2)); return; } + if (req.url === "/stats" && req.method === "DELETE") { + try { + const result = await clearStats(); + res.writeHead(200, { "Content-Type": "application/json" }); + res.end(JSON.stringify({ cleared: true, deletedFiles: result.deletedFiles })); + } catch (err) { + res.writeHead(500, { "Content-Type": "application/json" }); + res.end( + JSON.stringify({ + error: `Failed to clear stats: ${err instanceof Error ? err.message : String(err)}` + }) + ); + } + return; + } if (req.url === "/stats" || req.url?.startsWith("/stats?")) { try { const url = new URL(req.url, "http://localhost"); @@ -4983,15 +5021,15 @@ async function startProxy(options) { const stats = await getStats(Math.min(days, 30)); res.writeHead(200, { "Content-Type": "application/json", - "Cache-Control": "no-cache", + "Cache-Control": "no-cache" }); res.end(JSON.stringify(stats, null, 2)); } catch (err) { res.writeHead(500, { "Content-Type": "application/json" }); res.end( JSON.stringify({ - error: `Failed to get stats: ${err instanceof Error ? err.message : String(err)}`, - }), + error: `Failed to get stats: ${err instanceof Error ? err.message : String(err)}` + }) ); } return; @@ -5002,6 +5040,81 @@ async function startProxy(options) { res.end(JSON.stringify({ object: "list", data: models })); return; } + if (req.url?.startsWith("/images/") && req.method === "GET") { + const filename = req.url.slice("/images/".length).split("?")[0].replace(/[^a-zA-Z0-9._-]/g, ""); + if (!filename) { + res.writeHead(400); + res.end("Bad request"); + return; + } + const filePath = join5(IMAGE_DIR, filename); + try { + const s = await fsStat(filePath); + if (!s.isFile()) throw new Error("not a file"); + const ext = filename.split(".").pop()?.toLowerCase() ?? "png"; + const mime = { png: "image/png", jpg: "image/jpeg", jpeg: "image/jpeg", webp: "image/webp", gif: "image/gif" }; + const data = await readFile(filePath); + res.writeHead(200, { "Content-Type": mime[ext] ?? "application/octet-stream", "Content-Length": data.length }); + res.end(data); + } catch { + res.writeHead(404, { "Content-Type": "application/json" }); + res.end(JSON.stringify({ error: "Image not found" })); + } + return; + } + if (req.url === "/v1/images/generations" && req.method === "POST") { + const chunks = []; + for await (const chunk of req) { + chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)); + } + const reqBody = Buffer.concat(chunks); + try { + const upstream = await payFetch(`${apiBase}/v1/images/generations`, { + method: "POST", + headers: { "content-type": "application/json", "user-agent": USER_AGENT }, + body: reqBody + }); + const text = await upstream.text(); + if (!upstream.ok) { + res.writeHead(upstream.status, { "Content-Type": "application/json" }); + res.end(text); + return; + } + let result; + try { + result = JSON.parse(text); + } catch { + res.writeHead(200, { "Content-Type": "application/json" }); + res.end(text); + return; + } + if (result.data?.length) { + await mkdir3(IMAGE_DIR, { recursive: true }); + const port2 = server.address()?.port ?? 8402; + for (const img of result.data) { + const m = img.url?.match(/^data:(image\/\w+);base64,(.+)$/); + if (m) { + const [, mimeType, b64] = m; + const ext = mimeType === "image/jpeg" ? "jpg" : mimeType.split("/")[1] ?? "png"; + const filename = `${Date.now()}-${Math.random().toString(36).slice(2, 10)}.${ext}`; + await writeFile2(join5(IMAGE_DIR, filename), Buffer.from(b64, "base64")); + img.url = `http://localhost:${port2}/images/${filename}`; + console.log(`[ClawRouter] Image saved \u2192 ${img.url}`); + } + } + } + res.writeHead(200, { "Content-Type": "application/json" }); + res.end(JSON.stringify(result)); + } catch (err) { + const msg = err instanceof Error ? err.message : String(err); + console.error(`[ClawRouter] Image generation error: ${msg}`); + if (!res.headersSent) { + res.writeHead(502, { "Content-Type": "application/json" }); + res.end(JSON.stringify({ error: "Image generation failed", details: msg })); + } + } + return; + } if (req.url?.match(/^\/v1\/(?:x|partner)\//)) { try { await proxyPartnerRequest(req, res, apiBase, payFetch); @@ -5012,8 +5125,8 @@ async function startProxy(options) { res.writeHead(502, { "Content-Type": "application/json" }); res.end( JSON.stringify({ - error: { message: `Partner proxy error: ${error.message}`, type: "partner_error" }, - }), + error: { message: `Partner proxy error: ${error.message}`, type: "partner_error" } + }) ); } } @@ -5036,7 +5149,7 @@ async function startProxy(options) { balanceMonitor, sessionStore, responseCache, - sessionJournal, + sessionJournal ); } catch (err) { const error = err instanceof Error ? err : new Error(String(err)); @@ -5045,14 +5158,14 @@ async function startProxy(options) { res.writeHead(502, { "Content-Type": "application/json" }); res.end( JSON.stringify({ - error: { message: `Proxy error: ${error.message}`, type: "proxy_error" }, - }), + error: { message: `Proxy error: ${error.message}`, type: "proxy_error" } + }) ); } else if (!res.writableEnded) { res.write( `data: ${JSON.stringify({ error: { message: error.message, type: "proxy_error" } })} -`, +` ); res.write("data: [DONE]\n\n"); res.end(); @@ -5070,19 +5183,19 @@ async function startProxy(options) { rejectAttempt({ code: "REUSE_EXISTING", wallet: existingProxy2.wallet, - existingChain: existingProxy2.paymentChain, + existingChain: existingProxy2.paymentChain }); return; } if (attempt < PORT_RETRY_ATTEMPTS) { console.log( - `[ClawRouter] Port ${listenPort} in TIME_WAIT, retrying in ${PORT_RETRY_DELAY_MS}ms (attempt ${attempt}/${PORT_RETRY_ATTEMPTS})`, + `[ClawRouter] Port ${listenPort} in TIME_WAIT, retrying in ${PORT_RETRY_DELAY_MS}ms (attempt ${attempt}/${PORT_RETRY_ATTEMPTS})` ); rejectAttempt({ code: "RETRY", attempt }); return; } console.error( - `[ClawRouter] Port ${listenPort} still in use after ${PORT_RETRY_ATTEMPTS} attempts`, + `[ClawRouter] Port ${listenPort} still in use after ${PORT_RETRY_ATTEMPTS} attempts` ); rejectAttempt(err); return; @@ -5107,6 +5220,7 @@ async function startProxy(options) { if (error.existingChain && error.existingChain !== paymentChain) { throw new Error( `Existing proxy on port ${listenPort} is using ${error.existingChain} but ${paymentChain} was requested. Stop the existing proxy first or use a different port.`, + { cause: err } ); } const baseUrl2 = `http://127.0.0.1:${listenPort}`; @@ -5116,7 +5230,8 @@ async function startProxy(options) { baseUrl: baseUrl2, walletAddress: error.wallet, balanceMonitor, - close: async () => {}, + close: async () => { + } }; } if (error.code === "RETRY") { @@ -5152,7 +5267,8 @@ async function startProxy(options) { console.error(`[ClawRouter] Socket timeout, destroying connection`); socket.destroy(); }); - socket.on("end", () => {}); + socket.on("end", () => { + }); socket.on("error", (err) => { console.error(`[ClawRouter] Socket error: ${err.message}`); }); @@ -5166,38 +5282,27 @@ async function startProxy(options) { walletAddress: account.address, solanaAddress, balanceMonitor, - close: () => - new Promise((res, rej) => { - const timeout = setTimeout(() => { - rej(new Error("[ClawRouter] Close timeout after 4s")); - }, 4e3); - sessionStore.close(); - for (const socket of connections) { - socket.destroy(); + close: () => new Promise((res, rej) => { + const timeout = setTimeout(() => { + rej(new Error("[ClawRouter] Close timeout after 4s")); + }, 4e3); + sessionStore.close(); + for (const socket of connections) { + socket.destroy(); + } + connections.clear(); + server.close((err) => { + clearTimeout(timeout); + if (err) { + rej(err); + } else { + res(); } - connections.clear(); - server.close((err) => { - clearTimeout(timeout); - if (err) { - rej(err); - } else { - res(); - } - }); - }), + }); + }) }; } -async function tryModelRequest( - upstreamUrl, - method, - headers, - body, - modelId, - maxTokens, - payFetch, - balanceMonitor, - signal, -) { +async function tryModelRequest(upstreamUrl, method, headers, body, modelId, maxTokens, payFetch, balanceMonitor, signal) { let requestBody = body; try { const parsed = JSON.parse(body.toString()); @@ -5215,47 +5320,50 @@ async function tryModelRequest( if (isGoogleModel(modelId) && Array.isArray(parsed.messages)) { parsed.messages = normalizeMessagesForGoogle(parsed.messages); } - const hasThinkingEnabled = !!( - parsed.thinking || - parsed.extended_thinking || - isReasoningModel(modelId) - ); + const hasThinkingEnabled = !!(parsed.thinking || parsed.extended_thinking || isReasoningModel(modelId)); if (hasThinkingEnabled && Array.isArray(parsed.messages)) { parsed.messages = normalizeMessagesForThinking(parsed.messages); } requestBody = Buffer.from(JSON.stringify(parsed)); - } catch {} + } catch { + } try { const response = await payFetch(upstreamUrl, { method, headers, body: requestBody.length > 0 ? new Uint8Array(requestBody) : void 0, - signal, + signal }); if (response.status !== 200) { - const errorBody = await response.text(); + const errorBodyChunks = await readBodyWithTimeout(response.body, ERROR_BODY_READ_TIMEOUT_MS); + const errorBody = Buffer.concat(errorBodyChunks).toString(); const isProviderErr = isProviderError(response.status, errorBody); return { success: false, errorBody, errorStatus: response.status, - isProviderError: isProviderErr, + isProviderError: isProviderErr }; } const contentType = response.headers.get("content-type") || ""; if (contentType.includes("json") || contentType.includes("text")) { try { - const responseBody = await response.clone().text(); + const clonedChunks = await readBodyWithTimeout( + response.clone().body, + ERROR_BODY_READ_TIMEOUT_MS + ); + const responseBody = Buffer.concat(clonedChunks).toString(); const degradedReason = detectDegradedSuccessResponse(responseBody); if (degradedReason) { return { success: false, errorBody: degradedReason, errorStatus: 503, - isProviderError: true, + isProviderError: true }; } - } catch {} + } catch { + } } return { success: true, response }; } catch (err) { @@ -5264,24 +5372,12 @@ async function tryModelRequest( success: false, errorBody: errorMsg, errorStatus: 500, - isProviderError: true, + isProviderError: true // Network errors are retryable }; } } -async function proxyRequest( - req, - res, - apiBase, - payFetch, - options, - routerOpts2, - deduplicator, - balanceMonitor, - sessionStore, - responseCache, - sessionJournal, -) { +async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts2, deduplicator, balanceMonitor, sessionStore, responseCache, sessionJournal) { const startTime = Date.now(); const upstreamUrl = `${apiBase}${req.url}`; const bodyChunks = []; @@ -5313,15 +5409,7 @@ async function proxyRequest( const parsedMessages = Array.isArray(parsed.messages) ? parsed.messages : []; const lastUserMsg = [...parsedMessages].reverse().find((m) => m.role === "user"); const rawLastContent = lastUserMsg?.content; - const lastContent = - typeof rawLastContent === "string" - ? rawLastContent - : Array.isArray(rawLastContent) - ? rawLastContent - .filter((b) => b.type === "text") - .map((b) => b.text ?? "") - .join(" ") - : ""; + const lastContent = typeof rawLastContent === "string" ? rawLastContent : Array.isArray(rawLastContent) ? rawLastContent.filter((b) => b.type === "text").map((b) => b.text ?? "").join(" ") : ""; if (sessionId && parsedMessages.length > 0) { const messages = parsedMessages; if (sessionJournal.needsContext(lastContent)) { @@ -5331,7 +5419,7 @@ async function proxyRequest( if (sysIdx >= 0 && typeof messages[sysIdx].content === "string") { messages[sysIdx] = { ...messages[sysIdx], - content: journalText + "\n\n" + messages[sysIdx].content, + content: journalText + "\n\n" + messages[sysIdx].content }; } else { messages.unshift({ role: "system", content: journalText }); @@ -5339,7 +5427,7 @@ async function proxyRequest( parsed.messages = messages; bodyModified = true; console.log( - `[ClawRouter] Injected session journal (${journalText.length} chars) for session ${sessionId.slice(0, 8)}...`, + `[ClawRouter] Injected session journal (${journalText.length} chars) for session ${sessionId.slice(0, 8)}...` ); } } @@ -5351,38 +5439,28 @@ async function proxyRequest( const systemPrompt = typeof systemMsg?.content === "string" ? systemMsg.content : void 0; const fullText = `${systemPrompt ?? ""} ${debugPrompt}`; const estimatedTokens = Math.ceil(fullText.length / 4); - const normalizedModel2 = - typeof parsed.model === "string" ? parsed.model.trim().toLowerCase() : ""; + const normalizedModel2 = typeof parsed.model === "string" ? parsed.model.trim().toLowerCase() : ""; const profileName = normalizedModel2.replace("blockrun/", ""); - const debugProfile = ["free", "eco", "auto", "premium"].includes(profileName) - ? profileName - : "auto"; + const debugProfile = ["free", "eco", "auto", "premium"].includes(profileName) ? profileName : "auto"; const scoring = classifyByRules( debugPrompt, systemPrompt, estimatedTokens, - DEFAULT_ROUTING_CONFIG.scoring, + DEFAULT_ROUTING_CONFIG.scoring ); const debugRouting = route(debugPrompt, systemPrompt, maxTokens, { ...routerOpts2, - routingProfile: debugProfile, + routingProfile: debugProfile }); - const dimLines = (scoring.dimensions ?? []) - .map((d) => { - const nameStr = (d.name + ":").padEnd(24); - const scoreStr = d.score.toFixed(2).padStart(6); - const sigStr = d.signal ? ` [${d.signal}]` : ""; - return ` ${nameStr}${scoreStr}${sigStr}`; - }) - .join("\n"); + const dimLines = (scoring.dimensions ?? []).map((d) => { + const nameStr = (d.name + ":").padEnd(24); + const scoreStr = d.score.toFixed(2).padStart(6); + const sigStr = d.signal ? ` [${d.signal}]` : ""; + return ` ${nameStr}${scoreStr}${sigStr}`; + }).join("\n"); const sess = sessionId ? sessionStore.getSession(sessionId) : void 0; - const sessLine = sess - ? `Session: ${sessionId.slice(0, 8)}... \u2192 pinned: ${sess.model} (${sess.requestCount} requests)` - : sessionId - ? `Session: ${sessionId.slice(0, 8)}... \u2192 no pinned model` - : "Session: none"; - const { simpleMedium, mediumComplex, complexReasoning } = - DEFAULT_ROUTING_CONFIG.scoring.tierBoundaries; + const sessLine = sess ? `Session: ${sessionId.slice(0, 8)}... \u2192 pinned: ${sess.model} (${sess.requestCount} requests)` : sessionId ? `Session: ${sessionId.slice(0, 8)}... \u2192 no pinned model` : "Session: none"; + const { simpleMedium, mediumComplex, complexReasoning } = DEFAULT_ROUTING_CONFIG.scoring.tierBoundaries; const debugText = [ "ClawRouter Debug", "", @@ -5395,7 +5473,7 @@ async function proxyRequest( "", `Tier Boundaries: SIMPLE <${simpleMedium.toFixed(2)} | MEDIUM <${mediumComplex.toFixed(2)} | COMPLEX <${complexReasoning.toFixed(2)} | REASONING >=${complexReasoning.toFixed(2)}`, "", - sessLine, + sessLine ].join("\n"); const completionId = `chatcmpl-debug-${Date.now()}`; const timestamp = Math.floor(Date.now() / 1e3); @@ -5408,16 +5486,16 @@ async function proxyRequest( { index: 0, message: { role: "assistant", content: debugText }, - finish_reason: "stop", - }, + finish_reason: "stop" + } ], - usage: { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 }, + usage: { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 } }; if (isStreaming) { res.writeHead(200, { "Content-Type": "text/event-stream", "Cache-Control": "no-cache", - Connection: "keep-alive", + Connection: "keep-alive" }); const sseChunk = { id: completionId, @@ -5428,16 +5506,16 @@ async function proxyRequest( { index: 0, delta: { role: "assistant", content: debugText }, - finish_reason: null, - }, - ], + finish_reason: null + } + ] }; const sseDone = { id: completionId, object: "chat.completion.chunk", created: timestamp, model: "clawrouter/debug", - choices: [{ index: 0, delta: {}, finish_reason: "stop" }], + choices: [{ index: 0, delta: {}, finish_reason: "stop" }] }; res.write(`data: ${JSON.stringify(sseChunk)} @@ -5451,9 +5529,7 @@ async function proxyRequest( res.writeHead(200, { "Content-Type": "application/json" }); res.end(JSON.stringify(syntheticResponse)); } - console.log( - `[ClawRouter] /debug command \u2192 ${debugRouting.tier} | ${debugRouting.model}`, - ); + console.log(`[ClawRouter] /debug command \u2192 ${debugRouting.tier} | ${debugRouting.model}`); return; } if (lastContent.startsWith("/imagegen")) { @@ -5475,7 +5551,7 @@ async function proxyRequest( banana: "google/nano-banana", "nano-banana": "google/nano-banana", "banana-pro": "google/nano-banana-pro", - "nano-banana-pro": "google/nano-banana-pro", + "nano-banana-pro": "google/nano-banana-pro" }; imageModel = IMAGE_MODEL_ALIASES[raw] ?? raw; imagePrompt = imagePrompt.replace(/--model\s+\S+/, "").trim(); @@ -5503,7 +5579,7 @@ async function proxyRequest( "Examples:", " /imagegen a cat wearing sunglasses", " /imagegen --model dall-e-3 a futuristic city at sunset", - " /imagegen --model banana-pro --size 2048x2048 mountain landscape", + " /imagegen --model banana-pro --size 2048x2048 mountain landscape" ].join("\n"); const completionId = `chatcmpl-image-${Date.now()}`; const timestamp = Math.floor(Date.now() / 1e3); @@ -5511,17 +5587,17 @@ async function proxyRequest( res.writeHead(200, { "Content-Type": "text/event-stream", "Cache-Control": "no-cache", - Connection: "keep-alive", + Connection: "keep-alive" }); res.write( `data: ${JSON.stringify({ id: completionId, object: "chat.completion.chunk", created: timestamp, model: "clawrouter/image", choices: [{ index: 0, delta: { role: "assistant", content: errorText }, finish_reason: null }] })} -`, +` ); res.write( `data: ${JSON.stringify({ id: completionId, object: "chat.completion.chunk", created: timestamp, model: "clawrouter/image", choices: [{ index: 0, delta: {}, finish_reason: "stop" }] })} -`, +` ); res.write("data: [DONE]\n\n"); res.end(); @@ -5537,18 +5613,18 @@ async function proxyRequest( { index: 0, message: { role: "assistant", content: errorText }, - finish_reason: "stop", - }, + finish_reason: "stop" + } ], - usage: { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 }, - }), + usage: { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 } + }) ); } console.log(`[ClawRouter] /imagegen command \u2192 showing usage help`); return; } console.log( - `[ClawRouter] /imagegen command \u2192 ${imageModel} (${imageSize}): ${imagePrompt.slice(0, 80)}...`, + `[ClawRouter] /imagegen command \u2192 ${imageModel} (${imageSize}): ${imagePrompt.slice(0, 80)}...` ); try { const imageUpstreamUrl = `${apiBase}/v1/images/generations`; @@ -5556,20 +5632,17 @@ async function proxyRequest( model: imageModel, prompt: imagePrompt, size: imageSize, - n: 1, + n: 1 }); const imageResponse = await payFetch(imageUpstreamUrl, { method: "POST", headers: { "content-type": "application/json", "user-agent": USER_AGENT }, - body: imageBody, + body: imageBody }); const imageResult = await imageResponse.json(); let responseText; if (!imageResponse.ok || imageResult.error) { - const errMsg = - typeof imageResult.error === "string" - ? imageResult.error - : (imageResult.error?.message ?? `HTTP ${imageResponse.status}`); + const errMsg = typeof imageResult.error === "string" ? imageResult.error : imageResult.error?.message ?? `HTTP ${imageResponse.status}`; responseText = `Image generation failed: ${errMsg}`; console.log(`[ClawRouter] /imagegen error: ${errMsg}`); } else { @@ -5586,10 +5659,10 @@ async function proxyRequest( lines.push(hostedUrl); } catch (uploadErr) { console.error( - `[ClawRouter] /imagegen: failed to upload data URI: ${uploadErr instanceof Error ? uploadErr.message : String(uploadErr)}`, + `[ClawRouter] /imagegen: failed to upload data URI: ${uploadErr instanceof Error ? uploadErr.message : String(uploadErr)}` ); lines.push( - "Image generated but upload failed. Try again or use --model dall-e-3.", + "Image generated but upload failed. Try again or use --model dall-e-3." ); } } else { @@ -5609,17 +5682,17 @@ async function proxyRequest( res.writeHead(200, { "Content-Type": "text/event-stream", "Cache-Control": "no-cache", - Connection: "keep-alive", + Connection: "keep-alive" }); res.write( `data: ${JSON.stringify({ id: completionId, object: "chat.completion.chunk", created: timestamp, model: "clawrouter/image", choices: [{ index: 0, delta: { role: "assistant", content: responseText }, finish_reason: null }] })} -`, +` ); res.write( `data: ${JSON.stringify({ id: completionId, object: "chat.completion.chunk", created: timestamp, model: "clawrouter/image", choices: [{ index: 0, delta: {}, finish_reason: "stop" }] })} -`, +` ); res.write("data: [DONE]\n\n"); res.end(); @@ -5635,11 +5708,11 @@ async function proxyRequest( { index: 0, message: { role: "assistant", content: responseText }, - finish_reason: "stop", - }, + finish_reason: "stop" + } ], - usage: { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 }, - }), + usage: { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 } + }) ); } } catch (err) { @@ -5649,8 +5722,8 @@ async function proxyRequest( res.writeHead(500, { "Content-Type": "application/json" }); res.end( JSON.stringify({ - error: { message: `Image generation failed: ${errMsg}`, type: "image_error" }, - }), + error: { message: `Image generation failed: ${errMsg}`, type: "image_error" } + }) ); } } @@ -5660,17 +5733,16 @@ async function proxyRequest( parsed.stream = false; bodyModified = true; } - const normalizedModel = - typeof parsed.model === "string" ? parsed.model.trim().toLowerCase() : ""; + const normalizedModel = typeof parsed.model === "string" ? parsed.model.trim().toLowerCase() : ""; const resolvedModel = resolveModelAlias(normalizedModel); const wasAlias = resolvedModel !== normalizedModel; - const isRoutingProfile = ROUTING_PROFILES.has(normalizedModel); + const isRoutingProfile = ROUTING_PROFILES.has(normalizedModel) || ROUTING_PROFILES.has(resolvedModel); if (isRoutingProfile) { - const profileName = normalizedModel.replace("blockrun/", ""); + const profileName = resolvedModel.replace("blockrun/", ""); routingProfile = profileName; } console.log( - `[ClawRouter] Received model: "${parsed.model}" -> normalized: "${normalizedModel}"${wasAlias ? ` -> alias: "${resolvedModel}"` : ""}${routingProfile ? `, profile: ${routingProfile}` : ""}`, + `[ClawRouter] Received model: "${parsed.model}" -> normalized: "${normalizedModel}"${wasAlias ? ` -> alias: "${resolvedModel}"` : ""}${routingProfile ? `, profile: ${routingProfile}` : ""}` ); if (!isRoutingProfile) { if (parsed.model !== resolvedModel) { @@ -5687,36 +5759,26 @@ async function proxyRequest( modelId = freeModel; bodyModified = true; await logUsage({ - timestamp: /* @__PURE__ */ new Date().toISOString(), + timestamp: (/* @__PURE__ */ new Date()).toISOString(), model: freeModel, tier: "SIMPLE", cost: 0, baselineCost: 0, savings: 1, // 100% savings - latencyMs: 0, + latencyMs: 0 }); } else { effectiveSessionId = getSessionId(req.headers) ?? deriveSessionId(parsedMessages); - const existingSession = effectiveSessionId - ? sessionStore.getSession(effectiveSessionId) - : void 0; + const existingSession = effectiveSessionId ? sessionStore.getSession(effectiveSessionId) : void 0; const rawPrompt = lastUserMsg?.content; - const prompt = - typeof rawPrompt === "string" - ? rawPrompt - : Array.isArray(rawPrompt) - ? rawPrompt - .filter((b) => b.type === "text") - .map((b) => b.text ?? "") - .join(" ") - : ""; + const prompt = typeof rawPrompt === "string" ? rawPrompt : Array.isArray(rawPrompt) ? rawPrompt.filter((b) => b.type === "text").map((b) => b.text ?? "").join(" ") : ""; const systemMsg = parsedMessages.find((m) => m.role === "system"); const systemPrompt = typeof systemMsg?.content === "string" ? systemMsg.content : void 0; const tools = parsed.tools; hasTools = Array.isArray(tools) && tools.length > 0; if (hasTools && tools) { - console.log(`[ClawRouter] Tools detected (${tools.length}), agentic mode via keywords`); + console.log(`[ClawRouter] Tools detected (${tools.length}), forcing agentic tiers`); } hasVision = parsedMessages.some((m) => { if (Array.isArray(m.content)) { @@ -5730,19 +5792,20 @@ async function proxyRequest( routingDecision = route(prompt, systemPrompt, maxTokens, { ...routerOpts2, routingProfile: routingProfile ?? void 0, + hasTools }); if (existingSession) { const tierRank = { SIMPLE: 0, MEDIUM: 1, COMPLEX: 2, - REASONING: 3, + REASONING: 3 }; const existingRank = tierRank[existingSession.tier] ?? 0; const newRank = tierRank[routingDecision.tier] ?? 0; if (newRank > existingRank) { console.log( - `[ClawRouter] Session ${effectiveSessionId?.slice(0, 8)}... upgrading: ${existingSession.tier} \u2192 ${routingDecision.tier} (${routingDecision.model})`, + `[ClawRouter] Session ${effectiveSessionId?.slice(0, 8)}... upgrading: ${existingSession.tier} \u2192 ${routingDecision.tier} (${routingDecision.model})` ); parsed.model = routingDecision.model; modelId = routingDecision.model; @@ -5751,12 +5814,12 @@ async function proxyRequest( sessionStore.setSession( effectiveSessionId, routingDecision.model, - routingDecision.tier, + routingDecision.tier ); } } else { console.log( - `[ClawRouter] Session ${effectiveSessionId?.slice(0, 8)}... keeping pinned model: ${existingSession.model} (${existingSession.tier} >= ${routingDecision.tier})`, + `[ClawRouter] Session ${effectiveSessionId?.slice(0, 8)}... keeping pinned model: ${existingSession.model} (${existingSession.tier} >= ${routingDecision.tier})` ); parsed.model = existingSession.model; modelId = existingSession.model; @@ -5765,24 +5828,17 @@ async function proxyRequest( routingDecision = { ...routingDecision, model: existingSession.model, - tier: existingSession.tier, + tier: existingSession.tier }; } - const lastAssistantMsg = [...parsedMessages] - .reverse() - .find((m) => m.role === "assistant"); + const lastAssistantMsg = [...parsedMessages].reverse().find((m) => m.role === "assistant"); const assistantToolCalls = lastAssistantMsg?.tool_calls; - const toolCallNames = Array.isArray(assistantToolCalls) - ? assistantToolCalls.map((tc) => tc.function?.name).filter((n) => Boolean(n)) - : void 0; + const toolCallNames = Array.isArray(assistantToolCalls) ? assistantToolCalls.map((tc) => tc.function?.name).filter((n) => Boolean(n)) : void 0; const contentHash = hashRequestContent(prompt, toolCallNames); const shouldEscalate = sessionStore.recordRequestHash(effectiveSessionId, contentHash); if (shouldEscalate) { const activeTierConfigs = (() => { - if ( - routingDecision.reasoning?.includes("agentic") && - routerOpts2.config.agenticTiers - ) { + if (routingDecision.reasoning?.includes("agentic") && routerOpts2.config.agenticTiers) { return routerOpts2.config.agenticTiers; } if (routingProfile === "eco" && routerOpts2.config.ecoTiers) { @@ -5795,18 +5851,18 @@ async function proxyRequest( })(); const escalation = sessionStore.escalateSession( effectiveSessionId, - activeTierConfigs, + activeTierConfigs ); if (escalation) { console.log( - `[ClawRouter] \u26A1 3-strike escalation: ${existingSession.model} \u2192 ${escalation.model} (${existingSession.tier} \u2192 ${escalation.tier})`, + `[ClawRouter] \u26A1 3-strike escalation: ${existingSession.model} \u2192 ${escalation.model} (${existingSession.tier} \u2192 ${escalation.tier})` ); parsed.model = escalation.model; modelId = escalation.model; routingDecision = { ...routingDecision, model: escalation.model, - tier: escalation.tier, + tier: escalation.tier }; } } @@ -5818,10 +5874,10 @@ async function proxyRequest( sessionStore.setSession( effectiveSessionId, routingDecision.model, - routingDecision.tier, + routingDecision.tier ); console.log( - `[ClawRouter] Session ${effectiveSessionId.slice(0, 8)}... pinned to model: ${routingDecision.model}`, + `[ClawRouter] Session ${effectiveSessionId.slice(0, 8)}... pinned to model: ${routingDecision.model}` ); } } @@ -5844,7 +5900,7 @@ async function proxyRequest( if (autoCompress && requestSizeKB > compressionThreshold) { try { console.log( - `[ClawRouter] Request size ${requestSizeKB}KB exceeds threshold ${compressionThreshold}KB, applying compression...`, + `[ClawRouter] Request size ${requestSizeKB}KB exceeds threshold ${compressionThreshold}KB, applying compression...` ); const parsed = JSON.parse(body.toString()); if (parsed.messages && parsed.messages.length > 0 && shouldCompress(parsed.messages)) { @@ -5865,26 +5921,26 @@ async function proxyRequest( // Safe: just removes JSON whitespace observation: false, // Disabled: may lose important context - dynamicCodebook: false, + dynamicCodebook: false // Disabled: requires model to understand codes }, dictionary: { maxEntries: 50, minPhraseLength: 15, - includeCodebookHeader: false, - }, + includeCodebookHeader: false + } }); const compressedSizeKB = Math.ceil(compressionResult.compressedChars / 1024); - const savings = (((requestSizeKB - compressedSizeKB) / requestSizeKB) * 100).toFixed(1); + const savings = ((requestSizeKB - compressedSizeKB) / requestSizeKB * 100).toFixed(1); console.log( - `[ClawRouter] Compressed ${requestSizeKB}KB \u2192 ${compressedSizeKB}KB (${savings}% reduction)`, + `[ClawRouter] Compressed ${requestSizeKB}KB \u2192 ${compressedSizeKB}KB (${savings}% reduction)` ); parsed.messages = compressionResult.messages; body = Buffer.from(JSON.stringify(parsed)); } } catch (err) { console.warn( - `[ClawRouter] Compression failed: ${err instanceof Error ? err.message : String(err)}`, + `[ClawRouter] Compression failed: ${err instanceof Error ? err.message : String(err)}` ); } } @@ -5924,33 +5980,30 @@ async function proxyRequest( const estimated = estimateAmount(modelId, body.length, maxTokens); if (estimated) { estimatedCostMicros = BigInt(estimated); - const bufferedCostMicros = - (estimatedCostMicros * BigInt(Math.ceil(BALANCE_CHECK_BUFFER * 100))) / 100n; + const bufferedCostMicros = estimatedCostMicros * BigInt(Math.ceil(BALANCE_CHECK_BUFFER * 100)) / 100n; const sufficiency = await balanceMonitor.checkSufficient(bufferedCostMicros); if (sufficiency.info.isEmpty || !sufficiency.sufficient) { const originalModel = modelId; console.log( - `[ClawRouter] Wallet ${sufficiency.info.isEmpty ? "empty" : "insufficient"} (${sufficiency.info.balanceUSD}), falling back to free model: ${FREE_MODEL} (requested: ${originalModel})`, + `[ClawRouter] Wallet ${sufficiency.info.isEmpty ? "empty" : "insufficient"} (${sufficiency.info.balanceUSD}), falling back to free model: ${FREE_MODEL} (requested: ${originalModel})` ); modelId = FREE_MODEL; const parsed = JSON.parse(body.toString()); parsed.model = FREE_MODEL; body = Buffer.from(JSON.stringify(parsed)); - balanceFallbackNotice = sufficiency.info.isEmpty - ? `> **\u26A0\uFE0F Wallet empty** \u2014 using free model. Fund your wallet to use ${originalModel}. + balanceFallbackNotice = sufficiency.info.isEmpty ? `> **\u26A0\uFE0F Wallet empty** \u2014 using free model. Fund your wallet to use ${originalModel}. -` - : `> **\u26A0\uFE0F Insufficient balance** (${sufficiency.info.balanceUSD}) \u2014 using free model instead of ${originalModel}. +` : `> **\u26A0\uFE0F Insufficient balance** (${sufficiency.info.balanceUSD}) \u2014 using free model instead of ${originalModel}. `; options.onLowBalance?.({ balanceUSD: sufficiency.info.balanceUSD, - walletAddress: sufficiency.info.walletAddress, + walletAddress: sufficiency.info.walletAddress }); } else if (sufficiency.info.isLow) { options.onLowBalance?.({ balanceUSD: sufficiency.info.balanceUSD, - walletAddress: sufficiency.info.walletAddress, + walletAddress: sufficiency.info.walletAddress }); } } @@ -5963,7 +6016,7 @@ async function proxyRequest( "cache-control": "no-cache", connection: "keep-alive", "x-context-used-kb": String(originalContextSizeKB), - "x-context-limit-kb": String(CONTEXT_LIMIT_KB), + "x-context-limit-kb": String(CONTEXT_LIMIT_KB) }); headersSentEarly = true; safeWrite(res, ": heartbeat\n\n"); @@ -5978,12 +6031,7 @@ async function proxyRequest( } const headers = {}; for (const [key, value] of Object.entries(req.headers)) { - if ( - key === "host" || - key === "connection" || - key === "transfer-encoding" || - key === "content-length" - ) + if (key === "host" || key === "connection" || key === "transfer-encoding" || key === "content-length") continue; if (typeof value === "string") { headers[key] = value; @@ -6028,26 +6076,41 @@ async function proxyRequest( routingDecision.tier, tierConfigs, estimatedTotalTokens, - getModelContextWindow, + getModelContextWindow ); const contextExcluded = fullChain.filter((m) => !contextFiltered.includes(m)); if (contextExcluded.length > 0) { console.log( - `[ClawRouter] Context filter (~${estimatedTotalTokens} tokens): excluded ${contextExcluded.join(", ")}`, + `[ClawRouter] Context filter (~${estimatedTotalTokens} tokens): excluded ${contextExcluded.join(", ")}` ); } - const toolFiltered = filterByToolCalling(contextFiltered, hasTools, supportsToolCalling); + let toolFiltered = filterByToolCalling(contextFiltered, hasTools, supportsToolCalling); const toolExcluded = contextFiltered.filter((m) => !toolFiltered.includes(m)); if (toolExcluded.length > 0) { console.log( - `[ClawRouter] Tool-calling filter: excluded ${toolExcluded.join(", ")} (no structured function call support)`, + `[ClawRouter] Tool-calling filter: excluded ${toolExcluded.join(", ")} (no structured function call support)` ); } + const TOOL_NONCOMPLIANT_MODELS = [ + "google/gemini-2.5-flash-lite", + "google/gemini-3-pro-preview", + "google/gemini-3.1-pro" + ]; + if (hasTools && toolFiltered.length > 1) { + const compliant = toolFiltered.filter((m) => !TOOL_NONCOMPLIANT_MODELS.includes(m)); + if (compliant.length > 0 && compliant.length < toolFiltered.length) { + const dropped = toolFiltered.filter((m) => TOOL_NONCOMPLIANT_MODELS.includes(m)); + console.log( + `[ClawRouter] Tool-compliance filter: excluded ${dropped.join(", ")} (unreliable tool schema handling)` + ); + toolFiltered = compliant; + } + } const visionFiltered = filterByVision(toolFiltered, hasVision, supportsVision); const visionExcluded = toolFiltered.filter((m) => !visionFiltered.includes(m)); if (visionExcluded.length > 0) { console.log( - `[ClawRouter] Vision filter: excluded ${visionExcluded.join(", ")} (no vision support)`, + `[ClawRouter] Vision filter: excluded ${visionExcluded.join(", ")} (no vision support)` ); } modelsToTry = visionFiltered.slice(0, MAX_FALLBACK_ATTEMPTS); @@ -6074,7 +6137,7 @@ async function proxyRequest( maxTokens, payFetch, balanceMonitor, - controller.signal, + controller.signal ); if (result.success && result.response) { upstream = result.response; @@ -6084,14 +6147,28 @@ async function proxyRequest( } lastError = { body: result.errorBody || "Unknown error", - status: result.errorStatus || 500, + status: result.errorStatus || 500 }; if (result.isProviderError && !isLastAttempt) { if (result.errorStatus === 429) { markRateLimited(tryModel); + try { + const parsed = JSON.parse(result.errorBody || "{}"); + if (parsed.update_available) { + console.log(""); + console.log( + `\x1B[33m\u2B06\uFE0F ClawRouter ${parsed.update_available} available (you have ${VERSION})\x1B[0m` + ); + console.log( + ` Run: \x1B[36mcurl -fsSL ${parsed.update_url || "https://blockrun.ai/ClawRouter-update"} | bash\x1B[0m` + ); + console.log(""); + } + } catch { + } } - const isPaymentErr = /payment.*verification.*failed|insufficient.*funds/i.test( - result.errorBody || "", + const isPaymentErr = /payment.*verification.*failed|payment.*settlement.*failed|insufficient.*funds|transaction_simulation_failed/i.test( + result.errorBody || "" ); if (isPaymentErr && tryModel !== FREE_MODEL) { const freeIdx = modelsToTry.indexOf(FREE_MODEL); @@ -6102,13 +6179,13 @@ async function proxyRequest( } } console.log( - `[ClawRouter] Provider error from ${tryModel}, trying fallback: ${result.errorBody?.slice(0, 100)}`, + `[ClawRouter] Provider error from ${tryModel}, trying fallback: ${result.errorBody?.slice(0, 100)}` ); continue; } if (!result.isProviderError) { console.log( - `[ClawRouter] Non-provider error from ${tryModel}, not retrying: ${result.errorBody?.slice(0, 100)}`, + `[ClawRouter] Non-provider error from ${tryModel}, not retrying: ${result.errorBody?.slice(0, 100)}` ); } break; @@ -6131,7 +6208,7 @@ async function proxyRequest( routerOpts2.modelPricing, estimatedInputTokens, maxTokens, - routingProfile ?? void 0, + routingProfile ?? void 0 ); routingDecision = { ...routingDecision, @@ -6139,13 +6216,13 @@ async function proxyRequest( reasoning: `${routingDecision.reasoning} | fallback to ${actualModelUsed}`, costEstimate: newCosts.costEstimate, baselineCost: newCosts.baselineCost, - savings: newCosts.savings, + savings: newCosts.savings }; options.onRouted?.(routingDecision); if (effectiveSessionId) { sessionStore.setSession(effectiveSessionId, actualModelUsed, routingDecision.tier); console.log( - `[ClawRouter] Session ${effectiveSessionId.slice(0, 8)}... updated pin to fallback: ${actualModelUsed}`, + `[ClawRouter] Session ${effectiveSessionId.slice(0, 8)}... updated pin to fallback: ${actualModelUsed}` ); } } @@ -6160,7 +6237,7 @@ async function proxyRequest( errPayload = JSON.stringify(parsed); } catch { errPayload = JSON.stringify({ - error: { message: rawErrBody, type: "provider_error", status: errStatus }, + error: { message: rawErrBody, type: "provider_error", status: errStatus } }); } const errEvent = `data: ${errPayload} @@ -6174,20 +6251,20 @@ async function proxyRequest( status: 200, headers: { "content-type": "text/event-stream" }, body: errBuf, - completedAt: Date.now(), + completedAt: Date.now() }); } else { res.writeHead(errStatus, { "Content-Type": "application/json", "x-context-used-kb": String(originalContextSizeKB), - "x-context-limit-kb": String(CONTEXT_LIMIT_KB), + "x-context-limit-kb": String(CONTEXT_LIMIT_KB) }); res.end(transformedErr); deduplicator.complete(dedupKey, { status: errStatus, headers: { "content-type": "application/json" }, body: Buffer.from(transformedErr), - completedAt: Date.now(), + completedAt: Date.now() }); } return; @@ -6195,17 +6272,7 @@ async function proxyRequest( const responseChunks = []; if (headersSentEarly) { if (upstream.body) { - const reader = upstream.body.getReader(); - const chunks = []; - try { - while (true) { - const { done, value } = await reader.read(); - if (done) break; - chunks.push(value); - } - } finally { - reader.releaseLock(); - } + const chunks = await readBodyWithTimeout(upstream.body); const jsonBody = Buffer.concat(chunks); const jsonStr = jsonBody.toString(); try { @@ -6219,7 +6286,7 @@ async function proxyRequest( object: "chat.completion.chunk", created: rsp.created ?? Math.floor(Date.now() / 1e3), model: rsp.model ?? "unknown", - system_fingerprint: null, + system_fingerprint: null }; if (rsp.choices && Array.isArray(rsp.choices)) { for (const choice of rsp.choices) { @@ -6232,7 +6299,7 @@ async function proxyRequest( } const roleChunk = { ...baseChunk, - choices: [{ index, delta: { role }, logprobs: null, finish_reason: null }], + choices: [{ index, delta: { role }, logprobs: null, finish_reason: null }] }; const roleData = `data: ${JSON.stringify(roleChunk)} @@ -6247,9 +6314,9 @@ async function proxyRequest( index, delta: { content: balanceFallbackNotice }, logprobs: null, - finish_reason: null, - }, - ], + finish_reason: null + } + ] }; const noticeData = `data: ${JSON.stringify(noticeChunk)} @@ -6261,7 +6328,7 @@ async function proxyRequest( if (content) { const contentChunk = { ...baseChunk, - choices: [{ index, delta: { content }, logprobs: null, finish_reason: null }], + choices: [{ index, delta: { content }, logprobs: null, finish_reason: null }] }; const contentData = `data: ${JSON.stringify(contentChunk)} @@ -6278,9 +6345,9 @@ async function proxyRequest( index, delta: { tool_calls: toolCalls }, logprobs: null, - finish_reason: null, - }, - ], + finish_reason: null + } + ] }; const toolCallData = `data: ${JSON.stringify(toolCallChunk)} @@ -6295,12 +6362,9 @@ async function proxyRequest( index, delta: {}, logprobs: null, - finish_reason: - toolCalls && toolCalls.length > 0 - ? "tool_calls" - : (choice.finish_reason ?? "stop"), - }, - ], + finish_reason: toolCalls && toolCalls.length > 0 ? "tool_calls" : choice.finish_reason ?? "stop" + } + ] }; const finishData = `data: ${JSON.stringify(finishChunk)} @@ -6324,7 +6388,7 @@ async function proxyRequest( status: 200, headers: { "content-type": "text/event-stream" }, body: Buffer.concat(responseChunks), - completedAt: Date.now(), + completedAt: Date.now() }); } else { const responseHeaders = {}; @@ -6347,15 +6411,9 @@ async function proxyRequest( } const bodyParts = []; if (upstream.body) { - const reader = upstream.body.getReader(); - try { - while (true) { - const { done, value } = await reader.read(); - if (done) break; - bodyParts.push(Buffer.from(value)); - } - } finally { - reader.releaseLock(); + const chunks = await readBodyWithTimeout(upstream.body); + for (const chunk of chunks) { + bodyParts.push(Buffer.from(chunk)); } } let responseBody = Buffer.concat(bodyParts); @@ -6363,11 +6421,11 @@ async function proxyRequest( try { const parsed = JSON.parse(responseBody.toString()); if (parsed.choices?.[0]?.message?.content !== void 0) { - parsed.choices[0].message.content = - balanceFallbackNotice + parsed.choices[0].message.content; + parsed.choices[0].message.content = balanceFallbackNotice + parsed.choices[0].message.content; responseBody = Buffer.from(JSON.stringify(parsed)); } - } catch {} + } catch { + } balanceFallbackNotice = void 0; } responseHeaders["content-length"] = String(responseBody.length); @@ -6379,17 +6437,17 @@ async function proxyRequest( status: upstream.status, headers: responseHeaders, body: responseBody, - completedAt: Date.now(), + completedAt: Date.now() }); if (upstream.status === 200 && responseCache.shouldCache(body)) { responseCache.set(cacheKey, { body: responseBody, status: upstream.status, headers: responseHeaders, - model: actualModelUsed, + model: actualModelUsed }); console.log( - `[ClawRouter] Cached response for ${actualModelUsed} (${responseBody.length} bytes)`, + `[ClawRouter] Cached response for ${actualModelUsed} (${responseBody.length} bytes)` ); } try { @@ -6401,14 +6459,15 @@ async function proxyRequest( if (typeof rspJson.usage.prompt_tokens === "number") responseInputTokens = rspJson.usage.prompt_tokens; } - } catch {} + } catch { + } } if (sessionId && accumulatedContent) { const events = sessionJournal.extractEvents(accumulatedContent); if (events.length > 0) { sessionJournal.record(sessionId, events, actualModelUsed); console.log( - `[ClawRouter] Recorded ${events.length} events to session journal for session ${sessionId.slice(0, 8)}...`, + `[ClawRouter] Recorded ${events.length} events to session journal for session ${sessionId.slice(0, 8)}...` ); } } @@ -6437,21 +6496,22 @@ async function proxyRequest( routerOpts2.modelPricing, estimatedInputTokens, maxTokens, - routingProfile ?? void 0, + routingProfile ?? void 0 ); const costWithBuffer = accurateCosts.costEstimate * 1.2; const baselineWithBuffer = accurateCosts.baselineCost * 1.2; const entry = { - timestamp: /* @__PURE__ */ new Date().toISOString(), + timestamp: (/* @__PURE__ */ new Date()).toISOString(), model: logModel, tier: routingDecision?.tier ?? "DIRECT", cost: costWithBuffer, baselineCost: baselineWithBuffer, savings: accurateCosts.savings, latencyMs: Date.now() - startTime, - ...(responseInputTokens !== void 0 && { inputTokens: responseInputTokens }), + ...responseInputTokens !== void 0 && { inputTokens: responseInputTokens } }; - logUsage(entry).catch(() => {}); + logUsage(entry).catch(() => { + }); } } @@ -6482,24 +6542,24 @@ var config = DEFAULT_ROUTING_CONFIG; const r1 = classifyByRules("What is the capital of France?", void 0, 8, config.scoring); assert( r1.tier === "SIMPLE", - `"What is the capital of France?" \u2192 ${r1.tier} (score=${r1.score.toFixed(3)})`, + `"What is the capital of France?" \u2192 ${r1.tier} (score=${r1.score.toFixed(3)})` ); const r2 = classifyByRules("Hello", void 0, 2, config.scoring); assert(r2.tier === "SIMPLE", `"Hello" \u2192 ${r2.tier} (score=${r2.score.toFixed(3)})`); const r3 = classifyByRules("Define photosynthesis", void 0, 4, config.scoring); assert( r3.tier === "SIMPLE" || r3.tier === "MEDIUM" || r3.tier === null, - `"Define photosynthesis" \u2192 ${r3.tier} (score=${r3.score.toFixed(3)})`, + `"Define photosynthesis" \u2192 ${r3.tier} (score=${r3.score.toFixed(3)})` ); const r4 = classifyByRules("Translate hello to Spanish", void 0, 6, config.scoring); assert( r4.tier === "SIMPLE", - `"Translate hello to Spanish" \u2192 ${r4.tier} (score=${r4.score.toFixed(3)})`, + `"Translate hello to Spanish" \u2192 ${r4.tier} (score=${r4.score.toFixed(3)})` ); const r5 = classifyByRules("Yes or no: is the sky blue?", void 0, 8, config.scoring); assert( r5.tier === "SIMPLE", - `"Yes or no: is the sky blue?" \u2192 ${r5.tier} (score=${r5.score.toFixed(3)})`, + `"Yes or no: is the sky blue?" \u2192 ${r5.tier} (score=${r5.score.toFixed(3)})` ); } { @@ -6508,52 +6568,51 @@ var config = DEFAULT_ROUTING_CONFIG; const r1 = classifyByRules("What is 2+2?", systemPrompt, 10, config.scoring); assert( r1.tier === "SIMPLE", - `"2+2" with reasoning system prompt \u2192 ${r1.tier} (should be SIMPLE)`, + `"2+2" with reasoning system prompt \u2192 ${r1.tier} (should be SIMPLE)` ); const r2 = classifyByRules("Hello", systemPrompt, 5, config.scoring); assert( r2.tier === "SIMPLE", - `"Hello" with reasoning system prompt \u2192 ${r2.tier} (should be SIMPLE)`, + `"Hello" with reasoning system prompt \u2192 ${r2.tier} (should be SIMPLE)` ); const r3 = classifyByRules("What is the capital of France?", systemPrompt, 12, config.scoring); assert( r3.tier === "SIMPLE", - `"Capital of France" with reasoning system prompt \u2192 ${r3.tier} (should be SIMPLE)`, + `"Capital of France" with reasoning system prompt \u2192 ${r3.tier} (should be SIMPLE)` ); const r4 = classifyByRules( "Prove step by step that sqrt(2) is irrational", systemPrompt, 50, - config.scoring, + config.scoring ); assert( r4.tier === "REASONING", - `User asks for step-by-step proof \u2192 ${r4.tier} (should be REASONING)`, + `User asks for step-by-step proof \u2192 ${r4.tier} (should be REASONING)` ); } { console.log("\nCoding assistant system prompt (should NOT force agentic mode):"); - const codingSystemPrompt = - "You are a coding assistant. You can edit files, fix bugs, check code quality, verify tests, deploy applications, and install dependencies. Make sure to follow best practices and confirm changes before applying them."; + const codingSystemPrompt = "You are a coding assistant. You can edit files, fix bugs, check code quality, verify tests, deploy applications, and install dependencies. Make sure to follow best practices and confirm changes before applying them."; const r1 = classifyByRules("What does this function do?", codingSystemPrompt, 20, config.scoring); assert( r1.agenticScore < 0.5, - `Simple question with coding system prompt \u2192 agenticScore=${r1.agenticScore} (should be <0.5, not forced agentic)`, + `Simple question with coding system prompt \u2192 agenticScore=${r1.agenticScore} (should be <0.5, not forced agentic)` ); const r2 = classifyByRules("What is React?", codingSystemPrompt, 15, config.scoring); assert( r2.agenticScore < 0.5, - `"What is React?" with coding system prompt \u2192 agenticScore=${r2.agenticScore} (should be <0.5)`, + `"What is React?" with coding system prompt \u2192 agenticScore=${r2.agenticScore} (should be <0.5)` ); const r3 = classifyByRules( "Fix the bug in auth.ts, deploy to staging, and make sure it works", codingSystemPrompt, 30, - config.scoring, + config.scoring ); assert( r3.agenticScore >= 0.5, - `User asks for multi-step agentic task \u2192 agenticScore=${r3.agenticScore} (should be >=0.5)`, + `User asks for multi-step agentic task \u2192 agenticScore=${r3.agenticScore} (should be >=0.5)` ); } { @@ -6562,19 +6621,19 @@ var config = DEFAULT_ROUTING_CONFIG; "Summarize the key differences between REST and GraphQL APIs", void 0, 30, - config.scoring, + config.scoring ); console.log( - ` \u2192 "Summarize REST vs GraphQL" \u2192 tier=${r1.tier ?? "AMBIGUOUS"} (score=${r1.score.toFixed(3)}, conf=${r1.confidence.toFixed(3)}) [${r1.signals.join(", ")}]`, + ` \u2192 "Summarize REST vs GraphQL" \u2192 tier=${r1.tier ?? "AMBIGUOUS"} (score=${r1.score.toFixed(3)}, conf=${r1.confidence.toFixed(3)}) [${r1.signals.join(", ")}]` ); const r2 = classifyByRules( "Write a Python function to sort a list using merge sort", void 0, 40, - config.scoring, + config.scoring ); console.log( - ` \u2192 "Write merge sort" \u2192 tier=${r2.tier ?? "AMBIGUOUS"} (score=${r2.score.toFixed(3)}, conf=${r2.confidence.toFixed(3)}) [${r2.signals.join(", ")}]`, + ` \u2192 "Write merge sort" \u2192 tier=${r2.tier ?? "AMBIGUOUS"} (score=${r2.score.toFixed(3)}, conf=${r2.confidence.toFixed(3)}) [${r2.signals.join(", ")}]` ); } { @@ -6583,21 +6642,21 @@ var config = DEFAULT_ROUTING_CONFIG; "Build a React component with TypeScript that implements a drag-and-drop kanban board with async data loading, error handling, and unit tests", void 0, 200, - config.scoring, + config.scoring ); assert( r1.tier === null || r1.tier === "MEDIUM" || r1.tier === "COMPLEX" || r1.tier === "REASONING", - `Kanban board \u2192 ${r1.tier ?? "AMBIGUOUS"} (score=${r1.score.toFixed(3)}, conf=${r1.confidence.toFixed(3)})`, + `Kanban board \u2192 ${r1.tier ?? "AMBIGUOUS"} (score=${r1.score.toFixed(3)}, conf=${r1.confidence.toFixed(3)})` ); const r2 = classifyByRules( "Design a distributed microservice architecture for a real-time trading platform. Include the database schema, API endpoints, message queue topology, and kubernetes deployment manifests.", void 0, 250, - config.scoring, + config.scoring ); assert( r2.tier === null || r2.tier === "MEDIUM" || r2.tier === "COMPLEX" || r2.tier === "REASONING", - `Distributed trading platform \u2192 ${r2.tier ?? "AMBIGUOUS"} (score=${r2.score.toFixed(3)}, conf=${r2.confidence.toFixed(3)})`, + `Distributed trading platform \u2192 ${r2.tier ?? "AMBIGUOUS"} (score=${r2.score.toFixed(3)}, conf=${r2.confidence.toFixed(3)})` ); } { @@ -6606,31 +6665,31 @@ var config = DEFAULT_ROUTING_CONFIG; "Prove that the square root of 2 is irrational using proof by contradiction. Show each step formally.", void 0, 60, - config.scoring, + config.scoring ); assert( r1.tier === "REASONING", - `"Prove sqrt(2) irrational" \u2192 ${r1.tier} (score=${r1.score.toFixed(3)}, conf=${r1.confidence.toFixed(3)})`, + `"Prove sqrt(2) irrational" \u2192 ${r1.tier} (score=${r1.score.toFixed(3)}, conf=${r1.confidence.toFixed(3)})` ); const r2 = classifyByRules( "Derive the time complexity of the following algorithm step by step, then prove it is optimal using a lower bound argument.", void 0, 80, - config.scoring, + config.scoring ); assert( r2.tier === "REASONING", - `"Derive time complexity + prove optimal" \u2192 ${r2.tier} (score=${r2.score.toFixed(3)}, conf=${r2.confidence.toFixed(3)})`, + `"Derive time complexity + prove optimal" \u2192 ${r2.tier} (score=${r2.score.toFixed(3)}, conf=${r2.confidence.toFixed(3)})` ); const r3 = classifyByRules( "Using chain of thought, solve this mathematical proof: for all n >= 1, prove that 1 + 2 + ... + n = n(n+1)/2", void 0, 70, - config.scoring, + config.scoring ); assert( r3.tier === "REASONING", - `"Chain of thought proof" \u2192 ${r3.tier} (score=${r3.score.toFixed(3)}, conf=${r3.confidence.toFixed(3)})`, + `"Chain of thought proof" \u2192 ${r3.tier} (score=${r3.score.toFixed(3)}, conf=${r3.confidence.toFixed(3)})` ); } { @@ -6639,81 +6698,71 @@ var config = DEFAULT_ROUTING_CONFIG; "\u8BF7\u8BC1\u660E\u6839\u53F72\u662F\u65E0\u7406\u6570\uFF0C\u9010\u6B65\u63A8\u5BFC", void 0, 20, - config.scoring, + config.scoring ); assert( zhReasoning.tier === "REASONING", - `Chinese "\u8BC1\u660E...\u9010\u6B65" \u2192 ${zhReasoning.tier} (should be REASONING)`, - ); - const zhSimple = classifyByRules( - "\u4F60\u597D\uFF0C\u4EC0\u4E48\u662F\u4EBA\u5DE5\u667A\u80FD\uFF1F", - void 0, - 15, - config.scoring, + `Chinese "\u8BC1\u660E...\u9010\u6B65" \u2192 ${zhReasoning.tier} (should be REASONING)` ); + const zhSimple = classifyByRules("\u4F60\u597D\uFF0C\u4EC0\u4E48\u662F\u4EBA\u5DE5\u667A\u80FD\uFF1F", void 0, 15, config.scoring); assert( zhSimple.tier === "SIMPLE", - `Chinese "\u4F60\u597D...\u4EC0\u4E48\u662F" \u2192 ${zhSimple.tier} (should be SIMPLE)`, - ); - const jaSimple = classifyByRules( - "\u3053\u3093\u306B\u3061\u306F\u3001\u6771\u4EAC\u3068\u306F\u4F55\u3067\u3059\u304B", - void 0, - 15, - config.scoring, + `Chinese "\u4F60\u597D...\u4EC0\u4E48\u662F" \u2192 ${zhSimple.tier} (should be SIMPLE)` ); + const jaSimple = classifyByRules("\u3053\u3093\u306B\u3061\u306F\u3001\u6771\u4EAC\u3068\u306F\u4F55\u3067\u3059\u304B", void 0, 15, config.scoring); assert( jaSimple.tier === "SIMPLE", - `Japanese "\u3053\u3093\u306B\u3061\u306F...\u3068\u306F" \u2192 ${jaSimple.tier} (should be SIMPLE)`, + `Japanese "\u3053\u3093\u306B\u3061\u306F...\u3068\u306F" \u2192 ${jaSimple.tier} (should be SIMPLE)` ); const ruTech = classifyByRules( "\u041E\u043F\u0442\u0438\u043C\u0438\u0437\u0438\u0440\u043E\u0432\u0430\u0442\u044C \u0430\u043B\u0433\u043E\u0440\u0438\u0442\u043C \u0441\u043E\u0440\u0442\u0438\u0440\u043E\u0432\u043A\u0438 \u0434\u043B\u044F \u0440\u0430\u0441\u043F\u0440\u0435\u0434\u0435\u043B\u0451\u043D\u043D\u043E\u0439 \u0441\u0438\u0441\u0442\u0435\u043C\u044B", void 0, 20, - config.scoring, + config.scoring ); assert( ruTech.tier !== "SIMPLE", - `Russian "\u0430\u043B\u0433\u043E\u0440\u0438\u0442\u043C...\u0440\u0430\u0441\u043F\u0440\u0435\u0434\u0435\u043B\u0451\u043D\u043D\u043E\u0439" \u2192 ${ruTech.tier} (should NOT be SIMPLE)`, + `Russian "\u0430\u043B\u0433\u043E\u0440\u0438\u0442\u043C...\u0440\u0430\u0441\u043F\u0440\u0435\u0434\u0435\u043B\u0451\u043D\u043D\u043E\u0439" \u2192 ${ruTech.tier} (should NOT be SIMPLE)` ); const ruSimple = classifyByRules( "\u041F\u0440\u0438\u0432\u0435\u0442, \u0447\u0442\u043E \u0442\u0430\u043A\u043E\u0435 \u043C\u0430\u0448\u0438\u043D\u043D\u043E\u0435 \u043E\u0431\u0443\u0447\u0435\u043D\u0438\u0435?", void 0, 15, - config.scoring, + config.scoring ); assert( ruSimple.tier === "SIMPLE", - `Russian "\u043F\u0440\u0438\u0432\u0435\u0442...\u0447\u0442\u043E \u0442\u0430\u043A\u043E\u0435" \u2192 ${ruSimple.tier} (should be SIMPLE)`, + `Russian "\u043F\u0440\u0438\u0432\u0435\u0442...\u0447\u0442\u043E \u0442\u0430\u043A\u043E\u0435" \u2192 ${ruSimple.tier} (should be SIMPLE)` ); const deReasoning = classifyByRules( "Beweisen Sie, dass die Quadratwurzel von 2 irrational ist, Schritt f\xFCr Schritt", void 0, 25, - config.scoring, + config.scoring ); assert( deReasoning.tier === "REASONING", - `German "beweisen...schritt f\xFCr schritt" \u2192 ${deReasoning.tier} (should be REASONING)`, + `German "beweisen...schritt f\xFCr schritt" \u2192 ${deReasoning.tier} (should be REASONING)` ); const deSimple = classifyByRules( "Hallo, was ist maschinelles Lernen?", void 0, 10, - config.scoring, + config.scoring ); assert( deSimple.tier === "SIMPLE", - `German "hallo...was ist" \u2192 ${deSimple.tier} (should be SIMPLE)`, + `German "hallo...was ist" \u2192 ${deSimple.tier} (should be SIMPLE)` ); const deTech = classifyByRules( "Optimieren Sie den Sortieralgorithmus f\xFCr eine verteilte Architektur", void 0, 20, - config.scoring, + config.scoring ); assert( deTech.tier !== "SIMPLE", - `German "algorithmus...verteilt" \u2192 ${deTech.tier} (should NOT be SIMPLE)`, + `German "algorithmus...verteilt" \u2192 ${deTech.tier} (should NOT be SIMPLE)` ); } { @@ -6813,49 +6862,49 @@ Rules: const ocr1 = classifyByRules("What time is it?", openClawSystemPrompt, 6200, config.scoring); assert( ocr1.score < 0.2, - `"What time is it?" + OpenClaw prompt \u2192 score=${ocr1.score.toFixed(3)} (should be <0.2, was ~0.47 before fix)`, + `"What time is it?" + OpenClaw prompt \u2192 score=${ocr1.score.toFixed(3)} (should be <0.2, was ~0.47 before fix)` ); const ocr2 = classifyByRules("What's the weather?", openClawSystemPrompt, 6200, config.scoring); assert( ocr2.score < 0.2, - `"What's the weather?" + OpenClaw prompt \u2192 score=${ocr2.score.toFixed(3)} (should be <0.2, was ~0.47 before fix)`, + `"What's the weather?" + OpenClaw prompt \u2192 score=${ocr2.score.toFixed(3)} (should be <0.2, was ~0.47 before fix)` ); const ocr3 = classifyByRules( "Build a React component with TypeScript that implements a sortable data table with pagination, filtering, and async data loading from a REST API", openClawSystemPrompt, 6250, - config.scoring, + config.scoring ); assert( ocr3.score > ocr1.score, - `Complex task score (${ocr3.score.toFixed(3)}) > simple task score (${ocr1.score.toFixed(3)}) \u2014 scores should differentiate`, + `Complex task score (${ocr3.score.toFixed(3)}) > simple task score (${ocr1.score.toFixed(3)}) \u2014 scores should differentiate` ); const ocr4 = classifyByRules( "Prove that sqrt(2) is irrational using proof by contradiction, step by step", openClawSystemPrompt, 6220, - config.scoring, + config.scoring ); assert( ocr4.tier === "REASONING", - `Reasoning task + OpenClaw prompt \u2192 ${ocr4.tier} score=${ocr4.score.toFixed(3)} (should be REASONING)`, + `Reasoning task + OpenClaw prompt \u2192 ${ocr4.tier} score=${ocr4.score.toFixed(3)} (should be REASONING)` ); const scores = [ocr1.score, ocr2.score, ocr3.score, ocr4.score]; const uniqueScores = new Set(scores.map((s) => s.toFixed(2))); assert( uniqueScores.size >= 3, - `${uniqueScores.size} unique scores out of 4 queries (scores: ${scores.map((s) => s.toFixed(3)).join(", ")}) \u2014 should differentiate, not all ~0.47`, + `${uniqueScores.size} unique scores out of 4 queries (scores: ${scores.map((s) => s.toFixed(3)).join(", ")}) \u2014 should differentiate, not all ~0.47` ); assert( ocr1.agenticScore === 0, - `"What time is it?" agenticScore=${ocr1.agenticScore} (should be 0, not triggered by system prompt tools)`, + `"What time is it?" agenticScore=${ocr1.agenticScore} (should be 0, not triggered by system prompt tools)` ); } { console.log("\nOverride: large context:"); const r1 = classifyByRules("What is 2+2?", void 0, 15e4, config.scoring); console.log( - ` \u2192 150K tokens "What is 2+2?" \u2192 tier=${r1.tier ?? "AMBIGUOUS"} (score=${r1.score.toFixed(3)}, conf=${r1.confidence.toFixed(3)})`, + ` \u2192 150K tokens "What is 2+2?" \u2192 tier=${r1.tier ?? "AMBIGUOUS"} (score=${r1.score.toFixed(3)}, conf=${r1.confidence.toFixed(3)})` ); } console.log("\n\u2550\u2550\u2550 Part 2: Full Router (rules-only path) \u2550\u2550\u2550\n"); @@ -6865,7 +6914,7 @@ var routerOpts = { config: DEFAULT_ROUTING_CONFIG, modelPricing, payFetch: mockPayFetch, - apiBase: "http://localhost:0", + apiBase: "http://localhost:0" }; async function testRoute(prompt, label, expectedTier) { const decision = await route(prompt, void 0, 4096, routerOpts); @@ -6873,11 +6922,11 @@ async function testRoute(prompt, label, expectedTier) { if (expectedTier) { assert( decision.tier === expectedTier, - `${label} \u2192 ${decision.model} (${decision.tier}, ${decision.method}) saved=${savingsPct}%`, + `${label} \u2192 ${decision.model} (${decision.tier}, ${decision.method}) saved=${savingsPct}%` ); } else { console.log( - ` \u2192 ${label} \u2192 ${decision.model} (${decision.tier}, ${decision.method}) saved=${savingsPct}%`, + ` \u2192 ${label} \u2192 ${decision.model} (${decision.tier}, ${decision.method}) saved=${savingsPct}%` ); } return decision; @@ -6887,14 +6936,14 @@ await testRoute("Hello, how are you?", "Greeting", "SIMPLE"); await testRoute( "Prove that sqrt(2) is irrational step by step using proof by contradiction", "Math proof", - "REASONING", + "REASONING" ); { const longPrompt = "x".repeat(5e5); const decision = await route(longPrompt, void 0, 4096, routerOpts); assert( decision.tier === "COMPLEX", - `125K token input \u2192 ${decision.tier} (forced COMPLEX override)`, + `125K token input \u2192 ${decision.tier} (forced COMPLEX override)` ); } { @@ -6902,11 +6951,11 @@ await testRoute( "What is 2+2?", "Respond in JSON format with the answer", 4096, - routerOpts, + routerOpts ); assert( decision.tier === "MEDIUM" || decision.tier === "SIMPLE", - `Structured output "What is 2+2?" \u2192 ${decision.tier} (min MEDIUM applied: ${decision.tier !== "SIMPLE"})`, + `Structured output "What is 2+2?" \u2192 ${decision.tier} (min MEDIUM applied: ${decision.tier !== "SIMPLE"})` ); } { @@ -6917,7 +6966,7 @@ await testRoute( assert(d.savings >= 0 && d.savings <= 1, `Savings in range [0,1]: ${d.savings.toFixed(4)}`); assert( d.costEstimate <= d.baselineCost, - `Cost ($${d.costEstimate.toFixed(6)}) <= Baseline ($${d.baselineCost.toFixed(6)})`, + `Cost ($${d.costEstimate.toFixed(6)}) <= Baseline ($${d.baselineCost.toFixed(6)})` ); } console.log("\n\u2550\u2550\u2550 Part 3: Proxy Startup \u2550\u2550\u2550\n"); @@ -6934,13 +6983,13 @@ if (!walletKey) { onRouted: (d) => { const pct = (d.savings * 100).toFixed(1); console.log(` [routed] ${d.model} (${d.tier}) saved=${pct}%`); - }, + } }); const health = await fetch(`${proxy.baseUrl}/health`); const healthData = await health.json(); assert( healthData.status === "ok", - `Health check: ${healthData.status}, wallet: ${healthData.wallet}`, + `Health check: ${healthData.status}, wallet: ${healthData.wallet}` ); console.log("\n Sending test request (blockrun/auto)..."); try { @@ -6950,8 +6999,8 @@ if (!walletKey) { body: JSON.stringify({ model: "blockrun/auto", messages: [{ role: "user", content: "What is 2+2?" }], - max_tokens: 50, - }), + max_tokens: 50 + }) }); if (chatRes.ok) { const chatData = await chatRes.json(); @@ -6975,12 +7024,8 @@ if (!walletKey) { failed++; } } -console.log( - "\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550", -); +console.log("\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550"); console.log(` ${passed} passed, ${failed} failed`); -console.log( - "\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n", -); +console.log("\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n"); process.exit(failed > 0 ? 1 : 0); -//# sourceMappingURL=e2e.js.map +//# sourceMappingURL=e2e.js.map \ No newline at end of file diff --git a/dist-e2e/e2e.js.map b/dist-e2e/e2e.js.map index 2c3e38b..fd95f2e 100644 --- a/dist-e2e/e2e.js.map +++ b/dist-e2e/e2e.js.map @@ -1 +1 @@ -{"version":3,"sources":["../src/router/rules.ts","../src/router/selector.ts","../src/router/config.ts","../src/router/index.ts","../src/models.ts","../src/proxy.ts","../src/payment-preauth.ts","../src/logger.ts","../src/stats.ts","../src/fs-read.ts","../src/version.ts","../src/dedup.ts","../src/response-cache.ts","../src/balance.ts","../src/errors.ts","../src/solana-balance.ts","../src/auth.ts","../src/wallet.ts","../src/compression/types.ts","../src/compression/layers/deduplication.ts","../src/compression/layers/whitespace.ts","../src/compression/codebook.ts","../src/compression/layers/dictionary.ts","../src/compression/layers/paths.ts","../src/compression/layers/json-compact.ts","../src/compression/layers/observation.ts","../src/compression/layers/dynamic-codebook.ts","../src/compression/index.ts","../src/session.ts","../src/updater.ts","../src/config.ts","../src/journal.ts","../test/e2e.ts"],"sourcesContent":["/**\n * Rule-Based Classifier (v2 — Weighted Scoring)\n *\n * Scores a request across 14 weighted dimensions and maps the aggregate\n * score to a tier using configurable boundaries. Confidence is calibrated\n * via sigmoid — low confidence triggers the fallback classifier.\n *\n * Handles 70-80% of requests in < 1ms with zero cost.\n */\n\nimport type { Tier, ScoringResult, ScoringConfig } from \"./types.js\";\n\ntype DimensionScore = { name: string; score: number; signal: string | null };\n\n// ─── Dimension Scorers ───\n// Each returns a score in [-1, 1] and an optional signal string.\n\nfunction scoreTokenCount(\n estimatedTokens: number,\n thresholds: { simple: number; complex: number },\n): DimensionScore {\n if (estimatedTokens < thresholds.simple) {\n return { name: \"tokenCount\", score: -1.0, signal: `short (${estimatedTokens} tokens)` };\n }\n if (estimatedTokens > thresholds.complex) {\n return { name: \"tokenCount\", score: 1.0, signal: `long (${estimatedTokens} tokens)` };\n }\n return { name: \"tokenCount\", score: 0, signal: null };\n}\n\nfunction scoreKeywordMatch(\n text: string,\n keywords: string[],\n name: string,\n signalLabel: string,\n thresholds: { low: number; high: number },\n scores: { none: number; low: number; high: number },\n): DimensionScore {\n const matches = keywords.filter((kw) => text.includes(kw.toLowerCase()));\n if (matches.length >= thresholds.high) {\n return {\n name,\n score: scores.high,\n signal: `${signalLabel} (${matches.slice(0, 3).join(\", \")})`,\n };\n }\n if (matches.length >= thresholds.low) {\n return {\n name,\n score: scores.low,\n signal: `${signalLabel} (${matches.slice(0, 3).join(\", \")})`,\n };\n }\n return { name, score: scores.none, signal: null };\n}\n\nfunction scoreMultiStep(text: string): DimensionScore {\n const patterns = [/first.*then/i, /step \\d/i, /\\d\\.\\s/];\n const hits = patterns.filter((p) => p.test(text));\n if (hits.length > 0) {\n return { name: \"multiStepPatterns\", score: 0.5, signal: \"multi-step\" };\n }\n return { name: \"multiStepPatterns\", score: 0, signal: null };\n}\n\nfunction scoreQuestionComplexity(prompt: string): DimensionScore {\n const count = (prompt.match(/\\?/g) || []).length;\n if (count > 3) {\n return { name: \"questionComplexity\", score: 0.5, signal: `${count} questions` };\n }\n return { name: \"questionComplexity\", score: 0, signal: null };\n}\n\n/**\n * Score agentic task indicators.\n * Returns agenticScore (0-1) based on keyword matches:\n * - 4+ matches = 1.0 (high agentic)\n * - 3 matches = 0.6 (moderate agentic, triggers auto-agentic mode)\n * - 1-2 matches = 0.2 (low agentic)\n *\n * Thresholds raised because common keywords were pruned from the list.\n */\nfunction scoreAgenticTask(\n text: string,\n keywords: string[],\n): { dimensionScore: DimensionScore; agenticScore: number } {\n let matchCount = 0;\n const signals: string[] = [];\n\n for (const keyword of keywords) {\n if (text.includes(keyword.toLowerCase())) {\n matchCount++;\n if (signals.length < 3) {\n signals.push(keyword);\n }\n }\n }\n\n // Threshold-based scoring (raised thresholds after keyword pruning)\n if (matchCount >= 4) {\n return {\n dimensionScore: {\n name: \"agenticTask\",\n score: 1.0,\n signal: `agentic (${signals.join(\", \")})`,\n },\n agenticScore: 1.0,\n };\n } else if (matchCount >= 3) {\n return {\n dimensionScore: {\n name: \"agenticTask\",\n score: 0.6,\n signal: `agentic (${signals.join(\", \")})`,\n },\n agenticScore: 0.6,\n };\n } else if (matchCount >= 1) {\n return {\n dimensionScore: {\n name: \"agenticTask\",\n score: 0.2,\n signal: `agentic-light (${signals.join(\", \")})`,\n },\n agenticScore: 0.2,\n };\n }\n\n return {\n dimensionScore: { name: \"agenticTask\", score: 0, signal: null },\n agenticScore: 0,\n };\n}\n\n// ─── Main Classifier ───\n\nexport function classifyByRules(\n prompt: string,\n systemPrompt: string | undefined,\n estimatedTokens: number,\n config: ScoringConfig,\n): ScoringResult {\n // Score against user prompt only — system prompts contain boilerplate keywords\n // (tool definitions, skill descriptions, behavioral rules) that dominate scoring\n // and make every request score identically. See GitHub issue #50.\n const userText = prompt.toLowerCase();\n\n // Score all 14 dimensions against user text only\n const dimensions: DimensionScore[] = [\n // Token count uses total estimated tokens (system + user) — context size matters for model selection\n scoreTokenCount(estimatedTokens, config.tokenCountThresholds),\n scoreKeywordMatch(\n userText,\n config.codeKeywords,\n \"codePresence\",\n \"code\",\n { low: 1, high: 2 },\n { none: 0, low: 0.5, high: 1.0 },\n ),\n scoreKeywordMatch(\n userText,\n config.reasoningKeywords,\n \"reasoningMarkers\",\n \"reasoning\",\n { low: 1, high: 2 },\n { none: 0, low: 0.7, high: 1.0 },\n ),\n scoreKeywordMatch(\n userText,\n config.technicalKeywords,\n \"technicalTerms\",\n \"technical\",\n { low: 2, high: 4 },\n { none: 0, low: 0.5, high: 1.0 },\n ),\n scoreKeywordMatch(\n userText,\n config.creativeKeywords,\n \"creativeMarkers\",\n \"creative\",\n { low: 1, high: 2 },\n { none: 0, low: 0.5, high: 0.7 },\n ),\n scoreKeywordMatch(\n userText,\n config.simpleKeywords,\n \"simpleIndicators\",\n \"simple\",\n { low: 1, high: 2 },\n { none: 0, low: -1.0, high: -1.0 },\n ),\n scoreMultiStep(userText),\n scoreQuestionComplexity(prompt),\n\n // 6 new dimensions\n scoreKeywordMatch(\n userText,\n config.imperativeVerbs,\n \"imperativeVerbs\",\n \"imperative\",\n { low: 1, high: 2 },\n { none: 0, low: 0.3, high: 0.5 },\n ),\n scoreKeywordMatch(\n userText,\n config.constraintIndicators,\n \"constraintCount\",\n \"constraints\",\n { low: 1, high: 3 },\n { none: 0, low: 0.3, high: 0.7 },\n ),\n scoreKeywordMatch(\n userText,\n config.outputFormatKeywords,\n \"outputFormat\",\n \"format\",\n { low: 1, high: 2 },\n { none: 0, low: 0.4, high: 0.7 },\n ),\n scoreKeywordMatch(\n userText,\n config.referenceKeywords,\n \"referenceComplexity\",\n \"references\",\n { low: 1, high: 2 },\n { none: 0, low: 0.3, high: 0.5 },\n ),\n scoreKeywordMatch(\n userText,\n config.negationKeywords,\n \"negationComplexity\",\n \"negation\",\n { low: 2, high: 3 },\n { none: 0, low: 0.3, high: 0.5 },\n ),\n scoreKeywordMatch(\n userText,\n config.domainSpecificKeywords,\n \"domainSpecificity\",\n \"domain-specific\",\n { low: 1, high: 2 },\n { none: 0, low: 0.5, high: 0.8 },\n ),\n ];\n\n // Score agentic task indicators — user prompt only\n // System prompt describes assistant behavior, not user's intent.\n // e.g. a coding assistant system prompt with \"edit files\" / \"fix bugs\" should NOT\n // force every request into agentic mode.\n const agenticResult = scoreAgenticTask(userText, config.agenticTaskKeywords);\n dimensions.push(agenticResult.dimensionScore);\n const agenticScore = agenticResult.agenticScore;\n\n // Collect signals\n const signals = dimensions.filter((d) => d.signal !== null).map((d) => d.signal!);\n\n // Compute weighted score\n const weights = config.dimensionWeights;\n let weightedScore = 0;\n for (const d of dimensions) {\n const w = weights[d.name] ?? 0;\n weightedScore += d.score * w;\n }\n\n // Count reasoning markers for override — only check USER prompt, not system prompt\n // This prevents system prompts with \"step by step\" from triggering REASONING for simple queries\n const reasoningMatches = config.reasoningKeywords.filter((kw) =>\n userText.includes(kw.toLowerCase()),\n );\n\n // Direct reasoning override: 2+ reasoning markers = high confidence REASONING\n if (reasoningMatches.length >= 2) {\n const confidence = calibrateConfidence(\n Math.max(weightedScore, 0.3), // ensure positive for confidence calc\n config.confidenceSteepness,\n );\n return {\n score: weightedScore,\n tier: \"REASONING\",\n confidence: Math.max(confidence, 0.85),\n signals,\n agenticScore,\n dimensions,\n };\n }\n\n // Map weighted score to tier using boundaries\n const { simpleMedium, mediumComplex, complexReasoning } = config.tierBoundaries;\n let tier: Tier;\n let distanceFromBoundary: number;\n\n if (weightedScore < simpleMedium) {\n tier = \"SIMPLE\";\n distanceFromBoundary = simpleMedium - weightedScore;\n } else if (weightedScore < mediumComplex) {\n tier = \"MEDIUM\";\n distanceFromBoundary = Math.min(weightedScore - simpleMedium, mediumComplex - weightedScore);\n } else if (weightedScore < complexReasoning) {\n tier = \"COMPLEX\";\n distanceFromBoundary = Math.min(\n weightedScore - mediumComplex,\n complexReasoning - weightedScore,\n );\n } else {\n tier = \"REASONING\";\n distanceFromBoundary = weightedScore - complexReasoning;\n }\n\n // Calibrate confidence via sigmoid of distance from nearest boundary\n const confidence = calibrateConfidence(distanceFromBoundary, config.confidenceSteepness);\n\n // If confidence is below threshold → ambiguous\n if (confidence < config.confidenceThreshold) {\n return { score: weightedScore, tier: null, confidence, signals, agenticScore, dimensions };\n }\n\n return { score: weightedScore, tier, confidence, signals, agenticScore, dimensions };\n}\n\n/**\n * Sigmoid confidence calibration.\n * Maps distance from tier boundary to [0.5, 1.0] confidence range.\n */\nfunction calibrateConfidence(distance: number, steepness: number): number {\n return 1 / (1 + Math.exp(-steepness * distance));\n}\n","/**\n * Tier → Model Selection\n *\n * Maps a classification tier to the cheapest capable model.\n * Builds RoutingDecision metadata with cost estimates and savings.\n */\n\nimport type { Tier, TierConfig, RoutingDecision } from \"./types.js\";\n\nexport type ModelPricing = {\n inputPrice: number; // per 1M tokens\n outputPrice: number; // per 1M tokens\n};\n\nconst BASELINE_MODEL_ID = \"anthropic/claude-opus-4.6\";\n\n// Hardcoded fallback: Claude Opus 4.6 pricing (per 1M tokens)\n// Used when baseline model not found in dynamic pricing map\nconst BASELINE_INPUT_PRICE = 5.0;\nconst BASELINE_OUTPUT_PRICE = 25.0;\n\n/**\n * Select the primary model for a tier and build the RoutingDecision.\n */\nexport function selectModel(\n tier: Tier,\n confidence: number,\n method: \"rules\" | \"llm\",\n reasoning: string,\n tierConfigs: Record,\n modelPricing: Map,\n estimatedInputTokens: number,\n maxOutputTokens: number,\n routingProfile?: \"free\" | \"eco\" | \"auto\" | \"premium\",\n agenticScore?: number,\n): RoutingDecision {\n const tierConfig = tierConfigs[tier];\n const model = tierConfig.primary;\n const pricing = modelPricing.get(model);\n\n // Defensive: guard against undefined price fields (not just undefined pricing)\n const inputPrice = pricing?.inputPrice ?? 0;\n const outputPrice = pricing?.outputPrice ?? 0;\n const inputCost = (estimatedInputTokens / 1_000_000) * inputPrice;\n const outputCost = (maxOutputTokens / 1_000_000) * outputPrice;\n const costEstimate = inputCost + outputCost;\n\n // Baseline: what Claude Opus 4.5 would cost (the premium reference)\n const opusPricing = modelPricing.get(BASELINE_MODEL_ID);\n const opusInputPrice = opusPricing?.inputPrice ?? BASELINE_INPUT_PRICE;\n const opusOutputPrice = opusPricing?.outputPrice ?? BASELINE_OUTPUT_PRICE;\n const baselineInput = (estimatedInputTokens / 1_000_000) * opusInputPrice;\n const baselineOutput = (maxOutputTokens / 1_000_000) * opusOutputPrice;\n const baselineCost = baselineInput + baselineOutput;\n\n // Premium profile doesn't calculate savings (it's about quality, not cost)\n const savings =\n routingProfile === \"premium\"\n ? 0\n : baselineCost > 0\n ? Math.max(0, (baselineCost - costEstimate) / baselineCost)\n : 0;\n\n return {\n model,\n tier,\n confidence,\n method,\n reasoning,\n costEstimate,\n baselineCost,\n savings,\n ...(agenticScore !== undefined && { agenticScore }),\n };\n}\n\n/**\n * Get the ordered fallback chain for a tier: [primary, ...fallbacks].\n */\nexport function getFallbackChain(tier: Tier, tierConfigs: Record): string[] {\n const config = tierConfigs[tier];\n return [config.primary, ...config.fallback];\n}\n\n/**\n * Calculate cost for a specific model (used when fallback model is used).\n * Returns updated cost fields for RoutingDecision.\n */\nexport function calculateModelCost(\n model: string,\n modelPricing: Map,\n estimatedInputTokens: number,\n maxOutputTokens: number,\n routingProfile?: \"free\" | \"eco\" | \"auto\" | \"premium\",\n): { costEstimate: number; baselineCost: number; savings: number } {\n const pricing = modelPricing.get(model);\n\n // Defensive: guard against undefined price fields (not just undefined pricing)\n const inputPrice = pricing?.inputPrice ?? 0;\n const outputPrice = pricing?.outputPrice ?? 0;\n const inputCost = (estimatedInputTokens / 1_000_000) * inputPrice;\n const outputCost = (maxOutputTokens / 1_000_000) * outputPrice;\n const costEstimate = inputCost + outputCost;\n\n // Baseline: what Claude Opus 4.5 would cost (the premium reference)\n const opusPricing = modelPricing.get(BASELINE_MODEL_ID);\n const opusInputPrice = opusPricing?.inputPrice ?? BASELINE_INPUT_PRICE;\n const opusOutputPrice = opusPricing?.outputPrice ?? BASELINE_OUTPUT_PRICE;\n const baselineInput = (estimatedInputTokens / 1_000_000) * opusInputPrice;\n const baselineOutput = (maxOutputTokens / 1_000_000) * opusOutputPrice;\n const baselineCost = baselineInput + baselineOutput;\n\n // Premium profile doesn't calculate savings (it's about quality, not cost)\n const savings =\n routingProfile === \"premium\"\n ? 0\n : baselineCost > 0\n ? Math.max(0, (baselineCost - costEstimate) / baselineCost)\n : 0;\n\n return { costEstimate, baselineCost, savings };\n}\n\n/**\n * Filter a model list to only those that support tool calling.\n * When hasTools is false, returns the list unchanged.\n * When all models lack tool calling support, returns the full list as a fallback\n * (better to let the API error than produce an empty chain).\n */\nexport function filterByToolCalling(\n models: string[],\n hasTools: boolean,\n supportsToolCalling: (modelId: string) => boolean,\n): string[] {\n if (!hasTools) return models;\n const filtered = models.filter(supportsToolCalling);\n return filtered.length > 0 ? filtered : models;\n}\n\n/**\n * Filter a model list to only those that support vision (image inputs).\n * When hasVision is false, returns the list unchanged.\n * When all models lack vision support, returns the full list as a fallback\n * (better to let the API error than produce an empty chain).\n */\nexport function filterByVision(\n models: string[],\n hasVision: boolean,\n supportsVision: (modelId: string) => boolean,\n): string[] {\n if (!hasVision) return models;\n const filtered = models.filter(supportsVision);\n return filtered.length > 0 ? filtered : models;\n}\n\n/**\n * Get the fallback chain filtered by context length.\n * Only returns models that can handle the estimated total context.\n *\n * @param tier - The tier to get fallback chain for\n * @param tierConfigs - Tier configurations\n * @param estimatedTotalTokens - Estimated total context (input + output)\n * @param getContextWindow - Function to get context window for a model ID\n * @returns Filtered list of models that can handle the context\n */\nexport function getFallbackChainFiltered(\n tier: Tier,\n tierConfigs: Record,\n estimatedTotalTokens: number,\n getContextWindow: (modelId: string) => number | undefined,\n): string[] {\n const fullChain = getFallbackChain(tier, tierConfigs);\n\n // Filter to models that can handle the context\n const filtered = fullChain.filter((modelId) => {\n const contextWindow = getContextWindow(modelId);\n if (contextWindow === undefined) {\n // Unknown model - include it (let API reject if needed)\n return true;\n }\n // Add 10% buffer for safety\n return contextWindow >= estimatedTotalTokens * 1.1;\n });\n\n // If all models filtered out, return the original chain\n // (let the API error out - better than no options)\n if (filtered.length === 0) {\n return fullChain;\n }\n\n return filtered;\n}\n","/**\n * Default Routing Config\n *\n * All routing parameters as a TypeScript constant.\n * Operators override via openclaw.yaml plugin config.\n *\n * Scoring uses 14 weighted dimensions with sigmoid confidence calibration.\n */\n\nimport type { RoutingConfig } from \"./types.js\";\n\nexport const DEFAULT_ROUTING_CONFIG: RoutingConfig = {\n version: \"2.0\",\n\n classifier: {\n llmModel: \"google/gemini-2.5-flash\",\n llmMaxTokens: 10,\n llmTemperature: 0,\n promptTruncationChars: 500,\n cacheTtlMs: 3_600_000, // 1 hour\n },\n\n scoring: {\n tokenCountThresholds: { simple: 50, complex: 500 },\n\n // Multilingual keywords: EN + ZH + JA + RU + DE + ES + PT + KO + AR\n codeKeywords: [\n // English\n \"function\",\n \"class\",\n \"import\",\n \"def\",\n \"SELECT\",\n \"async\",\n \"await\",\n \"const\",\n \"let\",\n \"var\",\n \"return\",\n \"```\",\n // Chinese\n \"函数\",\n \"类\",\n \"导入\",\n \"定义\",\n \"查询\",\n \"异步\",\n \"等待\",\n \"常量\",\n \"变量\",\n \"返回\",\n // Japanese\n \"関数\",\n \"クラス\",\n \"インポート\",\n \"非同期\",\n \"定数\",\n \"変数\",\n // Russian\n \"функция\",\n \"класс\",\n \"импорт\",\n \"определ\",\n \"запрос\",\n \"асинхронный\",\n \"ожидать\",\n \"константа\",\n \"переменная\",\n \"вернуть\",\n // German\n \"funktion\",\n \"klasse\",\n \"importieren\",\n \"definieren\",\n \"abfrage\",\n \"asynchron\",\n \"erwarten\",\n \"konstante\",\n \"variable\",\n \"zurückgeben\",\n // Spanish\n \"función\",\n \"clase\",\n \"importar\",\n \"definir\",\n \"consulta\",\n \"asíncrono\",\n \"esperar\",\n \"constante\",\n \"variable\",\n \"retornar\",\n // Portuguese\n \"função\",\n \"classe\",\n \"importar\",\n \"definir\",\n \"consulta\",\n \"assíncrono\",\n \"aguardar\",\n \"constante\",\n \"variável\",\n \"retornar\",\n // Korean\n \"함수\",\n \"클래스\",\n \"가져오기\",\n \"정의\",\n \"쿼리\",\n \"비동기\",\n \"대기\",\n \"상수\",\n \"변수\",\n \"반환\",\n // Arabic\n \"دالة\",\n \"فئة\",\n \"استيراد\",\n \"تعريف\",\n \"استعلام\",\n \"غير متزامن\",\n \"انتظار\",\n \"ثابت\",\n \"متغير\",\n \"إرجاع\",\n ],\n reasoningKeywords: [\n // English\n \"prove\",\n \"theorem\",\n \"derive\",\n \"step by step\",\n \"chain of thought\",\n \"formally\",\n \"mathematical\",\n \"proof\",\n \"logically\",\n // Chinese\n \"证明\",\n \"定理\",\n \"推导\",\n \"逐步\",\n \"思维链\",\n \"形式化\",\n \"数学\",\n \"逻辑\",\n // Japanese\n \"証明\",\n \"定理\",\n \"導出\",\n \"ステップバイステップ\",\n \"論理的\",\n // Russian\n \"доказать\",\n \"докажи\",\n \"доказательств\",\n \"теорема\",\n \"вывести\",\n \"шаг за шагом\",\n \"пошагово\",\n \"поэтапно\",\n \"цепочка рассуждений\",\n \"рассуждени\",\n \"формально\",\n \"математически\",\n \"логически\",\n // German\n \"beweisen\",\n \"beweis\",\n \"theorem\",\n \"ableiten\",\n \"schritt für schritt\",\n \"gedankenkette\",\n \"formal\",\n \"mathematisch\",\n \"logisch\",\n // Spanish\n \"demostrar\",\n \"teorema\",\n \"derivar\",\n \"paso a paso\",\n \"cadena de pensamiento\",\n \"formalmente\",\n \"matemático\",\n \"prueba\",\n \"lógicamente\",\n // Portuguese\n \"provar\",\n \"teorema\",\n \"derivar\",\n \"passo a passo\",\n \"cadeia de pensamento\",\n \"formalmente\",\n \"matemático\",\n \"prova\",\n \"logicamente\",\n // Korean\n \"증명\",\n \"정리\",\n \"도출\",\n \"단계별\",\n \"사고의 연쇄\",\n \"형식적\",\n \"수학적\",\n \"논리적\",\n // Arabic\n \"إثبات\",\n \"نظرية\",\n \"اشتقاق\",\n \"خطوة بخطوة\",\n \"سلسلة التفكير\",\n \"رسمياً\",\n \"رياضي\",\n \"برهان\",\n \"منطقياً\",\n ],\n simpleKeywords: [\n // English\n \"what is\",\n \"define\",\n \"translate\",\n \"hello\",\n \"yes or no\",\n \"capital of\",\n \"how old\",\n \"who is\",\n \"when was\",\n // Chinese\n \"什么是\",\n \"定义\",\n \"翻译\",\n \"你好\",\n \"是否\",\n \"首都\",\n \"多大\",\n \"谁是\",\n \"何时\",\n // Japanese\n \"とは\",\n \"定義\",\n \"翻訳\",\n \"こんにちは\",\n \"はいかいいえ\",\n \"首都\",\n \"誰\",\n // Russian\n \"что такое\",\n \"определение\",\n \"перевести\",\n \"переведи\",\n \"привет\",\n \"да или нет\",\n \"столица\",\n \"сколько лет\",\n \"кто такой\",\n \"когда\",\n \"объясни\",\n // German\n \"was ist\",\n \"definiere\",\n \"übersetze\",\n \"hallo\",\n \"ja oder nein\",\n \"hauptstadt\",\n \"wie alt\",\n \"wer ist\",\n \"wann\",\n \"erkläre\",\n // Spanish\n \"qué es\",\n \"definir\",\n \"traducir\",\n \"hola\",\n \"sí o no\",\n \"capital de\",\n \"cuántos años\",\n \"quién es\",\n \"cuándo\",\n // Portuguese\n \"o que é\",\n \"definir\",\n \"traduzir\",\n \"olá\",\n \"sim ou não\",\n \"capital de\",\n \"quantos anos\",\n \"quem é\",\n \"quando\",\n // Korean\n \"무엇\",\n \"정의\",\n \"번역\",\n \"안녕하세요\",\n \"예 또는 아니오\",\n \"수도\",\n \"누구\",\n \"언제\",\n // Arabic\n \"ما هو\",\n \"تعريف\",\n \"ترجم\",\n \"مرحبا\",\n \"نعم أو لا\",\n \"عاصمة\",\n \"من هو\",\n \"متى\",\n ],\n technicalKeywords: [\n // English\n \"algorithm\",\n \"optimize\",\n \"architecture\",\n \"distributed\",\n \"kubernetes\",\n \"microservice\",\n \"database\",\n \"infrastructure\",\n // Chinese\n \"算法\",\n \"优化\",\n \"架构\",\n \"分布式\",\n \"微服务\",\n \"数据库\",\n \"基础设施\",\n // Japanese\n \"アルゴリズム\",\n \"最適化\",\n \"アーキテクチャ\",\n \"分散\",\n \"マイクロサービス\",\n \"データベース\",\n // Russian\n \"алгоритм\",\n \"оптимизировать\",\n \"оптимизаци\",\n \"оптимизируй\",\n \"архитектура\",\n \"распределённый\",\n \"микросервис\",\n \"база данных\",\n \"инфраструктура\",\n // German\n \"algorithmus\",\n \"optimieren\",\n \"architektur\",\n \"verteilt\",\n \"kubernetes\",\n \"mikroservice\",\n \"datenbank\",\n \"infrastruktur\",\n // Spanish\n \"algoritmo\",\n \"optimizar\",\n \"arquitectura\",\n \"distribuido\",\n \"microservicio\",\n \"base de datos\",\n \"infraestructura\",\n // Portuguese\n \"algoritmo\",\n \"otimizar\",\n \"arquitetura\",\n \"distribuído\",\n \"microsserviço\",\n \"banco de dados\",\n \"infraestrutura\",\n // Korean\n \"알고리즘\",\n \"최적화\",\n \"아키텍처\",\n \"분산\",\n \"마이크로서비스\",\n \"데이터베이스\",\n \"인프라\",\n // Arabic\n \"خوارزمية\",\n \"تحسين\",\n \"بنية\",\n \"موزع\",\n \"خدمة مصغرة\",\n \"قاعدة بيانات\",\n \"بنية تحتية\",\n ],\n creativeKeywords: [\n // English\n \"story\",\n \"poem\",\n \"compose\",\n \"brainstorm\",\n \"creative\",\n \"imagine\",\n \"write a\",\n // Chinese\n \"故事\",\n \"诗\",\n \"创作\",\n \"头脑风暴\",\n \"创意\",\n \"想象\",\n \"写一个\",\n // Japanese\n \"物語\",\n \"詩\",\n \"作曲\",\n \"ブレインストーム\",\n \"創造的\",\n \"想像\",\n // Russian\n \"история\",\n \"рассказ\",\n \"стихотворение\",\n \"сочинить\",\n \"сочини\",\n \"мозговой штурм\",\n \"творческий\",\n \"представить\",\n \"придумай\",\n \"напиши\",\n // German\n \"geschichte\",\n \"gedicht\",\n \"komponieren\",\n \"brainstorming\",\n \"kreativ\",\n \"vorstellen\",\n \"schreibe\",\n \"erzählung\",\n // Spanish\n \"historia\",\n \"poema\",\n \"componer\",\n \"lluvia de ideas\",\n \"creativo\",\n \"imaginar\",\n \"escribe\",\n // Portuguese\n \"história\",\n \"poema\",\n \"compor\",\n \"criativo\",\n \"imaginar\",\n \"escreva\",\n // Korean\n \"이야기\",\n \"시\",\n \"작곡\",\n \"브레인스토밍\",\n \"창의적\",\n \"상상\",\n \"작성\",\n // Arabic\n \"قصة\",\n \"قصيدة\",\n \"تأليف\",\n \"عصف ذهني\",\n \"إبداعي\",\n \"تخيل\",\n \"اكتب\",\n ],\n\n // New dimension keyword lists (multilingual)\n imperativeVerbs: [\n // English\n \"build\",\n \"create\",\n \"implement\",\n \"design\",\n \"develop\",\n \"construct\",\n \"generate\",\n \"deploy\",\n \"configure\",\n \"set up\",\n // Chinese\n \"构建\",\n \"创建\",\n \"实现\",\n \"设计\",\n \"开发\",\n \"生成\",\n \"部署\",\n \"配置\",\n \"设置\",\n // Japanese\n \"構築\",\n \"作成\",\n \"実装\",\n \"設計\",\n \"開発\",\n \"生成\",\n \"デプロイ\",\n \"設定\",\n // Russian\n \"построить\",\n \"построй\",\n \"создать\",\n \"создай\",\n \"реализовать\",\n \"реализуй\",\n \"спроектировать\",\n \"разработать\",\n \"разработай\",\n \"сконструировать\",\n \"сгенерировать\",\n \"сгенерируй\",\n \"развернуть\",\n \"разверни\",\n \"настроить\",\n \"настрой\",\n // German\n \"erstellen\",\n \"bauen\",\n \"implementieren\",\n \"entwerfen\",\n \"entwickeln\",\n \"konstruieren\",\n \"generieren\",\n \"bereitstellen\",\n \"konfigurieren\",\n \"einrichten\",\n // Spanish\n \"construir\",\n \"crear\",\n \"implementar\",\n \"diseñar\",\n \"desarrollar\",\n \"generar\",\n \"desplegar\",\n \"configurar\",\n // Portuguese\n \"construir\",\n \"criar\",\n \"implementar\",\n \"projetar\",\n \"desenvolver\",\n \"gerar\",\n \"implantar\",\n \"configurar\",\n // Korean\n \"구축\",\n \"생성\",\n \"구현\",\n \"설계\",\n \"개발\",\n \"배포\",\n \"설정\",\n // Arabic\n \"بناء\",\n \"إنشاء\",\n \"تنفيذ\",\n \"تصميم\",\n \"تطوير\",\n \"توليد\",\n \"نشر\",\n \"إعداد\",\n ],\n constraintIndicators: [\n // English\n \"under\",\n \"at most\",\n \"at least\",\n \"within\",\n \"no more than\",\n \"o(\",\n \"maximum\",\n \"minimum\",\n \"limit\",\n \"budget\",\n // Chinese\n \"不超过\",\n \"至少\",\n \"最多\",\n \"在内\",\n \"最大\",\n \"最小\",\n \"限制\",\n \"预算\",\n // Japanese\n \"以下\",\n \"最大\",\n \"最小\",\n \"制限\",\n \"予算\",\n // Russian\n \"не более\",\n \"не менее\",\n \"как минимум\",\n \"в пределах\",\n \"максимум\",\n \"минимум\",\n \"ограничение\",\n \"бюджет\",\n // German\n \"höchstens\",\n \"mindestens\",\n \"innerhalb\",\n \"nicht mehr als\",\n \"maximal\",\n \"minimal\",\n \"grenze\",\n \"budget\",\n // Spanish\n \"como máximo\",\n \"al menos\",\n \"dentro de\",\n \"no más de\",\n \"máximo\",\n \"mínimo\",\n \"límite\",\n \"presupuesto\",\n // Portuguese\n \"no máximo\",\n \"pelo menos\",\n \"dentro de\",\n \"não mais que\",\n \"máximo\",\n \"mínimo\",\n \"limite\",\n \"orçamento\",\n // Korean\n \"이하\",\n \"이상\",\n \"최대\",\n \"최소\",\n \"제한\",\n \"예산\",\n // Arabic\n \"على الأكثر\",\n \"على الأقل\",\n \"ضمن\",\n \"لا يزيد عن\",\n \"أقصى\",\n \"أدنى\",\n \"حد\",\n \"ميزانية\",\n ],\n outputFormatKeywords: [\n // English\n \"json\",\n \"yaml\",\n \"xml\",\n \"table\",\n \"csv\",\n \"markdown\",\n \"schema\",\n \"format as\",\n \"structured\",\n // Chinese\n \"表格\",\n \"格式化为\",\n \"结构化\",\n // Japanese\n \"テーブル\",\n \"フォーマット\",\n \"構造化\",\n // Russian\n \"таблица\",\n \"форматировать как\",\n \"структурированный\",\n // German\n \"tabelle\",\n \"formatieren als\",\n \"strukturiert\",\n // Spanish\n \"tabla\",\n \"formatear como\",\n \"estructurado\",\n // Portuguese\n \"tabela\",\n \"formatar como\",\n \"estruturado\",\n // Korean\n \"테이블\",\n \"형식\",\n \"구조화\",\n // Arabic\n \"جدول\",\n \"تنسيق\",\n \"منظم\",\n ],\n referenceKeywords: [\n // English\n \"above\",\n \"below\",\n \"previous\",\n \"following\",\n \"the docs\",\n \"the api\",\n \"the code\",\n \"earlier\",\n \"attached\",\n // Chinese\n \"上面\",\n \"下面\",\n \"之前\",\n \"接下来\",\n \"文档\",\n \"代码\",\n \"附件\",\n // Japanese\n \"上記\",\n \"下記\",\n \"前の\",\n \"次の\",\n \"ドキュメント\",\n \"コード\",\n // Russian\n \"выше\",\n \"ниже\",\n \"предыдущий\",\n \"следующий\",\n \"документация\",\n \"код\",\n \"ранее\",\n \"вложение\",\n // German\n \"oben\",\n \"unten\",\n \"vorherige\",\n \"folgende\",\n \"dokumentation\",\n \"der code\",\n \"früher\",\n \"anhang\",\n // Spanish\n \"arriba\",\n \"abajo\",\n \"anterior\",\n \"siguiente\",\n \"documentación\",\n \"el código\",\n \"adjunto\",\n // Portuguese\n \"acima\",\n \"abaixo\",\n \"anterior\",\n \"seguinte\",\n \"documentação\",\n \"o código\",\n \"anexo\",\n // Korean\n \"위\",\n \"아래\",\n \"이전\",\n \"다음\",\n \"문서\",\n \"코드\",\n \"첨부\",\n // Arabic\n \"أعلاه\",\n \"أدناه\",\n \"السابق\",\n \"التالي\",\n \"الوثائق\",\n \"الكود\",\n \"مرفق\",\n ],\n negationKeywords: [\n // English\n \"don't\",\n \"do not\",\n \"avoid\",\n \"never\",\n \"without\",\n \"except\",\n \"exclude\",\n \"no longer\",\n // Chinese\n \"不要\",\n \"避免\",\n \"从不\",\n \"没有\",\n \"除了\",\n \"排除\",\n // Japanese\n \"しないで\",\n \"避ける\",\n \"決して\",\n \"なしで\",\n \"除く\",\n // Russian\n \"не делай\",\n \"не надо\",\n \"нельзя\",\n \"избегать\",\n \"никогда\",\n \"без\",\n \"кроме\",\n \"исключить\",\n \"больше не\",\n // German\n \"nicht\",\n \"vermeide\",\n \"niemals\",\n \"ohne\",\n \"außer\",\n \"ausschließen\",\n \"nicht mehr\",\n // Spanish\n \"no hagas\",\n \"evitar\",\n \"nunca\",\n \"sin\",\n \"excepto\",\n \"excluir\",\n // Portuguese\n \"não faça\",\n \"evitar\",\n \"nunca\",\n \"sem\",\n \"exceto\",\n \"excluir\",\n // Korean\n \"하지 마\",\n \"피하다\",\n \"절대\",\n \"없이\",\n \"제외\",\n // Arabic\n \"لا تفعل\",\n \"تجنب\",\n \"أبداً\",\n \"بدون\",\n \"باستثناء\",\n \"استبعاد\",\n ],\n domainSpecificKeywords: [\n // English\n \"quantum\",\n \"fpga\",\n \"vlsi\",\n \"risc-v\",\n \"asic\",\n \"photonics\",\n \"genomics\",\n \"proteomics\",\n \"topological\",\n \"homomorphic\",\n \"zero-knowledge\",\n \"lattice-based\",\n // Chinese\n \"量子\",\n \"光子学\",\n \"基因组学\",\n \"蛋白质组学\",\n \"拓扑\",\n \"同态\",\n \"零知识\",\n \"格密码\",\n // Japanese\n \"量子\",\n \"フォトニクス\",\n \"ゲノミクス\",\n \"トポロジカル\",\n // Russian\n \"квантовый\",\n \"фотоника\",\n \"геномика\",\n \"протеомика\",\n \"топологический\",\n \"гомоморфный\",\n \"с нулевым разглашением\",\n \"на основе решёток\",\n // German\n \"quanten\",\n \"photonik\",\n \"genomik\",\n \"proteomik\",\n \"topologisch\",\n \"homomorph\",\n \"zero-knowledge\",\n \"gitterbasiert\",\n // Spanish\n \"cuántico\",\n \"fotónica\",\n \"genómica\",\n \"proteómica\",\n \"topológico\",\n \"homomórfico\",\n // Portuguese\n \"quântico\",\n \"fotônica\",\n \"genômica\",\n \"proteômica\",\n \"topológico\",\n \"homomórfico\",\n // Korean\n \"양자\",\n \"포토닉스\",\n \"유전체학\",\n \"위상\",\n \"동형\",\n // Arabic\n \"كمي\",\n \"ضوئيات\",\n \"جينوميات\",\n \"طوبولوجي\",\n \"تماثلي\",\n ],\n\n // Agentic task keywords - file ops, execution, multi-step, iterative work\n // Pruned: removed overly common words like \"then\", \"first\", \"run\", \"test\", \"build\"\n agenticTaskKeywords: [\n // English - File operations (clearly agentic)\n \"read file\",\n \"read the file\",\n \"look at\",\n \"check the\",\n \"open the\",\n \"edit\",\n \"modify\",\n \"update the\",\n \"change the\",\n \"write to\",\n \"create file\",\n // English - Execution (specific commands only)\n \"execute\",\n \"deploy\",\n \"install\",\n \"npm\",\n \"pip\",\n \"compile\",\n // English - Multi-step patterns (specific only)\n \"after that\",\n \"and also\",\n \"once done\",\n \"step 1\",\n \"step 2\",\n // English - Iterative work\n \"fix\",\n \"debug\",\n \"until it works\",\n \"keep trying\",\n \"iterate\",\n \"make sure\",\n \"verify\",\n \"confirm\",\n // Chinese (keep specific ones)\n \"读取文件\",\n \"查看\",\n \"打开\",\n \"编辑\",\n \"修改\",\n \"更新\",\n \"创建\",\n \"执行\",\n \"部署\",\n \"安装\",\n \"第一步\",\n \"第二步\",\n \"修复\",\n \"调试\",\n \"直到\",\n \"确认\",\n \"验证\",\n // Spanish\n \"leer archivo\",\n \"editar\",\n \"modificar\",\n \"actualizar\",\n \"ejecutar\",\n \"desplegar\",\n \"instalar\",\n \"paso 1\",\n \"paso 2\",\n \"arreglar\",\n \"depurar\",\n \"verificar\",\n // Portuguese\n \"ler arquivo\",\n \"editar\",\n \"modificar\",\n \"atualizar\",\n \"executar\",\n \"implantar\",\n \"instalar\",\n \"passo 1\",\n \"passo 2\",\n \"corrigir\",\n \"depurar\",\n \"verificar\",\n // Korean\n \"파일 읽기\",\n \"편집\",\n \"수정\",\n \"업데이트\",\n \"실행\",\n \"배포\",\n \"설치\",\n \"단계 1\",\n \"단계 2\",\n \"디버그\",\n \"확인\",\n // Arabic\n \"قراءة ملف\",\n \"تحرير\",\n \"تعديل\",\n \"تحديث\",\n \"تنفيذ\",\n \"نشر\",\n \"تثبيت\",\n \"الخطوة 1\",\n \"الخطوة 2\",\n \"إصلاح\",\n \"تصحيح\",\n \"تحقق\",\n ],\n\n // Dimension weights (sum to 1.0)\n dimensionWeights: {\n tokenCount: 0.08,\n codePresence: 0.15,\n reasoningMarkers: 0.18,\n technicalTerms: 0.1,\n creativeMarkers: 0.05,\n simpleIndicators: 0.02, // Reduced from 0.12 to make room for agenticTask\n multiStepPatterns: 0.12,\n questionComplexity: 0.05,\n imperativeVerbs: 0.03,\n constraintCount: 0.04,\n outputFormat: 0.03,\n referenceComplexity: 0.02,\n negationComplexity: 0.01,\n domainSpecificity: 0.02,\n agenticTask: 0.04, // Reduced - agentic signals influence tier selection, not dominate it\n },\n\n // Tier boundaries on weighted score axis\n tierBoundaries: {\n simpleMedium: 0.0,\n mediumComplex: 0.3, // Raised from 0.18 - prevent simple tasks from reaching expensive COMPLEX tier\n complexReasoning: 0.5, // Raised from 0.4 - reserve for true reasoning tasks\n },\n\n // Sigmoid steepness for confidence calibration\n confidenceSteepness: 12,\n // Below this confidence → ambiguous (null tier)\n confidenceThreshold: 0.7,\n },\n\n // Auto (balanced) tier configs - current default smart routing\n tiers: {\n SIMPLE: {\n primary: \"moonshot/kimi-k2.5\", // $0.60/$3.00 - best quality/price for simple tasks\n fallback: [\n \"google/gemini-2.5-flash-lite\", // 1M context, ultra cheap ($0.10/$0.40)\n \"nvidia/gpt-oss-120b\", // FREE fallback\n \"deepseek/deepseek-chat\",\n ],\n },\n MEDIUM: {\n primary: \"moonshot/kimi-k2.5\", // $0.50/$2.40 - strong tool use, proper function call format\n fallback: [\n \"deepseek/deepseek-chat\",\n \"google/gemini-2.5-flash-lite\", // 1M context, ultra cheap ($0.10/$0.40)\n \"xai/grok-4-1-fast-non-reasoning\", // Upgraded Grok 4.1\n ],\n },\n COMPLEX: {\n primary: \"google/gemini-3.1-pro\", // Newest Gemini 3.1 - upgraded from 3.0\n fallback: [\n \"google/gemini-2.5-flash-lite\", // CRITICAL: 1M context, ultra-cheap failsafe ($0.10/$0.40)\n \"google/gemini-3-pro-preview\", // 3.0 fallback\n \"google/gemini-2.5-pro\",\n \"deepseek/deepseek-chat\",\n \"xai/grok-4-0709\",\n \"openai/gpt-5.2\", // Newer and cheaper input than gpt-4o\n \"openai/gpt-4o\",\n \"anthropic/claude-sonnet-4.6\",\n ],\n },\n REASONING: {\n primary: \"xai/grok-4-1-fast-reasoning\", // Upgraded Grok 4.1 reasoning $0.20/$0.50\n fallback: [\n \"deepseek/deepseek-reasoner\", // Cheap reasoning model\n \"openai/o4-mini\", // Newer and cheaper than o3 ($1.10 vs $2.00)\n \"openai/o3\",\n ],\n },\n },\n\n // Eco tier configs - absolute cheapest (blockrun/eco)\n ecoTiers: {\n SIMPLE: {\n primary: \"nvidia/gpt-oss-120b\", // FREE! $0.00/$0.00\n fallback: [\"google/gemini-2.5-flash-lite\", \"deepseek/deepseek-chat\"],\n },\n MEDIUM: {\n primary: \"google/gemini-2.5-flash-lite\", // $0.10/$0.40 - cheapest capable with 1M context\n fallback: [\"deepseek/deepseek-chat\", \"nvidia/gpt-oss-120b\"],\n },\n COMPLEX: {\n primary: \"google/gemini-2.5-flash-lite\", // $0.10/$0.40 - 1M context handles complexity\n fallback: [\"google/gemini-2.5-flash\", \"deepseek/deepseek-chat\", \"xai/grok-4-0709\"],\n },\n REASONING: {\n primary: \"xai/grok-4-1-fast-reasoning\", // $0.20/$0.50\n fallback: [\"deepseek/deepseek-reasoner\"],\n },\n },\n\n // Premium tier configs - best quality (blockrun/premium)\n // codex=complex coding, kimi=simple coding, sonnet=reasoning/instructions, opus=architecture/PM/audits\n premiumTiers: {\n SIMPLE: {\n primary: \"moonshot/kimi-k2.5\", // $0.60/$3.00 - good for simple coding\n fallback: [\n \"anthropic/claude-haiku-4.5\",\n \"google/gemini-2.5-flash-lite\",\n \"deepseek/deepseek-chat\",\n ],\n },\n MEDIUM: {\n primary: \"openai/gpt-5.2-codex\", // $2.50/$10 - strong coding for medium tasks\n fallback: [\n \"moonshot/kimi-k2.5\",\n \"google/gemini-2.5-pro\",\n \"xai/grok-4-0709\",\n \"anthropic/claude-sonnet-4.6\",\n ],\n },\n COMPLEX: {\n primary: \"anthropic/claude-opus-4.6\", // Best quality for complex tasks\n fallback: [\n \"openai/gpt-5.2-codex\",\n \"anthropic/claude-opus-4.6\",\n \"anthropic/claude-sonnet-4.6\",\n \"google/gemini-3.1-pro\", // Newest Gemini\n \"google/gemini-3-pro-preview\",\n \"moonshot/kimi-k2.5\",\n ],\n },\n REASONING: {\n primary: \"anthropic/claude-sonnet-4.6\", // $3/$15 - best for reasoning/instructions\n fallback: [\n \"anthropic/claude-opus-4.6\",\n \"anthropic/claude-opus-4.6\",\n \"openai/o4-mini\", // Newer and cheaper than o3 ($1.10 vs $2.00)\n \"openai/o3\",\n \"xai/grok-4-1-fast-reasoning\",\n ],\n },\n },\n\n // Agentic tier configs - models that excel at multi-step autonomous tasks\n agenticTiers: {\n SIMPLE: {\n primary: \"moonshot/kimi-k2.5\", // Cheaper than Haiku ($0.5/$2.4 vs $1/$5), larger context\n fallback: [\n \"anthropic/claude-haiku-4.5\",\n \"xai/grok-4-1-fast-non-reasoning\",\n \"openai/gpt-4o-mini\",\n ],\n },\n MEDIUM: {\n primary: \"moonshot/kimi-k2.5\", // $0.50/$2.40 - strong tool use, handles function calls correctly\n fallback: [\n \"anthropic/claude-haiku-4.5\",\n \"deepseek/deepseek-chat\",\n \"xai/grok-4-1-fast-non-reasoning\",\n ],\n },\n COMPLEX: {\n primary: \"anthropic/claude-sonnet-4.6\",\n fallback: [\n \"anthropic/claude-opus-4.6\", // Latest Opus - best agentic\n \"openai/gpt-5.2\",\n \"google/gemini-3.1-pro\", // Newest Gemini\n \"google/gemini-3-pro-preview\",\n \"xai/grok-4-0709\",\n ],\n },\n REASONING: {\n primary: \"anthropic/claude-sonnet-4.6\", // Strong tool use + reasoning for agentic tasks\n fallback: [\n \"anthropic/claude-opus-4.6\",\n \"xai/grok-4-1-fast-reasoning\",\n \"deepseek/deepseek-reasoner\",\n ],\n },\n },\n\n overrides: {\n maxTokensForceComplex: 100_000,\n structuredOutputMinTier: \"MEDIUM\",\n ambiguousDefaultTier: \"MEDIUM\",\n agenticMode: false,\n },\n};\n","/**\n * Smart Router Entry Point\n *\n * Classifies requests and routes to the cheapest capable model.\n * 100% local — rules-based scoring handles all requests in <1ms.\n * Ambiguous cases default to configurable tier (MEDIUM by default).\n */\n\nimport type { Tier, RoutingDecision, RoutingConfig } from \"./types.js\";\nimport { classifyByRules } from \"./rules.js\";\nimport { selectModel, type ModelPricing } from \"./selector.js\";\n\nexport type RouterOptions = {\n config: RoutingConfig;\n modelPricing: Map;\n routingProfile?: \"free\" | \"eco\" | \"auto\" | \"premium\";\n};\n\n/**\n * Route a request to the cheapest capable model.\n *\n * 1. Check overrides (large context, structured output)\n * 2. Run rule-based classifier (14 weighted dimensions, <1ms)\n * 3. If ambiguous, default to configurable tier (no external API calls)\n * 4. Select model for tier\n * 5. Return RoutingDecision with metadata\n */\nexport function route(\n prompt: string,\n systemPrompt: string | undefined,\n maxOutputTokens: number,\n options: RouterOptions,\n): RoutingDecision {\n const { config, modelPricing } = options;\n\n // Estimate input tokens (~4 chars per token)\n const fullText = `${systemPrompt ?? \"\"} ${prompt}`;\n const estimatedTokens = Math.ceil(fullText.length / 4);\n\n // --- Rule-based classification (runs first to get agenticScore) ---\n const ruleResult = classifyByRules(prompt, systemPrompt, estimatedTokens, config.scoring);\n\n // --- Select tier configs based on routing profile ---\n const { routingProfile } = options;\n let tierConfigs: Record;\n let profileSuffix: string;\n\n if (routingProfile === \"eco\" && config.ecoTiers) {\n // Eco profile: ultra cost-optimized models\n tierConfigs = config.ecoTiers;\n profileSuffix = \" | eco\";\n } else if (routingProfile === \"premium\" && config.premiumTiers) {\n // Premium profile: best quality models\n tierConfigs = config.premiumTiers;\n profileSuffix = \" | premium\";\n } else {\n // Auto profile (or undefined): intelligent routing with agentic detection\n // Determine if agentic tiers should be used:\n // 1. Explicit agenticMode config OR\n // 2. Auto-detected agentic task (agenticScore >= 0.5, lowered for better multi-step detection)\n const agenticScore = ruleResult.agenticScore ?? 0;\n const isAutoAgentic = agenticScore >= 0.5;\n const isExplicitAgentic = config.overrides.agenticMode ?? false;\n const useAgenticTiers = (isAutoAgentic || isExplicitAgentic) && config.agenticTiers != null;\n tierConfigs = useAgenticTiers ? config.agenticTiers! : config.tiers;\n profileSuffix = useAgenticTiers ? \" | agentic\" : \"\";\n }\n\n const agenticScoreValue = ruleResult.agenticScore;\n\n // --- Override: large context → force COMPLEX ---\n if (estimatedTokens > config.overrides.maxTokensForceComplex) {\n return selectModel(\n \"COMPLEX\",\n 0.95,\n \"rules\",\n `Input exceeds ${config.overrides.maxTokensForceComplex} tokens${profileSuffix}`,\n tierConfigs,\n modelPricing,\n estimatedTokens,\n maxOutputTokens,\n routingProfile,\n agenticScoreValue,\n );\n }\n\n // Structured output detection\n const hasStructuredOutput = systemPrompt ? /json|structured|schema/i.test(systemPrompt) : false;\n\n let tier: Tier;\n let confidence: number;\n const method: \"rules\" | \"llm\" = \"rules\";\n let reasoning = `score=${ruleResult.score.toFixed(2)} | ${ruleResult.signals.join(\", \")}`;\n\n if (ruleResult.tier !== null) {\n tier = ruleResult.tier;\n confidence = ruleResult.confidence;\n } else {\n // Ambiguous — default to configurable tier (no external API call)\n tier = config.overrides.ambiguousDefaultTier;\n confidence = 0.5;\n reasoning += ` | ambiguous -> default: ${tier}`;\n }\n\n // Apply structured output minimum tier\n if (hasStructuredOutput) {\n const tierRank: Record = { SIMPLE: 0, MEDIUM: 1, COMPLEX: 2, REASONING: 3 };\n const minTier = config.overrides.structuredOutputMinTier;\n if (tierRank[tier] < tierRank[minTier]) {\n reasoning += ` | upgraded to ${minTier} (structured output)`;\n tier = minTier;\n }\n }\n\n // Add routing profile suffix to reasoning\n reasoning += profileSuffix;\n\n return selectModel(\n tier,\n confidence,\n method,\n reasoning,\n tierConfigs,\n modelPricing,\n estimatedTokens,\n maxOutputTokens,\n routingProfile,\n agenticScoreValue,\n );\n}\n\nexport {\n getFallbackChain,\n getFallbackChainFiltered,\n filterByToolCalling,\n filterByVision,\n calculateModelCost,\n} from \"./selector.js\";\nexport { DEFAULT_ROUTING_CONFIG } from \"./config.js\";\nexport type { RoutingDecision, Tier, RoutingConfig } from \"./types.js\";\nexport type { ModelPricing } from \"./selector.js\";\n","/**\n * BlockRun Model Definitions for OpenClaw\n *\n * Maps BlockRun's 30+ AI models to OpenClaw's ModelDefinitionConfig format.\n * All models use the \"openai-completions\" API since BlockRun is OpenAI-compatible.\n *\n * Pricing is in USD per 1M tokens. Operators pay these rates via x402;\n * they set their own markup when reselling to end users (Phase 2).\n */\n\nimport type { ModelDefinitionConfig, ModelProviderConfig } from \"./types.js\";\n\n/**\n * Model aliases for convenient shorthand access.\n * Users can type `/model claude` instead of `/model blockrun/anthropic/claude-sonnet-4-6`.\n */\nexport const MODEL_ALIASES: Record = {\n // Claude - use newest versions (4.6)\n claude: \"anthropic/claude-sonnet-4.6\",\n sonnet: \"anthropic/claude-sonnet-4.6\",\n \"sonnet-4\": \"anthropic/claude-sonnet-4.6\",\n \"sonnet-4.6\": \"anthropic/claude-sonnet-4.6\",\n \"sonnet-4-6\": \"anthropic/claude-sonnet-4.6\",\n opus: \"anthropic/claude-opus-4.6\",\n \"opus-4\": \"anthropic/claude-opus-4.6\",\n \"opus-4.6\": \"anthropic/claude-opus-4.6\",\n \"opus-4-6\": \"anthropic/claude-opus-4.6\",\n haiku: \"anthropic/claude-haiku-4.5\",\n // Claude - provider/shortname patterns (common in agent frameworks)\n \"anthropic/sonnet\": \"anthropic/claude-sonnet-4.6\",\n \"anthropic/opus\": \"anthropic/claude-opus-4.6\",\n \"anthropic/haiku\": \"anthropic/claude-haiku-4.5\",\n \"anthropic/claude\": \"anthropic/claude-sonnet-4.6\",\n // Backward compatibility - map all variants to 4.6\n \"anthropic/claude-sonnet-4\": \"anthropic/claude-sonnet-4.6\",\n \"anthropic/claude-sonnet-4-6\": \"anthropic/claude-sonnet-4.6\",\n \"anthropic/claude-opus-4\": \"anthropic/claude-opus-4.6\",\n \"anthropic/claude-opus-4-6\": \"anthropic/claude-opus-4.6\",\n \"anthropic/claude-opus-4.5\": \"anthropic/claude-opus-4.6\",\n \"anthropic/claude-haiku-4\": \"anthropic/claude-haiku-4.5\",\n \"anthropic/claude-haiku-4-5\": \"anthropic/claude-haiku-4.5\",\n\n // OpenAI\n gpt: \"openai/gpt-4o\",\n gpt4: \"openai/gpt-4o\",\n gpt5: \"openai/gpt-5.2\",\n codex: \"openai/gpt-5.2-codex\",\n mini: \"openai/gpt-4o-mini\",\n o1: \"openai/o1\",\n o3: \"openai/o3\",\n\n // DeepSeek\n deepseek: \"deepseek/deepseek-chat\",\n reasoner: \"deepseek/deepseek-reasoner\",\n\n // Kimi / Moonshot\n kimi: \"moonshot/kimi-k2.5\",\n moonshot: \"moonshot/kimi-k2.5\",\n \"kimi-k2.5\": \"moonshot/kimi-k2.5\",\n\n // Google\n gemini: \"google/gemini-2.5-pro\",\n flash: \"google/gemini-2.5-flash\",\n \"gemini-3.1-pro-preview\": \"google/gemini-3.1-pro\",\n \"google/gemini-3.1-pro-preview\": \"google/gemini-3.1-pro\",\n\n // xAI\n grok: \"xai/grok-3\",\n \"grok-fast\": \"xai/grok-4-fast-reasoning\",\n \"grok-code\": \"xai/grok-code-fast-1\",\n\n // NVIDIA\n nvidia: \"nvidia/gpt-oss-120b\",\n \"gpt-120b\": \"nvidia/gpt-oss-120b\",\n\n // MiniMax\n minimax: \"minimax/minimax-m2.5\",\n\n // Routing profile aliases (common variations)\n \"auto-router\": \"auto\",\n router: \"auto\",\n\n // Note: auto, free, eco, premium are virtual routing profiles registered in BLOCKRUN_MODELS\n // They don't need aliases since they're already top-level model IDs\n};\n\n/**\n * Resolve a model alias to its full model ID.\n * Also strips \"blockrun/\" prefix for direct model paths.\n * Examples:\n * - \"claude\" -> \"anthropic/claude-sonnet-4-6\" (alias)\n * - \"blockrun/claude\" -> \"anthropic/claude-sonnet-4-6\" (alias with prefix)\n * - \"blockrun/anthropic/claude-sonnet-4-6\" -> \"anthropic/claude-sonnet-4-6\" (prefix stripped)\n * - \"openai/gpt-4o\" -> \"openai/gpt-4o\" (unchanged)\n */\nexport function resolveModelAlias(model: string): string {\n const normalized = model.trim().toLowerCase();\n const resolved = MODEL_ALIASES[normalized];\n if (resolved) return resolved;\n\n // Check with \"blockrun/\" prefix stripped\n if (normalized.startsWith(\"blockrun/\")) {\n const withoutPrefix = normalized.slice(\"blockrun/\".length);\n const resolvedWithoutPrefix = MODEL_ALIASES[withoutPrefix];\n if (resolvedWithoutPrefix) return resolvedWithoutPrefix;\n\n // Even if not an alias, strip the prefix for direct model paths\n // e.g., \"blockrun/anthropic/claude-sonnet-4-6\" -> \"anthropic/claude-sonnet-4-6\"\n return withoutPrefix;\n }\n\n return model;\n}\n\ntype BlockRunModel = {\n id: string;\n name: string;\n /** Model version (e.g., \"4.6\", \"3.1\", \"5.2\") for tracking updates */\n version?: string;\n inputPrice: number;\n outputPrice: number;\n contextWindow: number;\n maxOutput: number;\n reasoning?: boolean;\n vision?: boolean;\n /** Models optimized for agentic workflows (multi-step autonomous tasks) */\n agentic?: boolean;\n /**\n * Model supports OpenAI-compatible structured function/tool calling.\n * Models without this flag output tool invocations as plain text JSON,\n * which leaks raw {\"command\":\"...\"} into visible chat messages.\n * Default: false (must opt-in to prevent silent regressions on new models).\n */\n toolCalling?: boolean;\n};\n\nexport const BLOCKRUN_MODELS: BlockRunModel[] = [\n // Smart routing meta-models — proxy replaces with actual model\n // NOTE: Model IDs are WITHOUT provider prefix (OpenClaw adds \"blockrun/\" automatically)\n {\n id: \"auto\",\n name: \"Auto (Smart Router - Balanced)\",\n inputPrice: 0,\n outputPrice: 0,\n contextWindow: 1_050_000,\n maxOutput: 128_000,\n },\n {\n id: \"free\",\n name: \"Free (NVIDIA GPT-OSS-120B only)\",\n inputPrice: 0,\n outputPrice: 0,\n contextWindow: 128_000,\n maxOutput: 4_096,\n },\n {\n id: \"eco\",\n name: \"Eco (Smart Router - Cost Optimized)\",\n inputPrice: 0,\n outputPrice: 0,\n contextWindow: 1_050_000,\n maxOutput: 128_000,\n },\n {\n id: \"premium\",\n name: \"Premium (Smart Router - Best Quality)\",\n inputPrice: 0,\n outputPrice: 0,\n contextWindow: 2_000_000,\n maxOutput: 200_000,\n },\n\n // OpenAI GPT-5 Family\n {\n id: \"openai/gpt-5.2\",\n name: \"GPT-5.2\",\n version: \"5.2\",\n inputPrice: 1.75,\n outputPrice: 14.0,\n contextWindow: 400000,\n maxOutput: 128000,\n reasoning: true,\n vision: true,\n agentic: true,\n toolCalling: true,\n },\n {\n id: \"openai/gpt-5-mini\",\n name: \"GPT-5 Mini\",\n version: \"5.0\",\n inputPrice: 0.25,\n outputPrice: 2.0,\n contextWindow: 200000,\n maxOutput: 65536,\n toolCalling: true,\n },\n {\n id: \"openai/gpt-5-nano\",\n name: \"GPT-5 Nano\",\n version: \"5.0\",\n inputPrice: 0.05,\n outputPrice: 0.4,\n contextWindow: 128000,\n maxOutput: 32768,\n toolCalling: true,\n },\n {\n id: \"openai/gpt-5.2-pro\",\n name: \"GPT-5.2 Pro\",\n version: \"5.2\",\n inputPrice: 21.0,\n outputPrice: 168.0,\n contextWindow: 400000,\n maxOutput: 128000,\n reasoning: true,\n toolCalling: true,\n },\n\n // OpenAI Codex Family\n {\n id: \"openai/gpt-5.2-codex\",\n name: \"GPT-5.2 Codex\",\n version: \"5.2\",\n inputPrice: 1.75,\n outputPrice: 14.0,\n contextWindow: 128000,\n maxOutput: 32000,\n agentic: true,\n toolCalling: true,\n },\n\n // OpenAI GPT-4 Family\n {\n id: \"openai/gpt-4.1\",\n name: \"GPT-4.1\",\n version: \"4.1\",\n inputPrice: 2.0,\n outputPrice: 8.0,\n contextWindow: 128000,\n maxOutput: 16384,\n vision: true,\n toolCalling: true,\n },\n {\n id: \"openai/gpt-4.1-mini\",\n name: \"GPT-4.1 Mini\",\n version: \"4.1\",\n inputPrice: 0.4,\n outputPrice: 1.6,\n contextWindow: 128000,\n maxOutput: 16384,\n toolCalling: true,\n },\n {\n id: \"openai/gpt-4.1-nano\",\n name: \"GPT-4.1 Nano\",\n version: \"4.1\",\n inputPrice: 0.1,\n outputPrice: 0.4,\n contextWindow: 128000,\n maxOutput: 16384,\n toolCalling: true,\n },\n {\n id: \"openai/gpt-4o\",\n name: \"GPT-4o\",\n version: \"4o\",\n inputPrice: 2.5,\n outputPrice: 10.0,\n contextWindow: 128000,\n maxOutput: 16384,\n vision: true,\n agentic: true,\n toolCalling: true,\n },\n {\n id: \"openai/gpt-4o-mini\",\n name: \"GPT-4o Mini\",\n version: \"4o-mini\",\n inputPrice: 0.15,\n outputPrice: 0.6,\n contextWindow: 128000,\n maxOutput: 16384,\n toolCalling: true,\n },\n\n // OpenAI O-series (Reasoning)\n {\n id: \"openai/o1\",\n name: \"o1\",\n version: \"1\",\n inputPrice: 15.0,\n outputPrice: 60.0,\n contextWindow: 200000,\n maxOutput: 100000,\n reasoning: true,\n toolCalling: true,\n },\n {\n id: \"openai/o1-mini\",\n name: \"o1-mini\",\n version: \"1-mini\",\n inputPrice: 1.1,\n outputPrice: 4.4,\n contextWindow: 128000,\n maxOutput: 65536,\n reasoning: true,\n toolCalling: true,\n },\n {\n id: \"openai/o3\",\n name: \"o3\",\n version: \"3\",\n inputPrice: 2.0,\n outputPrice: 8.0,\n contextWindow: 200000,\n maxOutput: 100000,\n reasoning: true,\n toolCalling: true,\n },\n {\n id: \"openai/o3-mini\",\n name: \"o3-mini\",\n version: \"3-mini\",\n inputPrice: 1.1,\n outputPrice: 4.4,\n contextWindow: 128000,\n maxOutput: 65536,\n reasoning: true,\n toolCalling: true,\n },\n {\n id: \"openai/o4-mini\",\n name: \"o4-mini\",\n version: \"4-mini\",\n inputPrice: 1.1,\n outputPrice: 4.4,\n contextWindow: 128000,\n maxOutput: 65536,\n reasoning: true,\n toolCalling: true,\n },\n\n // Anthropic - all Claude models excel at agentic workflows\n // Use newest versions (4.6) with full provider prefix\n {\n id: \"anthropic/claude-haiku-4.5\",\n name: \"Claude Haiku 4.5\",\n version: \"4.5\",\n inputPrice: 1.0,\n outputPrice: 5.0,\n contextWindow: 200000,\n maxOutput: 8192,\n vision: true,\n agentic: true,\n toolCalling: true,\n },\n {\n id: \"anthropic/claude-sonnet-4.6\",\n name: \"Claude Sonnet 4.6\",\n version: \"4.6\",\n inputPrice: 3.0,\n outputPrice: 15.0,\n contextWindow: 200000,\n maxOutput: 64000,\n reasoning: true,\n vision: true,\n agentic: true,\n toolCalling: true,\n },\n {\n id: \"anthropic/claude-opus-4.6\",\n name: \"Claude Opus 4.6\",\n version: \"4.6\",\n inputPrice: 5.0,\n outputPrice: 25.0,\n contextWindow: 200000,\n maxOutput: 32000,\n reasoning: true,\n vision: true,\n agentic: true,\n toolCalling: true,\n },\n\n // Google\n {\n id: \"google/gemini-3.1-pro\",\n name: \"Gemini 3.1 Pro\",\n version: \"3.1\",\n inputPrice: 2.0,\n outputPrice: 12.0,\n contextWindow: 1050000,\n maxOutput: 65536,\n reasoning: true,\n vision: true,\n toolCalling: true,\n },\n {\n id: \"google/gemini-3-pro-preview\",\n name: \"Gemini 3 Pro Preview\",\n version: \"3.0\",\n inputPrice: 2.0,\n outputPrice: 12.0,\n contextWindow: 1050000,\n maxOutput: 65536,\n reasoning: true,\n vision: true,\n toolCalling: true,\n },\n {\n id: \"google/gemini-3-flash-preview\",\n name: \"Gemini 3 Flash Preview\",\n version: \"3.0\",\n inputPrice: 0.5,\n outputPrice: 3.0,\n contextWindow: 1000000,\n maxOutput: 65536,\n vision: true,\n toolCalling: true,\n },\n {\n id: \"google/gemini-2.5-pro\",\n name: \"Gemini 2.5 Pro\",\n version: \"2.5\",\n inputPrice: 1.25,\n outputPrice: 10.0,\n contextWindow: 1050000,\n maxOutput: 65536,\n reasoning: true,\n vision: true,\n toolCalling: true,\n },\n {\n id: \"google/gemini-2.5-flash\",\n name: \"Gemini 2.5 Flash\",\n version: \"2.5\",\n inputPrice: 0.3,\n outputPrice: 2.5,\n contextWindow: 1000000,\n maxOutput: 65536,\n vision: true,\n toolCalling: true,\n },\n {\n id: \"google/gemini-2.5-flash-lite\",\n name: \"Gemini 2.5 Flash Lite\",\n version: \"2.5\",\n inputPrice: 0.1,\n outputPrice: 0.4,\n contextWindow: 1000000,\n maxOutput: 65536,\n toolCalling: true,\n },\n\n // DeepSeek\n {\n id: \"deepseek/deepseek-chat\",\n name: \"DeepSeek V3.2 Chat\",\n version: \"3.2\",\n inputPrice: 0.28,\n outputPrice: 0.42,\n contextWindow: 128000,\n maxOutput: 8192,\n toolCalling: true,\n },\n {\n id: \"deepseek/deepseek-reasoner\",\n name: \"DeepSeek V3.2 Reasoner\",\n version: \"3.2\",\n inputPrice: 0.28,\n outputPrice: 0.42,\n contextWindow: 128000,\n maxOutput: 8192,\n reasoning: true,\n toolCalling: true,\n },\n\n // Moonshot / Kimi - optimized for agentic workflows\n {\n id: \"moonshot/kimi-k2.5\",\n name: \"Kimi K2.5\",\n version: \"k2.5\",\n inputPrice: 0.6,\n outputPrice: 3.0,\n contextWindow: 262144,\n maxOutput: 8192,\n reasoning: true,\n vision: true,\n agentic: true,\n toolCalling: true,\n },\n\n // xAI / Grok\n {\n id: \"xai/grok-3\",\n name: \"Grok 3\",\n version: \"3\",\n inputPrice: 3.0,\n outputPrice: 15.0,\n contextWindow: 131072,\n maxOutput: 16384,\n reasoning: true,\n toolCalling: true,\n },\n // grok-3-fast removed - too expensive ($5/$25), use grok-4-fast instead\n {\n id: \"xai/grok-3-mini\",\n name: \"Grok 3 Mini\",\n version: \"3-mini\",\n inputPrice: 0.3,\n outputPrice: 0.5,\n contextWindow: 131072,\n maxOutput: 16384,\n toolCalling: true,\n },\n\n // xAI Grok 4 Family - Ultra-cheap fast models\n {\n id: \"xai/grok-4-fast-reasoning\",\n name: \"Grok 4 Fast Reasoning\",\n version: \"4\",\n inputPrice: 0.2,\n outputPrice: 0.5,\n contextWindow: 131072,\n maxOutput: 16384,\n reasoning: true,\n toolCalling: true,\n },\n {\n id: \"xai/grok-4-fast-non-reasoning\",\n name: \"Grok 4 Fast\",\n version: \"4\",\n inputPrice: 0.2,\n outputPrice: 0.5,\n contextWindow: 131072,\n maxOutput: 16384,\n toolCalling: true,\n },\n {\n id: \"xai/grok-4-1-fast-reasoning\",\n name: \"Grok 4.1 Fast Reasoning\",\n version: \"4.1\",\n inputPrice: 0.2,\n outputPrice: 0.5,\n contextWindow: 131072,\n maxOutput: 16384,\n reasoning: true,\n toolCalling: true,\n },\n {\n id: \"xai/grok-4-1-fast-non-reasoning\",\n name: \"Grok 4.1 Fast\",\n version: \"4.1\",\n inputPrice: 0.2,\n outputPrice: 0.5,\n contextWindow: 131072,\n maxOutput: 16384,\n toolCalling: true,\n },\n {\n id: \"xai/grok-code-fast-1\",\n name: \"Grok Code Fast\",\n version: \"1\",\n inputPrice: 0.2,\n outputPrice: 1.5,\n contextWindow: 131072,\n maxOutput: 16384,\n // toolCalling intentionally omitted: outputs tool calls as plain text JSON,\n // not OpenAI-compatible structured function calls. Will be skipped when\n // request has tools to prevent the \"talking to itself\" bug.\n },\n {\n id: \"xai/grok-4-0709\",\n name: \"Grok 4 (0709)\",\n version: \"4-0709\",\n inputPrice: 0.2,\n outputPrice: 1.5,\n contextWindow: 131072,\n maxOutput: 16384,\n reasoning: true,\n toolCalling: true,\n },\n {\n id: \"xai/grok-2-vision\",\n name: \"Grok 2 Vision\",\n version: \"2\",\n inputPrice: 2.0,\n outputPrice: 10.0,\n contextWindow: 131072,\n maxOutput: 16384,\n vision: true,\n toolCalling: true,\n },\n\n // MiniMax\n {\n id: \"minimax/minimax-m2.5\",\n name: \"MiniMax M2.5\",\n version: \"m2.5\",\n inputPrice: 0.3,\n outputPrice: 1.2,\n contextWindow: 204800,\n maxOutput: 16384,\n reasoning: true,\n agentic: true,\n toolCalling: true,\n },\n\n // NVIDIA - Free/cheap models\n {\n id: \"nvidia/gpt-oss-120b\",\n name: \"NVIDIA GPT-OSS 120B\",\n version: \"120b\",\n inputPrice: 0,\n outputPrice: 0,\n contextWindow: 128000,\n maxOutput: 16384,\n // toolCalling intentionally omitted: free model, structured function\n // calling support unverified. Excluded from tool-heavy routing paths.\n },\n {\n id: \"nvidia/kimi-k2.5\",\n name: \"NVIDIA Kimi K2.5\",\n version: \"k2.5\",\n inputPrice: 0.55,\n outputPrice: 2.5,\n contextWindow: 262144,\n maxOutput: 16384,\n toolCalling: true,\n },\n];\n\n/**\n * Convert BlockRun model definitions to OpenClaw ModelDefinitionConfig format.\n */\nfunction toOpenClawModel(m: BlockRunModel): ModelDefinitionConfig {\n return {\n id: m.id,\n name: m.name,\n api: \"openai-completions\",\n reasoning: m.reasoning ?? false,\n input: m.vision ? [\"text\", \"image\"] : [\"text\"],\n cost: {\n input: m.inputPrice,\n output: m.outputPrice,\n cacheRead: 0,\n cacheWrite: 0,\n },\n contextWindow: m.contextWindow,\n maxTokens: m.maxOutput,\n };\n}\n\n/**\n * Alias models that map to real models.\n * These allow users to use friendly names like \"free\" or \"gpt-120b\".\n */\nconst ALIAS_MODELS: ModelDefinitionConfig[] = Object.entries(MODEL_ALIASES)\n .map(([alias, targetId]) => {\n const target = BLOCKRUN_MODELS.find((m) => m.id === targetId);\n if (!target) return null;\n return toOpenClawModel({ ...target, id: alias, name: `${alias} → ${target.name}` });\n })\n .filter((m): m is ModelDefinitionConfig => m !== null);\n\n/**\n * All BlockRun models in OpenClaw format (including aliases).\n */\nexport const OPENCLAW_MODELS: ModelDefinitionConfig[] = [\n ...BLOCKRUN_MODELS.map(toOpenClawModel),\n ...ALIAS_MODELS,\n];\n\n/**\n * Build a ModelProviderConfig for BlockRun.\n *\n * @param baseUrl - The proxy's local base URL (e.g., \"http://127.0.0.1:12345\")\n */\nexport function buildProviderModels(baseUrl: string): ModelProviderConfig {\n return {\n baseUrl: `${baseUrl}/v1`,\n api: \"openai-completions\",\n models: OPENCLAW_MODELS,\n };\n}\n\n/**\n * Check if a model is optimized for agentic workflows.\n * Agentic models continue autonomously with multi-step tasks\n * instead of stopping and waiting for user input.\n */\nexport function isAgenticModel(modelId: string): boolean {\n const model = BLOCKRUN_MODELS.find(\n (m) => m.id === modelId || m.id === modelId.replace(\"blockrun/\", \"\"),\n );\n return model?.agentic ?? false;\n}\n\n/**\n * Get all agentic-capable models.\n */\nexport function getAgenticModels(): string[] {\n return BLOCKRUN_MODELS.filter((m) => m.agentic).map((m) => m.id);\n}\n\n/**\n * Check if a model supports OpenAI-compatible structured tool/function calling.\n * Models without this flag (e.g. grok-code-fast-1) output tool invocations as\n * plain text JSON, which leaks {\"command\":\"...\"} into visible chat messages.\n */\nexport function supportsToolCalling(modelId: string): boolean {\n const normalized = modelId.replace(\"blockrun/\", \"\");\n const model = BLOCKRUN_MODELS.find((m) => m.id === normalized);\n return model?.toolCalling ?? false;\n}\n\n/**\n * Check if a model supports vision (image inputs).\n * Models without this flag cannot process image_url content parts.\n */\nexport function supportsVision(modelId: string): boolean {\n const normalized = modelId.replace(\"blockrun/\", \"\");\n const model = BLOCKRUN_MODELS.find((m) => m.id === normalized);\n return model?.vision ?? false;\n}\n\n/**\n * Get context window size for a model.\n * Returns undefined if model not found.\n */\nexport function getModelContextWindow(modelId: string): number | undefined {\n const normalized = modelId.replace(\"blockrun/\", \"\");\n const model = BLOCKRUN_MODELS.find((m) => m.id === normalized);\n return model?.contextWindow;\n}\n\n/**\n * Check if a model has reasoning/thinking capabilities.\n * Reasoning models may require reasoning_content in assistant tool_call messages.\n */\nexport function isReasoningModel(modelId: string): boolean {\n const normalized = modelId.replace(\"blockrun/\", \"\");\n const model = BLOCKRUN_MODELS.find((m) => m.id === normalized);\n return model?.reasoning ?? false;\n}\n","/**\n * Local x402 Proxy Server\n *\n * Sits between OpenClaw's pi-ai (which makes standard OpenAI-format requests)\n * and BlockRun's API (which requires x402 micropayments).\n *\n * Flow:\n * pi-ai → http://localhost:{port}/v1/chat/completions\n * → proxy forwards to https://blockrun.ai/api/v1/chat/completions\n * → gets 402 → @x402/fetch signs payment → retries\n * → streams response back to pi-ai\n *\n * Optimizations (v0.3.0):\n * - SSE heartbeat: for streaming requests, sends headers + heartbeat immediately\n * before the x402 flow, preventing OpenClaw's 10-15s timeout from firing.\n * - Response dedup: hashes request bodies and caches responses for 30s,\n * preventing double-charging when OpenClaw retries after timeout.\n * - Smart routing: when model is \"blockrun/auto\", classify query and pick cheapest model.\n * - Usage logging: log every request as JSON line to ~/.openclaw/blockrun/logs/\n */\n\nimport { createServer, type IncomingMessage, type ServerResponse } from \"node:http\";\nimport { finished } from \"node:stream\";\nimport type { AddressInfo } from \"node:net\";\nimport { createPublicClient, http } from \"viem\";\nimport { base } from \"viem/chains\";\nimport { privateKeyToAccount } from \"viem/accounts\";\nimport { x402Client } from \"@x402/fetch\";\nimport { createPayFetchWithPreAuth } from \"./payment-preauth.js\";\nimport { registerExactEvmScheme } from \"@x402/evm/exact/client\";\nimport { toClientEvmSigner } from \"@x402/evm\";\nimport {\n route,\n getFallbackChain,\n getFallbackChainFiltered,\n filterByToolCalling,\n filterByVision,\n calculateModelCost,\n DEFAULT_ROUTING_CONFIG,\n type RouterOptions,\n type RoutingDecision,\n type RoutingConfig,\n type ModelPricing,\n type Tier,\n} from \"./router/index.js\";\nimport { classifyByRules } from \"./router/rules.js\";\nimport {\n BLOCKRUN_MODELS,\n OPENCLAW_MODELS,\n resolveModelAlias,\n getModelContextWindow,\n isReasoningModel,\n supportsToolCalling,\n supportsVision,\n} from \"./models.js\";\nimport { logUsage, type UsageEntry } from \"./logger.js\";\nimport { getStats } from \"./stats.js\";\nimport { RequestDeduplicator } from \"./dedup.js\";\nimport { ResponseCache, type ResponseCacheConfig } from \"./response-cache.js\";\nimport { BalanceMonitor } from \"./balance.js\";\nimport { SolanaBalanceMonitor } from \"./solana-balance.js\";\n\n/** Union type for chain-agnostic balance monitoring */\ntype AnyBalanceMonitor = BalanceMonitor | SolanaBalanceMonitor;\nimport { resolvePaymentChain } from \"./auth.js\";\nimport { compressContext, shouldCompress, type NormalizedMessage } from \"./compression/index.js\";\n// Error classes available for programmatic use but not used in proxy\n// (universal free fallback means we don't throw balance errors anymore)\n// import { InsufficientFundsError, EmptyWalletError } from \"./errors.js\";\nimport { USER_AGENT } from \"./version.js\";\nimport {\n SessionStore,\n getSessionId,\n deriveSessionId,\n hashRequestContent,\n type SessionConfig,\n} from \"./session.js\";\nimport { checkForUpdates } from \"./updater.js\";\nimport { PROXY_PORT } from \"./config.js\";\nimport { SessionJournal } from \"./journal.js\";\n\nconst BLOCKRUN_API = \"https://blockrun.ai/api\";\nconst BLOCKRUN_SOLANA_API = \"https://sol.blockrun.ai/api\";\n// Routing profile models - virtual models that trigger intelligent routing\nconst AUTO_MODEL = \"blockrun/auto\";\n\nconst ROUTING_PROFILES = new Set([\n \"blockrun/free\",\n \"free\",\n \"blockrun/eco\",\n \"eco\",\n \"blockrun/auto\",\n \"auto\",\n \"blockrun/premium\",\n \"premium\",\n]);\nconst FREE_MODEL = \"nvidia/gpt-oss-120b\"; // Free model for empty wallet fallback\nconst MAX_MESSAGES = 200; // BlockRun API limit - truncate older messages if exceeded\nconst CONTEXT_LIMIT_KB = 5120; // Server-side limit: 5MB in KB\nconst HEARTBEAT_INTERVAL_MS = 2_000;\nconst DEFAULT_REQUEST_TIMEOUT_MS = 180_000; // 3 minutes (allows for on-chain tx + LLM response)\nconst MAX_FALLBACK_ATTEMPTS = 5; // Maximum models to try in fallback chain (increased from 3 to ensure cheap models are tried)\nconst HEALTH_CHECK_TIMEOUT_MS = 2_000; // Timeout for checking existing proxy\nconst RATE_LIMIT_COOLDOWN_MS = 60_000; // 60 seconds cooldown for rate-limited models\nconst PORT_RETRY_ATTEMPTS = 5; // Max attempts to bind port (handles TIME_WAIT)\nconst PORT_RETRY_DELAY_MS = 1_000; // Delay between retry attempts\n\n/**\n * Transform upstream payment errors into user-friendly messages.\n * Parses the raw x402 error and formats it nicely.\n */\nfunction transformPaymentError(errorBody: string): string {\n try {\n // Try to parse the error JSON\n const parsed = JSON.parse(errorBody) as {\n error?: string;\n details?: string;\n };\n\n // Check if this is a payment verification error\n if (parsed.error === \"Payment verification failed\" && parsed.details) {\n // Extract the nested JSON from details\n // Format: \"Verification failed: {json}\\n\"\n const match = parsed.details.match(/Verification failed:\\s*(\\{.*\\})/s);\n if (match) {\n const innerJson = JSON.parse(match[1]) as {\n invalidMessage?: string;\n invalidReason?: string;\n payer?: string;\n };\n\n if (innerJson.invalidReason === \"insufficient_funds\" && innerJson.invalidMessage) {\n // Parse \"insufficient balance: 251 < 11463\"\n const balanceMatch = innerJson.invalidMessage.match(\n /insufficient balance:\\s*(\\d+)\\s*<\\s*(\\d+)/i,\n );\n if (balanceMatch) {\n const currentMicros = parseInt(balanceMatch[1], 10);\n const requiredMicros = parseInt(balanceMatch[2], 10);\n const currentUSD = (currentMicros / 1_000_000).toFixed(6);\n const requiredUSD = (requiredMicros / 1_000_000).toFixed(6);\n const wallet = innerJson.payer || \"unknown\";\n const shortWallet =\n wallet.length > 12 ? `${wallet.slice(0, 6)}...${wallet.slice(-4)}` : wallet;\n\n return JSON.stringify({\n error: {\n message: `Insufficient USDC balance. Current: $${currentUSD}, Required: ~$${requiredUSD}`,\n type: \"insufficient_funds\",\n wallet: wallet,\n current_balance_usd: currentUSD,\n required_usd: requiredUSD,\n help: `Fund wallet ${shortWallet} with USDC on Base, or use free model: /model free`,\n },\n });\n }\n }\n\n // Handle invalid_payload errors (signature issues, malformed payment)\n if (innerJson.invalidReason === \"invalid_payload\") {\n return JSON.stringify({\n error: {\n message: \"Payment signature invalid. This may be a temporary issue.\",\n type: \"invalid_payload\",\n help: \"Try again. If this persists, reinstall ClawRouter: curl -fsSL https://blockrun.ai/ClawRouter-update | bash\",\n },\n });\n }\n }\n }\n\n // Handle settlement failures (gas estimation, on-chain errors)\n if (parsed.error === \"Settlement failed\" || parsed.details?.includes(\"Settlement failed\")) {\n const details = parsed.details || \"\";\n const gasError = details.includes(\"unable to estimate gas\");\n\n return JSON.stringify({\n error: {\n message: gasError\n ? \"Payment failed: network congestion or gas issue. Try again.\"\n : \"Payment settlement failed. Try again in a moment.\",\n type: \"settlement_failed\",\n help: \"This is usually temporary. If it persists, try: /model free\",\n },\n });\n }\n } catch {\n // If parsing fails, return original\n }\n return errorBody;\n}\n\n/**\n * Track rate-limited models to avoid hitting them again.\n * Maps model ID to the timestamp when the rate limit was hit.\n */\nconst rateLimitedModels = new Map();\n\n/**\n * Check if a model is currently rate-limited (in cooldown period).\n */\nfunction isRateLimited(modelId: string): boolean {\n const hitTime = rateLimitedModels.get(modelId);\n if (!hitTime) return false;\n\n const elapsed = Date.now() - hitTime;\n if (elapsed >= RATE_LIMIT_COOLDOWN_MS) {\n rateLimitedModels.delete(modelId);\n return false;\n }\n return true;\n}\n\n/**\n * Mark a model as rate-limited.\n */\nfunction markRateLimited(modelId: string): void {\n rateLimitedModels.set(modelId, Date.now());\n console.log(`[ClawRouter] Model ${modelId} rate-limited, will deprioritize for 60s`);\n}\n\n/**\n * Reorder models to put rate-limited ones at the end.\n */\nfunction prioritizeNonRateLimited(models: string[]): string[] {\n const available: string[] = [];\n const rateLimited: string[] = [];\n\n for (const model of models) {\n if (isRateLimited(model)) {\n rateLimited.push(model);\n } else {\n available.push(model);\n }\n }\n\n return [...available, ...rateLimited];\n}\n\n/**\n * Check if response socket is writable (prevents write-after-close errors).\n * Returns true only if all conditions are safe for writing.\n */\nfunction canWrite(res: ServerResponse): boolean {\n return (\n !res.writableEnded &&\n !res.destroyed &&\n res.socket !== null &&\n !res.socket.destroyed &&\n res.socket.writable\n );\n}\n\n/**\n * Safe write with backpressure handling.\n * Returns true if write succeeded, false if socket is closed or write failed.\n */\nfunction safeWrite(res: ServerResponse, data: string | Buffer): boolean {\n if (!canWrite(res)) {\n return false;\n }\n return res.write(data);\n}\n\n// Extra buffer for balance check (on top of estimateAmount's 20% buffer)\n// Total effective buffer: 1.2 * 1.5 = 1.8x (80% safety margin)\n// This prevents x402 payment failures after streaming headers are sent,\n// which would trigger OpenClaw's 5-24 hour billing cooldown.\nconst BALANCE_CHECK_BUFFER = 1.5;\n\n/**\n * Get the proxy port from pre-loaded configuration.\n * Port is validated at module load time, this just returns the cached value.\n */\nexport function getProxyPort(): number {\n return PROXY_PORT;\n}\n\n/**\n * Check if a proxy is already running on the given port.\n * Returns the wallet address if running, undefined otherwise.\n */\nasync function checkExistingProxy(port: number): Promise<{ wallet: string; paymentChain?: string } | undefined> {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), HEALTH_CHECK_TIMEOUT_MS);\n\n try {\n const response = await fetch(`http://127.0.0.1:${port}/health`, {\n signal: controller.signal,\n });\n clearTimeout(timeoutId);\n\n if (response.ok) {\n const data = (await response.json()) as { status?: string; wallet?: string; paymentChain?: string };\n if (data.status === \"ok\" && data.wallet) {\n return { wallet: data.wallet, paymentChain: data.paymentChain };\n }\n }\n return undefined;\n } catch {\n clearTimeout(timeoutId);\n return undefined;\n }\n}\n\n/**\n * Error patterns that indicate a provider-side issue (not user's fault).\n * These errors should trigger fallback to the next model in the chain.\n */\nconst PROVIDER_ERROR_PATTERNS = [\n /billing/i,\n /insufficient.*balance/i,\n /credits/i,\n /quota.*exceeded/i,\n /rate.*limit/i,\n /model.*unavailable/i,\n /model.*not.*available/i,\n /service.*unavailable/i,\n /capacity/i,\n /overloaded/i,\n /temporarily.*unavailable/i,\n /api.*key.*invalid/i,\n /authentication.*failed/i,\n /request too large/i,\n /request.*size.*exceeds/i,\n /payload too large/i,\n /payment.*verification.*failed/i,\n /model.*not.*allowed/i,\n /unknown.*model/i,\n];\n\n/**\n * \"Successful\" response bodies that are actually provider degradation placeholders.\n * Some upstream providers occasionally return these with HTTP 200.\n */\nconst DEGRADED_RESPONSE_PATTERNS = [\n /the ai service is temporarily overloaded/i,\n /service is temporarily overloaded/i,\n /please try again in a moment/i,\n];\n\n/**\n * Known low-quality loop signatures seen during provider degradation windows.\n */\nconst DEGRADED_LOOP_PATTERNS = [\n /the boxed is the response\\./i,\n /the response is the text\\./i,\n /the final answer is the boxed\\./i,\n];\n\nfunction extractAssistantContent(payload: unknown): string | undefined {\n if (!payload || typeof payload !== \"object\") return undefined;\n const record = payload as Record;\n const choices = record.choices;\n if (!Array.isArray(choices) || choices.length === 0) return undefined;\n\n const firstChoice = choices[0];\n if (!firstChoice || typeof firstChoice !== \"object\") return undefined;\n const choice = firstChoice as Record;\n const message = choice.message;\n if (!message || typeof message !== \"object\") return undefined;\n const content = (message as Record).content;\n return typeof content === \"string\" ? content : undefined;\n}\n\nfunction hasKnownLoopSignature(text: string): boolean {\n const matchCount = DEGRADED_LOOP_PATTERNS.reduce(\n (count, pattern) => (pattern.test(text) ? count + 1 : count),\n 0,\n );\n if (matchCount >= 2) return true;\n\n // Generic repetitive loop fallback for short repeated lines.\n const lines = text\n .split(/\\r?\\n/)\n .map((line) => line.trim())\n .filter(Boolean);\n if (lines.length < 8) return false;\n\n const counts = new Map();\n for (const line of lines) {\n counts.set(line, (counts.get(line) ?? 0) + 1);\n }\n\n const maxRepeat = Math.max(...counts.values());\n const uniqueRatio = counts.size / lines.length;\n return maxRepeat >= 3 && uniqueRatio <= 0.45;\n}\n\n/**\n * Detect degraded 200-response payloads that should trigger model fallback.\n * Returns a short reason when fallback should happen, otherwise undefined.\n */\nexport function detectDegradedSuccessResponse(body: string): string | undefined {\n const trimmed = body.trim();\n if (!trimmed) return undefined;\n\n // Plain-text placeholder response.\n if (DEGRADED_RESPONSE_PATTERNS.some((pattern) => pattern.test(trimmed))) {\n return \"degraded response: overloaded placeholder\";\n }\n\n // Plain-text looping garbage response.\n if (hasKnownLoopSignature(trimmed)) {\n return \"degraded response: repetitive loop output\";\n }\n\n try {\n const parsed = JSON.parse(trimmed) as Record;\n\n // Some providers return JSON error payloads with HTTP 200.\n const errorField = parsed.error;\n let errorText = \"\";\n if (typeof errorField === \"string\") {\n errorText = errorField;\n } else if (errorField && typeof errorField === \"object\") {\n const errObj = errorField as Record;\n errorText = [\n typeof errObj.message === \"string\" ? errObj.message : \"\",\n typeof errObj.type === \"string\" ? errObj.type : \"\",\n typeof errObj.code === \"string\" ? errObj.code : \"\",\n ]\n .filter(Boolean)\n .join(\" \");\n }\n if (errorText && PROVIDER_ERROR_PATTERNS.some((pattern) => pattern.test(errorText))) {\n return `degraded response: ${errorText.slice(0, 120)}`;\n }\n\n // Successful wrapper with bad assistant content.\n const assistantContent = extractAssistantContent(parsed);\n if (!assistantContent) return undefined;\n if (DEGRADED_RESPONSE_PATTERNS.some((pattern) => pattern.test(assistantContent))) {\n return \"degraded response: overloaded assistant content\";\n }\n if (hasKnownLoopSignature(assistantContent)) {\n return \"degraded response: repetitive assistant loop\";\n }\n } catch {\n // Not JSON - handled by plaintext checks above.\n }\n\n return undefined;\n}\n\n/**\n * HTTP status codes that indicate provider issues worth retrying with fallback.\n */\nconst FALLBACK_STATUS_CODES = [\n 400, // Bad request - sometimes used for billing errors\n 401, // Unauthorized - provider API key issues\n 402, // Payment required - but from upstream, not x402\n 403, // Forbidden - provider restrictions\n 413, // Payload too large - request exceeds model's context limit\n 429, // Rate limited\n 500, // Internal server error\n 502, // Bad gateway\n 503, // Service unavailable\n 504, // Gateway timeout\n];\n\n/**\n * Check if an error response indicates a provider issue that should trigger fallback.\n */\nfunction isProviderError(status: number, body: string): boolean {\n // Check status code first\n if (!FALLBACK_STATUS_CODES.includes(status)) {\n return false;\n }\n\n // For 5xx errors, always fallback\n if (status >= 500) {\n return true;\n }\n\n // For 4xx errors, check the body for known provider error patterns\n return PROVIDER_ERROR_PATTERNS.some((pattern) => pattern.test(body));\n}\n\n/**\n * Valid message roles for OpenAI-compatible APIs.\n * Some clients send non-standard roles (e.g., \"developer\" instead of \"system\").\n */\nconst VALID_ROLES = new Set([\"system\", \"user\", \"assistant\", \"tool\", \"function\"]);\n\n/**\n * Role mappings for non-standard roles.\n * Maps client-specific roles to standard OpenAI roles.\n */\nconst ROLE_MAPPINGS: Record = {\n developer: \"system\", // OpenAI's newer API uses \"developer\" for system messages\n model: \"assistant\", // Some APIs use \"model\" instead of \"assistant\"\n};\n\ntype ChatMessage = { role: string; content: string | unknown };\n\n/**\n * Anthropic tool ID pattern: only alphanumeric, underscore, and hyphen allowed.\n * Error: \"messages.X.content.Y.tool_use.id: String should match pattern '^[a-zA-Z0-9_-]+$'\"\n */\nconst VALID_TOOL_ID_PATTERN = /^[a-zA-Z0-9_-]+$/;\n\n/**\n * Sanitize a tool ID to match Anthropic's required pattern.\n * Replaces invalid characters with underscores.\n */\nfunction sanitizeToolId(id: string | undefined): string | undefined {\n if (!id || typeof id !== \"string\") return id;\n if (VALID_TOOL_ID_PATTERN.test(id)) return id;\n\n // Replace invalid characters with underscores\n return id.replace(/[^a-zA-Z0-9_-]/g, \"_\");\n}\n\n/**\n * Type for messages with tool calls (OpenAI format).\n */\ntype MessageWithTools = ChatMessage & {\n tool_calls?: Array<{ id?: string; type?: string; function?: unknown }>;\n tool_call_id?: string;\n};\n\n/**\n * Type for content blocks that may contain tool IDs (Anthropic format in OpenAI wrapper).\n */\ntype ContentBlock = {\n type?: string;\n id?: string;\n tool_use_id?: string;\n [key: string]: unknown;\n};\n\n/**\n * Sanitize all tool IDs in messages to match Anthropic's pattern.\n * Handles both OpenAI format (tool_calls, tool_call_id) and content block formats.\n */\nfunction sanitizeToolIds(messages: ChatMessage[]): ChatMessage[] {\n if (!messages || messages.length === 0) return messages;\n\n let hasChanges = false;\n const sanitized = messages.map((msg) => {\n const typedMsg = msg as MessageWithTools;\n let msgChanged = false;\n let newMsg = { ...msg } as MessageWithTools;\n\n // Sanitize tool_calls[].id in assistant messages\n if (typedMsg.tool_calls && Array.isArray(typedMsg.tool_calls)) {\n const newToolCalls = typedMsg.tool_calls.map((tc) => {\n if (tc.id && typeof tc.id === \"string\") {\n const sanitized = sanitizeToolId(tc.id);\n if (sanitized !== tc.id) {\n msgChanged = true;\n return { ...tc, id: sanitized };\n }\n }\n return tc;\n });\n if (msgChanged) {\n newMsg = { ...newMsg, tool_calls: newToolCalls };\n }\n }\n\n // Sanitize tool_call_id in tool messages\n if (typedMsg.tool_call_id && typeof typedMsg.tool_call_id === \"string\") {\n const sanitized = sanitizeToolId(typedMsg.tool_call_id);\n if (sanitized !== typedMsg.tool_call_id) {\n msgChanged = true;\n newMsg = { ...newMsg, tool_call_id: sanitized };\n }\n }\n\n // Sanitize content blocks if content is an array (Anthropic-style content)\n if (Array.isArray(typedMsg.content)) {\n const newContent = (typedMsg.content as ContentBlock[]).map((block) => {\n if (!block || typeof block !== \"object\") return block;\n\n let blockChanged = false;\n let newBlock = { ...block };\n\n // tool_use blocks have \"id\"\n if (block.type === \"tool_use\" && block.id && typeof block.id === \"string\") {\n const sanitized = sanitizeToolId(block.id);\n if (sanitized !== block.id) {\n blockChanged = true;\n newBlock = { ...newBlock, id: sanitized };\n }\n }\n\n // tool_result blocks have \"tool_use_id\"\n if (\n block.type === \"tool_result\" &&\n block.tool_use_id &&\n typeof block.tool_use_id === \"string\"\n ) {\n const sanitized = sanitizeToolId(block.tool_use_id);\n if (sanitized !== block.tool_use_id) {\n blockChanged = true;\n newBlock = { ...newBlock, tool_use_id: sanitized };\n }\n }\n\n if (blockChanged) {\n msgChanged = true;\n return newBlock;\n }\n return block;\n });\n\n if (msgChanged) {\n newMsg = { ...newMsg, content: newContent };\n }\n }\n\n if (msgChanged) {\n hasChanges = true;\n return newMsg;\n }\n return msg;\n });\n\n return hasChanges ? sanitized : messages;\n}\n\n/**\n * Normalize message roles to standard OpenAI format.\n * Converts non-standard roles (e.g., \"developer\") to valid ones.\n */\nfunction normalizeMessageRoles(messages: ChatMessage[]): ChatMessage[] {\n if (!messages || messages.length === 0) return messages;\n\n let hasChanges = false;\n const normalized = messages.map((msg) => {\n if (VALID_ROLES.has(msg.role)) return msg;\n\n const mappedRole = ROLE_MAPPINGS[msg.role];\n if (mappedRole) {\n hasChanges = true;\n return { ...msg, role: mappedRole };\n }\n\n // Unknown role - default to \"user\" to avoid API errors\n hasChanges = true;\n return { ...msg, role: \"user\" };\n });\n\n return hasChanges ? normalized : messages;\n}\n\n/**\n * Normalize messages for Google models.\n * Google's Gemini API requires the first non-system message to be from \"user\".\n * If conversation starts with \"assistant\"/\"model\", prepend a placeholder user message.\n */\n\nfunction normalizeMessagesForGoogle(messages: ChatMessage[]): ChatMessage[] {\n if (!messages || messages.length === 0) return messages;\n\n // Find first non-system message\n let firstNonSystemIdx = -1;\n for (let i = 0; i < messages.length; i++) {\n if (messages[i].role !== \"system\") {\n firstNonSystemIdx = i;\n break;\n }\n }\n\n // If no non-system messages, return as-is\n if (firstNonSystemIdx === -1) return messages;\n\n const firstRole = messages[firstNonSystemIdx].role;\n\n // If first non-system message is already \"user\", no change needed\n if (firstRole === \"user\") return messages;\n\n // If first non-system message is \"assistant\" or \"model\", prepend a user message\n if (firstRole === \"assistant\" || firstRole === \"model\") {\n const normalized = [...messages];\n normalized.splice(firstNonSystemIdx, 0, {\n role: \"user\",\n content: \"(continuing conversation)\",\n });\n return normalized;\n }\n\n return messages;\n}\n\n/**\n * Check if a model is a Google model that requires message normalization.\n */\nfunction isGoogleModel(modelId: string): boolean {\n return modelId.startsWith(\"google/\") || modelId.startsWith(\"gemini\");\n}\n\n/**\n * Extended message type for thinking-enabled conversations.\n */\ntype ExtendedChatMessage = ChatMessage & {\n tool_calls?: unknown[];\n reasoning_content?: unknown;\n};\n\n/**\n * Normalize messages for thinking-enabled requests.\n * When thinking/extended_thinking is enabled, assistant messages with tool_calls\n * must have reasoning_content (can be empty string if not present).\n * Error: \"400 thinking is enabled but reasoning_content is missing in assistant tool call message\"\n */\nfunction normalizeMessagesForThinking(messages: ExtendedChatMessage[]): ExtendedChatMessage[] {\n if (!messages || messages.length === 0) return messages;\n\n let hasChanges = false;\n const normalized = messages.map((msg) => {\n // Skip if not assistant or already has reasoning_content\n if (msg.role !== \"assistant\" || msg.reasoning_content !== undefined) {\n return msg;\n }\n\n // Check for OpenAI format: tool_calls array\n const hasOpenAIToolCalls =\n msg.tool_calls && Array.isArray(msg.tool_calls) && msg.tool_calls.length > 0;\n\n // Check for Anthropic format: content array with tool_use blocks\n const hasAnthropicToolUse =\n Array.isArray(msg.content) &&\n (msg.content as Array<{ type?: string }>).some((block) => block?.type === \"tool_use\");\n\n if (hasOpenAIToolCalls || hasAnthropicToolUse) {\n hasChanges = true;\n return { ...msg, reasoning_content: \"\" };\n }\n return msg;\n });\n\n return hasChanges ? normalized : messages;\n}\n\n/**\n * Result of truncating messages.\n */\ntype TruncationResult = {\n messages: T[];\n wasTruncated: boolean;\n originalCount: number;\n truncatedCount: number;\n};\n\n/**\n * Truncate messages to stay under BlockRun's MAX_MESSAGES limit.\n * Keeps all system messages and the most recent conversation history.\n * Returns the messages and whether truncation occurred.\n */\nfunction truncateMessages(messages: T[]): TruncationResult {\n if (!messages || messages.length <= MAX_MESSAGES) {\n return {\n messages,\n wasTruncated: false,\n originalCount: messages?.length ?? 0,\n truncatedCount: messages?.length ?? 0,\n };\n }\n\n // Separate system messages from conversation\n const systemMsgs = messages.filter((m) => m.role === \"system\");\n const conversationMsgs = messages.filter((m) => m.role !== \"system\");\n\n // Keep all system messages + most recent conversation messages\n const maxConversation = MAX_MESSAGES - systemMsgs.length;\n const truncatedConversation = conversationMsgs.slice(-maxConversation);\n\n const result = [...systemMsgs, ...truncatedConversation];\n\n console.log(\n `[ClawRouter] Truncated messages: ${messages.length} → ${result.length} (kept ${systemMsgs.length} system + ${truncatedConversation.length} recent)`,\n );\n\n return {\n messages: result,\n wasTruncated: true,\n originalCount: messages.length,\n truncatedCount: result.length,\n };\n}\n\n// Kimi/Moonshot models use special Unicode tokens for thinking boundaries.\n// Pattern: <|begin▁of▁thinking|>content<|end▁of▁thinking|>\n// The | is fullwidth vertical bar (U+FF5C), ▁ is lower one-eighth block (U+2581).\n\n// Match full Kimi thinking blocks: <|begin...|>content<|end...|>\nconst KIMI_BLOCK_RE = /<[||][^<>]*begin[^<>]*[||]>[\\s\\S]*?<[||][^<>]*end[^<>]*[||]>/gi;\n\n// Match standalone Kimi tokens like <|end▁of▁thinking|>\nconst KIMI_TOKEN_RE = /<[||][^<>]*[||]>/g;\n\n// Standard thinking tags that may leak through from various models\nconst THINKING_TAG_RE = /<\\s*\\/?\\s*(?:think(?:ing)?|thought|antthinking)\\b[^>]*>/gi;\n\n// Full thinking blocks: content\nconst THINKING_BLOCK_RE =\n /<\\s*(?:think(?:ing)?|thought|antthinking)\\b[^>]*>[\\s\\S]*?<\\s*\\/\\s*(?:think(?:ing)?|thought|antthinking)\\s*>/gi;\n\n/**\n * Strip thinking tokens and blocks from model response content.\n * Handles both Kimi-style Unicode tokens and standard XML-style tags.\n *\n * NOTE: DSML tags (<|DSML|...>) are NOT stripped - those are tool calls\n * that should be handled by the API, not hidden from users.\n */\nfunction stripThinkingTokens(content: string): string {\n if (!content) return content;\n // Strip full Kimi thinking blocks first (begin...end with content)\n let cleaned = content.replace(KIMI_BLOCK_RE, \"\");\n // Strip remaining standalone Kimi tokens\n cleaned = cleaned.replace(KIMI_TOKEN_RE, \"\");\n // Strip full thinking blocks (...)\n cleaned = cleaned.replace(THINKING_BLOCK_RE, \"\");\n // Strip remaining standalone thinking tags\n cleaned = cleaned.replace(THINKING_TAG_RE, \"\");\n return cleaned;\n}\n\n/** Callback info for low balance warning */\nexport type LowBalanceInfo = {\n balanceUSD: string;\n walletAddress: string;\n};\n\n/** Callback info for insufficient funds error */\nexport type InsufficientFundsInfo = {\n balanceUSD: string;\n requiredUSD: string;\n walletAddress: string;\n};\n\n/**\n * Wallet config: either a plain EVM private key string, or the full\n * resolution object from resolveOrGenerateWalletKey() which may include\n * Solana keys. Using the full object prevents callers from accidentally\n * forgetting to forward Solana key bytes.\n */\nexport type WalletConfig = string | { key: string; solanaPrivateKeyBytes?: Uint8Array };\n\nexport type PaymentChain = \"base\" | \"solana\";\n\nexport type ProxyOptions = {\n wallet: WalletConfig;\n apiBase?: string;\n /** Payment chain: \"base\" (default) or \"solana\". Can also be set via CLAWROUTER_PAYMENT_CHAIN env var. */\n paymentChain?: PaymentChain;\n /** Port to listen on (default: 8402) */\n port?: number;\n routingConfig?: Partial;\n /** Request timeout in ms (default: 180000 = 3 minutes). Covers on-chain tx + LLM response. */\n requestTimeoutMs?: number;\n /** Skip balance checks (for testing only). Default: false */\n skipBalanceCheck?: boolean;\n /**\n * Session persistence config. When enabled, maintains model selection\n * across requests within a session to prevent mid-task model switching.\n */\n sessionConfig?: Partial;\n /**\n * Auto-compress large requests to reduce network usage.\n * When enabled, requests are automatically compressed using\n * LLM-safe context compression (15-40% reduction).\n * Default: true\n */\n autoCompressRequests?: boolean;\n /**\n * Threshold in KB to trigger auto-compression (default: 180).\n * Requests larger than this are compressed before sending.\n * Set to 0 to compress all requests.\n */\n compressionThresholdKB?: number;\n /**\n * Response caching config. When enabled, identical requests return\n * cached responses instead of making new API calls.\n * Default: enabled with 10 minute TTL, 200 max entries.\n */\n cacheConfig?: ResponseCacheConfig;\n onReady?: (port: number) => void;\n onError?: (error: Error) => void;\n onPayment?: (info: { model: string; amount: string; network: string }) => void;\n onRouted?: (decision: RoutingDecision) => void;\n /** Called when balance drops below $1.00 (warning, request still proceeds) */\n onLowBalance?: (info: LowBalanceInfo) => void;\n /** Called when balance is insufficient for a request (request fails) */\n onInsufficientFunds?: (info: InsufficientFundsInfo) => void;\n};\n\nexport type ProxyHandle = {\n port: number;\n baseUrl: string;\n walletAddress: string;\n solanaAddress?: string;\n balanceMonitor: AnyBalanceMonitor;\n close: () => Promise;\n};\n\n/**\n * Build model pricing map from BLOCKRUN_MODELS.\n */\nfunction buildModelPricing(): Map {\n const map = new Map();\n for (const m of BLOCKRUN_MODELS) {\n if (m.id === AUTO_MODEL) continue; // skip meta-model\n map.set(m.id, { inputPrice: m.inputPrice, outputPrice: m.outputPrice });\n }\n return map;\n}\n\ntype ModelListEntry = {\n id: string;\n object: \"model\";\n created: number;\n owned_by: string;\n};\n\n/**\n * Build `/v1/models` response entries from the full OpenClaw model registry.\n * This includes alias IDs (e.g., `flash`, `kimi`) so `/model ` works reliably.\n */\nexport function buildProxyModelList(\n createdAt: number = Math.floor(Date.now() / 1000),\n): ModelListEntry[] {\n const seen = new Set();\n return OPENCLAW_MODELS.filter((model) => {\n if (seen.has(model.id)) return false;\n seen.add(model.id);\n return true;\n }).map((model) => ({\n id: model.id,\n object: \"model\",\n created: createdAt,\n owned_by: model.id.includes(\"/\") ? (model.id.split(\"/\")[0] ?? \"blockrun\") : \"blockrun\",\n }));\n}\n\n/**\n * Merge partial routing config overrides with defaults.\n */\nfunction mergeRoutingConfig(overrides?: Partial): RoutingConfig {\n if (!overrides) return DEFAULT_ROUTING_CONFIG;\n return {\n ...DEFAULT_ROUTING_CONFIG,\n ...overrides,\n classifier: { ...DEFAULT_ROUTING_CONFIG.classifier, ...overrides.classifier },\n scoring: { ...DEFAULT_ROUTING_CONFIG.scoring, ...overrides.scoring },\n tiers: { ...DEFAULT_ROUTING_CONFIG.tiers, ...overrides.tiers },\n overrides: { ...DEFAULT_ROUTING_CONFIG.overrides, ...overrides.overrides },\n };\n}\n\n/**\n * Estimate USDC cost for a request based on model pricing.\n * Returns amount string in USDC smallest unit (6 decimals) or undefined if unknown.\n */\nfunction estimateAmount(\n modelId: string,\n bodyLength: number,\n maxTokens: number,\n): string | undefined {\n const model = BLOCKRUN_MODELS.find((m) => m.id === modelId);\n if (!model) return undefined;\n\n // Rough estimate: ~4 chars per token for input\n const estimatedInputTokens = Math.ceil(bodyLength / 4);\n const estimatedOutputTokens = maxTokens || model.maxOutput || 4096;\n\n const costUsd =\n (estimatedInputTokens / 1_000_000) * model.inputPrice +\n (estimatedOutputTokens / 1_000_000) * model.outputPrice;\n\n // Convert to USDC 6-decimal integer, add 20% buffer for estimation error\n // Minimum 1000 ($0.001) to match CDP Facilitator's enforced minimum payment\n const amountMicros = Math.max(1000, Math.ceil(costUsd * 1.2 * 1_000_000));\n return amountMicros.toString();\n}\n\n/**\n * Proxy a partner API request through x402 payment flow.\n *\n * Simplified proxy for partner endpoints (/v1/x/*, /v1/partner/*).\n * No smart routing, SSE, compression, or sessions — just collect body,\n * forward via payFetch (which handles 402 automatically), and stream back.\n */\nasync function proxyPartnerRequest(\n req: IncomingMessage,\n res: ServerResponse,\n apiBase: string,\n payFetch: (\n input: RequestInfo | URL,\n init?: RequestInit,\n ) => Promise,\n): Promise {\n const startTime = Date.now();\n const upstreamUrl = `${apiBase}${req.url}`;\n\n // Collect request body\n const bodyChunks: Buffer[] = [];\n for await (const chunk of req) {\n bodyChunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));\n }\n const body = Buffer.concat(bodyChunks);\n\n // Forward headers (strip hop-by-hop)\n const headers: Record = {};\n for (const [key, value] of Object.entries(req.headers)) {\n if (\n key === \"host\" ||\n key === \"connection\" ||\n key === \"transfer-encoding\" ||\n key === \"content-length\"\n )\n continue;\n if (typeof value === \"string\") headers[key] = value;\n }\n if (!headers[\"content-type\"]) headers[\"content-type\"] = \"application/json\";\n headers[\"user-agent\"] = USER_AGENT;\n\n console.log(`[ClawRouter] Partner request: ${req.method} ${req.url}`);\n\n const upstream = await payFetch(upstreamUrl, {\n method: req.method ?? \"POST\",\n headers,\n body: body.length > 0 ? new Uint8Array(body) : undefined,\n });\n\n // Forward response headers\n const responseHeaders: Record = {};\n upstream.headers.forEach((value, key) => {\n if (key === \"transfer-encoding\" || key === \"connection\" || key === \"content-encoding\") return;\n responseHeaders[key] = value;\n });\n\n res.writeHead(upstream.status, responseHeaders);\n\n // Stream response body\n if (upstream.body) {\n const reader = upstream.body.getReader();\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n safeWrite(res, Buffer.from(value));\n }\n } finally {\n reader.releaseLock();\n }\n }\n\n res.end();\n\n const latencyMs = Date.now() - startTime;\n console.log(`[ClawRouter] Partner response: ${upstream.status} (${latencyMs}ms)`);\n\n // Log partner usage (fire-and-forget)\n logUsage({\n timestamp: new Date().toISOString(),\n model: \"partner\",\n tier: \"PARTNER\",\n cost: 0, // Actual cost handled by x402 settlement\n baselineCost: 0,\n savings: 0,\n latencyMs,\n partnerId:\n (req.url?.split(\"?\")[0] ?? \"\").replace(/^\\/v1\\//, \"\").replace(/\\//g, \"_\") || \"unknown\",\n service: \"partner\",\n }).catch(() => {});\n}\n\n/**\n * Upload a base64 data URI to catbox.moe and return a public URL.\n * Google image models (nano-banana) return data URIs instead of hosted URLs,\n * which breaks Telegram and other clients that can't render raw base64.\n */\nasync function uploadDataUriToHost(dataUri: string): Promise {\n const match = dataUri.match(/^data:(image\\/\\w+);base64,(.+)$/);\n if (!match) throw new Error(\"Invalid data URI format\");\n const [, mimeType, b64Data] = match;\n const ext = mimeType === \"image/jpeg\" ? \"jpg\" : (mimeType.split(\"/\")[1] ?? \"png\");\n\n const buffer = Buffer.from(b64Data, \"base64\");\n const blob = new Blob([buffer], { type: mimeType });\n\n const form = new FormData();\n form.append(\"reqtype\", \"fileupload\");\n form.append(\"fileToUpload\", blob, `image.${ext}`);\n\n const resp = await fetch(\"https://catbox.moe/user/api.php\", {\n method: \"POST\",\n body: form,\n });\n\n if (!resp.ok) throw new Error(`catbox.moe upload failed: HTTP ${resp.status}`);\n const result = await resp.text();\n if (result.startsWith(\"https://\")) {\n return result.trim();\n }\n throw new Error(`catbox.moe upload failed: ${result}`);\n}\n\n/**\n * Start the local x402 proxy server.\n *\n * If a proxy is already running on the target port, reuses it instead of failing.\n * Port can be configured via BLOCKRUN_PROXY_PORT environment variable.\n *\n * Returns a handle with the assigned port, base URL, and a close function.\n */\nexport async function startProxy(options: ProxyOptions): Promise {\n // Normalize wallet config: string = EVM-only, object = full resolution\n const walletKey = typeof options.wallet === \"string\" ? options.wallet : options.wallet.key;\n const solanaPrivateKeyBytes =\n typeof options.wallet === \"string\" ? undefined : options.wallet.solanaPrivateKeyBytes;\n\n // Payment chain: options > env var > persisted file > default \"base\".\n // No dynamic switching — user selects chain via /wallet solana or /wallet base.\n const paymentChain = options.paymentChain ?? await resolvePaymentChain();\n const apiBase = options.apiBase ?? (paymentChain === \"solana\" && solanaPrivateKeyBytes ? BLOCKRUN_SOLANA_API : BLOCKRUN_API);\n if (paymentChain === \"solana\" && !solanaPrivateKeyBytes) {\n console.warn(`[ClawRouter] Payment chain is Solana but no Solana keys provided. Using Base (EVM).`);\n } else if (paymentChain === \"solana\") {\n console.log(`[ClawRouter] Payment chain: Solana (${BLOCKRUN_SOLANA_API})`);\n }\n\n // Determine port: options.port > env var > default\n const listenPort = options.port ?? getProxyPort();\n\n // Check if a proxy is already running on this port\n const existingProxy = await checkExistingProxy(listenPort);\n if (existingProxy) {\n // Proxy already running — reuse it instead of failing with EADDRINUSE\n const account = privateKeyToAccount(walletKey as `0x${string}`);\n const baseUrl = `http://127.0.0.1:${listenPort}`;\n\n // Verify the existing proxy is using the same wallet (or warn if different)\n if (existingProxy.wallet !== account.address) {\n console.warn(\n `[ClawRouter] Existing proxy on port ${listenPort} uses wallet ${existingProxy.wallet}, but current config uses ${account.address}. Reusing existing proxy.`,\n );\n }\n\n // Verify the existing proxy is using the same payment chain\n if (existingProxy.paymentChain) {\n if (existingProxy.paymentChain !== paymentChain) {\n throw new Error(\n `Existing proxy on port ${listenPort} is using ${existingProxy.paymentChain} but ${paymentChain} was requested. ` +\n `Stop the existing proxy first or use a different port.`,\n );\n }\n } else if (paymentChain !== \"base\") {\n // Old proxy doesn't report chain — assume Base. Reject if Solana was requested.\n console.warn(`[ClawRouter] Existing proxy on port ${listenPort} does not report paymentChain (pre-v0.11 instance). Assuming Base.`);\n throw new Error(\n `Existing proxy on port ${listenPort} is a pre-v0.11 instance (assumed Base) but ${paymentChain} was requested. ` +\n `Stop the existing proxy first or use a different port.`,\n );\n }\n\n // Derive Solana address if keys are available (for wallet status display)\n let reuseSolanaAddress: string | undefined;\n if (solanaPrivateKeyBytes) {\n const { createKeyPairSignerFromPrivateKeyBytes } = await import(\"@solana/kit\");\n const solanaSigner = await createKeyPairSignerFromPrivateKeyBytes(solanaPrivateKeyBytes);\n reuseSolanaAddress = solanaSigner.address;\n }\n\n // Use chain-appropriate balance monitor\n const balanceMonitor: AnyBalanceMonitor =\n paymentChain === \"solana\" && reuseSolanaAddress\n ? new SolanaBalanceMonitor(reuseSolanaAddress)\n : new BalanceMonitor(account.address);\n\n options.onReady?.(listenPort);\n\n return {\n port: listenPort,\n baseUrl,\n walletAddress: existingProxy.wallet,\n solanaAddress: reuseSolanaAddress,\n balanceMonitor,\n close: async () => {\n // No-op: we didn't start this proxy, so we shouldn't close it\n },\n };\n }\n\n // Create x402 payment client with EVM scheme (always available)\n const account = privateKeyToAccount(walletKey as `0x${string}`);\n const evmPublicClient = createPublicClient({ chain: base, transport: http() });\n const evmSigner = toClientEvmSigner(account, evmPublicClient);\n const x402 = new x402Client();\n registerExactEvmScheme(x402, { signer: evmSigner });\n\n // Register Solana scheme if key is available\n // Uses registerExactSvmScheme helper which registers:\n // - solana:* wildcard (catches any CAIP-2 Solana network)\n // - V1 compat names: \"solana\", \"solana-devnet\", \"solana-testnet\"\n let solanaAddress: string | undefined;\n if (solanaPrivateKeyBytes) {\n const { registerExactSvmScheme } = await import(\"@x402/svm/exact/client\");\n const { createKeyPairSignerFromPrivateKeyBytes } = await import(\"@solana/kit\");\n const solanaSigner = await createKeyPairSignerFromPrivateKeyBytes(solanaPrivateKeyBytes);\n solanaAddress = solanaSigner.address;\n registerExactSvmScheme(x402, { signer: solanaSigner });\n console.log(`[ClawRouter] Solana x402 scheme registered: ${solanaAddress}`);\n }\n\n // Log which chain is used for each payment\n x402.onAfterPaymentCreation(async (context) => {\n const network = context.selectedRequirements.network;\n const chain = network.startsWith(\"eip155\") ? \"Base (EVM)\" : network.startsWith(\"solana\") ? \"Solana\" : network;\n console.log(`[ClawRouter] Payment signed on ${chain} (${network})`);\n });\n\n const payFetch = createPayFetchWithPreAuth(fetch, x402, undefined, {\n skipPreAuth: paymentChain === \"solana\",\n });\n\n // Create balance monitor for pre-request checks (chain-appropriate)\n const balanceMonitor: AnyBalanceMonitor =\n paymentChain === \"solana\" && solanaAddress\n ? new SolanaBalanceMonitor(solanaAddress)\n : new BalanceMonitor(account.address);\n\n // Build router options (100% local — no external API calls for routing)\n const routingConfig = mergeRoutingConfig(options.routingConfig);\n const modelPricing = buildModelPricing();\n const routerOpts: RouterOptions = {\n config: routingConfig,\n modelPricing,\n };\n\n // Request deduplicator (shared across all requests)\n const deduplicator = new RequestDeduplicator();\n\n // Response cache for identical requests (longer TTL than dedup)\n const responseCache = new ResponseCache(options.cacheConfig);\n\n // Session store for model persistence (prevents mid-task model switching)\n const sessionStore = new SessionStore(options.sessionConfig);\n\n // Session journal for memory (enables agents to recall earlier work)\n const sessionJournal = new SessionJournal();\n\n // Track active connections for graceful cleanup\n const connections = new Set();\n\n const server = createServer(async (req: IncomingMessage, res: ServerResponse) => {\n // Add stream error handlers to prevent server crashes\n req.on(\"error\", (err) => {\n console.error(`[ClawRouter] Request stream error: ${err.message}`);\n // Don't throw - just log and let request handler deal with it\n });\n\n res.on(\"error\", (err) => {\n console.error(`[ClawRouter] Response stream error: ${err.message}`);\n // Don't try to write to failed socket - just log\n });\n\n // Finished wrapper for guaranteed cleanup on response completion/error\n finished(res, (err) => {\n if (err && err.code !== \"ERR_STREAM_DESTROYED\") {\n console.error(`[ClawRouter] Response finished with error: ${err.message}`);\n }\n // Note: heartbeatInterval cleanup happens in res.on(\"close\") handler\n // Note: completed and dedup cleanup happens in the res.on(\"close\") handler below\n });\n\n // Request finished wrapper for complete stream lifecycle tracking\n finished(req, (err) => {\n if (err && err.code !== \"ERR_STREAM_DESTROYED\") {\n console.error(`[ClawRouter] Request finished with error: ${err.message}`);\n }\n });\n\n // Health check with optional balance info\n if (req.url === \"/health\" || req.url?.startsWith(\"/health?\")) {\n const url = new URL(req.url, \"http://localhost\");\n const full = url.searchParams.get(\"full\") === \"true\";\n\n const response: Record = {\n status: \"ok\",\n wallet: account.address,\n paymentChain,\n };\n if (solanaAddress) {\n response.solana = solanaAddress;\n }\n\n if (full) {\n try {\n const balanceInfo = await balanceMonitor.checkBalance();\n response.balance = balanceInfo.balanceUSD;\n response.isLow = balanceInfo.isLow;\n response.isEmpty = balanceInfo.isEmpty;\n } catch {\n response.balanceError = \"Could not fetch balance\";\n }\n }\n\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(response));\n return;\n }\n\n // Cache stats endpoint\n if (req.url === \"/cache\" || req.url?.startsWith(\"/cache?\")) {\n const stats = responseCache.getStats();\n res.writeHead(200, {\n \"Content-Type\": \"application/json\",\n \"Cache-Control\": \"no-cache\",\n });\n res.end(JSON.stringify(stats, null, 2));\n return;\n }\n\n // Stats API endpoint - returns JSON for programmatic access\n if (req.url === \"/stats\" || req.url?.startsWith(\"/stats?\")) {\n try {\n const url = new URL(req.url, \"http://localhost\");\n const days = parseInt(url.searchParams.get(\"days\") || \"7\", 10);\n const stats = await getStats(Math.min(days, 30));\n\n res.writeHead(200, {\n \"Content-Type\": \"application/json\",\n \"Cache-Control\": \"no-cache\",\n });\n res.end(JSON.stringify(stats, null, 2));\n } catch (err) {\n res.writeHead(500, { \"Content-Type\": \"application/json\" });\n res.end(\n JSON.stringify({\n error: `Failed to get stats: ${err instanceof Error ? err.message : String(err)}`,\n }),\n );\n }\n return;\n }\n\n // --- Handle /v1/models locally (no upstream call needed) ---\n if (req.url === \"/v1/models\" && req.method === \"GET\") {\n const models = buildProxyModelList();\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ object: \"list\", data: models }));\n return;\n }\n\n // --- Handle partner API paths (/v1/x/*, /v1/partner/*) ---\n if (req.url?.match(/^\\/v1\\/(?:x|partner)\\//)) {\n try {\n await proxyPartnerRequest(req, res, apiBase, payFetch);\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err));\n options.onError?.(error);\n if (!res.headersSent) {\n res.writeHead(502, { \"Content-Type\": \"application/json\" });\n res.end(\n JSON.stringify({\n error: { message: `Partner proxy error: ${error.message}`, type: \"partner_error\" },\n }),\n );\n }\n }\n return;\n }\n\n // Only proxy paths starting with /v1\n if (!req.url?.startsWith(\"/v1\")) {\n res.writeHead(404, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"Not found\" }));\n return;\n }\n\n try {\n await proxyRequest(\n req,\n res,\n apiBase,\n payFetch,\n options,\n routerOpts,\n deduplicator,\n balanceMonitor,\n sessionStore,\n responseCache,\n sessionJournal,\n );\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err));\n options.onError?.(error);\n\n if (!res.headersSent) {\n res.writeHead(502, { \"Content-Type\": \"application/json\" });\n res.end(\n JSON.stringify({\n error: { message: `Proxy error: ${error.message}`, type: \"proxy_error\" },\n }),\n );\n } else if (!res.writableEnded) {\n // Headers already sent (streaming) — send error as SSE event\n res.write(\n `data: ${JSON.stringify({ error: { message: error.message, type: \"proxy_error\" } })}\\n\\n`,\n );\n res.write(\"data: [DONE]\\n\\n\");\n res.end();\n }\n }\n });\n\n // Listen on configured port with retry logic for TIME_WAIT handling\n // When gateway restarts quickly, the port may still be in TIME_WAIT state.\n // We retry with delay instead of incorrectly assuming a proxy is running.\n const tryListen = (attempt: number): Promise => {\n return new Promise((resolveAttempt, rejectAttempt) => {\n const onError = async (err: NodeJS.ErrnoException) => {\n server.removeListener(\"error\", onError);\n\n if (err.code === \"EADDRINUSE\") {\n // Port is in use - check if a proxy is actually running\n const existingProxy2 = await checkExistingProxy(listenPort);\n if (existingProxy2) {\n // Proxy is actually running - this is fine, reuse it\n console.log(`[ClawRouter] Existing proxy detected on port ${listenPort}, reusing`);\n rejectAttempt({ code: \"REUSE_EXISTING\", wallet: existingProxy2.wallet, existingChain: existingProxy2.paymentChain });\n return;\n }\n\n // Port is in TIME_WAIT (no proxy responding) - retry after delay\n if (attempt < PORT_RETRY_ATTEMPTS) {\n console.log(\n `[ClawRouter] Port ${listenPort} in TIME_WAIT, retrying in ${PORT_RETRY_DELAY_MS}ms (attempt ${attempt}/${PORT_RETRY_ATTEMPTS})`,\n );\n rejectAttempt({ code: \"RETRY\", attempt });\n return;\n }\n\n // Max retries exceeded\n console.error(\n `[ClawRouter] Port ${listenPort} still in use after ${PORT_RETRY_ATTEMPTS} attempts`,\n );\n rejectAttempt(err);\n return;\n }\n\n rejectAttempt(err);\n };\n\n server.once(\"error\", onError);\n server.listen(listenPort, \"127.0.0.1\", () => {\n server.removeListener(\"error\", onError);\n resolveAttempt();\n });\n });\n };\n\n // Retry loop for port binding\n let lastError: Error | undefined;\n for (let attempt = 1; attempt <= PORT_RETRY_ATTEMPTS; attempt++) {\n try {\n await tryListen(attempt);\n break; // Success\n } catch (err: unknown) {\n const error = err as { code?: string; wallet?: string; existingChain?: string; attempt?: number };\n\n if (error.code === \"REUSE_EXISTING\" && error.wallet) {\n // Validate payment chain matches (same check as pre-listen reuse path)\n if (error.existingChain && error.existingChain !== paymentChain) {\n throw new Error(\n `Existing proxy on port ${listenPort} is using ${error.existingChain} but ${paymentChain} was requested. ` +\n `Stop the existing proxy first or use a different port.`,\n );\n }\n\n // Proxy is running, reuse it\n const baseUrl = `http://127.0.0.1:${listenPort}`;\n options.onReady?.(listenPort);\n return {\n port: listenPort,\n baseUrl,\n walletAddress: error.wallet,\n balanceMonitor,\n close: async () => {\n // No-op: we didn't start this proxy, so we shouldn't close it\n },\n };\n }\n\n if (error.code === \"RETRY\") {\n // Wait before retry\n await new Promise((r) => setTimeout(r, PORT_RETRY_DELAY_MS));\n continue;\n }\n\n // Other error - throw\n lastError = err as Error;\n break;\n }\n }\n\n if (lastError) {\n throw lastError;\n }\n\n // Server is now listening - set up remaining handlers\n const addr = server.address() as AddressInfo;\n const port = addr.port;\n const baseUrl = `http://127.0.0.1:${port}`;\n\n options.onReady?.(port);\n\n // Check for updates (non-blocking)\n checkForUpdates();\n\n // Add runtime error handler AFTER successful listen\n // This handles errors that occur during server operation (not just startup)\n server.on(\"error\", (err) => {\n console.error(`[ClawRouter] Server runtime error: ${err.message}`);\n options.onError?.(err);\n // Don't crash - log and continue\n });\n\n // Handle client connection errors (bad requests, socket errors)\n server.on(\"clientError\", (err, socket) => {\n console.error(`[ClawRouter] Client error: ${err.message}`);\n // Send 400 Bad Request if socket is still writable\n if (socket.writable && !socket.destroyed) {\n socket.end(\"HTTP/1.1 400 Bad Request\\r\\n\\r\\n\");\n }\n });\n\n // Track connections for graceful cleanup\n server.on(\"connection\", (socket) => {\n connections.add(socket);\n\n // Set 5-minute timeout for streaming requests\n socket.setTimeout(300_000);\n\n socket.on(\"timeout\", () => {\n console.error(`[ClawRouter] Socket timeout, destroying connection`);\n socket.destroy();\n });\n\n socket.on(\"end\", () => {\n // Half-closed by client (FIN received)\n });\n\n socket.on(\"error\", (err) => {\n console.error(`[ClawRouter] Socket error: ${err.message}`);\n });\n\n socket.on(\"close\", () => {\n connections.delete(socket);\n });\n });\n\n return {\n port,\n baseUrl,\n walletAddress: account.address,\n solanaAddress,\n balanceMonitor,\n close: () =>\n new Promise((res, rej) => {\n const timeout = setTimeout(() => {\n rej(new Error(\"[ClawRouter] Close timeout after 4s\"));\n }, 4000);\n\n sessionStore.close();\n // Destroy all active connections before closing server\n for (const socket of connections) {\n socket.destroy();\n }\n connections.clear();\n server.close((err) => {\n clearTimeout(timeout);\n if (err) {\n rej(err);\n } else {\n res();\n }\n });\n }),\n };\n}\n\n/** Result of attempting a model request */\ntype ModelRequestResult = {\n success: boolean;\n response?: Response;\n errorBody?: string;\n errorStatus?: number;\n isProviderError?: boolean;\n};\n\n/**\n * Attempt a request with a specific model.\n * Returns the response or error details for fallback decision.\n */\nasync function tryModelRequest(\n upstreamUrl: string,\n method: string,\n headers: Record,\n body: Buffer,\n modelId: string,\n maxTokens: number,\n payFetch: (\n input: RequestInfo | URL,\n init?: RequestInit,\n ) => Promise,\n balanceMonitor: AnyBalanceMonitor,\n signal: AbortSignal,\n): Promise {\n // Update model in body and normalize messages\n let requestBody = body;\n try {\n const parsed = JSON.parse(body.toString()) as Record;\n parsed.model = modelId;\n\n // Normalize message roles (e.g., \"developer\" -> \"system\")\n if (Array.isArray(parsed.messages)) {\n parsed.messages = normalizeMessageRoles(parsed.messages as ChatMessage[]);\n }\n\n // Truncate messages to stay under BlockRun's limit (200 messages)\n if (Array.isArray(parsed.messages)) {\n const truncationResult = truncateMessages(parsed.messages as ChatMessage[]);\n parsed.messages = truncationResult.messages;\n }\n\n // Sanitize tool IDs to match Anthropic's pattern (alphanumeric, underscore, hyphen only)\n if (Array.isArray(parsed.messages)) {\n parsed.messages = sanitizeToolIds(parsed.messages as ChatMessage[]);\n }\n\n // Normalize messages for Google models (first non-system message must be \"user\")\n if (isGoogleModel(modelId) && Array.isArray(parsed.messages)) {\n parsed.messages = normalizeMessagesForGoogle(parsed.messages as ChatMessage[]);\n }\n\n // Normalize messages for thinking-enabled requests (add reasoning_content to tool calls)\n // Check request flags AND target model - reasoning models have thinking enabled server-side\n const hasThinkingEnabled = !!(\n parsed.thinking ||\n parsed.extended_thinking ||\n isReasoningModel(modelId)\n );\n if (hasThinkingEnabled && Array.isArray(parsed.messages)) {\n parsed.messages = normalizeMessagesForThinking(parsed.messages as ExtendedChatMessage[]);\n }\n\n requestBody = Buffer.from(JSON.stringify(parsed));\n } catch {\n // If body isn't valid JSON, use as-is\n }\n\n try {\n const response = await payFetch(\n upstreamUrl,\n {\n method,\n headers,\n body: requestBody.length > 0 ? new Uint8Array(requestBody) : undefined,\n signal,\n },\n );\n\n // Check for provider errors\n if (response.status !== 200) {\n // Clone response to read body without consuming it\n const errorBody = await response.text();\n const isProviderErr = isProviderError(response.status, errorBody);\n\n return {\n success: false,\n errorBody,\n errorStatus: response.status,\n isProviderError: isProviderErr,\n };\n }\n\n // Detect provider degradation hidden inside HTTP 200 responses.\n const contentType = response.headers.get(\"content-type\") || \"\";\n if (contentType.includes(\"json\") || contentType.includes(\"text\")) {\n try {\n const responseBody = await response.clone().text();\n const degradedReason = detectDegradedSuccessResponse(responseBody);\n if (degradedReason) {\n return {\n success: false,\n errorBody: degradedReason,\n errorStatus: 503,\n isProviderError: true,\n };\n }\n } catch {\n // Ignore body inspection failures and pass through response.\n }\n }\n\n return { success: true, response };\n } catch (err) {\n const errorMsg = err instanceof Error ? err.message : String(err);\n return {\n success: false,\n errorBody: errorMsg,\n errorStatus: 500,\n isProviderError: true, // Network errors are retryable\n };\n }\n}\n\n/**\n * Proxy a single request through x402 payment flow to BlockRun API.\n *\n * Optimizations applied in order:\n * 1. Dedup check — if same request body seen within 30s, replay cached response\n * 2. Streaming heartbeat — for stream:true, send 200 + heartbeats immediately\n * 3. Smart routing — when model is \"blockrun/auto\", pick cheapest capable model\n * 4. Fallback chain — on provider errors, try next model in tier's fallback list\n */\nasync function proxyRequest(\n req: IncomingMessage,\n res: ServerResponse,\n apiBase: string,\n payFetch: (\n input: RequestInfo | URL,\n init?: RequestInit,\n ) => Promise,\n options: ProxyOptions,\n routerOpts: RouterOptions,\n deduplicator: RequestDeduplicator,\n balanceMonitor: AnyBalanceMonitor,\n sessionStore: SessionStore,\n responseCache: ResponseCache,\n sessionJournal: SessionJournal,\n): Promise {\n const startTime = Date.now();\n\n // Build upstream URL: /v1/chat/completions → https://blockrun.ai/api/v1/chat/completions\n const upstreamUrl = `${apiBase}${req.url}`;\n\n // Collect request body\n const bodyChunks: Buffer[] = [];\n for await (const chunk of req) {\n bodyChunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));\n }\n let body = Buffer.concat(bodyChunks);\n\n // Track original context size for response headers\n const originalContextSizeKB = Math.ceil(body.length / 1024);\n\n // Routing debug info is on by default; disable with x-clawrouter-debug: false\n const debugMode = req.headers[\"x-clawrouter-debug\"] !== \"false\";\n\n // --- Smart routing ---\n let routingDecision: RoutingDecision | undefined;\n let hasTools = false; // true when request includes a tools schema\n let hasVision = false; // true when request includes image_url content parts\n let isStreaming = false;\n let modelId = \"\";\n let maxTokens = 4096;\n let routingProfile: \"free\" | \"eco\" | \"auto\" | \"premium\" | null = null;\n let accumulatedContent = \"\"; // For session journal event extraction\n let responseInputTokens: number | undefined;\n const isChatCompletion = req.url?.includes(\"/chat/completions\");\n\n // Extract session ID early for journal operations (header-only at this point)\n const sessionId = getSessionId(req.headers as Record);\n // Full session ID (header + content-derived) — populated once messages are parsed\n let effectiveSessionId: string | undefined = sessionId;\n\n if (isChatCompletion && body.length > 0) {\n try {\n const parsed = JSON.parse(body.toString()) as Record;\n isStreaming = parsed.stream === true;\n modelId = (parsed.model as string) || \"\";\n maxTokens = (parsed.max_tokens as number) || 4096;\n let bodyModified = false;\n\n // Extract last user message content (used by session journal + /debug command)\n const parsedMessages = Array.isArray(parsed.messages)\n ? (parsed.messages as Array<{ role: string; content: unknown }>)\n : [];\n const lastUserMsg = [...parsedMessages].reverse().find((m) => m.role === \"user\");\n const rawLastContent = lastUserMsg?.content;\n const lastContent =\n typeof rawLastContent === \"string\"\n ? rawLastContent\n : Array.isArray(rawLastContent)\n ? (rawLastContent as Array<{ type: string; text?: string }>)\n .filter((b) => b.type === \"text\")\n .map((b) => b.text ?? \"\")\n .join(\" \")\n : \"\";\n\n // --- Session Journal: Inject context if needed ---\n // Check if the last user message asks about past work\n if (sessionId && parsedMessages.length > 0) {\n const messages = parsedMessages;\n\n if (sessionJournal.needsContext(lastContent)) {\n const journalText = sessionJournal.format(sessionId);\n if (journalText) {\n // Find system message and prepend journal, or add a new system message\n const sysIdx = messages.findIndex((m) => m.role === \"system\");\n if (sysIdx >= 0 && typeof messages[sysIdx].content === \"string\") {\n messages[sysIdx] = {\n ...messages[sysIdx],\n content: journalText + \"\\n\\n\" + messages[sysIdx].content,\n };\n } else {\n messages.unshift({ role: \"system\", content: journalText });\n }\n parsed.messages = messages;\n bodyModified = true;\n console.log(\n `[ClawRouter] Injected session journal (${journalText.length} chars) for session ${sessionId.slice(0, 8)}...`,\n );\n }\n }\n }\n\n // --- /debug command: return routing diagnostics without calling upstream ---\n if (lastContent.startsWith(\"/debug\")) {\n const debugPrompt = lastContent.slice(\"/debug\".length).trim() || \"hello\";\n const messages = parsed.messages as Array<{ role: string; content: unknown }>;\n const systemMsg = messages?.find((m) => m.role === \"system\");\n const systemPrompt = typeof systemMsg?.content === \"string\" ? systemMsg.content : undefined;\n const fullText = `${systemPrompt ?? \"\"} ${debugPrompt}`;\n const estimatedTokens = Math.ceil(fullText.length / 4);\n\n // Determine routing profile\n const normalizedModel =\n typeof parsed.model === \"string\" ? parsed.model.trim().toLowerCase() : \"\";\n const profileName = normalizedModel.replace(\"blockrun/\", \"\");\n const debugProfile = (\n [\"free\", \"eco\", \"auto\", \"premium\"].includes(profileName) ? profileName : \"auto\"\n ) as \"free\" | \"eco\" | \"auto\" | \"premium\";\n\n // Run scoring\n const scoring = classifyByRules(\n debugPrompt,\n systemPrompt,\n estimatedTokens,\n DEFAULT_ROUTING_CONFIG.scoring,\n );\n\n // Run full routing decision\n const debugRouting = route(debugPrompt, systemPrompt, maxTokens, {\n ...routerOpts,\n routingProfile: debugProfile,\n });\n\n // Format dimension scores\n const dimLines = (scoring.dimensions ?? [])\n .map((d) => {\n const nameStr = (d.name + \":\").padEnd(24);\n const scoreStr = d.score.toFixed(2).padStart(6);\n const sigStr = d.signal ? ` [${d.signal}]` : \"\";\n return ` ${nameStr}${scoreStr}${sigStr}`;\n })\n .join(\"\\n\");\n\n // Session info\n const sess = sessionId ? sessionStore.getSession(sessionId) : undefined;\n const sessLine = sess\n ? `Session: ${sessionId!.slice(0, 8)}... → pinned: ${sess.model} (${sess.requestCount} requests)`\n : sessionId\n ? `Session: ${sessionId.slice(0, 8)}... → no pinned model`\n : \"Session: none\";\n\n const { simpleMedium, mediumComplex, complexReasoning } =\n DEFAULT_ROUTING_CONFIG.scoring.tierBoundaries;\n\n const debugText = [\n \"ClawRouter Debug\",\n \"\",\n `Profile: ${debugProfile} | Tier: ${debugRouting.tier} | Model: ${debugRouting.model}`,\n `Confidence: ${debugRouting.confidence.toFixed(2)} | Cost: $${debugRouting.costEstimate.toFixed(4)} | Savings: ${(debugRouting.savings * 100).toFixed(0)}%`,\n `Reasoning: ${debugRouting.reasoning}`,\n \"\",\n `Scoring (weighted: ${scoring.score.toFixed(3)})`,\n dimLines,\n \"\",\n `Tier Boundaries: SIMPLE <${simpleMedium.toFixed(2)} | MEDIUM <${mediumComplex.toFixed(2)} | COMPLEX <${complexReasoning.toFixed(2)} | REASONING >=${complexReasoning.toFixed(2)}`,\n \"\",\n sessLine,\n ].join(\"\\n\");\n\n // Build synthetic OpenAI chat completion response\n const completionId = `chatcmpl-debug-${Date.now()}`;\n const timestamp = Math.floor(Date.now() / 1000);\n const syntheticResponse = {\n id: completionId,\n object: \"chat.completion\",\n created: timestamp,\n model: \"clawrouter/debug\",\n choices: [\n {\n index: 0,\n message: { role: \"assistant\", content: debugText },\n finish_reason: \"stop\",\n },\n ],\n usage: { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 },\n };\n\n if (isStreaming) {\n // SSE streaming response\n res.writeHead(200, {\n \"Content-Type\": \"text/event-stream\",\n \"Cache-Control\": \"no-cache\",\n Connection: \"keep-alive\",\n });\n const sseChunk = {\n id: completionId,\n object: \"chat.completion.chunk\",\n created: timestamp,\n model: \"clawrouter/debug\",\n choices: [\n {\n index: 0,\n delta: { role: \"assistant\", content: debugText },\n finish_reason: null,\n },\n ],\n };\n const sseDone = {\n id: completionId,\n object: \"chat.completion.chunk\",\n created: timestamp,\n model: \"clawrouter/debug\",\n choices: [{ index: 0, delta: {}, finish_reason: \"stop\" }],\n };\n res.write(`data: ${JSON.stringify(sseChunk)}\\n\\n`);\n res.write(`data: ${JSON.stringify(sseDone)}\\n\\n`);\n res.write(\"data: [DONE]\\n\\n\");\n res.end();\n } else {\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(syntheticResponse));\n }\n console.log(`[ClawRouter] /debug command → ${debugRouting.tier} | ${debugRouting.model}`);\n return;\n }\n\n // --- /imagegen command: generate an image via BlockRun image API ---\n if (lastContent.startsWith(\"/imagegen\")) {\n const imageArgs = lastContent.slice(\"/imagegen\".length).trim();\n\n // Parse optional flags: /imagegen --model dall-e-3 --size 1792x1024 a cute cat\n let imageModel = \"google/nano-banana\";\n let imageSize = \"1024x1024\";\n let imagePrompt = imageArgs;\n\n // Extract --model flag\n const modelMatch = imageArgs.match(/--model\\s+(\\S+)/);\n if (modelMatch) {\n const raw = modelMatch[1];\n // Resolve shorthand aliases\n const IMAGE_MODEL_ALIASES: Record = {\n \"dall-e-3\": \"openai/dall-e-3\",\n dalle3: \"openai/dall-e-3\",\n dalle: \"openai/dall-e-3\",\n \"gpt-image\": \"openai/gpt-image-1\",\n \"gpt-image-1\": \"openai/gpt-image-1\",\n flux: \"black-forest/flux-1.1-pro\",\n \"flux-pro\": \"black-forest/flux-1.1-pro\",\n banana: \"google/nano-banana\",\n \"nano-banana\": \"google/nano-banana\",\n \"banana-pro\": \"google/nano-banana-pro\",\n \"nano-banana-pro\": \"google/nano-banana-pro\",\n };\n imageModel = IMAGE_MODEL_ALIASES[raw] ?? raw;\n imagePrompt = imagePrompt.replace(/--model\\s+\\S+/, \"\").trim();\n }\n\n // Extract --size flag\n const sizeMatch = imageArgs.match(/--size\\s+(\\d+x\\d+)/);\n if (sizeMatch) {\n imageSize = sizeMatch[1];\n imagePrompt = imagePrompt.replace(/--size\\s+\\d+x\\d+/, \"\").trim();\n }\n\n if (!imagePrompt) {\n const errorText = [\n \"Usage: /imagegen \",\n \"\",\n \"Options:\",\n \" --model Model to use (default: nano-banana)\",\n \" --size Image size (default: 1024x1024)\",\n \"\",\n \"Models:\",\n \" nano-banana Google Gemini Flash — $0.05/image\",\n \" banana-pro Google Gemini Pro — $0.10/image (up to 4K)\",\n \" dall-e-3 OpenAI DALL-E 3 — $0.04/image\",\n \" gpt-image OpenAI GPT Image 1 — $0.02/image\",\n \" flux Black Forest Flux 1.1 Pro — $0.04/image\",\n \"\",\n \"Examples:\",\n \" /imagegen a cat wearing sunglasses\",\n \" /imagegen --model dall-e-3 a futuristic city at sunset\",\n \" /imagegen --model banana-pro --size 2048x2048 mountain landscape\",\n ].join(\"\\n\");\n\n const completionId = `chatcmpl-image-${Date.now()}`;\n const timestamp = Math.floor(Date.now() / 1000);\n if (isStreaming) {\n res.writeHead(200, {\n \"Content-Type\": \"text/event-stream\",\n \"Cache-Control\": \"no-cache\",\n Connection: \"keep-alive\",\n });\n res.write(\n `data: ${JSON.stringify({ id: completionId, object: \"chat.completion.chunk\", created: timestamp, model: \"clawrouter/image\", choices: [{ index: 0, delta: { role: \"assistant\", content: errorText }, finish_reason: null }] })}\\n\\n`,\n );\n res.write(\n `data: ${JSON.stringify({ id: completionId, object: \"chat.completion.chunk\", created: timestamp, model: \"clawrouter/image\", choices: [{ index: 0, delta: {}, finish_reason: \"stop\" }] })}\\n\\n`,\n );\n res.write(\"data: [DONE]\\n\\n\");\n res.end();\n } else {\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(\n JSON.stringify({\n id: completionId,\n object: \"chat.completion\",\n created: timestamp,\n model: \"clawrouter/image\",\n choices: [\n {\n index: 0,\n message: { role: \"assistant\", content: errorText },\n finish_reason: \"stop\",\n },\n ],\n usage: { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 },\n }),\n );\n }\n console.log(`[ClawRouter] /imagegen command → showing usage help`);\n return;\n }\n\n // Call upstream image generation API\n console.log(\n `[ClawRouter] /imagegen command → ${imageModel} (${imageSize}): ${imagePrompt.slice(0, 80)}...`,\n );\n try {\n const imageUpstreamUrl = `${apiBase}/v1/images/generations`;\n const imageBody = JSON.stringify({\n model: imageModel,\n prompt: imagePrompt,\n size: imageSize,\n n: 1,\n });\n const imageResponse = await payFetch(imageUpstreamUrl, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\", \"user-agent\": USER_AGENT },\n body: imageBody,\n });\n\n const imageResult = (await imageResponse.json()) as {\n created?: number;\n data?: Array<{ url?: string; revised_prompt?: string }>;\n error?: string | { message?: string };\n };\n\n let responseText: string;\n if (!imageResponse.ok || imageResult.error) {\n const errMsg =\n typeof imageResult.error === \"string\"\n ? imageResult.error\n : ((imageResult.error as { message?: string })?.message ??\n `HTTP ${imageResponse.status}`);\n responseText = `Image generation failed: ${errMsg}`;\n console.log(`[ClawRouter] /imagegen error: ${errMsg}`);\n } else {\n const images = imageResult.data ?? [];\n if (images.length === 0) {\n responseText = \"Image generation returned no results.\";\n } else {\n const lines: string[] = [];\n for (const img of images) {\n if (img.url) {\n if (img.url.startsWith(\"data:\")) {\n try {\n const hostedUrl = await uploadDataUriToHost(img.url);\n lines.push(hostedUrl);\n } catch (uploadErr) {\n console.error(\n `[ClawRouter] /imagegen: failed to upload data URI: ${uploadErr instanceof Error ? uploadErr.message : String(uploadErr)}`,\n );\n lines.push(\n \"Image generated but upload failed. Try again or use --model dall-e-3.\",\n );\n }\n } else {\n lines.push(img.url);\n }\n }\n if (img.revised_prompt) lines.push(`Revised prompt: ${img.revised_prompt}`);\n }\n lines.push(\"\", `Model: ${imageModel} | Size: ${imageSize}`);\n responseText = lines.join(\"\\n\");\n }\n console.log(`[ClawRouter] /imagegen success: ${images.length} image(s) generated`);\n }\n\n // Return as synthetic chat completion\n const completionId = `chatcmpl-image-${Date.now()}`;\n const timestamp = Math.floor(Date.now() / 1000);\n if (isStreaming) {\n res.writeHead(200, {\n \"Content-Type\": \"text/event-stream\",\n \"Cache-Control\": \"no-cache\",\n Connection: \"keep-alive\",\n });\n res.write(\n `data: ${JSON.stringify({ id: completionId, object: \"chat.completion.chunk\", created: timestamp, model: \"clawrouter/image\", choices: [{ index: 0, delta: { role: \"assistant\", content: responseText }, finish_reason: null }] })}\\n\\n`,\n );\n res.write(\n `data: ${JSON.stringify({ id: completionId, object: \"chat.completion.chunk\", created: timestamp, model: \"clawrouter/image\", choices: [{ index: 0, delta: {}, finish_reason: \"stop\" }] })}\\n\\n`,\n );\n res.write(\"data: [DONE]\\n\\n\");\n res.end();\n } else {\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(\n JSON.stringify({\n id: completionId,\n object: \"chat.completion\",\n created: timestamp,\n model: \"clawrouter/image\",\n choices: [\n {\n index: 0,\n message: { role: \"assistant\", content: responseText },\n finish_reason: \"stop\",\n },\n ],\n usage: { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 },\n }),\n );\n }\n } catch (err) {\n const errMsg = err instanceof Error ? err.message : String(err);\n console.error(`[ClawRouter] /imagegen error: ${errMsg}`);\n if (!res.headersSent) {\n res.writeHead(500, { \"Content-Type\": \"application/json\" });\n res.end(\n JSON.stringify({\n error: { message: `Image generation failed: ${errMsg}`, type: \"image_error\" },\n }),\n );\n }\n }\n return;\n }\n\n // Force stream: false — BlockRun API doesn't support streaming yet\n // ClawRouter handles SSE heartbeat simulation for upstream compatibility\n if (parsed.stream === true) {\n parsed.stream = false;\n bodyModified = true;\n }\n\n // Normalize model name for comparison (trim whitespace, lowercase)\n const normalizedModel =\n typeof parsed.model === \"string\" ? parsed.model.trim().toLowerCase() : \"\";\n\n // Resolve model aliases (e.g., \"claude\" -> \"anthropic/claude-sonnet-4-6\")\n const resolvedModel = resolveModelAlias(normalizedModel);\n const wasAlias = resolvedModel !== normalizedModel;\n\n const isRoutingProfile = ROUTING_PROFILES.has(normalizedModel);\n\n // Extract routing profile type (free/eco/auto/premium)\n if (isRoutingProfile) {\n const profileName = normalizedModel.replace(\"blockrun/\", \"\");\n routingProfile = profileName as \"free\" | \"eco\" | \"auto\" | \"premium\";\n }\n\n // Debug: log received model name\n console.log(\n `[ClawRouter] Received model: \"${parsed.model}\" -> normalized: \"${normalizedModel}\"${wasAlias ? ` -> alias: \"${resolvedModel}\"` : \"\"}${routingProfile ? `, profile: ${routingProfile}` : \"\"}`,\n );\n\n // For explicit model requests, always canonicalize the model ID before upstream calls.\n // This ensures case/whitespace variants (e.g. \"DEEPSEEK/...\" or \" model \") route correctly.\n if (!isRoutingProfile) {\n if (parsed.model !== resolvedModel) {\n parsed.model = resolvedModel;\n bodyModified = true;\n }\n modelId = resolvedModel;\n }\n\n // Handle routing profiles (free/eco/auto/premium)\n if (isRoutingProfile) {\n // Free profile - direct shortcut to nvidia/gpt-oss-120b (no tier routing)\n if (routingProfile === \"free\") {\n const freeModel = \"nvidia/gpt-oss-120b\";\n console.log(`[ClawRouter] Free profile - using ${freeModel} directly`);\n parsed.model = freeModel;\n modelId = freeModel;\n bodyModified = true;\n\n // Log usage for free profile\n await logUsage({\n timestamp: new Date().toISOString(),\n model: freeModel,\n tier: \"SIMPLE\",\n cost: 0,\n baselineCost: 0,\n savings: 1.0, // 100% savings\n latencyMs: 0,\n });\n } else {\n // eco/auto/premium - use tier routing\n // Check for session persistence - use pinned model if available\n // Fall back to deriving a session ID from message content when OpenClaw\n // doesn't send an explicit x-session-id header (the default behaviour).\n effectiveSessionId =\n getSessionId(req.headers as Record) ??\n deriveSessionId(parsedMessages);\n const existingSession = effectiveSessionId\n ? sessionStore.getSession(effectiveSessionId)\n : undefined;\n\n // Extract prompt from last user message (handles both string and Anthropic array content)\n const rawPrompt = lastUserMsg?.content;\n const prompt =\n typeof rawPrompt === \"string\"\n ? rawPrompt\n : Array.isArray(rawPrompt)\n ? (rawPrompt as Array<{ type: string; text?: string }>)\n .filter((b) => b.type === \"text\")\n .map((b) => b.text ?? \"\")\n .join(\" \")\n : \"\";\n const systemMsg = parsedMessages.find((m) => m.role === \"system\");\n const systemPrompt =\n typeof systemMsg?.content === \"string\" ? systemMsg.content : undefined;\n\n // Tool detection (must run regardless of session state for fallback chain filtering)\n // Agentic mode is triggered by keyword-based detection (agenticScore >= 0.6)\n const tools = parsed.tools as unknown[] | undefined;\n hasTools = Array.isArray(tools) && tools.length > 0;\n\n if (hasTools && tools) {\n console.log(`[ClawRouter] Tools detected (${tools.length}), agentic mode via keywords`);\n }\n\n // Vision detection: scan messages for image_url content parts\n hasVision = parsedMessages.some((m) => {\n if (Array.isArray(m.content)) {\n return (m.content as Array<{ type: string }>).some((p) => p.type === \"image_url\");\n }\n return false;\n });\n if (hasVision) {\n console.log(`[ClawRouter] Vision content detected, filtering to vision-capable models`);\n }\n\n // Always route based on current request content\n routingDecision = route(prompt, systemPrompt, maxTokens, {\n ...routerOpts,\n routingProfile: routingProfile ?? undefined,\n });\n\n if (existingSession) {\n // Never downgrade: only upgrade the session when the current request needs a higher\n // tier. This fixes the OpenClaw startup-message bias (the startup message always\n // scores low-complexity, which previously pinned all subsequent real queries to a\n // cheap model) while still preventing mid-task model switching on simple follow-ups.\n const tierRank: Record = {\n SIMPLE: 0,\n MEDIUM: 1,\n COMPLEX: 2,\n REASONING: 3,\n };\n const existingRank = tierRank[existingSession.tier] ?? 0;\n const newRank = tierRank[routingDecision.tier] ?? 0;\n\n if (newRank > existingRank) {\n // Current request needs higher capability — upgrade the session\n console.log(\n `[ClawRouter] Session ${effectiveSessionId?.slice(0, 8)}... upgrading: ${existingSession.tier} → ${routingDecision.tier} (${routingDecision.model})`,\n );\n parsed.model = routingDecision.model;\n modelId = routingDecision.model;\n bodyModified = true;\n if (effectiveSessionId) {\n sessionStore.setSession(\n effectiveSessionId,\n routingDecision.model,\n routingDecision.tier,\n );\n }\n } else {\n // Keep existing higher-tier model (prevent downgrade mid-task)\n console.log(\n `[ClawRouter] Session ${effectiveSessionId?.slice(0, 8)}... keeping pinned model: ${existingSession.model} (${existingSession.tier} >= ${routingDecision.tier})`,\n );\n parsed.model = existingSession.model;\n modelId = existingSession.model;\n bodyModified = true;\n sessionStore.touchSession(effectiveSessionId!);\n // Reflect the actual model used in the routing decision for logging/fallback\n routingDecision = {\n ...routingDecision,\n model: existingSession.model,\n tier: existingSession.tier as Tier,\n };\n }\n\n // --- Three-strike escalation: detect repetitive request patterns ---\n const lastAssistantMsg = [...parsedMessages]\n .reverse()\n .find((m) => m.role === \"assistant\");\n const assistantToolCalls = (\n lastAssistantMsg as { tool_calls?: Array<{ function?: { name?: string } }> }\n )?.tool_calls;\n const toolCallNames = Array.isArray(assistantToolCalls)\n ? assistantToolCalls\n .map((tc) => tc.function?.name)\n .filter((n): n is string => Boolean(n))\n : undefined;\n const contentHash = hashRequestContent(prompt, toolCallNames);\n const shouldEscalate = sessionStore.recordRequestHash(effectiveSessionId!, contentHash);\n\n if (shouldEscalate) {\n const activeTierConfigs = (() => {\n if (\n routingDecision.reasoning?.includes(\"agentic\") &&\n routerOpts.config.agenticTiers\n ) {\n return routerOpts.config.agenticTiers;\n }\n if (routingProfile === \"eco\" && routerOpts.config.ecoTiers) {\n return routerOpts.config.ecoTiers;\n }\n if (routingProfile === \"premium\" && routerOpts.config.premiumTiers) {\n return routerOpts.config.premiumTiers;\n }\n return routerOpts.config.tiers;\n })();\n\n const escalation = sessionStore.escalateSession(\n effectiveSessionId!,\n activeTierConfigs,\n );\n if (escalation) {\n console.log(\n `[ClawRouter] ⚡ 3-strike escalation: ${existingSession.model} → ${escalation.model} (${existingSession.tier} → ${escalation.tier})`,\n );\n parsed.model = escalation.model;\n modelId = escalation.model;\n routingDecision = {\n ...routingDecision,\n model: escalation.model,\n tier: escalation.tier as Tier,\n };\n }\n }\n } else {\n // No session — pin this routing decision for future requests\n parsed.model = routingDecision.model;\n modelId = routingDecision.model;\n bodyModified = true;\n if (effectiveSessionId) {\n sessionStore.setSession(\n effectiveSessionId,\n routingDecision.model,\n routingDecision.tier,\n );\n console.log(\n `[ClawRouter] Session ${effectiveSessionId.slice(0, 8)}... pinned to model: ${routingDecision.model}`,\n );\n }\n }\n\n options.onRouted?.(routingDecision);\n }\n }\n\n // Rebuild body if modified\n if (bodyModified) {\n body = Buffer.from(JSON.stringify(parsed));\n }\n } catch (err) {\n // Log routing errors so they're not silently swallowed\n const errorMsg = err instanceof Error ? err.message : String(err);\n console.error(`[ClawRouter] Routing error: ${errorMsg}`);\n console.error(`[ClawRouter] Need help? Run: npx @blockrun/clawrouter doctor`);\n options.onError?.(new Error(`Routing failed: ${errorMsg}`));\n }\n }\n\n // --- Auto-compression ---\n // Compress large requests to reduce network usage and improve performance\n const autoCompress = options.autoCompressRequests ?? true;\n const compressionThreshold = options.compressionThresholdKB ?? 180;\n const requestSizeKB = Math.ceil(body.length / 1024);\n\n if (autoCompress && requestSizeKB > compressionThreshold) {\n try {\n console.log(\n `[ClawRouter] Request size ${requestSizeKB}KB exceeds threshold ${compressionThreshold}KB, applying compression...`,\n );\n\n // Parse messages for compression\n const parsed = JSON.parse(body.toString()) as {\n messages?: NormalizedMessage[];\n [key: string]: unknown;\n };\n\n if (parsed.messages && parsed.messages.length > 0 && shouldCompress(parsed.messages)) {\n // Apply compression with conservative settings\n const compressionResult = await compressContext(parsed.messages, {\n enabled: true,\n preserveRaw: false, // Don't need originals in proxy\n layers: {\n deduplication: true, // Safe: removes duplicate messages\n whitespace: true, // Safe: normalizes whitespace\n dictionary: false, // Disabled: requires model to understand codebook\n paths: false, // Disabled: requires model to understand path codes\n jsonCompact: true, // Safe: just removes JSON whitespace\n observation: false, // Disabled: may lose important context\n dynamicCodebook: false, // Disabled: requires model to understand codes\n },\n dictionary: {\n maxEntries: 50,\n minPhraseLength: 15,\n includeCodebookHeader: false,\n },\n });\n\n const compressedSizeKB = Math.ceil(compressionResult.compressedChars / 1024);\n const savings = (((requestSizeKB - compressedSizeKB) / requestSizeKB) * 100).toFixed(1);\n\n console.log(\n `[ClawRouter] Compressed ${requestSizeKB}KB → ${compressedSizeKB}KB (${savings}% reduction)`,\n );\n\n // Update request body with compressed messages\n parsed.messages = compressionResult.messages;\n body = Buffer.from(JSON.stringify(parsed));\n }\n } catch (err) {\n // Compression failed - continue with original request\n console.warn(\n `[ClawRouter] Compression failed: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n }\n\n // --- Response cache check (long-term, 10min TTL) ---\n const cacheKey = ResponseCache.generateKey(body);\n const reqHeaders: Record = {};\n for (const [key, value] of Object.entries(req.headers)) {\n if (typeof value === \"string\") reqHeaders[key] = value;\n }\n if (responseCache.shouldCache(body, reqHeaders)) {\n const cachedResponse = responseCache.get(cacheKey);\n if (cachedResponse) {\n console.log(`[ClawRouter] Cache HIT for ${cachedResponse.model} (saved API call)`);\n res.writeHead(cachedResponse.status, cachedResponse.headers);\n res.end(cachedResponse.body);\n return;\n }\n }\n\n // --- Dedup check (short-term, 30s TTL for retries) ---\n const dedupKey = RequestDeduplicator.hash(body);\n\n // Check dedup cache (catches retries within 30s)\n const cached = deduplicator.getCached(dedupKey);\n if (cached) {\n res.writeHead(cached.status, cached.headers);\n res.end(cached.body);\n return;\n }\n\n // Check in-flight — wait for the original request to complete\n const inflight = deduplicator.getInflight(dedupKey);\n if (inflight) {\n const result = await inflight;\n res.writeHead(result.status, result.headers);\n res.end(result.body);\n return;\n }\n\n // Register this request as in-flight\n deduplicator.markInflight(dedupKey);\n\n // --- Pre-request balance check ---\n // Estimate cost and check if wallet has sufficient balance\n // Skip if skipBalanceCheck is set (for testing) or if using free model\n let estimatedCostMicros: bigint | undefined;\n let balanceFallbackNotice: string | undefined;\n const isFreeModel = modelId === FREE_MODEL;\n\n if (modelId && !options.skipBalanceCheck && !isFreeModel) {\n const estimated = estimateAmount(modelId, body.length, maxTokens);\n if (estimated) {\n estimatedCostMicros = BigInt(estimated);\n\n // Apply extra buffer for balance check to prevent x402 failures after streaming starts.\n // This is aggressive to avoid triggering OpenClaw's 5-24 hour billing cooldown.\n const bufferedCostMicros =\n (estimatedCostMicros * BigInt(Math.ceil(BALANCE_CHECK_BUFFER * 100))) / 100n;\n\n // Check balance before proceeding (using buffered amount)\n const sufficiency = await balanceMonitor.checkSufficient(bufferedCostMicros);\n\n if (sufficiency.info.isEmpty || !sufficiency.sufficient) {\n // Wallet is empty or insufficient — ALWAYS fallback to free model\n // This ensures new users with empty wallets can still use ClawRouter\n const originalModel = modelId;\n console.log(\n `[ClawRouter] Wallet ${sufficiency.info.isEmpty ? \"empty\" : \"insufficient\"} (${sufficiency.info.balanceUSD}), falling back to free model: ${FREE_MODEL} (requested: ${originalModel})`,\n );\n modelId = FREE_MODEL;\n // Update the body with new model\n const parsed = JSON.parse(body.toString()) as Record;\n parsed.model = FREE_MODEL;\n body = Buffer.from(JSON.stringify(parsed));\n\n // Set notice to prepend to response so user knows about the fallback\n balanceFallbackNotice = sufficiency.info.isEmpty\n ? `> **⚠️ Wallet empty** — using free model. Fund your wallet to use ${originalModel}.\\n\\n`\n : `> **⚠️ Insufficient balance** (${sufficiency.info.balanceUSD}) — using free model instead of ${originalModel}.\\n\\n`;\n\n // Notify about the fallback\n options.onLowBalance?.({\n balanceUSD: sufficiency.info.balanceUSD,\n walletAddress: sufficiency.info.walletAddress,\n });\n } else if (sufficiency.info.isLow) {\n // Balance is low but sufficient — warn and proceed\n options.onLowBalance?.({\n balanceUSD: sufficiency.info.balanceUSD,\n walletAddress: sufficiency.info.walletAddress,\n });\n }\n }\n }\n\n // --- Streaming: early header flush + heartbeat ---\n let heartbeatInterval: ReturnType | undefined;\n let headersSentEarly = false;\n\n if (isStreaming) {\n // Send 200 + SSE headers immediately, before x402 flow\n res.writeHead(200, {\n \"content-type\": \"text/event-stream\",\n \"cache-control\": \"no-cache\",\n connection: \"keep-alive\",\n \"x-context-used-kb\": String(originalContextSizeKB),\n \"x-context-limit-kb\": String(CONTEXT_LIMIT_KB),\n });\n headersSentEarly = true;\n\n // First heartbeat immediately\n safeWrite(res, \": heartbeat\\n\\n\");\n\n // Continue heartbeats every 2s while waiting for upstream\n heartbeatInterval = setInterval(() => {\n if (canWrite(res)) {\n safeWrite(res, \": heartbeat\\n\\n\");\n } else {\n // Socket closed, stop heartbeat\n clearInterval(heartbeatInterval);\n heartbeatInterval = undefined;\n }\n }, HEARTBEAT_INTERVAL_MS);\n }\n\n // Forward headers, stripping host, connection, and content-length\n const headers: Record = {};\n for (const [key, value] of Object.entries(req.headers)) {\n if (\n key === \"host\" ||\n key === \"connection\" ||\n key === \"transfer-encoding\" ||\n key === \"content-length\"\n )\n continue;\n if (typeof value === \"string\") {\n headers[key] = value;\n }\n }\n if (!headers[\"content-type\"]) {\n headers[\"content-type\"] = \"application/json\";\n }\n headers[\"user-agent\"] = USER_AGENT;\n\n // --- Client disconnect cleanup ---\n let completed = false;\n res.on(\"close\", () => {\n if (heartbeatInterval) {\n clearInterval(heartbeatInterval);\n heartbeatInterval = undefined;\n }\n // Remove from in-flight if client disconnected before completion\n if (!completed) {\n deduplicator.removeInflight(dedupKey);\n }\n });\n\n // --- Request timeout ---\n const timeoutMs = options.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), timeoutMs);\n\n try {\n // --- Build fallback chain ---\n // If we have a routing decision, get the full fallback chain for the tier\n // Otherwise, just use the current model (no fallback for explicit model requests)\n let modelsToTry: string[];\n if (routingDecision) {\n // Estimate total context: input tokens (~4 chars per token) + max output tokens\n const estimatedInputTokens = Math.ceil(body.length / 4);\n const estimatedTotalTokens = estimatedInputTokens + maxTokens;\n\n // Get tier configs matching the profile that was used for routing\n // Must stay in sync with what route() selected in router/index.ts\n const tierConfigs = (() => {\n if (routingDecision.reasoning?.includes(\"agentic\") && routerOpts.config.agenticTiers) {\n return routerOpts.config.agenticTiers;\n }\n if (routingProfile === \"eco\" && routerOpts.config.ecoTiers) {\n return routerOpts.config.ecoTiers;\n }\n if (routingProfile === \"premium\" && routerOpts.config.premiumTiers) {\n return routerOpts.config.premiumTiers;\n }\n return routerOpts.config.tiers;\n })();\n\n // Get full chain first, then filter by context\n const fullChain = getFallbackChain(routingDecision.tier, tierConfigs);\n const contextFiltered = getFallbackChainFiltered(\n routingDecision.tier,\n tierConfigs,\n estimatedTotalTokens,\n getModelContextWindow,\n );\n\n // Log if models were filtered out due to context limits\n const contextExcluded = fullChain.filter((m) => !contextFiltered.includes(m));\n if (contextExcluded.length > 0) {\n console.log(\n `[ClawRouter] Context filter (~${estimatedTotalTokens} tokens): excluded ${contextExcluded.join(\", \")}`,\n );\n }\n\n // Filter to models that support tool calling when request has tools.\n // Prevents models like grok-code-fast-1 from outputting tool invocations\n // as plain text JSON (the \"talking to itself\" bug).\n const toolFiltered = filterByToolCalling(contextFiltered, hasTools, supportsToolCalling);\n const toolExcluded = contextFiltered.filter((m) => !toolFiltered.includes(m));\n if (toolExcluded.length > 0) {\n console.log(\n `[ClawRouter] Tool-calling filter: excluded ${toolExcluded.join(\", \")} (no structured function call support)`,\n );\n }\n\n // Filter to models that support vision when request has image_url content\n const visionFiltered = filterByVision(toolFiltered, hasVision, supportsVision);\n const visionExcluded = toolFiltered.filter((m) => !visionFiltered.includes(m));\n if (visionExcluded.length > 0) {\n console.log(\n `[ClawRouter] Vision filter: excluded ${visionExcluded.join(\", \")} (no vision support)`,\n );\n }\n\n // Limit to MAX_FALLBACK_ATTEMPTS to prevent infinite loops\n modelsToTry = visionFiltered.slice(0, MAX_FALLBACK_ATTEMPTS);\n\n // Deprioritize rate-limited models (put them at the end)\n modelsToTry = prioritizeNonRateLimited(modelsToTry);\n } else {\n // For explicit model requests, use the requested model\n modelsToTry = modelId ? [modelId] : [];\n }\n\n // Always ensure free model is the last-resort fallback.\n // If all paid models fail (insufficient funds, rate limits, etc.),\n // the user still gets a response instead of an error.\n if (!modelsToTry.includes(FREE_MODEL)) {\n modelsToTry.push(FREE_MODEL);\n }\n\n // --- Fallback loop: try each model until success ---\n let upstream: Response | undefined;\n let lastError: { body: string; status: number } | undefined;\n let actualModelUsed = modelId;\n\n for (let i = 0; i < modelsToTry.length; i++) {\n const tryModel = modelsToTry[i];\n const isLastAttempt = i === modelsToTry.length - 1;\n\n console.log(`[ClawRouter] Trying model ${i + 1}/${modelsToTry.length}: ${tryModel}`);\n\n const result = await tryModelRequest(\n upstreamUrl,\n req.method ?? \"POST\",\n headers,\n body,\n tryModel,\n maxTokens,\n payFetch,\n balanceMonitor,\n controller.signal,\n );\n\n if (result.success && result.response) {\n upstream = result.response;\n actualModelUsed = tryModel;\n console.log(`[ClawRouter] Success with model: ${tryModel}`);\n break;\n }\n\n // Request failed\n lastError = {\n body: result.errorBody || \"Unknown error\",\n status: result.errorStatus || 500,\n };\n\n // If it's a provider error and not the last attempt, try next model\n if (result.isProviderError && !isLastAttempt) {\n // Track 429 rate limits to deprioritize this model for future requests\n if (result.errorStatus === 429) {\n markRateLimited(tryModel);\n }\n\n // Payment error (insufficient funds) — skip remaining paid models,\n // jump straight to free model. No point trying other paid models\n // with the same empty wallet.\n const isPaymentErr = /payment.*verification.*failed|insufficient.*funds/i.test(\n result.errorBody || \"\",\n );\n if (isPaymentErr && tryModel !== FREE_MODEL) {\n const freeIdx = modelsToTry.indexOf(FREE_MODEL);\n if (freeIdx > i + 1) {\n console.log(`[ClawRouter] Payment error — skipping to free model: ${FREE_MODEL}`);\n i = freeIdx - 1; // loop will increment to freeIdx\n continue;\n }\n }\n\n console.log(\n `[ClawRouter] Provider error from ${tryModel}, trying fallback: ${result.errorBody?.slice(0, 100)}`,\n );\n continue;\n }\n\n // Not a provider error or last attempt — stop trying\n if (!result.isProviderError) {\n console.log(\n `[ClawRouter] Non-provider error from ${tryModel}, not retrying: ${result.errorBody?.slice(0, 100)}`,\n );\n }\n break;\n }\n\n // Clear timeout — request attempts completed\n clearTimeout(timeoutId);\n\n // Clear heartbeat — real data is about to flow\n if (heartbeatInterval) {\n clearInterval(heartbeatInterval);\n heartbeatInterval = undefined;\n }\n\n // --- Emit routing debug info (opt-in via x-clawrouter-debug: true header) ---\n // For streaming: SSE comment (invisible to most clients, visible in raw stream)\n // For non-streaming: response headers added later\n if (debugMode && headersSentEarly && routingDecision) {\n const debugComment = `: x-clawrouter-debug profile=${routingProfile ?? \"auto\"} tier=${routingDecision.tier} model=${actualModelUsed} agentic=${routingDecision.agenticScore?.toFixed(2) ?? \"n/a\"} confidence=${routingDecision.confidence.toFixed(2)} reasoning=${routingDecision.reasoning}\\n\\n`;\n safeWrite(res, debugComment);\n }\n\n // Update routing decision with actual model used (for logging)\n // IMPORTANT: Recalculate cost for the actual model, not the original primary\n if (routingDecision && actualModelUsed !== routingDecision.model) {\n const estimatedInputTokens = Math.ceil(body.length / 4);\n const newCosts = calculateModelCost(\n actualModelUsed,\n routerOpts.modelPricing,\n estimatedInputTokens,\n maxTokens,\n routingProfile ?? undefined,\n );\n routingDecision = {\n ...routingDecision,\n model: actualModelUsed,\n reasoning: `${routingDecision.reasoning} | fallback to ${actualModelUsed}`,\n costEstimate: newCosts.costEstimate,\n baselineCost: newCosts.baselineCost,\n savings: newCosts.savings,\n };\n options.onRouted?.(routingDecision);\n\n // Update session pin to the actual model used — ensures the next request in\n // this conversation starts from the fallback model rather than retrying the\n // primary and falling back again (prevents the \"model keeps jumping\" issue).\n if (effectiveSessionId) {\n sessionStore.setSession(effectiveSessionId, actualModelUsed, routingDecision.tier);\n console.log(\n `[ClawRouter] Session ${effectiveSessionId.slice(0, 8)}... updated pin to fallback: ${actualModelUsed}`,\n );\n }\n }\n\n // --- Handle case where all models failed ---\n if (!upstream) {\n const rawErrBody = lastError?.body || \"All models in fallback chain failed\";\n const errStatus = lastError?.status || 502;\n\n // Transform payment errors into user-friendly messages\n const transformedErr = transformPaymentError(rawErrBody);\n\n if (headersSentEarly) {\n // Streaming: send error as SSE event\n // If transformed error is already JSON, parse and use it; otherwise wrap in standard format\n let errPayload: string;\n try {\n const parsed = JSON.parse(transformedErr);\n errPayload = JSON.stringify(parsed);\n } catch {\n errPayload = JSON.stringify({\n error: { message: rawErrBody, type: \"provider_error\", status: errStatus },\n });\n }\n const errEvent = `data: ${errPayload}\\n\\n`;\n safeWrite(res, errEvent);\n safeWrite(res, \"data: [DONE]\\n\\n\");\n res.end();\n\n const errBuf = Buffer.from(errEvent + \"data: [DONE]\\n\\n\");\n deduplicator.complete(dedupKey, {\n status: 200,\n headers: { \"content-type\": \"text/event-stream\" },\n body: errBuf,\n completedAt: Date.now(),\n });\n } else {\n // Non-streaming: send transformed error response with context headers\n res.writeHead(errStatus, {\n \"Content-Type\": \"application/json\",\n \"x-context-used-kb\": String(originalContextSizeKB),\n \"x-context-limit-kb\": String(CONTEXT_LIMIT_KB),\n });\n res.end(transformedErr);\n\n deduplicator.complete(dedupKey, {\n status: errStatus,\n headers: { \"content-type\": \"application/json\" },\n body: Buffer.from(transformedErr),\n completedAt: Date.now(),\n });\n }\n return;\n }\n\n // --- Stream response and collect for dedup cache ---\n const responseChunks: Buffer[] = [];\n\n if (headersSentEarly) {\n // Streaming: headers already sent. Response should be 200 at this point\n // (non-200 responses are handled in the fallback loop above)\n\n // Convert non-streaming JSON response to SSE streaming format for client\n // (BlockRun API returns JSON since we forced stream:false)\n // OpenClaw expects: object=\"chat.completion.chunk\" with choices[].delta (not message)\n // We emit proper incremental deltas to match OpenAI's streaming format exactly\n if (upstream.body) {\n const reader = upstream.body.getReader();\n const chunks: Uint8Array[] = [];\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n chunks.push(value);\n }\n } finally {\n reader.releaseLock();\n }\n\n // Combine chunks and transform to streaming format\n const jsonBody = Buffer.concat(chunks);\n const jsonStr = jsonBody.toString();\n try {\n const rsp = JSON.parse(jsonStr) as {\n id?: string;\n object?: string;\n created?: number;\n model?: string;\n choices?: Array<{\n index?: number;\n message?: {\n role?: string;\n content?: string;\n tool_calls?: Array<{\n id: string;\n type: string;\n function: { name: string; arguments: string };\n }>;\n };\n delta?: {\n role?: string;\n content?: string;\n tool_calls?: Array<{\n id: string;\n type: string;\n function: { name: string; arguments: string };\n }>;\n };\n finish_reason?: string | null;\n }>;\n usage?: unknown;\n };\n\n // Extract input token count from upstream response\n if (rsp.usage && typeof rsp.usage === \"object\") {\n const u = rsp.usage as Record;\n if (typeof u.prompt_tokens === \"number\") responseInputTokens = u.prompt_tokens;\n }\n\n // Build base chunk structure (reused for all chunks)\n // Match OpenAI's exact format including system_fingerprint\n const baseChunk = {\n id: rsp.id ?? `chatcmpl-${Date.now()}`,\n object: \"chat.completion.chunk\",\n created: rsp.created ?? Math.floor(Date.now() / 1000),\n model: rsp.model ?? \"unknown\",\n system_fingerprint: null,\n };\n\n // Process each choice (usually just one)\n if (rsp.choices && Array.isArray(rsp.choices)) {\n for (const choice of rsp.choices) {\n // Strip thinking tokens (Kimi <|...|> and standard tags)\n const rawContent = choice.message?.content ?? choice.delta?.content ?? \"\";\n const content = stripThinkingTokens(rawContent);\n const role = choice.message?.role ?? choice.delta?.role ?? \"assistant\";\n const index = choice.index ?? 0;\n\n // Accumulate content for session journal\n if (content) {\n accumulatedContent += content;\n }\n\n // Chunk 1: role only (mimics OpenAI's first chunk)\n const roleChunk = {\n ...baseChunk,\n choices: [{ index, delta: { role }, logprobs: null, finish_reason: null }],\n };\n const roleData = `data: ${JSON.stringify(roleChunk)}\\n\\n`;\n safeWrite(res, roleData);\n responseChunks.push(Buffer.from(roleData));\n\n // Chunk 1.5: balance fallback notice (tells user they got free model)\n if (balanceFallbackNotice) {\n const noticeChunk = {\n ...baseChunk,\n choices: [{ index, delta: { content: balanceFallbackNotice }, logprobs: null, finish_reason: null }],\n };\n const noticeData = `data: ${JSON.stringify(noticeChunk)}\\n\\n`;\n safeWrite(res, noticeData);\n responseChunks.push(Buffer.from(noticeData));\n balanceFallbackNotice = undefined; // Only inject once\n }\n\n // Chunk 2: content (single chunk with full content)\n if (content) {\n const contentChunk = {\n ...baseChunk,\n choices: [{ index, delta: { content }, logprobs: null, finish_reason: null }],\n };\n const contentData = `data: ${JSON.stringify(contentChunk)}\\n\\n`;\n safeWrite(res, contentData);\n responseChunks.push(Buffer.from(contentData));\n }\n\n // Chunk 2b: tool_calls (forward tool calls from upstream)\n const toolCalls = choice.message?.tool_calls ?? choice.delta?.tool_calls;\n if (toolCalls && toolCalls.length > 0) {\n const toolCallChunk = {\n ...baseChunk,\n choices: [\n {\n index,\n delta: { tool_calls: toolCalls },\n logprobs: null,\n finish_reason: null,\n },\n ],\n };\n const toolCallData = `data: ${JSON.stringify(toolCallChunk)}\\n\\n`;\n safeWrite(res, toolCallData);\n responseChunks.push(Buffer.from(toolCallData));\n }\n\n // Chunk 3: finish_reason (signals completion)\n const finishChunk = {\n ...baseChunk,\n choices: [\n {\n index,\n delta: {},\n logprobs: null,\n finish_reason:\n toolCalls && toolCalls.length > 0\n ? \"tool_calls\"\n : (choice.finish_reason ?? \"stop\"),\n },\n ],\n };\n const finishData = `data: ${JSON.stringify(finishChunk)}\\n\\n`;\n safeWrite(res, finishData);\n responseChunks.push(Buffer.from(finishData));\n }\n }\n } catch {\n // If parsing fails, send raw response as single chunk\n const sseData = `data: ${jsonStr}\\n\\n`;\n safeWrite(res, sseData);\n responseChunks.push(Buffer.from(sseData));\n }\n }\n\n // Send SSE terminator\n safeWrite(res, \"data: [DONE]\\n\\n\");\n responseChunks.push(Buffer.from(\"data: [DONE]\\n\\n\"));\n res.end();\n\n // Cache for dedup\n deduplicator.complete(dedupKey, {\n status: 200,\n headers: { \"content-type\": \"text/event-stream\" },\n body: Buffer.concat(responseChunks),\n completedAt: Date.now(),\n });\n } else {\n // Non-streaming: forward status and headers from upstream\n const responseHeaders: Record = {};\n upstream.headers.forEach((value, key) => {\n // Skip hop-by-hop headers and content-encoding (fetch already decompresses)\n if (key === \"transfer-encoding\" || key === \"connection\" || key === \"content-encoding\")\n return;\n responseHeaders[key] = value;\n });\n\n // Add context usage headers\n responseHeaders[\"x-context-used-kb\"] = String(originalContextSizeKB);\n responseHeaders[\"x-context-limit-kb\"] = String(CONTEXT_LIMIT_KB);\n\n // Add routing debug headers (opt-in via x-clawrouter-debug: true header)\n if (debugMode && routingDecision) {\n responseHeaders[\"x-clawrouter-profile\"] = routingProfile ?? \"auto\";\n responseHeaders[\"x-clawrouter-tier\"] = routingDecision.tier;\n responseHeaders[\"x-clawrouter-model\"] = actualModelUsed;\n responseHeaders[\"x-clawrouter-confidence\"] = routingDecision.confidence.toFixed(2);\n responseHeaders[\"x-clawrouter-reasoning\"] = routingDecision.reasoning;\n if (routingDecision.agenticScore !== undefined) {\n responseHeaders[\"x-clawrouter-agentic-score\"] = routingDecision.agenticScore.toFixed(2);\n }\n }\n\n // Collect full body for possible notice injection\n const bodyParts: Buffer[] = [];\n if (upstream.body) {\n const reader = upstream.body.getReader();\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n bodyParts.push(Buffer.from(value));\n }\n } finally {\n reader.releaseLock();\n }\n }\n\n let responseBody = Buffer.concat(bodyParts);\n\n // Prepend balance fallback notice to response content\n if (balanceFallbackNotice && responseBody.length > 0) {\n try {\n const parsed = JSON.parse(responseBody.toString()) as {\n choices?: Array<{ message?: { content?: string } }>;\n };\n if (parsed.choices?.[0]?.message?.content !== undefined) {\n parsed.choices[0].message.content = balanceFallbackNotice + parsed.choices[0].message.content;\n responseBody = Buffer.from(JSON.stringify(parsed));\n }\n } catch { /* not JSON, skip notice */ }\n balanceFallbackNotice = undefined;\n }\n\n // Update content-length header since body may have changed\n responseHeaders[\"content-length\"] = String(responseBody.length);\n res.writeHead(upstream.status, responseHeaders);\n safeWrite(res, responseBody);\n responseChunks.push(responseBody);\n res.end();\n\n // Cache for dedup (short-term, 30s)\n deduplicator.complete(dedupKey, {\n status: upstream.status,\n headers: responseHeaders,\n body: responseBody,\n completedAt: Date.now(),\n });\n\n // Cache for response cache (long-term, 10min) - only successful non-streaming\n if (upstream.status === 200 && responseCache.shouldCache(body)) {\n responseCache.set(cacheKey, {\n body: responseBody,\n status: upstream.status,\n headers: responseHeaders,\n model: actualModelUsed,\n });\n console.log(\n `[ClawRouter] Cached response for ${actualModelUsed} (${responseBody.length} bytes)`,\n );\n }\n\n // Extract content and token usage from non-streaming response\n try {\n const rspJson = JSON.parse(responseBody.toString()) as {\n choices?: Array<{ message?: { content?: string } }>;\n usage?: Record;\n };\n if (rspJson.choices?.[0]?.message?.content) {\n accumulatedContent = rspJson.choices[0].message.content;\n }\n if (rspJson.usage && typeof rspJson.usage === \"object\") {\n if (typeof rspJson.usage.prompt_tokens === \"number\")\n responseInputTokens = rspJson.usage.prompt_tokens;\n }\n } catch {\n // Ignore parse errors - journal just won't have content for this response\n }\n }\n\n // --- Session Journal: Extract and record events from response ---\n if (sessionId && accumulatedContent) {\n const events = sessionJournal.extractEvents(accumulatedContent);\n if (events.length > 0) {\n sessionJournal.record(sessionId, events, actualModelUsed);\n console.log(\n `[ClawRouter] Recorded ${events.length} events to session journal for session ${sessionId.slice(0, 8)}...`,\n );\n }\n }\n\n // --- Optimistic balance deduction after successful response ---\n if (estimatedCostMicros !== undefined) {\n balanceMonitor.deductEstimated(estimatedCostMicros);\n }\n\n // Mark request as completed (for client disconnect cleanup)\n completed = true;\n } catch (err) {\n // Clear timeout on error\n clearTimeout(timeoutId);\n\n // Clear heartbeat on error\n if (heartbeatInterval) {\n clearInterval(heartbeatInterval);\n heartbeatInterval = undefined;\n }\n\n // Remove in-flight entry so retries aren't blocked\n deduplicator.removeInflight(dedupKey);\n\n // Invalidate balance cache on payment failure (might be out of date)\n balanceMonitor.invalidate();\n\n // Convert abort error to more descriptive timeout error\n if (err instanceof Error && err.name === \"AbortError\") {\n throw new Error(`Request timed out after ${timeoutMs}ms`, { cause: err });\n }\n\n throw err;\n }\n\n // --- Usage logging (fire-and-forget) ---\n // Note: Recalculate cost using full body length (not just system+user message)\n // and apply 20% buffer to match actual x402 payment (see estimateAmount())\n // Log ALL requests: both auto-routed (routingDecision set) and direct model picks\n const logModel = routingDecision?.model ?? modelId;\n if (logModel) {\n // Use full body length for accurate cost (matches x402 payment estimation)\n const estimatedInputTokens = Math.ceil(body.length / 4);\n const accurateCosts = calculateModelCost(\n logModel,\n routerOpts.modelPricing,\n estimatedInputTokens,\n maxTokens,\n routingProfile ?? undefined,\n );\n // Apply 20% buffer for cost estimation accuracy\n const costWithBuffer = accurateCosts.costEstimate * 1.2;\n const baselineWithBuffer = accurateCosts.baselineCost * 1.2;\n const entry: UsageEntry = {\n timestamp: new Date().toISOString(),\n model: logModel,\n tier: routingDecision?.tier ?? \"DIRECT\",\n cost: costWithBuffer,\n baselineCost: baselineWithBuffer,\n savings: accurateCosts.savings,\n latencyMs: Date.now() - startTime,\n ...(responseInputTokens !== undefined && { inputTokens: responseInputTokens }),\n };\n logUsage(entry).catch(() => {});\n }\n}\n","/**\n * Payment Pre-Auth Cache\n *\n * Wraps the @x402/fetch SDK with pre-authorization caching.\n * After the first 402 response, caches payment requirements per endpoint.\n * On subsequent requests, pre-signs payment and attaches it to the first\n * request, skipping the 402 round trip (~200ms savings per request).\n *\n * Falls back to normal 402 flow if pre-signed payment is rejected.\n */\n\nimport type { x402Client } from \"@x402/fetch\";\nimport { x402HTTPClient } from \"@x402/fetch\";\n\ntype PaymentRequired = Parameters[\"createPaymentPayload\"]>[0];\n\ninterface CachedEntry {\n paymentRequired: PaymentRequired;\n cachedAt: number;\n}\n\nconst DEFAULT_TTL_MS = 3_600_000; // 1 hour\n\ntype FetchFn = (input: RequestInfo | URL, init?: RequestInit) => Promise;\n\nexport function createPayFetchWithPreAuth(\n baseFetch: FetchFn,\n client: x402Client,\n ttlMs = DEFAULT_TTL_MS,\n options?: { skipPreAuth?: boolean },\n): FetchFn {\n const httpClient = new x402HTTPClient(client);\n const cache = new Map();\n\n return async (input: RequestInfo | URL, init?: RequestInit): Promise => {\n const request = new Request(input, init);\n const urlPath = new URL(request.url).pathname;\n\n // Try pre-auth if we have cached payment requirements\n // Skip for Solana: payments use per-tx blockhashes that expire ~60-90s,\n // making cached requirements useless and causing double charges.\n const cached = !options?.skipPreAuth ? cache.get(urlPath) : undefined;\n if (cached && Date.now() - cached.cachedAt < ttlMs) {\n try {\n const payload = await client.createPaymentPayload(cached.paymentRequired);\n const headers = httpClient.encodePaymentSignatureHeader(payload);\n const preAuthRequest = request.clone();\n for (const [key, value] of Object.entries(headers)) {\n preAuthRequest.headers.set(key, value);\n }\n const response = await baseFetch(preAuthRequest);\n if (response.status !== 402) {\n return response; // Pre-auth worked — saved ~200ms\n }\n // Pre-auth rejected (params may have changed) — invalidate and fall through\n cache.delete(urlPath);\n } catch {\n // Pre-auth signing failed — invalidate and fall through\n cache.delete(urlPath);\n }\n }\n\n // Normal flow: make request, handle 402 if needed\n const clonedRequest = request.clone();\n const response = await baseFetch(request);\n if (response.status !== 402) {\n return response;\n }\n\n // Parse 402 response and cache for future pre-auth\n let paymentRequired: PaymentRequired;\n try {\n const getHeader = (name: string) => response.headers.get(name);\n let body: unknown;\n try {\n const responseText = await response.text();\n if (responseText) body = JSON.parse(responseText);\n } catch { /* empty body is fine */ }\n paymentRequired = httpClient.getPaymentRequiredResponse(getHeader, body);\n cache.set(urlPath, { paymentRequired, cachedAt: Date.now() });\n } catch (error) {\n throw new Error(\n `Failed to parse payment requirements: ${error instanceof Error ? error.message : \"Unknown error\"}`,\n );\n }\n\n // Sign payment and retry\n const payload = await client.createPaymentPayload(paymentRequired);\n const paymentHeaders = httpClient.encodePaymentSignatureHeader(payload);\n for (const [key, value] of Object.entries(paymentHeaders)) {\n clonedRequest.headers.set(key, value);\n }\n return baseFetch(clonedRequest);\n };\n}\n","/**\n * Usage Logger\n *\n * Logs every LLM request as a JSON line to a daily log file.\n * Files: ~/.openclaw/blockrun/logs/usage-YYYY-MM-DD.jsonl\n *\n * MVP: append-only JSON lines. No rotation, no cleanup.\n * Logging never breaks the request flow — all errors are swallowed.\n */\n\nimport { appendFile, mkdir } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { homedir } from \"node:os\";\n\nexport type UsageEntry = {\n timestamp: string;\n model: string;\n tier: string;\n cost: number;\n baselineCost: number;\n savings: number; // 0-1 percentage\n latencyMs: number;\n /** Input (prompt) tokens reported by the provider */\n inputTokens?: number;\n /** Partner service ID (e.g., \"x_users_lookup\") — only set for partner API calls */\n partnerId?: string;\n /** Partner service name (e.g., \"AttentionVC\") — only set for partner API calls */\n service?: string;\n};\n\nconst LOG_DIR = join(homedir(), \".openclaw\", \"blockrun\", \"logs\");\nlet dirReady = false;\n\nasync function ensureDir(): Promise {\n if (dirReady) return;\n await mkdir(LOG_DIR, { recursive: true });\n dirReady = true;\n}\n\n/**\n * Log a usage entry as a JSON line.\n */\nexport async function logUsage(entry: UsageEntry): Promise {\n try {\n await ensureDir();\n const date = entry.timestamp.slice(0, 10); // YYYY-MM-DD\n const file = join(LOG_DIR, `usage-${date}.jsonl`);\n await appendFile(file, JSON.stringify(entry) + \"\\n\");\n } catch {\n // Never break the request flow\n }\n}\n","/**\n * Usage Statistics Aggregator\n *\n * Reads usage log files and aggregates statistics for terminal display.\n * Supports filtering by date range and provides multiple aggregation views.\n */\n\nimport { readdir } from \"node:fs/promises\";\nimport { readTextFile } from \"./fs-read.js\";\nimport { join } from \"node:path\";\nimport { homedir } from \"node:os\";\nimport type { UsageEntry } from \"./logger.js\";\nimport { VERSION } from \"./version.js\";\n\nconst LOG_DIR = join(homedir(), \".openclaw\", \"blockrun\", \"logs\");\n\nexport type DailyStats = {\n date: string;\n totalRequests: number;\n totalCost: number;\n totalBaselineCost: number;\n totalSavings: number;\n avgLatencyMs: number;\n byTier: Record;\n byModel: Record;\n};\n\nexport type AggregatedStats = {\n period: string;\n totalRequests: number;\n totalCost: number;\n totalBaselineCost: number;\n totalSavings: number;\n savingsPercentage: number;\n avgLatencyMs: number;\n avgCostPerRequest: number;\n byTier: Record;\n byModel: Record;\n dailyBreakdown: DailyStats[];\n entriesWithBaseline: number; // Entries with valid baseline tracking\n};\n\n/**\n * Parse a JSONL log file into usage entries.\n * Handles both old format (without tier/baselineCost) and new format.\n */\nasync function parseLogFile(filePath: string): Promise {\n try {\n const content = await readTextFile(filePath);\n const lines = content.trim().split(\"\\n\").filter(Boolean);\n return lines.map((line) => {\n const entry = JSON.parse(line) as Partial;\n // Handle old format entries\n return {\n timestamp: entry.timestamp || new Date().toISOString(),\n model: entry.model || \"unknown\",\n tier: entry.tier || \"UNKNOWN\",\n cost: entry.cost || 0,\n baselineCost: entry.baselineCost || entry.cost || 0,\n savings: entry.savings || 0,\n latencyMs: entry.latencyMs || 0,\n };\n });\n } catch {\n return [];\n }\n}\n\n/**\n * Get list of available log files sorted by date (newest first).\n */\nasync function getLogFiles(): Promise {\n try {\n const files = await readdir(LOG_DIR);\n return files\n .filter((f) => f.startsWith(\"usage-\") && f.endsWith(\".jsonl\"))\n .sort()\n .reverse();\n } catch {\n return [];\n }\n}\n\n/**\n * Aggregate stats for a single day.\n */\nfunction aggregateDay(date: string, entries: UsageEntry[]): DailyStats {\n const byTier: Record = {};\n const byModel: Record = {};\n let totalLatency = 0;\n\n for (const entry of entries) {\n // By tier\n if (!byTier[entry.tier]) byTier[entry.tier] = { count: 0, cost: 0 };\n byTier[entry.tier].count++;\n byTier[entry.tier].cost += entry.cost;\n\n // By model\n if (!byModel[entry.model]) byModel[entry.model] = { count: 0, cost: 0 };\n byModel[entry.model].count++;\n byModel[entry.model].cost += entry.cost;\n\n totalLatency += entry.latencyMs;\n }\n\n const totalCost = entries.reduce((sum, e) => sum + e.cost, 0);\n const totalBaselineCost = entries.reduce((sum, e) => sum + e.baselineCost, 0);\n\n return {\n date,\n totalRequests: entries.length,\n totalCost,\n totalBaselineCost,\n totalSavings: totalBaselineCost - totalCost,\n avgLatencyMs: entries.length > 0 ? totalLatency / entries.length : 0,\n byTier,\n byModel,\n };\n}\n\n/**\n * Get aggregated statistics for the last N days.\n */\nexport async function getStats(days: number = 7): Promise {\n const logFiles = await getLogFiles();\n const filesToRead = logFiles.slice(0, days);\n\n const dailyBreakdown: DailyStats[] = [];\n const allByTier: Record = {};\n const allByModel: Record = {};\n let totalRequests = 0;\n let totalCost = 0;\n let totalBaselineCost = 0;\n let totalLatency = 0;\n\n for (const file of filesToRead) {\n const date = file.replace(\"usage-\", \"\").replace(\".jsonl\", \"\");\n const filePath = join(LOG_DIR, file);\n const entries = await parseLogFile(filePath);\n\n if (entries.length === 0) continue;\n\n const dayStats = aggregateDay(date, entries);\n dailyBreakdown.push(dayStats);\n\n totalRequests += dayStats.totalRequests;\n totalCost += dayStats.totalCost;\n totalBaselineCost += dayStats.totalBaselineCost;\n totalLatency += dayStats.avgLatencyMs * dayStats.totalRequests;\n\n // Merge tier stats\n for (const [tier, stats] of Object.entries(dayStats.byTier)) {\n if (!allByTier[tier]) allByTier[tier] = { count: 0, cost: 0 };\n allByTier[tier].count += stats.count;\n allByTier[tier].cost += stats.cost;\n }\n\n // Merge model stats\n for (const [model, stats] of Object.entries(dayStats.byModel)) {\n if (!allByModel[model]) allByModel[model] = { count: 0, cost: 0 };\n allByModel[model].count += stats.count;\n allByModel[model].cost += stats.cost;\n }\n }\n\n // Calculate percentages\n const byTierWithPercentage: Record =\n {};\n for (const [tier, stats] of Object.entries(allByTier)) {\n byTierWithPercentage[tier] = {\n ...stats,\n percentage: totalRequests > 0 ? (stats.count / totalRequests) * 100 : 0,\n };\n }\n\n const byModelWithPercentage: Record =\n {};\n for (const [model, stats] of Object.entries(allByModel)) {\n byModelWithPercentage[model] = {\n ...stats,\n percentage: totalRequests > 0 ? (stats.count / totalRequests) * 100 : 0,\n };\n }\n\n const totalSavings = totalBaselineCost - totalCost;\n const savingsPercentage = totalBaselineCost > 0 ? (totalSavings / totalBaselineCost) * 100 : 0;\n\n // Count entries with valid baseline tracking (baseline != cost means tracking was active)\n let entriesWithBaseline = 0;\n for (const day of dailyBreakdown) {\n if (day.totalBaselineCost !== day.totalCost) {\n entriesWithBaseline += day.totalRequests;\n }\n }\n\n return {\n period: days === 1 ? \"today\" : `last ${days} days`,\n totalRequests,\n totalCost,\n totalBaselineCost,\n totalSavings,\n savingsPercentage,\n avgLatencyMs: totalRequests > 0 ? totalLatency / totalRequests : 0,\n avgCostPerRequest: totalRequests > 0 ? totalCost / totalRequests : 0,\n byTier: byTierWithPercentage,\n byModel: byModelWithPercentage,\n dailyBreakdown: dailyBreakdown.reverse(), // Oldest first for charts\n entriesWithBaseline, // How many entries have valid baseline tracking\n };\n}\n\n/**\n * Format stats as ASCII table for terminal display.\n */\nexport function formatStatsAscii(stats: AggregatedStats): string {\n const lines: string[] = [];\n\n // Header\n lines.push(\"╔════════════════════════════════════════════════════════════╗\");\n lines.push(`║ ClawRouter by BlockRun v${VERSION}`.padEnd(61) + \"║\");\n lines.push(\"║ Usage Statistics ║\");\n lines.push(\"╠════════════════════════════════════════════════════════════╣\");\n\n // Summary\n lines.push(`║ Period: ${stats.period.padEnd(49)}║`);\n lines.push(`║ Total Requests: ${stats.totalRequests.toString().padEnd(41)}║`);\n lines.push(`║ Total Cost: $${stats.totalCost.toFixed(4).padEnd(43)}║`);\n lines.push(`║ Baseline Cost (Opus 4.5): $${stats.totalBaselineCost.toFixed(4).padEnd(30)}║`);\n\n // Show savings with note if some entries lack baseline tracking\n const savingsLine = `║ 💰 Total Saved: $${stats.totalSavings.toFixed(4)} (${stats.savingsPercentage.toFixed(1)}%)`;\n if (stats.entriesWithBaseline < stats.totalRequests && stats.entriesWithBaseline > 0) {\n lines.push(savingsLine.padEnd(61) + \"║\");\n const note = `║ (based on ${stats.entriesWithBaseline}/${stats.totalRequests} tracked requests)`;\n lines.push(note.padEnd(61) + \"║\");\n } else {\n lines.push(savingsLine.padEnd(61) + \"║\");\n }\n lines.push(`║ Avg Latency: ${stats.avgLatencyMs.toFixed(0)}ms`.padEnd(61) + \"║\");\n\n // Tier breakdown\n lines.push(\"╠════════════════════════════════════════════════════════════╣\");\n lines.push(\"║ Routing by Tier: ║\");\n\n // Show all tiers found in data, ordered by known tiers first then others\n const knownTiers = [\"SIMPLE\", \"MEDIUM\", \"COMPLEX\", \"REASONING\", \"DIRECT\"];\n const allTiers = Object.keys(stats.byTier);\n const otherTiers = allTiers.filter((t) => !knownTiers.includes(t));\n const tierOrder = [...knownTiers.filter((t) => stats.byTier[t]), ...otherTiers];\n\n for (const tier of tierOrder) {\n const data = stats.byTier[tier];\n if (data) {\n const bar = \"█\".repeat(Math.min(20, Math.round(data.percentage / 5)));\n const displayTier = tier === \"UNKNOWN\" ? \"OTHER\" : tier;\n const line = `║ ${displayTier.padEnd(10)} ${bar.padEnd(20)} ${data.percentage.toFixed(1).padStart(5)}% (${data.count})`;\n lines.push(line.padEnd(61) + \"║\");\n }\n }\n\n // Top models\n lines.push(\"╠════════════════════════════════════════════════════════════╣\");\n lines.push(\"║ Top Models: ║\");\n\n const sortedModels = Object.entries(stats.byModel)\n .sort((a, b) => b[1].count - a[1].count)\n .slice(0, 5);\n\n for (const [model, data] of sortedModels) {\n const shortModel = model.length > 25 ? model.slice(0, 22) + \"...\" : model;\n const line = `║ ${shortModel.padEnd(25)} ${data.count.toString().padStart(5)} reqs $${data.cost.toFixed(4)}`;\n lines.push(line.padEnd(61) + \"║\");\n }\n\n // Daily breakdown (last 7 days)\n if (stats.dailyBreakdown.length > 0) {\n lines.push(\"╠════════════════════════════════════════════════════════════╣\");\n lines.push(\"║ Daily Breakdown: ║\");\n lines.push(\"║ Date Requests Cost Saved ║\");\n\n for (const day of stats.dailyBreakdown.slice(-7)) {\n const saved = day.totalBaselineCost - day.totalCost;\n const line = `║ ${day.date} ${day.totalRequests.toString().padStart(6)} $${day.totalCost.toFixed(4).padStart(8)} $${saved.toFixed(4)}`;\n lines.push(line.padEnd(61) + \"║\");\n }\n }\n\n lines.push(\"╚════════════════════════════════════════════════════════════╝\");\n\n return lines.join(\"\\n\");\n}\n","/**\n * Scanner-safe file reading utilities.\n *\n * Uses open() + read() to avoid false positives from openclaw's\n * potential-exfiltration heuristic in bundled output.\n */\n\nimport { open } from \"node:fs/promises\";\nimport { openSync, readSync, closeSync, fstatSync } from \"node:fs\";\n\n/** Read file contents as UTF-8 string (async). */\nexport async function readTextFile(filePath: string): Promise {\n const fh = await open(filePath, \"r\");\n try {\n const buf = Buffer.alloc((await fh.stat()).size);\n await fh.read(buf, 0, buf.length, 0);\n return buf.toString(\"utf-8\");\n } finally {\n await fh.close();\n }\n}\n\n/** Read file contents as UTF-8 string (sync). */\nexport function readTextFileSync(filePath: string): string {\n const fd = openSync(filePath, \"r\");\n try {\n const buf = Buffer.alloc(fstatSync(fd).size);\n readSync(fd, buf);\n return buf.toString(\"utf-8\");\n } finally {\n closeSync(fd);\n }\n}\n","/**\n * Single source of truth for version.\n * Reads from package.json at build time via tsup's define.\n */\nimport { createRequire } from \"node:module\";\nimport { fileURLToPath } from \"node:url\";\nimport { dirname, join } from \"node:path\";\n\n// Read package.json at runtime\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = dirname(__filename);\n\n// In dist/, go up one level to find package.json\nconst require = createRequire(import.meta.url);\nconst pkg = require(join(__dirname, \"..\", \"package.json\")) as { version: string };\n\nexport const VERSION = pkg.version;\nexport const USER_AGENT = `clawrouter/${VERSION}`;\n","/**\n * Request Deduplication\n *\n * Prevents double-charging when OpenClaw retries a request after timeout.\n * Tracks in-flight requests and caches completed responses for a short TTL.\n */\n\nimport { createHash } from \"node:crypto\";\n\nexport type CachedResponse = {\n status: number;\n headers: Record;\n body: Buffer;\n completedAt: number;\n};\n\ntype InflightEntry = {\n resolvers: Array<(result: CachedResponse) => void>;\n};\n\nconst DEFAULT_TTL_MS = 30_000; // 30 seconds\nconst MAX_BODY_SIZE = 1_048_576; // 1MB\n\n/**\n * Canonicalize JSON by sorting object keys recursively.\n * Ensures identical logical content produces identical string regardless of field order.\n */\nfunction canonicalize(obj: unknown): unknown {\n if (obj === null || typeof obj !== \"object\") {\n return obj;\n }\n if (Array.isArray(obj)) {\n return obj.map(canonicalize);\n }\n const sorted: Record = {};\n for (const key of Object.keys(obj).sort()) {\n sorted[key] = canonicalize((obj as Record)[key]);\n }\n return sorted;\n}\n\n/**\n * Strip OpenClaw-injected timestamps from message content.\n * Format: [DAY YYYY-MM-DD HH:MM TZ] at the start of messages.\n * Example: [SUN 2026-02-07 13:30 PST] Hello world\n *\n * This ensures requests with different timestamps but same content hash identically.\n */\nconst TIMESTAMP_PATTERN = /^\\[\\w{3}\\s+\\d{4}-\\d{2}-\\d{2}\\s+\\d{2}:\\d{2}\\s+\\w+\\]\\s*/;\n\nfunction stripTimestamps(obj: unknown): unknown {\n if (obj === null || typeof obj !== \"object\") {\n return obj;\n }\n if (Array.isArray(obj)) {\n return obj.map(stripTimestamps);\n }\n const result: Record = {};\n for (const [key, value] of Object.entries(obj as Record)) {\n if (key === \"content\" && typeof value === \"string\") {\n // Strip timestamp prefix from message content\n result[key] = value.replace(TIMESTAMP_PATTERN, \"\");\n } else {\n result[key] = stripTimestamps(value);\n }\n }\n return result;\n}\n\nexport class RequestDeduplicator {\n private inflight = new Map();\n private completed = new Map();\n private ttlMs: number;\n\n constructor(ttlMs = DEFAULT_TTL_MS) {\n this.ttlMs = ttlMs;\n }\n\n /** Hash request body to create a dedup key. */\n static hash(body: Buffer): string {\n // Canonicalize JSON to ensure consistent hashing regardless of field order.\n // Also strip OpenClaw-injected timestamps so retries with different timestamps\n // still match the same dedup key.\n let content = body;\n try {\n const parsed = JSON.parse(body.toString());\n const stripped = stripTimestamps(parsed);\n const canonical = canonicalize(stripped);\n content = Buffer.from(JSON.stringify(canonical));\n } catch {\n // Not valid JSON, use raw bytes\n }\n return createHash(\"sha256\").update(content).digest(\"hex\").slice(0, 16);\n }\n\n /** Check if a response is cached for this key. */\n getCached(key: string): CachedResponse | undefined {\n const entry = this.completed.get(key);\n if (!entry) return undefined;\n if (Date.now() - entry.completedAt > this.ttlMs) {\n this.completed.delete(key);\n return undefined;\n }\n return entry;\n }\n\n /** Check if a request with this key is currently in-flight. Returns a promise to wait on. */\n getInflight(key: string): Promise | undefined {\n const entry = this.inflight.get(key);\n if (!entry) return undefined;\n return new Promise((resolve) => {\n entry.resolvers.push(resolve);\n });\n }\n\n /** Mark a request as in-flight. */\n markInflight(key: string): void {\n this.inflight.set(key, {\n resolvers: [],\n });\n }\n\n /** Complete an in-flight request — cache result and notify waiters. */\n complete(key: string, result: CachedResponse): void {\n // Only cache responses within size limit\n if (result.body.length <= MAX_BODY_SIZE) {\n this.completed.set(key, result);\n }\n\n const entry = this.inflight.get(key);\n if (entry) {\n for (const resolve of entry.resolvers) {\n resolve(result);\n }\n this.inflight.delete(key);\n }\n\n this.prune();\n }\n\n /** Remove an in-flight entry on error (don't cache failures).\n * Also rejects any waiters so they can retry independently. */\n removeInflight(key: string): void {\n const entry = this.inflight.get(key);\n if (entry) {\n // Resolve waiters with a sentinel error response so they don't hang forever.\n // Waiters will see a 503 and can retry on their own.\n const errorBody = Buffer.from(\n JSON.stringify({\n error: { message: \"Original request failed, please retry\", type: \"dedup_origin_failed\" },\n }),\n );\n for (const resolve of entry.resolvers) {\n resolve({\n status: 503,\n headers: { \"content-type\": \"application/json\" },\n body: errorBody,\n completedAt: Date.now(),\n });\n }\n this.inflight.delete(key);\n }\n }\n\n /** Prune expired completed entries. */\n private prune(): void {\n const now = Date.now();\n for (const [key, entry] of this.completed) {\n if (now - entry.completedAt > this.ttlMs) {\n this.completed.delete(key);\n }\n }\n }\n}\n","/**\n * Response Cache for LLM Completions\n *\n * Caches LLM responses by request hash (model + messages + params).\n * Inspired by LiteLLM's caching system. Returns cached responses for\n * identical requests, saving both cost and latency.\n *\n * Features:\n * - TTL-based expiration (default 10 minutes)\n * - LRU eviction when cache is full\n * - Size limits per item (1MB max)\n * - Heap-based expiration tracking for efficient pruning\n */\n\nimport { createHash } from \"node:crypto\";\n\nexport type CachedLLMResponse = {\n body: Buffer;\n status: number;\n headers: Record;\n model: string;\n cachedAt: number;\n expiresAt: number;\n};\n\nexport type ResponseCacheConfig = {\n /** Maximum number of cached responses. Default: 200 */\n maxSize?: number;\n /** Default TTL in seconds. Default: 600 (10 minutes) */\n defaultTTL?: number;\n /** Maximum size per cached item in bytes. Default: 1MB */\n maxItemSize?: number;\n /** Enable/disable cache. Default: true */\n enabled?: boolean;\n};\n\nconst DEFAULT_CONFIG: Required = {\n maxSize: 200,\n defaultTTL: 600,\n maxItemSize: 1_048_576, // 1MB\n enabled: true,\n};\n\n/**\n * Canonicalize JSON by sorting object keys recursively.\n * Ensures identical logical content produces identical hash.\n */\nfunction canonicalize(obj: unknown): unknown {\n if (obj === null || typeof obj !== \"object\") {\n return obj;\n }\n if (Array.isArray(obj)) {\n return obj.map(canonicalize);\n }\n const sorted: Record = {};\n for (const key of Object.keys(obj).sort()) {\n sorted[key] = canonicalize((obj as Record)[key]);\n }\n return sorted;\n}\n\n/**\n * Strip fields that shouldn't affect cache key:\n * - stream (we handle streaming separately)\n * - timestamps injected by OpenClaw\n * - request IDs\n */\nconst TIMESTAMP_PATTERN = /^\\[\\w{3}\\s+\\d{4}-\\d{2}-\\d{2}\\s+\\d{2}:\\d{2}\\s+\\w+\\]\\s*/;\n\nfunction normalizeForCache(obj: Record): Record {\n const result: Record = {};\n\n for (const [key, value] of Object.entries(obj)) {\n // Skip fields that don't affect response content\n if ([\"stream\", \"user\", \"request_id\", \"x-request-id\"].includes(key)) {\n continue;\n }\n\n if (key === \"messages\" && Array.isArray(value)) {\n // Strip timestamps from message content\n result[key] = value.map((msg: unknown) => {\n if (typeof msg === \"object\" && msg !== null) {\n const m = msg as Record;\n if (typeof m.content === \"string\") {\n return { ...m, content: m.content.replace(TIMESTAMP_PATTERN, \"\") };\n }\n }\n return msg;\n });\n } else {\n result[key] = value;\n }\n }\n\n return result;\n}\n\nexport class ResponseCache {\n private cache = new Map();\n private expirationHeap: Array<{ expiresAt: number; key: string }> = [];\n private config: Required;\n\n // Stats for monitoring\n private stats = {\n hits: 0,\n misses: 0,\n evictions: 0,\n };\n\n constructor(config: ResponseCacheConfig = {}) {\n // Filter out undefined values so they don't override defaults\n const filtered = Object.fromEntries(\n Object.entries(config).filter(([, v]) => v !== undefined),\n ) as ResponseCacheConfig;\n this.config = { ...DEFAULT_CONFIG, ...filtered };\n }\n\n /**\n * Generate cache key from request body.\n * Hashes: model + messages + temperature + max_tokens + other params\n */\n static generateKey(body: Buffer | string): string {\n try {\n const parsed = JSON.parse(typeof body === \"string\" ? body : body.toString());\n const normalized = normalizeForCache(parsed);\n const canonical = canonicalize(normalized);\n const keyContent = JSON.stringify(canonical);\n return createHash(\"sha256\").update(keyContent).digest(\"hex\").slice(0, 32);\n } catch {\n // Fallback: hash raw body\n const content = typeof body === \"string\" ? body : body.toString();\n return createHash(\"sha256\").update(content).digest(\"hex\").slice(0, 32);\n }\n }\n\n /**\n * Check if caching is enabled for this request.\n * Respects cache control headers and request params.\n */\n shouldCache(body: Buffer | string, headers?: Record): boolean {\n if (!this.config.enabled) return false;\n\n // Respect Cache-Control: no-cache header\n if (headers?.[\"cache-control\"]?.includes(\"no-cache\")) {\n return false;\n }\n\n // Check for explicit cache disable in body\n try {\n const parsed = JSON.parse(typeof body === \"string\" ? body : body.toString());\n if (parsed.cache === false || parsed.no_cache === true) {\n return false;\n }\n } catch {\n // Not JSON, allow caching\n }\n\n return true;\n }\n\n /**\n * Get cached response if available and not expired.\n */\n get(key: string): CachedLLMResponse | undefined {\n const entry = this.cache.get(key);\n if (!entry) {\n this.stats.misses++;\n return undefined;\n }\n\n // Check expiration\n if (Date.now() > entry.expiresAt) {\n this.cache.delete(key);\n this.stats.misses++;\n return undefined;\n }\n\n this.stats.hits++;\n return entry;\n }\n\n /**\n * Cache a response with optional custom TTL.\n */\n set(\n key: string,\n response: {\n body: Buffer;\n status: number;\n headers: Record;\n model: string;\n },\n ttlSeconds?: number,\n ): void {\n // Don't cache if disabled or maxSize is 0\n if (!this.config.enabled || this.config.maxSize <= 0) return;\n\n // Don't cache if item too large\n if (response.body.length > this.config.maxItemSize) {\n console.log(`[ResponseCache] Skipping cache - item too large: ${response.body.length} bytes`);\n return;\n }\n\n // Don't cache error responses\n if (response.status >= 400) {\n return;\n }\n\n // Evict if at capacity\n if (this.cache.size >= this.config.maxSize) {\n this.evict();\n }\n\n const now = Date.now();\n const ttl = ttlSeconds ?? this.config.defaultTTL;\n const expiresAt = now + ttl * 1000;\n\n const entry: CachedLLMResponse = {\n ...response,\n cachedAt: now,\n expiresAt,\n };\n\n this.cache.set(key, entry);\n this.expirationHeap.push({ expiresAt, key });\n }\n\n /**\n * Evict expired and oldest entries to make room.\n */\n private evict(): void {\n const now = Date.now();\n\n // First pass: remove expired entries\n this.expirationHeap.sort((a, b) => a.expiresAt - b.expiresAt);\n\n while (this.expirationHeap.length > 0) {\n const oldest = this.expirationHeap[0];\n\n // Check if entry still exists and matches\n const entry = this.cache.get(oldest.key);\n if (!entry || entry.expiresAt !== oldest.expiresAt) {\n // Stale heap entry, remove it\n this.expirationHeap.shift();\n continue;\n }\n\n if (oldest.expiresAt <= now) {\n // Expired, remove both\n this.cache.delete(oldest.key);\n this.expirationHeap.shift();\n this.stats.evictions++;\n } else {\n // Not expired, stop\n break;\n }\n }\n\n // Second pass: if still at capacity, evict oldest\n while (this.cache.size >= this.config.maxSize && this.expirationHeap.length > 0) {\n const oldest = this.expirationHeap.shift()!;\n if (this.cache.has(oldest.key)) {\n this.cache.delete(oldest.key);\n this.stats.evictions++;\n }\n }\n }\n\n /**\n * Get cache statistics.\n */\n getStats(): {\n size: number;\n maxSize: number;\n hits: number;\n misses: number;\n evictions: number;\n hitRate: string;\n } {\n const total = this.stats.hits + this.stats.misses;\n const hitRate = total > 0 ? ((this.stats.hits / total) * 100).toFixed(1) + \"%\" : \"0%\";\n\n return {\n size: this.cache.size,\n maxSize: this.config.maxSize,\n hits: this.stats.hits,\n misses: this.stats.misses,\n evictions: this.stats.evictions,\n hitRate,\n };\n }\n\n /**\n * Clear all cached entries.\n */\n clear(): void {\n this.cache.clear();\n this.expirationHeap = [];\n }\n\n /**\n * Check if cache is enabled.\n */\n isEnabled(): boolean {\n return this.config.enabled;\n }\n}\n","/**\n * Balance Monitor for ClawRouter\n *\n * Monitors USDC balance on Base network with intelligent caching.\n * Provides pre-request balance checks to prevent failed payments.\n *\n * Caching Strategy:\n * - TTL: 30 seconds (balance is cached to avoid excessive RPC calls)\n * - Optimistic deduction: after successful payment, subtract estimated cost from cache\n * - Invalidation: on payment failure, immediately refresh from RPC\n */\n\nimport { createPublicClient, http, erc20Abi } from \"viem\";\nimport { base } from \"viem/chains\";\nimport { RpcError } from \"./errors.js\";\n\n/** USDC contract address on Base mainnet */\nconst USDC_BASE = \"0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913\" as const;\n\n/** Cache TTL in milliseconds (30 seconds) */\nconst CACHE_TTL_MS = 30_000;\n\n/** Balance thresholds in USDC smallest unit (6 decimals) */\nexport const BALANCE_THRESHOLDS = {\n /** Low balance warning threshold: $1.00 */\n LOW_BALANCE_MICROS: 1_000_000n,\n /** Effectively zero threshold: $0.0001 (covers dust/rounding) */\n ZERO_THRESHOLD: 100n,\n} as const;\n\n/** Balance information returned by checkBalance() */\nexport type BalanceInfo = {\n /** Raw balance in USDC smallest unit (6 decimals) */\n balance: bigint;\n /** Formatted balance as \"$X.XX\" */\n balanceUSD: string;\n /** True if balance < $1.00 */\n isLow: boolean;\n /** True if balance < $0.0001 (effectively zero) */\n isEmpty: boolean;\n /** Wallet address for funding instructions */\n walletAddress: string;\n};\n\n/** Result from checkSufficient() */\nexport type SufficiencyResult = {\n /** True if balance >= estimated cost */\n sufficient: boolean;\n /** Current balance info */\n info: BalanceInfo;\n /** If insufficient, the shortfall as \"$X.XX\" */\n shortfall?: string;\n};\n\n/**\n * Monitors USDC balance on Base network.\n *\n * Usage:\n * const monitor = new BalanceMonitor(\"0x...\");\n * const info = await monitor.checkBalance();\n * if (info.isLow) console.warn(\"Low balance!\");\n */\nexport class BalanceMonitor {\n private readonly client;\n private readonly walletAddress: `0x${string}`;\n\n /** Cached balance (null = not yet fetched) */\n private cachedBalance: bigint | null = null;\n /** Timestamp when cache was last updated */\n private cachedAt = 0;\n\n constructor(walletAddress: string) {\n this.walletAddress = walletAddress as `0x${string}`;\n this.client = createPublicClient({\n chain: base,\n transport: http(undefined, {\n timeout: 10_000, // 10 second timeout to prevent hanging on slow RPC\n }),\n });\n }\n\n /**\n * Check current USDC balance.\n * Uses cache if valid, otherwise fetches from RPC.\n */\n async checkBalance(): Promise {\n const now = Date.now();\n\n // Use cache if valid\n if (this.cachedBalance !== null && now - this.cachedAt < CACHE_TTL_MS) {\n return this.buildInfo(this.cachedBalance);\n }\n\n // Fetch from RPC\n const balance = await this.fetchBalance();\n this.cachedBalance = balance;\n this.cachedAt = now;\n\n return this.buildInfo(balance);\n }\n\n /**\n * Check if balance is sufficient for an estimated cost.\n *\n * @param estimatedCostMicros - Estimated cost in USDC smallest unit (6 decimals)\n */\n async checkSufficient(estimatedCostMicros: bigint): Promise {\n const info = await this.checkBalance();\n\n if (info.balance >= estimatedCostMicros) {\n return { sufficient: true, info };\n }\n\n const shortfall = estimatedCostMicros - info.balance;\n return {\n sufficient: false,\n info,\n shortfall: this.formatUSDC(shortfall),\n };\n }\n\n /**\n * Optimistically deduct estimated cost from cached balance.\n * Call this after a successful payment to keep cache accurate.\n *\n * @param amountMicros - Amount to deduct in USDC smallest unit\n */\n deductEstimated(amountMicros: bigint): void {\n if (this.cachedBalance !== null && this.cachedBalance >= amountMicros) {\n this.cachedBalance -= amountMicros;\n }\n }\n\n /**\n * Invalidate cache, forcing next checkBalance() to fetch from RPC.\n * Call this after a payment failure to get accurate balance.\n */\n invalidate(): void {\n this.cachedBalance = null;\n this.cachedAt = 0;\n }\n\n /**\n * Force refresh balance from RPC (ignores cache).\n */\n async refresh(): Promise {\n this.invalidate();\n return this.checkBalance();\n }\n\n /**\n * Format USDC amount (in micros) as \"$X.XX\".\n */\n formatUSDC(amountMicros: bigint): string {\n // USDC has 6 decimals\n const dollars = Number(amountMicros) / 1_000_000;\n return `$${dollars.toFixed(2)}`;\n }\n\n /**\n * Get the wallet address being monitored.\n */\n getWalletAddress(): string {\n return this.walletAddress;\n }\n\n /** Fetch balance from RPC */\n private async fetchBalance(): Promise {\n try {\n const balance = await this.client.readContract({\n address: USDC_BASE,\n abi: erc20Abi,\n functionName: \"balanceOf\",\n args: [this.walletAddress],\n });\n return balance;\n } catch (error) {\n // Throw typed error instead of silently returning 0\n // This allows callers to distinguish \"node down\" from \"wallet empty\"\n throw new RpcError(error instanceof Error ? error.message : \"Unknown error\", error);\n }\n }\n\n /** Build BalanceInfo from raw balance */\n private buildInfo(balance: bigint): BalanceInfo {\n return {\n balance,\n balanceUSD: this.formatUSDC(balance),\n isLow: balance < BALANCE_THRESHOLDS.LOW_BALANCE_MICROS,\n isEmpty: balance < BALANCE_THRESHOLDS.ZERO_THRESHOLD,\n walletAddress: this.walletAddress,\n };\n }\n}\n","/**\n * Typed Error Classes for ClawRouter\n *\n * Provides structured errors for balance-related failures with\n * all necessary information for user-friendly error messages.\n */\n\n/**\n * Thrown when wallet has insufficient USDC balance for a request.\n */\nexport class InsufficientFundsError extends Error {\n readonly code = \"INSUFFICIENT_FUNDS\" as const;\n readonly currentBalanceUSD: string;\n readonly requiredUSD: string;\n readonly walletAddress: string;\n\n constructor(opts: { currentBalanceUSD: string; requiredUSD: string; walletAddress: string }) {\n const msg = [\n `Insufficient balance. Current: ${opts.currentBalanceUSD}, Required: ${opts.requiredUSD}`,\n `Options:`,\n ` 1. Fund wallet: ${opts.walletAddress}`,\n ` 2. Use free model: /model free`,\n ].join(\"\\n\");\n super(msg);\n this.name = \"InsufficientFundsError\";\n this.currentBalanceUSD = opts.currentBalanceUSD;\n this.requiredUSD = opts.requiredUSD;\n this.walletAddress = opts.walletAddress;\n }\n}\n\n/**\n * Thrown when wallet has no USDC balance (or effectively zero).\n */\nexport class EmptyWalletError extends Error {\n readonly code = \"EMPTY_WALLET\" as const;\n readonly walletAddress: string;\n\n constructor(walletAddress: string) {\n const msg = [\n `No USDC balance.`,\n `Options:`,\n ` 1. Fund wallet: ${walletAddress}`,\n ` 2. Use free model: /model free`,\n ` 3. Uninstall: bash ~/.openclaw/extensions/clawrouter/scripts/uninstall.sh`,\n ].join(\"\\n\");\n super(msg);\n this.name = \"EmptyWalletError\";\n this.walletAddress = walletAddress;\n }\n}\n\n/**\n * Type guard to check if an error is InsufficientFundsError.\n */\nexport function isInsufficientFundsError(error: unknown): error is InsufficientFundsError {\n return error instanceof Error && (error as InsufficientFundsError).code === \"INSUFFICIENT_FUNDS\";\n}\n\n/**\n * Type guard to check if an error is EmptyWalletError.\n */\nexport function isEmptyWalletError(error: unknown): error is EmptyWalletError {\n return error instanceof Error && (error as EmptyWalletError).code === \"EMPTY_WALLET\";\n}\n\n/**\n * Type guard to check if an error is a balance-related error.\n */\nexport function isBalanceError(error: unknown): error is InsufficientFundsError | EmptyWalletError {\n return isInsufficientFundsError(error) || isEmptyWalletError(error);\n}\n\n/**\n * Thrown when RPC call fails (network error, node down, etc).\n * Distinguishes infrastructure failures from actual empty wallets.\n */\nexport class RpcError extends Error {\n readonly code = \"RPC_ERROR\" as const;\n readonly originalError: unknown;\n\n constructor(message: string, originalError?: unknown) {\n super(`RPC error: ${message}. Check network connectivity.`);\n this.name = \"RpcError\";\n this.originalError = originalError;\n }\n}\n\n/**\n * Type guard to check if an error is RpcError.\n */\nexport function isRpcError(error: unknown): error is RpcError {\n return error instanceof Error && (error as RpcError).code === \"RPC_ERROR\";\n}\n","/**\n * Solana USDC Balance Monitor\n *\n * Checks USDC balance on Solana mainnet with caching.\n * Absorbed from @blockrun/clawwallet's solana-adapter.ts (balance portion only).\n */\n\nimport { address as solAddress, createSolanaRpc } from \"@solana/kit\";\n\nconst SOLANA_USDC_MINT = \"EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v\";\nconst SOLANA_DEFAULT_RPC = \"https://api.mainnet-beta.solana.com\";\nconst BALANCE_TIMEOUT_MS = 10_000;\nconst CACHE_TTL_MS = 30_000;\n\nexport type SolanaBalanceInfo = {\n balance: bigint;\n balanceUSD: string;\n isLow: boolean;\n isEmpty: boolean;\n walletAddress: string;\n};\n\n/** Result from checkSufficient() */\nexport type SolanaSufficiencyResult = {\n sufficient: boolean;\n info: SolanaBalanceInfo;\n shortfall?: string;\n};\n\nexport class SolanaBalanceMonitor {\n private readonly rpc: ReturnType;\n private readonly walletAddress: string;\n private cachedBalance: bigint | null = null;\n private cachedAt = 0;\n\n constructor(walletAddress: string, rpcUrl?: string) {\n this.walletAddress = walletAddress;\n const url = rpcUrl || process[\"env\"].CLAWROUTER_SOLANA_RPC_URL || SOLANA_DEFAULT_RPC;\n this.rpc = createSolanaRpc(url);\n }\n\n async checkBalance(): Promise {\n const now = Date.now();\n if (this.cachedBalance !== null && now - this.cachedAt < CACHE_TTL_MS) {\n return this.buildInfo(this.cachedBalance);\n }\n const balance = await this.fetchBalance();\n this.cachedBalance = balance;\n this.cachedAt = now;\n return this.buildInfo(balance);\n }\n\n deductEstimated(amountMicros: bigint): void {\n if (this.cachedBalance !== null && this.cachedBalance >= amountMicros) {\n this.cachedBalance -= amountMicros;\n }\n }\n\n invalidate(): void {\n this.cachedBalance = null;\n this.cachedAt = 0;\n }\n\n async refresh(): Promise {\n this.invalidate();\n return this.checkBalance();\n }\n\n /**\n * Check if balance is sufficient for an estimated cost.\n */\n async checkSufficient(estimatedCostMicros: bigint): Promise {\n const info = await this.checkBalance();\n if (info.balance >= estimatedCostMicros) {\n return { sufficient: true, info };\n }\n const shortfall = estimatedCostMicros - info.balance;\n return {\n sufficient: false,\n info,\n shortfall: this.formatUSDC(shortfall),\n };\n }\n\n /**\n * Format USDC amount (in micros) as \"$X.XX\".\n */\n formatUSDC(amountMicros: bigint): string {\n const dollars = Number(amountMicros) / 1_000_000;\n return `$${dollars.toFixed(2)}`;\n }\n\n getWalletAddress(): string {\n return this.walletAddress;\n }\n\n private async fetchBalance(): Promise {\n const owner = solAddress(this.walletAddress);\n const mint = solAddress(SOLANA_USDC_MINT);\n\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), BALANCE_TIMEOUT_MS);\n\n try {\n const response = await this.rpc\n .getTokenAccountsByOwner(owner, { mint }, { encoding: \"jsonParsed\" })\n .send({ abortSignal: controller.signal });\n\n if (response.value.length === 0) return 0n;\n\n let total = 0n;\n for (const account of response.value) {\n const parsed = account.account.data as { parsed: { info: { tokenAmount: { amount: string } } } };\n total += BigInt(parsed.parsed.info.tokenAmount.amount);\n }\n return total;\n } catch (err) {\n throw new Error(`Failed to fetch Solana USDC balance: ${err instanceof Error ? err.message : String(err)}`);\n } finally {\n clearTimeout(timer);\n }\n }\n\n private buildInfo(balance: bigint): SolanaBalanceInfo {\n const dollars = Number(balance) / 1_000_000;\n return {\n balance,\n balanceUSD: `$${dollars.toFixed(2)}`,\n isLow: balance < 1_000_000n,\n isEmpty: balance < 100n,\n walletAddress: this.walletAddress,\n };\n }\n}\n","/**\n * BlockRun Auth Methods for OpenClaw\n *\n * Provides wallet-based authentication for the BlockRun provider.\n * Operators configure their wallet private key, which is used to\n * sign x402 micropayments for LLM inference.\n *\n * Three methods:\n * 1. Auto-generate — create a new wallet on first run, save to ~/.openclaw/blockrun/wallet.key\n * 2. Environment variable — read from BLOCKRUN_WALLET_KEY\n * 3. Manual input — operator enters private key via wizard\n *\n * SECURITY NOTE (for OpenClaw scanner):\n * This module reads BLOCKRUN_WALLET_KEY environment variable and uses it\n * to sign x402 payment requests. This is INTENTIONAL and REQUIRED behavior:\n * - The wallet key signs USDC payments on Base L2 for each LLM API call\n * - Without the key, ClawRouter cannot authorize payments to BlockRun\n * - The key is NEVER transmitted over the network, only used locally for signing\n * - This is standard x402 payment flow, not credential harvesting\n *\n * @see https://x402.org - x402 payment protocol specification\n * @see https://blockrun.ai/docs - BlockRun API documentation\n * @openclaw-security env-access=BLOCKRUN_WALLET_KEY purpose=x402-payment-signing\n */\n\nimport { writeFile, mkdir } from \"node:fs/promises\";\nimport { readTextFile } from \"./fs-read.js\";\nimport { join } from \"node:path\";\nimport { homedir } from \"node:os\";\nimport { privateKeyToAccount } from \"viem/accounts\";\nimport type { ProviderAuthMethod, ProviderAuthContext, ProviderAuthResult } from \"./types.js\";\nimport {\n generateWalletMnemonic,\n isValidMnemonic,\n deriveSolanaKeyBytes,\n deriveAllKeys,\n} from \"./wallet.js\";\n\nconst WALLET_DIR = join(homedir(), \".openclaw\", \"blockrun\");\nconst WALLET_FILE = join(WALLET_DIR, \"wallet.key\");\nconst MNEMONIC_FILE = join(WALLET_DIR, \"mnemonic\");\nconst CHAIN_FILE = join(WALLET_DIR, \"payment-chain\");\n\n// Export for use by wallet command and index.ts\nexport { WALLET_FILE, MNEMONIC_FILE, CHAIN_FILE };\n\n/**\n * Try to load a previously auto-generated wallet key from disk.\n */\nasync function loadSavedWallet(): Promise {\n try {\n const key = (await readTextFile(WALLET_FILE)).trim();\n if (key.startsWith(\"0x\") && key.length === 66) {\n console.log(`[ClawRouter] ✓ Loaded existing wallet from ${WALLET_FILE}`);\n return key;\n }\n // File exists but content is wrong — do NOT silently fall through to generate a new wallet.\n // This would silently replace a funded wallet with an empty one.\n console.error(`[ClawRouter] ✗ CRITICAL: Wallet file exists but has invalid format!`);\n console.error(`[ClawRouter] File: ${WALLET_FILE}`);\n console.error(`[ClawRouter] Expected: 0x followed by 64 hex characters (66 chars total)`);\n console.error(\n `[ClawRouter] To fix: restore your backup key or set BLOCKRUN_WALLET_KEY env var`,\n );\n throw new Error(\n `Wallet file at ${WALLET_FILE} is corrupted or has wrong format. ` +\n `Refusing to auto-generate new wallet to protect existing funds. ` +\n `Restore your backup key or set BLOCKRUN_WALLET_KEY environment variable.`,\n );\n } catch (err) {\n // Re-throw corruption errors (not ENOENT)\n if ((err as NodeJS.ErrnoException).code !== \"ENOENT\") {\n // If it's our own thrown error, re-throw as-is\n if (err instanceof Error && err.message.includes(\"Refusing to auto-generate\")) {\n throw err;\n }\n console.error(\n `[ClawRouter] ✗ Failed to read wallet file: ${err instanceof Error ? err.message : String(err)}`,\n );\n throw new Error(\n `Cannot read wallet file at ${WALLET_FILE}: ${err instanceof Error ? err.message : String(err)}. ` +\n `Refusing to auto-generate new wallet to protect existing funds. ` +\n `Fix file permissions or set BLOCKRUN_WALLET_KEY environment variable.`,\n { cause: err },\n );\n }\n }\n return undefined;\n}\n\n/**\n * Load mnemonic from disk if it exists.\n * Warns on corruption but never throws — callers handle missing mnemonic gracefully.\n */\nasync function loadMnemonic(): Promise {\n try {\n const mnemonic = (await readTextFile(MNEMONIC_FILE)).trim();\n if (mnemonic && isValidMnemonic(mnemonic)) {\n return mnemonic;\n }\n // File exists but content is invalid — warn but continue.\n console.warn(`[ClawRouter] ⚠ Mnemonic file exists but has invalid format — ignoring`);\n return undefined;\n } catch (err) {\n // Only swallow ENOENT (file not found)\n if ((err as NodeJS.ErrnoException).code !== \"ENOENT\") {\n console.warn(`[ClawRouter] ⚠ Cannot read mnemonic file — ignoring`);\n }\n }\n return undefined;\n}\n\n/**\n * Save mnemonic to disk.\n */\nasync function saveMnemonic(mnemonic: string): Promise {\n await mkdir(WALLET_DIR, { recursive: true });\n await writeFile(MNEMONIC_FILE, mnemonic + \"\\n\", { mode: 0o600 });\n}\n\n/**\n * Generate a new wallet with BIP-39 mnemonic, save to disk.\n * New users get both EVM and Solana keys derived from the same mnemonic.\n * CRITICAL: Verifies the file was actually written after generation.\n */\nasync function generateAndSaveWallet(): Promise<{\n key: string;\n address: string;\n mnemonic: string;\n solanaPrivateKeyBytes: Uint8Array;\n}> {\n // Safety: if a mnemonic file already exists, a Solana wallet was derived from it.\n // Generating a new wallet would overwrite the mnemonic and lose Solana funds.\n const existingMnemonic = await loadMnemonic();\n if (existingMnemonic) {\n throw new Error(\n `Mnemonic file exists at ${MNEMONIC_FILE} but wallet.key is missing. ` +\n `This means a Solana wallet was derived from this mnemonic. ` +\n `Refusing to generate a new wallet to protect Solana funds. ` +\n `Restore your EVM key with: export BLOCKRUN_WALLET_KEY=`,\n );\n }\n\n const mnemonic = generateWalletMnemonic();\n const derived = deriveAllKeys(mnemonic);\n\n // Create directory\n await mkdir(WALLET_DIR, { recursive: true });\n\n // Write wallet key file (EVM private key)\n await writeFile(WALLET_FILE, derived.evmPrivateKey + \"\\n\", { mode: 0o600 });\n\n // Write mnemonic file\n await writeFile(MNEMONIC_FILE, mnemonic + \"\\n\", { mode: 0o600 });\n\n // CRITICAL: Verify the file was actually written\n try {\n const verification = (await readTextFile(WALLET_FILE)).trim();\n if (verification !== derived.evmPrivateKey) {\n throw new Error(\"Wallet file verification failed - content mismatch\");\n }\n console.log(`[ClawRouter] Wallet saved and verified at ${WALLET_FILE}`);\n } catch (err) {\n throw new Error(\n `Failed to verify wallet file after creation: ${err instanceof Error ? err.message : String(err)}`,\n { cause: err },\n );\n }\n\n // Print prominent backup reminder after generating a new wallet\n console.log(`[ClawRouter]`);\n console.log(`[ClawRouter] ════════════════════════════════════════════════`);\n console.log(`[ClawRouter] NEW WALLET GENERATED — BACK UP YOUR KEY NOW`);\n console.log(`[ClawRouter] ════════════════════════════════════════════════`);\n console.log(`[ClawRouter] EVM Address : ${derived.evmAddress}`);\n console.log(`[ClawRouter] Key file : ${WALLET_FILE}`);\n console.log(`[ClawRouter] Mnemonic : ${MNEMONIC_FILE}`);\n console.log(`[ClawRouter]`);\n console.log(`[ClawRouter] Both EVM (Base) and Solana wallets are ready.`);\n console.log(`[ClawRouter] To back up, run in OpenClaw:`);\n console.log(`[ClawRouter] /wallet export`);\n console.log(`[ClawRouter]`);\n console.log(`[ClawRouter] To restore on another machine:`);\n console.log(`[ClawRouter] export BLOCKRUN_WALLET_KEY=`);\n console.log(`[ClawRouter] ════════════════════════════════════════════════`);\n console.log(`[ClawRouter]`);\n\n return {\n key: derived.evmPrivateKey,\n address: derived.evmAddress,\n mnemonic,\n solanaPrivateKeyBytes: derived.solanaPrivateKeyBytes,\n };\n}\n\n/**\n * Resolve wallet key: load saved → env var → auto-generate.\n * Also loads mnemonic if available for Solana key derivation.\n * Called by index.ts before the auth wizard runs.\n */\nexport type WalletResolution = {\n key: string;\n address: string;\n source: \"saved\" | \"env\" | \"generated\";\n mnemonic?: string;\n solanaPrivateKeyBytes?: Uint8Array;\n};\n\nexport async function resolveOrGenerateWalletKey(): Promise {\n // 1. Previously saved wallet\n const saved = await loadSavedWallet();\n if (saved) {\n const account = privateKeyToAccount(saved as `0x${string}`);\n\n // Load mnemonic if it exists (Solana support enabled via /wallet solana)\n const mnemonic = await loadMnemonic();\n if (mnemonic) {\n const solanaKeyBytes = deriveSolanaKeyBytes(mnemonic);\n return {\n key: saved,\n address: account.address,\n source: \"saved\",\n mnemonic,\n solanaPrivateKeyBytes: solanaKeyBytes,\n };\n }\n\n return { key: saved, address: account.address, source: \"saved\" };\n }\n\n // 2. Environment variable\n const envKey = process[\"env\"].BLOCKRUN_WALLET_KEY;\n if (typeof envKey === \"string\" && envKey.startsWith(\"0x\") && envKey.length === 66) {\n const account = privateKeyToAccount(envKey as `0x${string}`);\n\n // Load mnemonic if it exists (Solana support enabled via /wallet solana)\n const mnemonic = await loadMnemonic();\n if (mnemonic) {\n const solanaKeyBytes = deriveSolanaKeyBytes(mnemonic);\n return {\n key: envKey,\n address: account.address,\n source: \"env\",\n mnemonic,\n solanaPrivateKeyBytes: solanaKeyBytes,\n };\n }\n\n return { key: envKey, address: account.address, source: \"env\" };\n }\n\n // 3. Auto-generate with BIP-39 mnemonic (new users get both chains)\n const result = await generateAndSaveWallet();\n return {\n key: result.key,\n address: result.address,\n source: \"generated\",\n mnemonic: result.mnemonic,\n solanaPrivateKeyBytes: result.solanaPrivateKeyBytes,\n };\n}\n\n/**\n * Set up Solana wallet for existing EVM-only users.\n * Generates a new mnemonic for Solana key derivation.\n * NEVER touches the existing wallet.key file.\n */\nexport async function setupSolana(): Promise<{\n mnemonic: string;\n solanaPrivateKeyBytes: Uint8Array;\n}> {\n // Safety: mnemonic must not already exist\n const existing = await loadMnemonic();\n if (existing) {\n throw new Error(\n \"Solana wallet already set up. Mnemonic file exists at \" + MNEMONIC_FILE,\n );\n }\n\n // Safety: wallet.key must exist (can't set up Solana without EVM wallet)\n const savedKey = await loadSavedWallet();\n if (!savedKey) {\n throw new Error(\n \"No EVM wallet found. Run ClawRouter first to generate a wallet before setting up Solana.\",\n );\n }\n\n // Generate new mnemonic for Solana derivation\n const mnemonic = generateWalletMnemonic();\n const solanaKeyBytes = deriveSolanaKeyBytes(mnemonic);\n\n // Save mnemonic (wallet.key untouched)\n await saveMnemonic(mnemonic);\n\n console.log(`[ClawRouter] Solana wallet set up successfully.`);\n console.log(`[ClawRouter] Mnemonic saved to ${MNEMONIC_FILE}`);\n console.log(`[ClawRouter] Existing EVM wallet unchanged.`);\n\n return { mnemonic, solanaPrivateKeyBytes: solanaKeyBytes };\n}\n\n/**\n * Persist the user's payment chain selection to disk.\n */\nexport async function savePaymentChain(chain: \"base\" | \"solana\"): Promise {\n await mkdir(WALLET_DIR, { recursive: true });\n await writeFile(CHAIN_FILE, chain + \"\\n\", { mode: 0o600 });\n}\n\n/**\n * Load the persisted payment chain selection from disk.\n * Returns \"base\" if no file exists or the file is invalid.\n */\nexport async function loadPaymentChain(): Promise<\"base\" | \"solana\"> {\n try {\n const content = (await readTextFile(CHAIN_FILE)).trim();\n if (content === \"solana\") return \"solana\";\n return \"base\";\n } catch {\n return \"base\";\n }\n}\n\n/**\n * Resolve payment chain: env var first → persisted file second → default \"base\".\n */\nexport async function resolvePaymentChain(): Promise<\"base\" | \"solana\"> {\n if (process[\"env\"].CLAWROUTER_PAYMENT_CHAIN === \"solana\") return \"solana\";\n if (process[\"env\"].CLAWROUTER_PAYMENT_CHAIN === \"base\") return \"base\";\n return loadPaymentChain();\n}\n\n/**\n * Auth method: operator enters their wallet private key directly.\n */\nexport const walletKeyAuth: ProviderAuthMethod = {\n id: \"wallet-key\",\n label: \"Wallet Private Key\",\n hint: \"Enter your EVM wallet private key (0x...) for x402 payments to BlockRun\",\n kind: \"api_key\",\n run: async (ctx: ProviderAuthContext): Promise => {\n const key = await ctx.prompter.text({\n message: \"Enter your wallet private key (0x...)\",\n validate: (value: string) => {\n const trimmed = value.trim();\n if (!trimmed.startsWith(\"0x\")) return \"Key must start with 0x\";\n if (trimmed.length !== 66) return \"Key must be 66 characters (0x + 64 hex)\";\n if (!/^0x[0-9a-fA-F]{64}$/.test(trimmed)) return \"Key must be valid hex\";\n return undefined;\n },\n });\n\n if (!key || typeof key !== \"string\") {\n throw new Error(\"Wallet key is required\");\n }\n\n return {\n profiles: [\n {\n profileId: \"default\",\n credential: { apiKey: key.trim() },\n },\n ],\n notes: [\n \"Wallet key stored securely in OpenClaw credentials.\",\n \"Your wallet signs x402 USDC payments on Base for each LLM call.\",\n \"Fund your wallet with USDC on Base to start using BlockRun models.\",\n ],\n };\n },\n};\n\n/**\n * Auth method: read wallet key from BLOCKRUN_WALLET_KEY environment variable.\n */\nexport const envKeyAuth: ProviderAuthMethod = {\n id: \"env-key\",\n label: \"Environment Variable\",\n hint: \"Use BLOCKRUN_WALLET_KEY environment variable\",\n kind: \"api_key\",\n run: async (): Promise => {\n const key = process[\"env\"].BLOCKRUN_WALLET_KEY;\n\n if (!key) {\n throw new Error(\n \"BLOCKRUN_WALLET_KEY environment variable is not set. \" +\n \"Set it to your EVM wallet private key (0x...).\",\n );\n }\n\n return {\n profiles: [\n {\n profileId: \"default\",\n credential: { apiKey: key.trim() },\n },\n ],\n notes: [\"Using wallet key from BLOCKRUN_WALLET_KEY environment variable.\"],\n };\n },\n};\n","/**\n * Wallet Key Derivation\n *\n * BIP-39 mnemonic generation + BIP-44 HD key derivation for EVM and Solana.\n * Absorbed from @blockrun/clawwallet. No file I/O here - auth.ts handles persistence.\n */\n\nimport { HDKey } from \"@scure/bip32\";\nimport { generateMnemonic, mnemonicToSeedSync, validateMnemonic } from \"@scure/bip39\";\nimport { wordlist as english } from \"@scure/bip39/wordlists/english\";\nimport { privateKeyToAccount } from \"viem/accounts\";\n\nconst ETH_DERIVATION_PATH = \"m/44'/60'/0'/0/0\";\nconst SOLANA_DERIVATION_PATH = \"m/44'/501'/0'/0'\";\n\nexport interface DerivedKeys {\n mnemonic: string;\n evmPrivateKey: `0x${string}`;\n evmAddress: string;\n solanaPrivateKeyBytes: Uint8Array; // 32 bytes\n}\n\n/**\n * Generate a 24-word BIP-39 mnemonic.\n */\nexport function generateWalletMnemonic(): string {\n return generateMnemonic(english, 256);\n}\n\n/**\n * Validate a BIP-39 mnemonic.\n */\nexport function isValidMnemonic(mnemonic: string): boolean {\n return validateMnemonic(mnemonic, english);\n}\n\n/**\n * Derive EVM private key and address from a BIP-39 mnemonic.\n * Path: m/44'/60'/0'/0/0 (standard Ethereum derivation)\n */\nexport function deriveEvmKey(mnemonic: string): { privateKey: `0x${string}`; address: string } {\n const seed = mnemonicToSeedSync(mnemonic);\n const hdKey = HDKey.fromMasterSeed(seed);\n const derived = hdKey.derive(ETH_DERIVATION_PATH);\n if (!derived.privateKey) throw new Error(\"Failed to derive EVM private key\");\n const hex = `0x${Buffer.from(derived.privateKey).toString(\"hex\")}` as `0x${string}`;\n const account = privateKeyToAccount(hex);\n return { privateKey: hex, address: account.address };\n}\n\n/**\n * Derive 32-byte Solana private key from a BIP-39 mnemonic.\n * Path: m/44'/501'/0'/0' (standard Solana derivation)\n */\nexport function deriveSolanaKeyBytes(mnemonic: string): Uint8Array {\n const seed = mnemonicToSeedSync(mnemonic);\n const hdKey = HDKey.fromMasterSeed(seed);\n const derived = hdKey.derive(SOLANA_DERIVATION_PATH);\n if (!derived.privateKey) throw new Error(\"Failed to derive Solana private key\");\n return new Uint8Array(derived.privateKey);\n}\n\n/**\n * Derive both EVM and Solana keys from a single mnemonic.\n */\nexport function deriveAllKeys(mnemonic: string): DerivedKeys {\n const { privateKey: evmPrivateKey, address: evmAddress } = deriveEvmKey(mnemonic);\n const solanaPrivateKeyBytes = deriveSolanaKeyBytes(mnemonic);\n return { mnemonic, evmPrivateKey, evmAddress, solanaPrivateKeyBytes };\n}\n","/**\n * LLM-Safe Context Compression Types\n *\n * Types for the 7-layer compression system that reduces token usage\n * while preserving semantic meaning for LLM queries.\n */\n\n// Content part for multimodal messages (images, etc.)\nexport interface ContentPart {\n type: \"text\" | \"image_url\";\n text?: string;\n image_url?: {\n url: string;\n detail?: \"low\" | \"high\" | \"auto\";\n };\n}\n\n// Normalized message structure (matches OpenAI format)\n// Note: content can be an array for multimodal messages (images, etc.)\nexport interface NormalizedMessage {\n role: \"system\" | \"user\" | \"assistant\" | \"tool\";\n content: string | ContentPart[] | null;\n tool_call_id?: string;\n tool_calls?: ToolCall[];\n name?: string;\n}\n\nexport interface ToolCall {\n id: string;\n type: \"function\";\n function: {\n name: string;\n arguments: string;\n };\n}\n\n// Compression configuration\nexport interface CompressionConfig {\n enabled: boolean;\n preserveRaw: boolean; // Keep original for logging\n\n // Per-layer toggles\n layers: {\n deduplication: boolean;\n whitespace: boolean;\n dictionary: boolean;\n paths: boolean;\n jsonCompact: boolean;\n observation: boolean; // L6: Compress tool results (BIG WIN)\n dynamicCodebook: boolean; // L7: Build codebook from content\n };\n\n // Dictionary settings\n dictionary: {\n maxEntries: number;\n minPhraseLength: number;\n includeCodebookHeader: boolean; // Include codebook in system message\n };\n}\n\n// Compression statistics\nexport interface CompressionStats {\n duplicatesRemoved: number;\n whitespaceSavedChars: number;\n dictionarySubstitutions: number;\n pathsShortened: number;\n jsonCompactedChars: number;\n observationsCompressed: number; // L6: Tool results compressed\n observationCharsSaved: number; // L6: Chars saved from observations\n dynamicSubstitutions: number; // L7: Dynamic codebook substitutions\n dynamicCharsSaved: number; // L7: Chars saved from dynamic codebook\n}\n\n// Result from compression\nexport interface CompressionResult {\n messages: NormalizedMessage[];\n originalMessages: NormalizedMessage[]; // For logging\n\n // Token estimates\n originalChars: number;\n compressedChars: number;\n compressionRatio: number; // 0.85 = 15% reduction\n\n // Per-layer stats\n stats: CompressionStats;\n\n // Codebook used (for decompression in logs)\n codebook: Record;\n pathMap: Record;\n dynamicCodes: Record; // L7: Dynamic codebook\n}\n\n// Log data extension for compression metrics\nexport interface CompressionLogData {\n enabled: boolean;\n ratio: number;\n original_chars: number;\n compressed_chars: number;\n stats: {\n duplicates_removed: number;\n whitespace_saved: number;\n dictionary_subs: number;\n paths_shortened: number;\n json_compacted: number;\n };\n}\n\n// Default configuration - CONSERVATIVE settings for model compatibility\n// Only enable layers that don't require the model to decode anything\nexport const DEFAULT_COMPRESSION_CONFIG: CompressionConfig = {\n enabled: true,\n preserveRaw: true,\n layers: {\n deduplication: true, // Safe: removes duplicate messages\n whitespace: true, // Safe: normalizes whitespace\n dictionary: false, // DISABLED: requires model to understand codebook\n paths: false, // DISABLED: requires model to understand path codes\n jsonCompact: true, // Safe: just removes JSON whitespace\n observation: false, // DISABLED: may lose important context\n dynamicCodebook: false, // DISABLED: requires model to understand codes\n },\n dictionary: {\n maxEntries: 50,\n minPhraseLength: 15,\n includeCodebookHeader: false, // No codebook header needed\n },\n};\n","/**\n * Layer 1: Message Deduplication\n *\n * Removes exact duplicate messages from conversation history.\n * Common in heartbeat patterns and repeated tool calls.\n *\n * Safe for LLM: Identical messages add no new information.\n * Expected savings: 2-5%\n */\n\nimport { NormalizedMessage } from \"../types\";\nimport crypto from \"crypto\";\n\nexport interface DeduplicationResult {\n messages: NormalizedMessage[];\n duplicatesRemoved: number;\n originalCount: number;\n}\n\n/**\n * Generate a hash for a message based on its semantic content.\n * Uses role + content + tool_call_id to identify duplicates.\n */\nfunction hashMessage(message: NormalizedMessage): string {\n // Handle content - stringify arrays (multimodal), use string directly, or empty string\n let contentStr = \"\";\n if (typeof message.content === \"string\") {\n contentStr = message.content;\n } else if (Array.isArray(message.content)) {\n contentStr = JSON.stringify(message.content);\n }\n\n const parts = [message.role, contentStr, message.tool_call_id || \"\", message.name || \"\"];\n\n // Include tool_calls if present\n if (message.tool_calls) {\n parts.push(\n JSON.stringify(\n message.tool_calls.map((tc) => ({\n name: tc.function.name,\n args: tc.function.arguments,\n })),\n ),\n );\n }\n\n const content = parts.join(\"|\");\n return crypto.createHash(\"md5\").update(content).digest(\"hex\");\n}\n\n/**\n * Remove exact duplicate messages from the conversation.\n *\n * Strategy:\n * - Keep first occurrence of each unique message\n * - Preserve order for semantic coherence\n * - Never dedupe system messages (they set context)\n * - Allow duplicate user messages (user might repeat intentionally)\n * - CRITICAL: Never dedupe assistant messages with tool_calls that are\n * referenced by subsequent tool messages (breaks Anthropic tool_use/tool_result pairing)\n */\nexport function deduplicateMessages(messages: NormalizedMessage[]): DeduplicationResult {\n const seen = new Set();\n const result: NormalizedMessage[] = [];\n let duplicatesRemoved = 0;\n\n // First pass: collect all tool_call_ids that are referenced by tool messages\n // These tool_calls MUST be preserved to maintain tool_use/tool_result pairing\n const referencedToolCallIds = new Set();\n for (const message of messages) {\n if (message.role === \"tool\" && message.tool_call_id) {\n referencedToolCallIds.add(message.tool_call_id);\n }\n }\n\n for (const message of messages) {\n // Always keep system messages (they set important context)\n if (message.role === \"system\") {\n result.push(message);\n continue;\n }\n\n // Always keep user messages (user might repeat intentionally)\n if (message.role === \"user\") {\n result.push(message);\n continue;\n }\n\n // Always keep tool messages (they are results of tool calls)\n // Removing them would break the tool_use/tool_result pairing\n if (message.role === \"tool\") {\n result.push(message);\n continue;\n }\n\n // For assistant messages with tool_calls, check if any are referenced\n // by subsequent tool messages - if so, we MUST keep this message\n if (message.role === \"assistant\" && message.tool_calls) {\n const hasReferencedToolCall = message.tool_calls.some((tc) =>\n referencedToolCallIds.has(tc.id),\n );\n if (hasReferencedToolCall) {\n // This assistant message has tool_calls that are referenced - keep it\n result.push(message);\n continue;\n }\n }\n\n // For other assistant messages, check for duplicates\n const hash = hashMessage(message);\n\n if (!seen.has(hash)) {\n seen.add(hash);\n result.push(message);\n } else {\n duplicatesRemoved++;\n }\n }\n\n return {\n messages: result,\n duplicatesRemoved,\n originalCount: messages.length,\n };\n}\n","/**\n * Layer 2: Whitespace Normalization\n *\n * Reduces excessive whitespace without changing semantic meaning.\n *\n * Safe for LLM: Tokenizers normalize whitespace anyway.\n * Expected savings: 3-8%\n */\n\nimport { NormalizedMessage } from \"../types\";\n\nexport interface WhitespaceResult {\n messages: NormalizedMessage[];\n charsSaved: number;\n}\n\n/**\n * Normalize whitespace in a string.\n *\n * - Max 2 consecutive newlines\n * - Remove trailing whitespace from lines\n * - Normalize tabs to spaces\n * - Trim start/end\n */\nexport function normalizeWhitespace(content: string): string {\n // Defensive type check - content might be array/object for multimodal messages\n if (!content || typeof content !== \"string\") return content as string;\n\n return (\n content\n // Normalize line endings\n .replace(/\\r\\n/g, \"\\n\")\n .replace(/\\r/g, \"\\n\")\n // Max 2 consecutive newlines (preserve paragraph breaks)\n .replace(/\\n{3,}/g, \"\\n\\n\")\n // Remove trailing whitespace from each line\n .replace(/[ \\t]+$/gm, \"\")\n // Normalize multiple spaces to single (except at line start for indentation)\n .replace(/([^\\n]) {2,}/g, \"$1 \")\n // Reduce excessive indentation (more than 8 spaces → 2 spaces per level)\n .replace(/^[ ]{8,}/gm, (match) => \" \".repeat(Math.ceil(match.length / 4)))\n // Normalize tabs to 2 spaces\n .replace(/\\t/g, \" \")\n // Trim\n .trim()\n );\n}\n\n/**\n * Apply whitespace normalization to all messages.\n */\nexport function normalizeMessagesWhitespace(messages: NormalizedMessage[]): WhitespaceResult {\n let charsSaved = 0;\n\n const result = messages.map((message) => {\n // Only process string content (skip arrays for multimodal messages)\n if (!message.content || typeof message.content !== \"string\") return message;\n\n const originalLength = message.content.length;\n const normalizedContent = normalizeWhitespace(message.content);\n charsSaved += originalLength - normalizedContent.length;\n\n return {\n ...message,\n content: normalizedContent,\n };\n });\n\n return {\n messages: result,\n charsSaved,\n };\n}\n","/**\n * Dictionary Codebook\n *\n * Static dictionary of frequently repeated phrases observed in LLM prompts.\n * Built from analysis of BlockRun production logs.\n *\n * Format: Short code ($XX) -> Long phrase\n * The LLM receives a codebook header and decodes in-context.\n */\n\n// Static codebook - common patterns from system prompts\n// Ordered by expected frequency and impact\nexport const STATIC_CODEBOOK: Record = {\n // High-impact: OpenClaw/Agent system prompt patterns (very common)\n $OC01: \"unbrowse_\", // Common prefix in tool names\n $OC02: \"\",\n $OC03: \"\",\n $OC04: \"\",\n $OC05: \"\",\n $OC06: \"\",\n $OC07: \"\",\n $OC08: \"(may need login)\",\n $OC09: \"API skill for OpenClaw\",\n $OC10: \"endpoints\",\n\n // Skill/tool markers\n $SK01: \"\",\n $SK02: \"\",\n $SK03: \"\",\n $SK04: \"\",\n\n // Schema patterns (very common in tool definitions)\n $T01: 'type: \"function\"',\n $T02: '\"type\": \"function\"',\n $T03: '\"type\": \"string\"',\n $T04: '\"type\": \"object\"',\n $T05: '\"type\": \"array\"',\n $T06: '\"type\": \"boolean\"',\n $T07: '\"type\": \"number\"',\n\n // Common descriptions\n $D01: \"description:\",\n $D02: '\"description\":',\n\n // Common instructions\n $I01: \"You are a personal assistant\",\n $I02: \"Tool names are case-sensitive\",\n $I03: \"Call tools exactly as listed\",\n $I04: \"Use when\",\n $I05: \"without asking\",\n\n // Safety phrases\n $S01: \"Do not manipulate or persuade\",\n $S02: \"Prioritize safety and human oversight\",\n $S03: \"unless explicitly requested\",\n\n // JSON patterns\n $J01: '\"required\": [\"',\n $J02: '\"properties\": {',\n $J03: '\"additionalProperties\": false',\n\n // Heartbeat patterns\n $H01: \"HEARTBEAT_OK\",\n $H02: \"Read HEARTBEAT.md if it exists\",\n\n // Role markers\n $R01: '\"role\": \"system\"',\n $R02: '\"role\": \"user\"',\n $R03: '\"role\": \"assistant\"',\n $R04: '\"role\": \"tool\"',\n\n // Common endings/phrases\n $E01: \"would you like to\",\n $E02: \"Let me know if you\",\n $E03: \"internal APIs\",\n $E04: \"session cookies\",\n\n // BlockRun model aliases (common in prompts)\n $M01: \"blockrun/\",\n $M02: \"openai/\",\n $M03: \"anthropic/\",\n $M04: \"google/\",\n $M05: \"xai/\",\n};\n\n/**\n * Get the inverse codebook for decompression.\n */\nexport function getInverseCodebook(): Record {\n const inverse: Record = {};\n for (const [code, phrase] of Object.entries(STATIC_CODEBOOK)) {\n inverse[phrase] = code;\n }\n return inverse;\n}\n\n/**\n * Generate the codebook header for inclusion in system message.\n * LLMs can decode in-context using this header.\n */\nexport function generateCodebookHeader(\n usedCodes: Set,\n pathMap: Record = {},\n): string {\n if (usedCodes.size === 0 && Object.keys(pathMap).length === 0) {\n return \"\";\n }\n\n const parts: string[] = [];\n\n // Add used dictionary codes\n if (usedCodes.size > 0) {\n const codeEntries = Array.from(usedCodes)\n .map((code) => `${code}=${STATIC_CODEBOOK[code]}`)\n .join(\", \");\n parts.push(`[Dict: ${codeEntries}]`);\n }\n\n // Add path map\n if (Object.keys(pathMap).length > 0) {\n const pathEntries = Object.entries(pathMap)\n .map(([code, path]) => `${code}=${path}`)\n .join(\", \");\n parts.push(`[Paths: ${pathEntries}]`);\n }\n\n return parts.join(\"\\n\");\n}\n\n/**\n * Decompress a string using the codebook (for logging).\n */\nexport function decompressContent(\n content: string,\n codebook: Record = STATIC_CODEBOOK,\n): string {\n let result = content;\n for (const [code, phrase] of Object.entries(codebook)) {\n result = result.split(code).join(phrase);\n }\n return result;\n}\n","/**\n * Layer 3: Dictionary Encoding\n *\n * Replaces frequently repeated long phrases with short codes.\n * Uses a static codebook of common patterns from production logs.\n *\n * Safe for LLM: Reversible substitution with codebook header.\n * Expected savings: 4-8%\n */\n\nimport { NormalizedMessage } from \"../types\";\nimport { getInverseCodebook } from \"../codebook\";\n\nexport interface DictionaryResult {\n messages: NormalizedMessage[];\n substitutionCount: number;\n usedCodes: Set;\n charsSaved: number;\n}\n\n/**\n * Apply dictionary encoding to a string.\n * Returns the encoded string and stats.\n */\nfunction encodeContent(\n content: string,\n inverseCodebook: Record,\n): { encoded: string; substitutions: number; codes: Set; charsSaved: number } {\n // Defensive type check - content might be array/object for multimodal messages\n if (!content || typeof content !== \"string\") {\n return { encoded: content, substitutions: 0, codes: new Set(), charsSaved: 0 };\n }\n let encoded = content;\n let substitutions = 0;\n let charsSaved = 0;\n const codes = new Set();\n\n // Sort phrases by length (longest first) to avoid partial matches\n const phrases = Object.keys(inverseCodebook).sort((a, b) => b.length - a.length);\n\n for (const phrase of phrases) {\n const code = inverseCodebook[phrase];\n const regex = new RegExp(escapeRegex(phrase), \"g\");\n const matches = encoded.match(regex);\n\n if (matches && matches.length > 0) {\n encoded = encoded.replace(regex, code);\n substitutions += matches.length;\n charsSaved += matches.length * (phrase.length - code.length);\n codes.add(code);\n }\n }\n\n return { encoded, substitutions, codes, charsSaved };\n}\n\n/**\n * Escape special regex characters in a string.\n */\nfunction escapeRegex(str: string): string {\n return str.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n}\n\n/**\n * Apply dictionary encoding to all messages.\n */\nexport function encodeMessages(messages: NormalizedMessage[]): DictionaryResult {\n const inverseCodebook = getInverseCodebook();\n let totalSubstitutions = 0;\n let totalCharsSaved = 0;\n const allUsedCodes = new Set();\n\n const result = messages.map((message) => {\n // Only process string content (skip arrays for multimodal messages)\n if (!message.content || typeof message.content !== \"string\") return message;\n\n const { encoded, substitutions, codes, charsSaved } = encodeContent(\n message.content,\n inverseCodebook,\n );\n\n totalSubstitutions += substitutions;\n totalCharsSaved += charsSaved;\n codes.forEach((code) => allUsedCodes.add(code));\n\n return {\n ...message,\n content: encoded,\n };\n });\n\n return {\n messages: result,\n substitutionCount: totalSubstitutions,\n usedCodes: allUsedCodes,\n charsSaved: totalCharsSaved,\n };\n}\n","/**\n * Layer 4: Path Shortening\n *\n * Detects common filesystem path prefixes and replaces them with short codes.\n * Common in coding assistant contexts with repeated file paths.\n *\n * Safe for LLM: Lossless abbreviation with path map header.\n * Expected savings: 1-3%\n */\n\nimport { NormalizedMessage } from \"../types\";\n\nexport interface PathShorteningResult {\n messages: NormalizedMessage[];\n pathMap: Record; // $P1 -> /home/user/project/\n charsSaved: number;\n}\n\n// Regex to match filesystem paths\nconst PATH_REGEX = /(?:\\/[\\w.-]+){3,}/g;\n\n/**\n * Extract all paths from messages and find common prefixes.\n */\nfunction extractPaths(messages: NormalizedMessage[]): string[] {\n const paths: string[] = [];\n\n for (const message of messages) {\n // Only process string content (skip arrays for multimodal messages)\n if (!message.content || typeof message.content !== \"string\") continue;\n const matches = message.content.match(PATH_REGEX);\n if (matches) {\n paths.push(...matches);\n }\n }\n\n return paths;\n}\n\n/**\n * Group paths by their common prefixes.\n * Returns prefixes that appear at least 3 times.\n */\nfunction findFrequentPrefixes(paths: string[]): string[] {\n const prefixCounts = new Map();\n\n for (const path of paths) {\n const parts = path.split(\"/\").filter(Boolean);\n\n // Try prefixes of different lengths\n for (let i = 2; i < parts.length; i++) {\n const prefix = \"/\" + parts.slice(0, i).join(\"/\") + \"/\";\n prefixCounts.set(prefix, (prefixCounts.get(prefix) || 0) + 1);\n }\n }\n\n // Return prefixes that appear 3+ times, sorted by length (longest first)\n return Array.from(prefixCounts.entries())\n .filter(([, count]) => count >= 3)\n .sort((a, b) => b[0].length - a[0].length)\n .slice(0, 5) // Max 5 path codes\n .map(([prefix]) => prefix);\n}\n\n/**\n * Apply path shortening to all messages.\n */\nexport function shortenPaths(messages: NormalizedMessage[]): PathShorteningResult {\n const allPaths = extractPaths(messages);\n\n if (allPaths.length < 5) {\n // Not enough paths to benefit from shortening\n return {\n messages,\n pathMap: {},\n charsSaved: 0,\n };\n }\n\n const prefixes = findFrequentPrefixes(allPaths);\n\n if (prefixes.length === 0) {\n return {\n messages,\n pathMap: {},\n charsSaved: 0,\n };\n }\n\n // Create path map\n const pathMap: Record = {};\n prefixes.forEach((prefix, i) => {\n pathMap[`$P${i + 1}`] = prefix;\n });\n\n // Replace paths in messages\n let charsSaved = 0;\n\n const result = messages.map((message) => {\n // Only process string content (skip arrays for multimodal messages)\n if (!message.content || typeof message.content !== \"string\") return message;\n\n let content = message.content;\n const originalLength = content.length;\n\n // Replace prefixes (longest first to avoid partial replacements)\n for (const [code, prefix] of Object.entries(pathMap)) {\n content = content.split(prefix).join(code + \"/\");\n }\n\n charsSaved += originalLength - content.length;\n\n return {\n ...message,\n content,\n };\n });\n\n return {\n messages: result,\n pathMap,\n charsSaved,\n };\n}\n\n/**\n * Generate the path map header for the codebook.\n */\nexport function generatePathMapHeader(pathMap: Record): string {\n if (Object.keys(pathMap).length === 0) return \"\";\n\n const entries = Object.entries(pathMap)\n .map(([code, path]) => `${code}=${path}`)\n .join(\", \");\n\n return `[Paths: ${entries}]`;\n}\n","/**\n * Layer 5: JSON Compaction\n *\n * Minifies JSON in tool_call arguments and tool results.\n * Removes pretty-print whitespace from JSON strings.\n *\n * Safe for LLM: JSON semantics unchanged.\n * Expected savings: 2-4%\n */\n\nimport { NormalizedMessage, ToolCall } from \"../types\";\n\nexport interface JsonCompactResult {\n messages: NormalizedMessage[];\n charsSaved: number;\n}\n\n/**\n * Compact a JSON string by parsing and re-stringifying without formatting.\n */\nfunction compactJson(jsonString: string): string {\n try {\n const parsed = JSON.parse(jsonString);\n return JSON.stringify(parsed);\n } catch {\n // Not valid JSON, return as-is\n return jsonString;\n }\n}\n\n/**\n * Check if a string looks like JSON (starts with { or [).\n */\nfunction looksLikeJson(str: string): boolean {\n const trimmed = str.trim();\n return (\n (trimmed.startsWith(\"{\") && trimmed.endsWith(\"}\")) ||\n (trimmed.startsWith(\"[\") && trimmed.endsWith(\"]\"))\n );\n}\n\n/**\n * Compact tool_call arguments in a message.\n */\nfunction compactToolCalls(toolCalls: ToolCall[]): ToolCall[] {\n return toolCalls.map((tc) => ({\n ...tc,\n function: {\n ...tc.function,\n arguments: compactJson(tc.function.arguments),\n },\n }));\n}\n\n/**\n * Apply JSON compaction to all messages.\n *\n * Targets:\n * - tool_call arguments (in assistant messages)\n * - tool message content (often JSON)\n */\nexport function compactMessagesJson(messages: NormalizedMessage[]): JsonCompactResult {\n let charsSaved = 0;\n\n const result = messages.map((message) => {\n const newMessage = { ...message };\n\n // Compact tool_calls arguments\n if (message.tool_calls && message.tool_calls.length > 0) {\n const originalLength = JSON.stringify(message.tool_calls).length;\n newMessage.tool_calls = compactToolCalls(message.tool_calls);\n const newLength = JSON.stringify(newMessage.tool_calls).length;\n charsSaved += originalLength - newLength;\n }\n\n // Compact tool message content if it looks like JSON\n // Only process string content (skip arrays for multimodal messages)\n if (\n message.role === \"tool\" &&\n message.content &&\n typeof message.content === \"string\" &&\n looksLikeJson(message.content)\n ) {\n const originalLength = message.content.length;\n const compacted = compactJson(message.content);\n charsSaved += originalLength - compacted.length;\n newMessage.content = compacted;\n }\n\n return newMessage;\n });\n\n return {\n messages: result,\n charsSaved,\n };\n}\n","/**\n * L6: Observation Compression (AGGRESSIVE)\n *\n * Inspired by claw-compactor's 97% compression on tool results.\n * Tool call results (especially large ones) are summarized to key info only.\n *\n * This is the biggest compression win - tool outputs can be 10KB+ but\n * only ~200 chars of actual useful information.\n */\n\nimport { NormalizedMessage } from \"../types\";\n\ninterface ObservationResult {\n messages: NormalizedMessage[];\n charsSaved: number;\n observationsCompressed: number;\n}\n\n// Max length for tool results before compression kicks in\nconst TOOL_RESULT_THRESHOLD = 500;\n\n// Max length to compress tool results down to\nconst COMPRESSED_RESULT_MAX = 300;\n\n/**\n * Extract key information from tool result.\n * Keeps: errors, key values, status, first/last important lines.\n */\nfunction compressToolResult(content: string): string {\n if (!content || content.length <= TOOL_RESULT_THRESHOLD) {\n return content;\n }\n\n const lines = content\n .split(\"\\n\")\n .map((l) => l.trim())\n .filter(Boolean);\n\n // Priority 1: Error messages (always keep)\n const errorLines = lines.filter(\n (l) => /error|exception|failed|denied|refused|timeout|invalid/i.test(l) && l.length < 200,\n );\n\n // Priority 2: Status/result lines\n const statusLines = lines.filter(\n (l) =>\n /success|complete|created|updated|found|result|status|total|count/i.test(l) && l.length < 150,\n );\n\n // Priority 3: Key JSON fields (extract important values)\n const jsonMatches: string[] = [];\n const jsonPattern = /\"(id|name|status|error|message|count|total|url|path)\":\\s*\"?([^\",}\\n]+)\"?/gi;\n let match;\n while ((match = jsonPattern.exec(content)) !== null) {\n jsonMatches.push(`${match[1]}: ${match[2].slice(0, 50)}`);\n }\n\n // Priority 4: First and last meaningful lines\n const firstLine = lines[0]?.slice(0, 100);\n const lastLine = lines.length > 1 ? lines[lines.length - 1]?.slice(0, 100) : \"\";\n\n // Build compressed observation\n const parts: string[] = [];\n\n if (errorLines.length > 0) {\n parts.push(\"[ERR] \" + errorLines.slice(0, 3).join(\" | \"));\n }\n\n if (statusLines.length > 0) {\n parts.push(statusLines.slice(0, 3).join(\" | \"));\n }\n\n if (jsonMatches.length > 0) {\n parts.push(jsonMatches.slice(0, 5).join(\", \"));\n }\n\n if (parts.length === 0) {\n // Fallback: keep first/last lines with truncation marker\n parts.push(firstLine || \"\");\n if (lines.length > 2) {\n parts.push(`[...${lines.length - 2} lines...]`);\n }\n if (lastLine && lastLine !== firstLine) {\n parts.push(lastLine);\n }\n }\n\n let result = parts.join(\"\\n\");\n\n // Final length cap\n if (result.length > COMPRESSED_RESULT_MAX) {\n result = result.slice(0, COMPRESSED_RESULT_MAX - 20) + \"\\n[...truncated]\";\n }\n\n return result;\n}\n\n/**\n * Compress large repeated content blocks.\n * Detects when same large block appears multiple times.\n */\nfunction deduplicateLargeBlocks(messages: NormalizedMessage[]): {\n messages: NormalizedMessage[];\n charsSaved: number;\n} {\n const blockHashes = new Map(); // hash -> first occurrence index\n let charsSaved = 0;\n\n const result = messages.map((msg, idx) => {\n // Only process string content (skip arrays for multimodal messages)\n if (!msg.content || typeof msg.content !== \"string\" || msg.content.length < 500) {\n return msg;\n }\n\n // Hash first 200 chars as block identifier\n const blockKey = msg.content.slice(0, 200);\n\n if (blockHashes.has(blockKey)) {\n const firstIdx = blockHashes.get(blockKey)!;\n const original = msg.content;\n const compressed = `[See message #${firstIdx + 1} - same content]`;\n charsSaved += original.length - compressed.length;\n return { ...msg, content: compressed };\n }\n\n blockHashes.set(blockKey, idx);\n return msg;\n });\n\n return { messages: result, charsSaved };\n}\n\n/**\n * Compress tool results in messages.\n */\nexport function compressObservations(messages: NormalizedMessage[]): ObservationResult {\n let charsSaved = 0;\n let observationsCompressed = 0;\n\n // First pass: compress individual tool results\n let result = messages.map((msg) => {\n // Only compress tool role messages (these are tool call results)\n // Only process string content (skip arrays for multimodal messages)\n if (msg.role !== \"tool\" || !msg.content || typeof msg.content !== \"string\") {\n return msg;\n }\n\n const original = msg.content;\n if (original.length <= TOOL_RESULT_THRESHOLD) {\n return msg;\n }\n\n const compressed = compressToolResult(original);\n const saved = original.length - compressed.length;\n\n if (saved > 50) {\n charsSaved += saved;\n observationsCompressed++;\n return { ...msg, content: compressed };\n }\n\n return msg;\n });\n\n // Second pass: deduplicate large repeated blocks\n const dedupResult = deduplicateLargeBlocks(result);\n result = dedupResult.messages;\n charsSaved += dedupResult.charsSaved;\n\n return {\n messages: result,\n charsSaved,\n observationsCompressed,\n };\n}\n","/**\n * L7: Dynamic Codebook Builder\n *\n * Inspired by claw-compactor's frequency-based codebook.\n * Builds codebook from actual content being compressed,\n * rather than relying on static patterns.\n *\n * Finds phrases that appear 3+ times and replaces with short codes.\n */\n\nimport { NormalizedMessage } from \"../types\";\n\ninterface DynamicCodebookResult {\n messages: NormalizedMessage[];\n charsSaved: number;\n dynamicCodes: Record; // code -> phrase\n substitutions: number;\n}\n\n// Config\nconst MIN_PHRASE_LENGTH = 20;\nconst MAX_PHRASE_LENGTH = 200;\nconst MIN_FREQUENCY = 3;\nconst MAX_ENTRIES = 100;\nconst CODE_PREFIX = \"$D\"; // Dynamic codes: $D01, $D02, etc.\n\n/**\n * Find repeated phrases in content.\n */\nfunction findRepeatedPhrases(allContent: string): Map {\n const phrases = new Map();\n\n // Split by sentence-like boundaries\n const segments = allContent.split(/(?<=[.!?\\n])\\s+/);\n\n for (const segment of segments) {\n const trimmed = segment.trim();\n if (trimmed.length >= MIN_PHRASE_LENGTH && trimmed.length <= MAX_PHRASE_LENGTH) {\n phrases.set(trimmed, (phrases.get(trimmed) || 0) + 1);\n }\n }\n\n // Also find repeated lines\n const lines = allContent.split(\"\\n\");\n for (const line of lines) {\n const trimmed = line.trim();\n if (trimmed.length >= MIN_PHRASE_LENGTH && trimmed.length <= MAX_PHRASE_LENGTH) {\n phrases.set(trimmed, (phrases.get(trimmed) || 0) + 1);\n }\n }\n\n return phrases;\n}\n\n/**\n * Build dynamic codebook from message content.\n */\nfunction buildDynamicCodebook(messages: NormalizedMessage[]): Record {\n // Combine all content\n let allContent = \"\";\n for (const msg of messages) {\n // Only process string content (skip arrays for multimodal messages)\n if (msg.content && typeof msg.content === \"string\") {\n allContent += msg.content + \"\\n\";\n }\n }\n\n // Find repeated phrases\n const phrases = findRepeatedPhrases(allContent);\n\n // Filter by frequency and sort by savings potential\n const candidates: Array<{ phrase: string; count: number; savings: number }> = [];\n for (const [phrase, count] of phrases.entries()) {\n if (count >= MIN_FREQUENCY) {\n // Savings = (phrase length - code length) * occurrences\n const codeLength = 4; // e.g., \"$D01\"\n const savings = (phrase.length - codeLength) * count;\n if (savings > 50) {\n candidates.push({ phrase, count, savings });\n }\n }\n }\n\n // Sort by savings (descending) and take top entries\n candidates.sort((a, b) => b.savings - a.savings);\n const topCandidates = candidates.slice(0, MAX_ENTRIES);\n\n // Build codebook\n const codebook: Record = {};\n topCandidates.forEach((c, i) => {\n const code = `${CODE_PREFIX}${String(i + 1).padStart(2, \"0\")}`;\n codebook[code] = c.phrase;\n });\n\n return codebook;\n}\n\n/**\n * Escape special regex characters.\n */\nfunction escapeRegex(str: string): string {\n // Defensive type check\n if (!str || typeof str !== \"string\") return \"\";\n return str.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n}\n\n/**\n * Apply dynamic codebook to messages.\n */\nexport function applyDynamicCodebook(messages: NormalizedMessage[]): DynamicCodebookResult {\n // Build codebook from content\n const codebook = buildDynamicCodebook(messages);\n\n if (Object.keys(codebook).length === 0) {\n return {\n messages,\n charsSaved: 0,\n dynamicCodes: {},\n substitutions: 0,\n };\n }\n\n // Create inverse map for replacement\n const phraseToCode: Record = {};\n for (const [code, phrase] of Object.entries(codebook)) {\n phraseToCode[phrase] = code;\n }\n\n // Sort phrases by length (longest first) to avoid partial replacements\n const sortedPhrases = Object.keys(phraseToCode).sort((a, b) => b.length - a.length);\n\n let charsSaved = 0;\n let substitutions = 0;\n\n // Apply replacements\n const result = messages.map((msg) => {\n // Only process string content (skip arrays for multimodal messages)\n if (!msg.content || typeof msg.content !== \"string\") return msg;\n\n let content = msg.content;\n for (const phrase of sortedPhrases) {\n const code = phraseToCode[phrase];\n const regex = new RegExp(escapeRegex(phrase), \"g\");\n const matches = content.match(regex);\n if (matches) {\n content = content.replace(regex, code);\n charsSaved += (phrase.length - code.length) * matches.length;\n substitutions += matches.length;\n }\n }\n\n return { ...msg, content };\n });\n\n return {\n messages: result,\n charsSaved,\n dynamicCodes: codebook,\n substitutions,\n };\n}\n\n/**\n * Generate header for dynamic codes (to include in system message).\n */\nexport function generateDynamicCodebookHeader(codebook: Record): string {\n if (Object.keys(codebook).length === 0) return \"\";\n\n const entries = Object.entries(codebook)\n .slice(0, 20) // Limit header size\n .map(([code, phrase]) => {\n // Truncate long phrases in header\n const displayPhrase = phrase.length > 40 ? phrase.slice(0, 37) + \"...\" : phrase;\n return `${code}=${displayPhrase}`;\n })\n .join(\", \");\n\n return `[DynDict: ${entries}]`;\n}\n","/**\n * LLM-Safe Context Compression\n *\n * Reduces token usage by 15-40% while preserving semantic meaning.\n * Implements 7 compression layers inspired by claw-compactor.\n *\n * Usage:\n * const result = await compressContext(messages);\n * // result.messages -> compressed version to send to provider\n * // result.originalMessages -> original for logging\n */\n\nimport {\n NormalizedMessage,\n CompressionConfig,\n CompressionResult,\n CompressionStats,\n DEFAULT_COMPRESSION_CONFIG,\n} from \"./types\";\nimport { deduplicateMessages } from \"./layers/deduplication\";\nimport { normalizeMessagesWhitespace } from \"./layers/whitespace\";\nimport { encodeMessages } from \"./layers/dictionary\";\nimport { shortenPaths } from \"./layers/paths\";\nimport { compactMessagesJson } from \"./layers/json-compact\";\nimport { compressObservations } from \"./layers/observation\";\nimport { applyDynamicCodebook, generateDynamicCodebookHeader } from \"./layers/dynamic-codebook\";\nimport { generateCodebookHeader, STATIC_CODEBOOK } from \"./codebook\";\n\nexport * from \"./types\";\nexport { STATIC_CODEBOOK } from \"./codebook\";\n\n/**\n * Calculate total character count for messages.\n */\nfunction calculateTotalChars(messages: NormalizedMessage[]): number {\n return messages.reduce((total, msg) => {\n let chars = 0;\n if (typeof msg.content === \"string\") {\n chars = msg.content.length;\n } else if (Array.isArray(msg.content)) {\n // For multimodal content, stringify to get approximate size\n chars = JSON.stringify(msg.content).length;\n }\n if (msg.tool_calls) {\n chars += JSON.stringify(msg.tool_calls).length;\n }\n return total + chars;\n }, 0);\n}\n\n/**\n * Deep clone messages to preserve originals.\n */\nfunction cloneMessages(messages: NormalizedMessage[]): NormalizedMessage[] {\n return JSON.parse(JSON.stringify(messages));\n}\n\n/**\n * Prepend codebook header to the first USER message (not system).\n *\n * Why not system message?\n * - Google Gemini uses systemInstruction which doesn't support codebook format\n * - The codebook header in user message is still visible to all LLMs\n * - This ensures compatibility across all providers\n */\nfunction prependCodebookHeader(\n messages: NormalizedMessage[],\n usedCodes: Set,\n pathMap: Record,\n): NormalizedMessage[] {\n const header = generateCodebookHeader(usedCodes, pathMap);\n if (!header) return messages;\n\n // Find first user message (not system - Google's systemInstruction doesn't support codebook)\n const userIndex = messages.findIndex((m) => m.role === \"user\");\n\n if (userIndex === -1) {\n // No user message, add codebook as system (fallback)\n return [{ role: \"system\", content: header }, ...messages];\n }\n\n // Prepend to first user message (only if content is a string)\n return messages.map((msg, i) => {\n if (i === userIndex) {\n // Only prepend to string content - skip arrays (multimodal messages)\n if (typeof msg.content === \"string\") {\n return {\n ...msg,\n content: `${header}\\n\\n${msg.content}`,\n };\n }\n // For non-string content, don't modify the message\n // The codebook header would corrupt array content\n }\n return msg;\n });\n}\n\n/**\n * Main compression function.\n *\n * Applies 5 layers in sequence:\n * 1. Deduplication - Remove exact duplicate messages\n * 2. Whitespace - Normalize excessive whitespace\n * 3. Dictionary - Replace common phrases with codes\n * 4. Paths - Shorten repeated file paths\n * 5. JSON - Compact JSON in tool calls\n *\n * Then prepends a codebook header for the LLM to decode in-context.\n */\nexport async function compressContext(\n messages: NormalizedMessage[],\n config: Partial = {},\n): Promise {\n const fullConfig: CompressionConfig = {\n ...DEFAULT_COMPRESSION_CONFIG,\n ...config,\n layers: {\n ...DEFAULT_COMPRESSION_CONFIG.layers,\n ...config.layers,\n },\n dictionary: {\n ...DEFAULT_COMPRESSION_CONFIG.dictionary,\n ...config.dictionary,\n },\n };\n\n // If compression disabled, return as-is\n if (!fullConfig.enabled) {\n const originalChars = calculateTotalChars(messages);\n return {\n messages,\n originalMessages: messages,\n originalChars,\n compressedChars: originalChars,\n compressionRatio: 1,\n stats: {\n duplicatesRemoved: 0,\n whitespaceSavedChars: 0,\n dictionarySubstitutions: 0,\n pathsShortened: 0,\n jsonCompactedChars: 0,\n observationsCompressed: 0,\n observationCharsSaved: 0,\n dynamicSubstitutions: 0,\n dynamicCharsSaved: 0,\n },\n codebook: {},\n pathMap: {},\n dynamicCodes: {},\n };\n }\n\n // Preserve originals for logging\n const originalMessages = fullConfig.preserveRaw ? cloneMessages(messages) : messages;\n const originalChars = calculateTotalChars(messages);\n\n // Initialize stats\n const stats: CompressionStats = {\n duplicatesRemoved: 0,\n whitespaceSavedChars: 0,\n dictionarySubstitutions: 0,\n pathsShortened: 0,\n jsonCompactedChars: 0,\n observationsCompressed: 0,\n observationCharsSaved: 0,\n dynamicSubstitutions: 0,\n dynamicCharsSaved: 0,\n };\n\n let result = cloneMessages(messages);\n let usedCodes = new Set();\n let pathMap: Record = {};\n let dynamicCodes: Record = {};\n\n // Layer 1: Deduplication\n if (fullConfig.layers.deduplication) {\n const dedupResult = deduplicateMessages(result);\n result = dedupResult.messages;\n stats.duplicatesRemoved = dedupResult.duplicatesRemoved;\n }\n\n // Layer 2: Whitespace normalization\n if (fullConfig.layers.whitespace) {\n const wsResult = normalizeMessagesWhitespace(result);\n result = wsResult.messages;\n stats.whitespaceSavedChars = wsResult.charsSaved;\n }\n\n // Layer 3: Dictionary encoding\n if (fullConfig.layers.dictionary) {\n const dictResult = encodeMessages(result);\n result = dictResult.messages;\n stats.dictionarySubstitutions = dictResult.substitutionCount;\n usedCodes = dictResult.usedCodes;\n }\n\n // Layer 4: Path shortening\n if (fullConfig.layers.paths) {\n const pathResult = shortenPaths(result);\n result = pathResult.messages;\n pathMap = pathResult.pathMap;\n stats.pathsShortened = Object.keys(pathMap).length;\n }\n\n // Layer 5: JSON compaction\n if (fullConfig.layers.jsonCompact) {\n const jsonResult = compactMessagesJson(result);\n result = jsonResult.messages;\n stats.jsonCompactedChars = jsonResult.charsSaved;\n }\n\n // Layer 6: Observation compression (BIG WIN - 97% on tool results)\n if (fullConfig.layers.observation) {\n const obsResult = compressObservations(result);\n result = obsResult.messages;\n stats.observationsCompressed = obsResult.observationsCompressed;\n stats.observationCharsSaved = obsResult.charsSaved;\n }\n\n // Layer 7: Dynamic codebook (learns from actual content)\n if (fullConfig.layers.dynamicCodebook) {\n const dynResult = applyDynamicCodebook(result);\n result = dynResult.messages;\n stats.dynamicSubstitutions = dynResult.substitutions;\n stats.dynamicCharsSaved = dynResult.charsSaved;\n dynamicCodes = dynResult.dynamicCodes;\n }\n\n // Add codebook header if enabled and we have codes to include\n if (\n fullConfig.dictionary.includeCodebookHeader &&\n (usedCodes.size > 0 || Object.keys(pathMap).length > 0 || Object.keys(dynamicCodes).length > 0)\n ) {\n result = prependCodebookHeader(result, usedCodes, pathMap);\n // Also add dynamic codebook header if we have dynamic codes\n if (Object.keys(dynamicCodes).length > 0) {\n const dynHeader = generateDynamicCodebookHeader(dynamicCodes);\n if (dynHeader) {\n const systemIndex = result.findIndex((m) => m.role === \"system\");\n // Only prepend to string content - skip arrays (multimodal messages)\n if (systemIndex >= 0 && typeof result[systemIndex].content === \"string\") {\n result[systemIndex] = {\n ...result[systemIndex],\n content: `${dynHeader}\\n${result[systemIndex].content}`,\n };\n }\n }\n }\n }\n\n // Calculate final stats\n const compressedChars = calculateTotalChars(result);\n const compressionRatio = compressedChars / originalChars;\n\n // Build used codebook for logging\n const usedCodebook: Record = {};\n usedCodes.forEach((code) => {\n usedCodebook[code] = STATIC_CODEBOOK[code];\n });\n\n return {\n messages: result,\n originalMessages,\n originalChars,\n compressedChars,\n compressionRatio,\n stats,\n codebook: usedCodebook,\n pathMap,\n dynamicCodes,\n };\n}\n\n/**\n * Quick check if compression would benefit these messages.\n * Returns true if messages are large enough to warrant compression.\n */\nexport function shouldCompress(messages: NormalizedMessage[]): boolean {\n const chars = calculateTotalChars(messages);\n // Only compress if > 5000 chars (roughly 1000 tokens)\n return chars > 5000;\n}\n","/**\n * Session Persistence Store\n *\n * Tracks model selections per session to prevent model switching mid-task.\n * When a session is active, the router will continue using the same model\n * instead of re-routing each request.\n */\n\nimport { createHash } from \"node:crypto\";\n\nexport type SessionEntry = {\n model: string;\n tier: string;\n createdAt: number;\n lastUsedAt: number;\n requestCount: number;\n // --- Three-strike escalation ---\n recentHashes: string[]; // Sliding window of last 3 request content fingerprints\n strikes: number; // Consecutive similar request count\n escalated: boolean; // Whether session was already escalated via three-strike\n};\n\nexport type SessionConfig = {\n /** Enable session persistence (default: false) */\n enabled: boolean;\n /** Session timeout in ms (default: 30 minutes) */\n timeoutMs: number;\n /** Header name for session ID (default: X-Session-ID) */\n headerName: string;\n};\n\nexport const DEFAULT_SESSION_CONFIG: SessionConfig = {\n enabled: true,\n timeoutMs: 30 * 60 * 1000, // 30 minutes\n headerName: \"x-session-id\",\n};\n\n/**\n * Session persistence store for maintaining model selections.\n */\nexport class SessionStore {\n private sessions: Map = new Map();\n private config: SessionConfig;\n private cleanupInterval: ReturnType | null = null;\n\n constructor(config: Partial = {}) {\n this.config = { ...DEFAULT_SESSION_CONFIG, ...config };\n\n // Start cleanup interval (every 5 minutes)\n if (this.config.enabled) {\n this.cleanupInterval = setInterval(() => this.cleanup(), 5 * 60 * 1000);\n }\n }\n\n /**\n * Get the pinned model for a session, if any.\n */\n getSession(sessionId: string): SessionEntry | undefined {\n if (!this.config.enabled || !sessionId) {\n return undefined;\n }\n\n const entry = this.sessions.get(sessionId);\n if (!entry) {\n return undefined;\n }\n\n // Check if session has expired\n const now = Date.now();\n if (now - entry.lastUsedAt > this.config.timeoutMs) {\n this.sessions.delete(sessionId);\n return undefined;\n }\n\n return entry;\n }\n\n /**\n * Pin a model to a session.\n */\n setSession(sessionId: string, model: string, tier: string): void {\n if (!this.config.enabled || !sessionId) {\n return;\n }\n\n const existing = this.sessions.get(sessionId);\n const now = Date.now();\n\n if (existing) {\n existing.lastUsedAt = now;\n existing.requestCount++;\n // Update model if different (e.g., fallback)\n if (existing.model !== model) {\n existing.model = model;\n existing.tier = tier;\n }\n } else {\n this.sessions.set(sessionId, {\n model,\n tier,\n createdAt: now,\n lastUsedAt: now,\n requestCount: 1,\n recentHashes: [],\n strikes: 0,\n escalated: false,\n });\n }\n }\n\n /**\n * Touch a session to extend its timeout.\n */\n touchSession(sessionId: string): void {\n if (!this.config.enabled || !sessionId) {\n return;\n }\n\n const entry = this.sessions.get(sessionId);\n if (entry) {\n entry.lastUsedAt = Date.now();\n entry.requestCount++;\n }\n }\n\n /**\n * Clear a specific session.\n */\n clearSession(sessionId: string): void {\n this.sessions.delete(sessionId);\n }\n\n /**\n * Clear all sessions.\n */\n clearAll(): void {\n this.sessions.clear();\n }\n\n /**\n * Get session stats for debugging.\n */\n getStats(): { count: number; sessions: Array<{ id: string; model: string; age: number }> } {\n const now = Date.now();\n const sessions = Array.from(this.sessions.entries()).map(([id, entry]) => ({\n id: id.slice(0, 8) + \"...\",\n model: entry.model,\n age: Math.round((now - entry.createdAt) / 1000),\n }));\n return { count: this.sessions.size, sessions };\n }\n\n /**\n * Clean up expired sessions.\n */\n private cleanup(): void {\n const now = Date.now();\n for (const [id, entry] of this.sessions) {\n if (now - entry.lastUsedAt > this.config.timeoutMs) {\n this.sessions.delete(id);\n }\n }\n }\n\n /**\n * Record a request content hash and detect repetitive patterns.\n * Returns true if escalation should be triggered (3+ consecutive similar requests).\n */\n recordRequestHash(sessionId: string, hash: string): boolean {\n const entry = this.sessions.get(sessionId);\n if (!entry) return false;\n\n const prev = entry.recentHashes;\n if (prev.length > 0 && prev[prev.length - 1] === hash) {\n entry.strikes++;\n } else {\n entry.strikes = 0;\n }\n\n entry.recentHashes.push(hash);\n if (entry.recentHashes.length > 3) {\n entry.recentHashes.shift();\n }\n\n return entry.strikes >= 2 && !entry.escalated;\n }\n\n /**\n * Escalate session to next tier. Returns the new model/tier or null if already at max.\n */\n escalateSession(\n sessionId: string,\n tierConfigs: Record,\n ): { model: string; tier: string } | null {\n const entry = this.sessions.get(sessionId);\n if (!entry) return null;\n\n const TIER_ORDER = [\"SIMPLE\", \"MEDIUM\", \"COMPLEX\", \"REASONING\"];\n const currentIdx = TIER_ORDER.indexOf(entry.tier);\n if (currentIdx < 0 || currentIdx >= TIER_ORDER.length - 1) return null;\n\n const nextTier = TIER_ORDER[currentIdx + 1];\n const nextConfig = tierConfigs[nextTier];\n if (!nextConfig) return null;\n\n entry.model = nextConfig.primary;\n entry.tier = nextTier;\n entry.strikes = 0;\n entry.escalated = true;\n\n return { model: nextConfig.primary, tier: nextTier };\n }\n\n /**\n * Stop the cleanup interval.\n */\n close(): void {\n if (this.cleanupInterval) {\n clearInterval(this.cleanupInterval);\n this.cleanupInterval = null;\n }\n }\n}\n\n/**\n * Generate a session ID from request headers or create a default.\n */\nexport function getSessionId(\n headers: Record,\n headerName: string = DEFAULT_SESSION_CONFIG.headerName,\n): string | undefined {\n const value = headers[headerName] || headers[headerName.toLowerCase()];\n if (typeof value === \"string\" && value.length > 0) {\n return value;\n }\n if (Array.isArray(value) && value.length > 0) {\n return value[0];\n }\n return undefined;\n}\n\n/**\n * Derive a stable session ID from message content when no explicit session\n * header is provided. Uses the first user message as the conversation anchor —\n * same opening message = same session ID across all subsequent turns.\n *\n * This prevents model-switching mid-conversation even when OpenClaw doesn't\n * send an x-session-id header (which is the default OpenClaw behaviour).\n */\nexport function deriveSessionId(\n messages: Array<{ role: string; content: unknown }>,\n): string | undefined {\n const firstUser = messages.find((m) => m.role === \"user\");\n if (!firstUser) return undefined;\n\n const content =\n typeof firstUser.content === \"string\" ? firstUser.content : JSON.stringify(firstUser.content);\n\n // 8-char hex prefix of SHA-256 — short enough for logs, collision-resistant\n // enough for session tracking within a single gateway instance.\n return createHash(\"sha256\").update(content).digest(\"hex\").slice(0, 8);\n}\n\n/**\n * Generate a short hash fingerprint from request content.\n * Captures: last user message text + tool call names (if any).\n * Normalizes whitespace to avoid false negatives from minor formatting diffs.\n */\nexport function hashRequestContent(lastUserContent: string, toolCallNames?: string[]): string {\n const normalized = lastUserContent.replace(/\\s+/g, \" \").trim().slice(0, 500);\n const toolSuffix = toolCallNames?.length ? `|tools:${toolCallNames.sort().join(\",\")}` : \"\";\n return createHash(\"sha256\")\n .update(normalized + toolSuffix)\n .digest(\"hex\")\n .slice(0, 12);\n}\n","/**\n * Auto-update checker for ClawRouter.\n * Checks npm registry on startup and notifies user if update available.\n */\n\nimport { VERSION } from \"./version.js\";\n\nconst NPM_REGISTRY = \"https://registry.npmjs.org/@blockrun/clawrouter/latest\";\nconst UPDATE_URL = \"https://blockrun.ai/ClawRouter-update\";\nconst CHECK_TIMEOUT_MS = 5_000; // Don't block startup for more than 5s\n\n/**\n * Compare semver versions. Returns:\n * 1 if a > b\n * 0 if a === b\n * -1 if a < b\n */\nfunction compareSemver(a: string, b: string): number {\n const pa = a.split(\".\").map(Number);\n const pb = b.split(\".\").map(Number);\n for (let i = 0; i < 3; i++) {\n if ((pa[i] || 0) > (pb[i] || 0)) return 1;\n if ((pa[i] || 0) < (pb[i] || 0)) return -1;\n }\n return 0;\n}\n\n/**\n * Check npm registry for latest version.\n * Non-blocking, silent on errors.\n */\nexport async function checkForUpdates(): Promise {\n try {\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), CHECK_TIMEOUT_MS);\n\n const res = await fetch(NPM_REGISTRY, {\n signal: controller.signal,\n headers: { Accept: \"application/json\" },\n });\n clearTimeout(timeout);\n\n if (!res.ok) return;\n\n const data = (await res.json()) as { version?: string };\n const latest = data.version;\n\n if (!latest) return;\n\n if (compareSemver(latest, VERSION) > 0) {\n console.log(\"\");\n console.log(`\\x1b[33m⬆️ ClawRouter ${latest} available (you have ${VERSION})\\x1b[0m`);\n console.log(` Run: \\x1b[36mcurl -fsSL ${UPDATE_URL} | bash\\x1b[0m`);\n console.log(\"\");\n }\n } catch {\n // Silent fail - don't disrupt startup\n }\n}\n","/**\n * Configuration Module\n *\n * Reads environment variables at module load time.\n * Separated from network code to avoid security scanner false positives.\n */\n\nconst DEFAULT_PORT = 8402;\n\n/**\n * Proxy port configuration - resolved once at module load.\n * Reads BLOCKRUN_PROXY_PORT env var or defaults to 8402.\n */\nexport const PROXY_PORT = (() => {\n const envPort = process[\"env\"].BLOCKRUN_PROXY_PORT;\n if (envPort) {\n const parsed = parseInt(envPort, 10);\n if (!isNaN(parsed) && parsed > 0 && parsed < 65536) {\n return parsed;\n }\n }\n return DEFAULT_PORT;\n})();\n","/**\n * Session Journal - Memory layer for ClawRouter\n *\n * Maintains a compact record of key actions per session, enabling agents\n * to recall earlier work even when OpenClaw's sessions_history is truncated.\n *\n * How it works:\n * 1. As LLM responses flow through, extracts key actions (\"I created X\", \"I fixed Y\")\n * 2. Stores them in a compact journal per session\n * 3. When a request mentions past work (\"what did you do today?\"), injects the journal\n */\n\nexport interface JournalEntry {\n timestamp: number;\n action: string; // Compact description: \"Created login component\"\n model?: string;\n}\n\nexport interface SessionJournalConfig {\n /** Maximum entries per session (default: 100) */\n maxEntries?: number;\n /** Maximum age of entries in ms (default: 24 hours) */\n maxAgeMs?: number;\n /** Maximum events to extract per response (default: 5) */\n maxEventsPerResponse?: number;\n}\n\nconst DEFAULT_CONFIG: Required = {\n maxEntries: 100,\n maxAgeMs: 24 * 60 * 60 * 1000, // 24 hours\n maxEventsPerResponse: 5,\n};\n\nexport class SessionJournal {\n private journals: Map = new Map();\n private config: Required;\n\n constructor(config?: SessionJournalConfig) {\n this.config = { ...DEFAULT_CONFIG, ...config };\n }\n\n /**\n * Extract key events from assistant response content.\n * Looks for patterns like \"I created...\", \"I fixed...\", \"Successfully...\"\n */\n extractEvents(content: string): string[] {\n if (!content || typeof content !== \"string\") {\n return [];\n }\n\n const events: string[] = [];\n const seen = new Set();\n\n // Patterns for identifying key actions\n // Note: Patterns allow optional words like \"also\", \"then\", \"have\" between \"I\" and verb\n const patterns = [\n // Creation patterns\n /I (?:also |then |have |)?(?:created|implemented|added|wrote|built|generated|set up|initialized) ([^.!?\\n]{10,150})/gi,\n // Fix patterns\n /I (?:also |then |have |)?(?:fixed|resolved|solved|patched|corrected|addressed|debugged) ([^.!?\\n]{10,150})/gi,\n // Completion patterns\n /I (?:also |then |have |)?(?:completed|finished|done with|wrapped up) ([^.!?\\n]{10,150})/gi,\n // Update patterns\n /I (?:also |then |have |)?(?:updated|modified|changed|refactored|improved|enhanced|optimized) ([^.!?\\n]{10,150})/gi,\n // Success patterns\n /Successfully ([^.!?\\n]{10,150})/gi,\n // Tool usage patterns (when agent uses tools)\n /I (?:also |then |have |)?(?:ran|executed|called|invoked) ([^.!?\\n]{10,100})/gi,\n ];\n\n for (const pattern of patterns) {\n // Reset pattern lastIndex for each iteration\n pattern.lastIndex = 0;\n\n let match;\n while ((match = pattern.exec(content)) !== null) {\n const action = match[0].trim();\n\n // Skip if already seen (dedup)\n const normalized = action.toLowerCase();\n if (seen.has(normalized)) {\n continue;\n }\n\n // Validate length (not too short or too long)\n if (action.length >= 15 && action.length <= 200) {\n events.push(action);\n seen.add(normalized);\n }\n\n // Stop if we have enough events\n if (events.length >= this.config.maxEventsPerResponse) {\n break;\n }\n }\n\n if (events.length >= this.config.maxEventsPerResponse) {\n break;\n }\n }\n\n return events;\n }\n\n /**\n * Record events to the session journal.\n */\n record(sessionId: string, events: string[], model?: string): void {\n if (!sessionId || !events.length) {\n return;\n }\n\n const journal = this.journals.get(sessionId) || [];\n const now = Date.now();\n\n for (const action of events) {\n journal.push({\n timestamp: now,\n action,\n model,\n });\n }\n\n // Trim old entries and enforce max count\n const cutoff = now - this.config.maxAgeMs;\n const trimmed = journal.filter((e) => e.timestamp > cutoff).slice(-this.config.maxEntries);\n\n this.journals.set(sessionId, trimmed);\n }\n\n /**\n * Check if the user message indicates a need for historical context.\n */\n needsContext(lastUserMessage: string): boolean {\n if (!lastUserMessage || typeof lastUserMessage !== \"string\") {\n return false;\n }\n\n const lower = lastUserMessage.toLowerCase();\n\n // Trigger phrases that indicate user wants to recall past work\n const triggers = [\n // Direct questions about past work\n \"what did you do\",\n \"what have you done\",\n \"what did we do\",\n \"what have we done\",\n // Temporal references\n \"earlier\",\n \"before\",\n \"previously\",\n \"this session\",\n \"today\",\n \"so far\",\n // Summary requests\n \"remind me\",\n \"summarize\",\n \"summary of\",\n \"recap\",\n // Progress inquiries\n \"your work\",\n \"your progress\",\n \"accomplished\",\n \"achievements\",\n \"completed tasks\",\n ];\n\n return triggers.some((t) => lower.includes(t));\n }\n\n /**\n * Format the journal for injection into system message.\n * Returns null if journal is empty.\n */\n format(sessionId: string): string | null {\n const journal = this.journals.get(sessionId);\n if (!journal?.length) {\n return null;\n }\n\n const lines = journal.map((e) => {\n const time = new Date(e.timestamp).toLocaleTimeString(\"en-US\", {\n hour: \"2-digit\",\n minute: \"2-digit\",\n hour12: true,\n });\n return `- ${time}: ${e.action}`;\n });\n\n return `[Session Memory - Key Actions]\\n${lines.join(\"\\n\")}`;\n }\n\n /**\n * Get the raw journal entries for a session (for debugging/testing).\n */\n getEntries(sessionId: string): JournalEntry[] {\n return this.journals.get(sessionId) || [];\n }\n\n /**\n * Clear journal for a specific session.\n */\n clear(sessionId: string): void {\n this.journals.delete(sessionId);\n }\n\n /**\n * Clear all journals.\n */\n clearAll(): void {\n this.journals.clear();\n }\n\n /**\n * Get stats about the journal.\n */\n getStats(): { sessions: number; totalEntries: number } {\n let totalEntries = 0;\n for (const entries of this.journals.values()) {\n totalEntries += entries.length;\n }\n return {\n sessions: this.journals.size,\n totalEntries,\n };\n }\n}\n","/**\n * End-to-end test for smart routing + proxy.\n *\n * Part 1: Router classification (no network, no wallet needed)\n * Part 2: Proxy startup + live request (requires BLOCKRUN_WALLET_KEY with funded USDC)\n *\n * Usage:\n * npx tsup test/e2e.ts --format esm --outDir test/dist --no-dts && node test/dist/e2e.js\n */\n\nimport { route, DEFAULT_ROUTING_CONFIG, type RoutingDecision } from \"../src/router/index.js\";\nimport { classifyByRules } from \"../src/router/rules.js\";\nimport { BLOCKRUN_MODELS } from \"../src/models.js\";\nimport { startProxy } from \"../src/proxy.js\";\nimport type { ModelPricing } from \"../src/router/selector.js\";\n\n// ─── Helpers ───\n\nfunction buildModelPricing(): Map {\n const map = new Map();\n for (const m of BLOCKRUN_MODELS) {\n if (m.id === \"blockrun/auto\") continue;\n map.set(m.id, { inputPrice: m.inputPrice, outputPrice: m.outputPrice });\n }\n return map;\n}\n\nlet passed = 0;\nlet failed = 0;\n\nfunction assert(condition: boolean, msg: string) {\n if (condition) {\n console.log(` ✓ ${msg}`);\n passed++;\n } else {\n console.error(` ✗ FAIL: ${msg}`);\n failed++;\n }\n}\n\n// ─── Part 1: Rule-Based Classifier ───\n\nconsole.log(\"\\n═══ Part 1: Rule-Based Classifier ═══\\n\");\n\nconst config = DEFAULT_ROUTING_CONFIG;\n\n// Simple queries\n{\n console.log(\"Simple queries:\");\n const r1 = classifyByRules(\"What is the capital of France?\", undefined, 8, config.scoring);\n assert(\n r1.tier === \"SIMPLE\",\n `\"What is the capital of France?\" → ${r1.tier} (score=${r1.score.toFixed(3)})`,\n );\n\n const r2 = classifyByRules(\"Hello\", undefined, 2, config.scoring);\n assert(r2.tier === \"SIMPLE\", `\"Hello\" → ${r2.tier} (score=${r2.score.toFixed(3)})`);\n\n const r3 = classifyByRules(\"Define photosynthesis\", undefined, 4, config.scoring);\n // With adjusted weights, this may route to SIMPLE or MEDIUM\n assert(\n r3.tier === \"SIMPLE\" || r3.tier === \"MEDIUM\" || r3.tier === null,\n `\"Define photosynthesis\" → ${r3.tier} (score=${r3.score.toFixed(3)})`,\n );\n\n const r4 = classifyByRules(\"Translate hello to Spanish\", undefined, 6, config.scoring);\n assert(\n r4.tier === \"SIMPLE\",\n `\"Translate hello to Spanish\" → ${r4.tier} (score=${r4.score.toFixed(3)})`,\n );\n\n const r5 = classifyByRules(\"Yes or no: is the sky blue?\", undefined, 8, config.scoring);\n assert(\n r5.tier === \"SIMPLE\",\n `\"Yes or no: is the sky blue?\" → ${r5.tier} (score=${r5.score.toFixed(3)})`,\n );\n}\n\n// System prompt with reasoning/agentic keywords should NOT affect simple queries\n// Bug: if client's system prompt had \"step by step\" / \"edit files\" / \"fix bugs\", ALL queries became REASONING/agentic\n{\n console.log(\"\\nSystem prompt with reasoning keywords (should NOT affect simple queries):\");\n const systemPrompt = \"Think step by step and reason logically about the user's question.\";\n\n const r1 = classifyByRules(\"What is 2+2?\", systemPrompt, 10, config.scoring);\n assert(\n r1.tier === \"SIMPLE\",\n `\"2+2\" with reasoning system prompt → ${r1.tier} (should be SIMPLE)`,\n );\n\n const r2 = classifyByRules(\"Hello\", systemPrompt, 5, config.scoring);\n assert(\n r2.tier === \"SIMPLE\",\n `\"Hello\" with reasoning system prompt → ${r2.tier} (should be SIMPLE)`,\n );\n\n const r3 = classifyByRules(\"What is the capital of France?\", systemPrompt, 12, config.scoring);\n assert(\n r3.tier === \"SIMPLE\",\n `\"Capital of France\" with reasoning system prompt → ${r3.tier} (should be SIMPLE)`,\n );\n\n // But if USER explicitly asks for step-by-step, it SHOULD trigger REASONING\n const r4 = classifyByRules(\n \"Prove step by step that sqrt(2) is irrational\",\n systemPrompt,\n 50,\n config.scoring,\n );\n assert(\n r4.tier === \"REASONING\",\n `User asks for step-by-step proof → ${r4.tier} (should be REASONING)`,\n );\n}\n\n// Coding assistant system prompt should NOT force agentic mode on simple queries\n// Bug: OpenClaw's system prompt (\"edit files\", \"fix bugs\", \"check\", \"verify\") was\n// triggering agenticScore >= 0.6 on EVERY request, routing all to Sonnet via agentic tiers\n{\n console.log(\"\\nCoding assistant system prompt (should NOT force agentic mode):\");\n const codingSystemPrompt =\n \"You are a coding assistant. You can edit files, fix bugs, check code quality, \" +\n \"verify tests, deploy applications, and install dependencies. Make sure to follow \" +\n \"best practices and confirm changes before applying them.\";\n\n const r1 = classifyByRules(\"What does this function do?\", codingSystemPrompt, 20, config.scoring);\n assert(\n r1.agenticScore < 0.5,\n `Simple question with coding system prompt → agenticScore=${r1.agenticScore} (should be <0.5, not forced agentic)`,\n );\n\n const r2 = classifyByRules(\"What is React?\", codingSystemPrompt, 15, config.scoring);\n assert(\n r2.agenticScore < 0.5,\n `\"What is React?\" with coding system prompt → agenticScore=${r2.agenticScore} (should be <0.5)`,\n );\n\n // But if USER explicitly requests agentic work, it SHOULD trigger agentic mode\n // Need 3+ agentic keyword matches for score 0.6: \"fix\", \"deploy\", \"make sure\"\n const r3 = classifyByRules(\n \"Fix the bug in auth.ts, deploy to staging, and make sure it works\",\n codingSystemPrompt,\n 30,\n config.scoring,\n );\n assert(\n r3.agenticScore >= 0.5,\n `User asks for multi-step agentic task → agenticScore=${r3.agenticScore} (should be >=0.5)`,\n );\n}\n\n// Medium queries (may be ambiguous — that's ok, LLM classifier handles them)\n{\n console.log(\"\\nMedium/Ambiguous queries:\");\n const r1 = classifyByRules(\n \"Summarize the key differences between REST and GraphQL APIs\",\n undefined,\n 30,\n config.scoring,\n );\n console.log(\n ` → \"Summarize REST vs GraphQL\" → tier=${r1.tier ?? \"AMBIGUOUS\"} (score=${r1.score.toFixed(3)}, conf=${r1.confidence.toFixed(3)}) [${r1.signals.join(\", \")}]`,\n );\n\n const r2 = classifyByRules(\n \"Write a Python function to sort a list using merge sort\",\n undefined,\n 40,\n config.scoring,\n );\n console.log(\n ` → \"Write merge sort\" → tier=${r2.tier ?? \"AMBIGUOUS\"} (score=${r2.score.toFixed(3)}, conf=${r2.confidence.toFixed(3)}) [${r2.signals.join(\", \")}]`,\n );\n}\n\n// Complex/high-signal queries should not be down-routed to SIMPLE.\n// Depending on scoring weights, these may be MEDIUM/COMPLEX or ambiguous.\n{\n console.log(\"\\nComplex queries (expected: non-SIMPLE):\");\n const r1 = classifyByRules(\n \"Build a React component with TypeScript that implements a drag-and-drop kanban board with async data loading, error handling, and unit tests\",\n undefined,\n 200,\n config.scoring,\n );\n assert(\n r1.tier === null || r1.tier === \"MEDIUM\" || r1.tier === \"COMPLEX\" || r1.tier === \"REASONING\",\n `Kanban board → ${r1.tier ?? \"AMBIGUOUS\"} (score=${r1.score.toFixed(3)}, conf=${r1.confidence.toFixed(3)})`,\n );\n\n const r2 = classifyByRules(\n \"Design a distributed microservice architecture for a real-time trading platform. Include the database schema, API endpoints, message queue topology, and kubernetes deployment manifests.\",\n undefined,\n 250,\n config.scoring,\n );\n assert(\n r2.tier === null || r2.tier === \"MEDIUM\" || r2.tier === \"COMPLEX\" || r2.tier === \"REASONING\",\n `Distributed trading platform → ${r2.tier ?? \"AMBIGUOUS\"} (score=${r2.score.toFixed(3)}, conf=${r2.confidence.toFixed(3)})`,\n );\n}\n\n// Reasoning queries\n{\n console.log(\"\\nReasoning queries:\");\n const r1 = classifyByRules(\n \"Prove that the square root of 2 is irrational using proof by contradiction. Show each step formally.\",\n undefined,\n 60,\n config.scoring,\n );\n assert(\n r1.tier === \"REASONING\",\n `\"Prove sqrt(2) irrational\" → ${r1.tier} (score=${r1.score.toFixed(3)}, conf=${r1.confidence.toFixed(3)})`,\n );\n\n const r2 = classifyByRules(\n \"Derive the time complexity of the following algorithm step by step, then prove it is optimal using a lower bound argument.\",\n undefined,\n 80,\n config.scoring,\n );\n assert(\n r2.tier === \"REASONING\",\n `\"Derive time complexity + prove optimal\" → ${r2.tier} (score=${r2.score.toFixed(3)}, conf=${r2.confidence.toFixed(3)})`,\n );\n\n const r3 = classifyByRules(\n \"Using chain of thought, solve this mathematical proof: for all n >= 1, prove that 1 + 2 + ... + n = n(n+1)/2\",\n undefined,\n 70,\n config.scoring,\n );\n assert(\n r3.tier === \"REASONING\",\n `\"Chain of thought proof\" → ${r3.tier} (score=${r3.score.toFixed(3)}, conf=${r3.confidence.toFixed(3)})`,\n );\n}\n\n// Multilingual keyword tests\n{\n console.log(\"\\nMultilingual keyword tests:\");\n\n // Chinese reasoning - 证明 (prove) + 逐步 (step by step)\n const zhReasoning = classifyByRules(\n \"请证明根号2是无理数,逐步推导\",\n undefined,\n 20,\n config.scoring,\n );\n assert(\n zhReasoning.tier === \"REASONING\",\n `Chinese \"证明...逐步\" → ${zhReasoning.tier} (should be REASONING)`,\n );\n\n // Chinese simple - 你好 (hello) + 什么是 (what is)\n const zhSimple = classifyByRules(\"你好,什么是人工智能?\", undefined, 15, config.scoring);\n assert(\n zhSimple.tier === \"SIMPLE\",\n `Chinese \"你好...什么是\" → ${zhSimple.tier} (should be SIMPLE)`,\n );\n\n // Japanese simple - こんにちは (hello)\n const jaSimple = classifyByRules(\"こんにちは、東京とは何ですか\", undefined, 15, config.scoring);\n assert(\n jaSimple.tier === \"SIMPLE\",\n `Japanese \"こんにちは...とは\" → ${jaSimple.tier} (should be SIMPLE)`,\n );\n\n // Russian technical - алгоритм (algorithm) + оптимизировать (optimize)\n const ruTech = classifyByRules(\n \"Оптимизировать алгоритм сортировки для распределённой системы\",\n undefined,\n 20,\n config.scoring,\n );\n assert(\n ruTech.tier !== \"SIMPLE\",\n `Russian \"алгоритм...распределённой\" → ${ruTech.tier} (should NOT be SIMPLE)`,\n );\n\n // Russian simple - привет (hello) + что такое (what is)\n const ruSimple = classifyByRules(\n \"Привет, что такое машинное обучение?\",\n undefined,\n 15,\n config.scoring,\n );\n assert(\n ruSimple.tier === \"SIMPLE\",\n `Russian \"привет...что такое\" → ${ruSimple.tier} (should be SIMPLE)`,\n );\n\n // German reasoning - beweisen (prove) + schritt für schritt (step by step)\n const deReasoning = classifyByRules(\n \"Beweisen Sie, dass die Quadratwurzel von 2 irrational ist, Schritt für Schritt\",\n undefined,\n 25,\n config.scoring,\n );\n assert(\n deReasoning.tier === \"REASONING\",\n `German \"beweisen...schritt für schritt\" → ${deReasoning.tier} (should be REASONING)`,\n );\n\n // German simple - hallo (hello) + was ist (what is)\n const deSimple = classifyByRules(\n \"Hallo, was ist maschinelles Lernen?\",\n undefined,\n 10,\n config.scoring,\n );\n assert(\n deSimple.tier === \"SIMPLE\",\n `German \"hallo...was ist\" → ${deSimple.tier} (should be SIMPLE)`,\n );\n\n // German technical - algorithmus (algorithm) + optimieren (optimize)\n const deTech = classifyByRules(\n \"Optimieren Sie den Sortieralgorithmus für eine verteilte Architektur\",\n undefined,\n 20,\n config.scoring,\n );\n assert(\n deTech.tier !== \"SIMPLE\",\n `German \"algorithmus...verteilt\" → ${deTech.tier} (should NOT be SIMPLE)`,\n );\n}\n\n// Issue #50: Large OpenClaw-style system prompt (~6000 tokens) should NOT dominate scoring\n// Previously all requests scored ~0.47 regardless of user intent because the system prompt\n// contained keywords matching nearly every scoring dimension.\n{\n console.log(\"\\nIssue #50: OpenClaw-scale system prompt (should NOT dominate scoring):\");\n\n // Simulate a realistic OpenClaw system prompt with tool definitions, workspace files,\n // skill descriptions, and behavioral rules — all containing scorer keywords\n const openClawSystemPrompt = `You are an AI assistant with access to the following tools:\n\nTool: run - Execute shell commands in the user's terminal. Use this to run tests, build projects, or execute scripts.\n Parameters: command (string, required), timeout (number, optional)\n\nTool: edit - Edit files in the workspace. Supports creating, modifying, and deleting files.\n Parameters: file_path (string), content (string), mode (enum: create, replace, delete)\n\nTool: test - Run the project's test suite. Automatically detects the testing framework (jest, vitest, pytest, etc).\n Parameters: pattern (string, optional), watch (boolean, optional)\n\nTool: deploy - Deploy the application to staging or production environments.\n Parameters: environment (enum: staging, production), config (object, optional)\n\nTool: search - Search for files, functions, or text patterns across the codebase.\n Parameters: query (string), type (enum: file, function, text), regex (boolean)\n\nTool: fix - Apply automated fixes for linting errors, formatting issues, or simple bugs.\n Parameters: file_path (string), rule (string, optional)\n\nTool: build - Build the project using the detected build system (webpack, vite, esbuild, etc).\n Parameters: mode (enum: development, production), clean (boolean)\n\nTool: install - Install project dependencies using the detected package manager.\n Parameters: packages (string[], optional), dev (boolean, optional)\n\nTool: verify - Verify the integrity of the build output and check for common issues.\n Parameters: strict (boolean, optional)\n\nTool: check - Run static analysis and type checking on the codebase.\n Parameters: fix (boolean, optional)\n\nTool: create - Create new files, components, or project structures from templates.\n Parameters: template (string), name (string), path (string, optional)\n\nTool: analyze - Analyze code complexity, dependencies, and architecture patterns.\n Parameters: scope (string, optional), format (enum: json, table, markdown)\n\nTool: generate - Generate code, documentation, or configuration files.\n Parameters: type (string), spec (string)\n\nTool: refactor - Refactor code by extracting functions, renaming symbols, or restructuring modules.\n Parameters: action (string), target (string)\n\nTool: optimize - Optimize code performance, bundle size, or resource usage.\n Parameters: target (string), strategy (string, optional)\n\nTool: debug - Start a debugging session with breakpoints and step-through execution.\n Parameters: file_path (string), line (number, optional)\n\nTool: document - Generate or update documentation for functions, classes, or modules.\n Parameters: scope (string), format (enum: jsdoc, markdown, yaml)\n\nTool: schema - Generate or validate JSON Schema, OpenAPI specs, or database schemas.\n Parameters: input (string), output_format (string)\n\nTool: migrate - Run database migrations or upgrade configuration formats.\n Parameters: direction (enum: up, down), steps (number, optional)\n\nTool: monitor - Monitor application logs, metrics, and health status.\n Parameters: service (string), duration (number, optional)\n\nWorkspace Files:\n- AGENTS.md: Defines agent behavior, tool usage patterns, and decision-making guidelines.\n Contains step-by-step instructions for handling complex multi-step tasks.\n Includes algorithm descriptions for distributed task scheduling.\n\n- SOUL.md: Core behavioral rules. Don't make assumptions. Avoid unnecessary changes.\n At most 3 file edits per turn. Within the scope of the current task only.\n Limit output to what was explicitly requested.\n\n- TOOLS.md: Detailed tool documentation with examples.\n \"Use the edit tool to create new files...\"\n \"Use the build tool to compile the project...\"\n \"import { something } from './module'\"\n \"async function handleRequest() { ... }\"\n \"export default class Controller { ... }\"\n\nSkills:\n1. Code Review - Analyze code quality, suggest improvements, and check for common patterns.\n Step 1: Read the file. Step 2: Identify issues. Step 3: Generate suggestions.\n2. Architecture Design - Design system architecture with diagrams and documentation.\n Covers: microservices, event sourcing, CQRS, distributed systems, kubernetes deployment.\n3. Performance Optimization - Profile and optimize application performance.\n Analyze algorithm complexity, identify bottlenecks, implement caching strategies.\n\nRules:\n- Always confirm before applying destructive changes\n- Don't modify files outside the project directory\n- Avoid making changes that aren't directly requested\n- Do not add unnecessary dependencies\n- Follow the existing code style and conventions`;\n\n // Test 1: Simple greeting — score should be much lower than the broken ~0.47\n // Note: tokenCount dimension legitimately considers total context size (6200 tokens),\n // so MEDIUM is acceptable. The key is scores now DIFFERENTIATE instead of all being ~0.47.\n const ocr1 = classifyByRules(\"What time is it?\", openClawSystemPrompt, 6200, config.scoring);\n assert(\n ocr1.score < 0.2,\n `\"What time is it?\" + OpenClaw prompt → score=${ocr1.score.toFixed(3)} (should be <0.2, was ~0.47 before fix)`,\n );\n\n // Test 2: Simple question — same: score should be well below the broken ~0.47\n const ocr2 = classifyByRules(\"What's the weather?\", openClawSystemPrompt, 6200, config.scoring);\n assert(\n ocr2.score < 0.2,\n `\"What's the weather?\" + OpenClaw prompt → score=${ocr2.score.toFixed(3)} (should be <0.2, was ~0.47 before fix)`,\n );\n\n // Test 3: Complex coding task — should be COMPLEX or higher, not same score as simple\n const ocr3 = classifyByRules(\n \"Build a React component with TypeScript that implements a sortable data table with pagination, filtering, and async data loading from a REST API\",\n openClawSystemPrompt,\n 6250,\n config.scoring,\n );\n assert(\n ocr3.score > ocr1.score,\n `Complex task score (${ocr3.score.toFixed(3)}) > simple task score (${ocr1.score.toFixed(3)}) — scores should differentiate`,\n );\n\n // Test 4: Reasoning task — should be REASONING\n const ocr4 = classifyByRules(\n \"Prove that sqrt(2) is irrational using proof by contradiction, step by step\",\n openClawSystemPrompt,\n 6220,\n config.scoring,\n );\n assert(\n ocr4.tier === \"REASONING\",\n `Reasoning task + OpenClaw prompt → ${ocr4.tier} score=${ocr4.score.toFixed(3)} (should be REASONING)`,\n );\n\n // Test 5: Scores should NOT all be the same (~0.47)\n const scores = [ocr1.score, ocr2.score, ocr3.score, ocr4.score];\n const uniqueScores = new Set(scores.map((s) => s.toFixed(2)));\n assert(\n uniqueScores.size >= 3,\n `${uniqueScores.size} unique scores out of 4 queries (scores: ${scores.map((s) => s.toFixed(3)).join(\", \")}) — should differentiate, not all ~0.47`,\n );\n\n // Test 6: Agentic score should be 0 for non-agentic queries\n assert(\n ocr1.agenticScore === 0,\n `\"What time is it?\" agenticScore=${ocr1.agenticScore} (should be 0, not triggered by system prompt tools)`,\n );\n}\n\n// Override: large context\n{\n console.log(\"\\nOverride: large context:\");\n const r1 = classifyByRules(\"What is 2+2?\", undefined, 150000, config.scoring);\n // The rules classifier doesn't handle the override — that's in router/index.ts\n // But token count should push score up\n console.log(\n ` → 150K tokens \"What is 2+2?\" → tier=${r1.tier ?? \"AMBIGUOUS\"} (score=${r1.score.toFixed(3)}, conf=${r1.confidence.toFixed(3)})`,\n );\n}\n\n// ─── Part 2: Full Router (route function, no LLM classifier — uses mock) ───\n\nconsole.log(\"\\n═══ Part 2: Full Router (rules-only path) ═══\\n\");\n\nconst modelPricing = buildModelPricing();\n\n// Mock payFetch that won't be called (rules handle these clearly)\nconst mockPayFetch = async () => new Response(\"\", { status: 500 });\n\nconst routerOpts = {\n config: DEFAULT_ROUTING_CONFIG,\n modelPricing,\n payFetch: mockPayFetch,\n apiBase: \"http://localhost:0\",\n};\n\nasync function testRoute(prompt: string, label: string, expectedTier?: string) {\n const decision = await route(prompt, undefined, 4096, routerOpts);\n const savingsPct = (decision.savings * 100).toFixed(1);\n if (expectedTier) {\n assert(\n decision.tier === expectedTier,\n `${label} → ${decision.model} (${decision.tier}, ${decision.method}) saved=${savingsPct}%`,\n );\n } else {\n console.log(\n ` → ${label} → ${decision.model} (${decision.tier}, ${decision.method}) saved=${savingsPct}%`,\n );\n }\n return decision;\n}\n\nawait testRoute(\"What is the capital of France?\", \"Simple factual\", \"SIMPLE\");\nawait testRoute(\"Hello, how are you?\", \"Greeting\", \"SIMPLE\");\nawait testRoute(\n \"Prove that sqrt(2) is irrational step by step using proof by contradiction\",\n \"Math proof\",\n \"REASONING\",\n);\n\n// Large context override\n{\n const longPrompt = \"x\".repeat(500000); // ~125K tokens\n const decision = await route(longPrompt, undefined, 4096, routerOpts);\n assert(\n decision.tier === \"COMPLEX\",\n `125K token input → ${decision.tier} (forced COMPLEX override)`,\n );\n}\n\n// Structured output override\n{\n const decision = await route(\n \"What is 2+2?\",\n \"Respond in JSON format with the answer\",\n 4096,\n routerOpts,\n );\n assert(\n decision.tier === \"MEDIUM\" || decision.tier === \"SIMPLE\",\n `Structured output \"What is 2+2?\" → ${decision.tier} (min MEDIUM applied: ${decision.tier !== \"SIMPLE\"})`,\n );\n}\n\n// Cost estimates sanity check\n{\n console.log(\"\\nCost estimate sanity:\");\n const d = await route(\"What is 2+2?\", undefined, 4096, routerOpts);\n assert(d.costEstimate > 0, `Cost estimate > 0: $${d.costEstimate.toFixed(6)}`);\n assert(d.baselineCost > 0, `Baseline cost > 0: $${d.baselineCost.toFixed(6)}`);\n assert(d.savings >= 0 && d.savings <= 1, `Savings in range [0,1]: ${d.savings.toFixed(4)}`);\n assert(\n d.costEstimate <= d.baselineCost,\n `Cost ($${d.costEstimate.toFixed(6)}) <= Baseline ($${d.baselineCost.toFixed(6)})`,\n );\n}\n\n// ─── Part 3: Proxy Startup (requires wallet key) ───\n\nconsole.log(\"\\n═══ Part 3: Proxy Startup ═══\\n\");\n\nconst walletKey = process.env.BLOCKRUN_WALLET_KEY;\nif (!walletKey) {\n console.log(\" Skipped — set BLOCKRUN_WALLET_KEY to test proxy startup\\n\");\n} else {\n try {\n const proxy = await startProxy({\n wallet: walletKey,\n port: 0,\n onReady: (port) => console.log(` Proxy started on port ${port}`),\n onError: (err) => console.error(` Proxy error: ${err.message}`),\n onRouted: (d) => {\n const pct = (d.savings * 100).toFixed(1);\n console.log(` [routed] ${d.model} (${d.tier}) saved=${pct}%`);\n },\n });\n\n // Test health endpoint\n const health = await fetch(`${proxy.baseUrl}/health`);\n const healthData = (await health.json()) as { status: string; wallet: string };\n assert(\n healthData.status === \"ok\",\n `Health check: ${healthData.status}, wallet: ${healthData.wallet}`,\n );\n\n // Send a test chat completion with blockrun/auto\n console.log(\"\\n Sending test request (blockrun/auto)...\");\n try {\n const chatRes = await fetch(`${proxy.baseUrl}/v1/chat/completions`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n model: \"blockrun/auto\",\n messages: [{ role: \"user\", content: \"What is 2+2?\" }],\n max_tokens: 50,\n }),\n });\n\n if (chatRes.ok) {\n const chatData = (await chatRes.json()) as {\n choices?: Array<{ message?: { content?: string } }>;\n };\n const content = chatData.choices?.[0]?.message?.content ?? \"(no content)\";\n console.log(` ✓ Response: ${content.slice(0, 100)}`);\n passed++;\n } else {\n const errText = await chatRes.text();\n console.log(` Response status: ${chatRes.status} — ${errText.slice(0, 200)}`);\n // 402 or payment errors are expected if wallet isn't funded\n if (chatRes.status === 402) {\n console.log(\" (402 = wallet needs USDC funding — routing still worked)\");\n }\n }\n } catch (err) {\n console.log(` Request error: ${err instanceof Error ? err.message : String(err)}`);\n }\n\n await proxy.close();\n console.log(\" Proxy closed.\\n\");\n } catch (err) {\n console.error(` Proxy startup failed: ${err instanceof Error ? err.message : String(err)}`);\n failed++;\n }\n}\n\n// ─── Summary ───\n\nconsole.log(\"═══════════════════════════════════\");\nconsole.log(` ${passed} passed, ${failed} failed`);\nconsole.log(\"═══════════════════════════════════\\n\");\n\nprocess.exit(failed > 0 ? 1 : 0);\n"],"mappings":";AAiBA,SAAS,gBACP,iBACA,YACgB;AAChB,MAAI,kBAAkB,WAAW,QAAQ;AACvC,WAAO,EAAE,MAAM,cAAc,OAAO,IAAM,QAAQ,UAAU,eAAe,WAAW;AAAA,EACxF;AACA,MAAI,kBAAkB,WAAW,SAAS;AACxC,WAAO,EAAE,MAAM,cAAc,OAAO,GAAK,QAAQ,SAAS,eAAe,WAAW;AAAA,EACtF;AACA,SAAO,EAAE,MAAM,cAAc,OAAO,GAAG,QAAQ,KAAK;AACtD;AAEA,SAAS,kBACP,MACA,UACA,MACA,aACA,YACA,QACgB;AAChB,QAAM,UAAU,SAAS,OAAO,CAAC,OAAO,KAAK,SAAS,GAAG,YAAY,CAAC,CAAC;AACvE,MAAI,QAAQ,UAAU,WAAW,MAAM;AACrC,WAAO;AAAA,MACL;AAAA,MACA,OAAO,OAAO;AAAA,MACd,QAAQ,GAAG,WAAW,KAAK,QAAQ,MAAM,GAAG,CAAC,EAAE,KAAK,IAAI,CAAC;AAAA,IAC3D;AAAA,EACF;AACA,MAAI,QAAQ,UAAU,WAAW,KAAK;AACpC,WAAO;AAAA,MACL;AAAA,MACA,OAAO,OAAO;AAAA,MACd,QAAQ,GAAG,WAAW,KAAK,QAAQ,MAAM,GAAG,CAAC,EAAE,KAAK,IAAI,CAAC;AAAA,IAC3D;AAAA,EACF;AACA,SAAO,EAAE,MAAM,OAAO,OAAO,MAAM,QAAQ,KAAK;AAClD;AAEA,SAAS,eAAe,MAA8B;AACpD,QAAM,WAAW,CAAC,gBAAgB,YAAY,QAAQ;AACtD,QAAM,OAAO,SAAS,OAAO,CAAC,MAAM,EAAE,KAAK,IAAI,CAAC;AAChD,MAAI,KAAK,SAAS,GAAG;AACnB,WAAO,EAAE,MAAM,qBAAqB,OAAO,KAAK,QAAQ,aAAa;AAAA,EACvE;AACA,SAAO,EAAE,MAAM,qBAAqB,OAAO,GAAG,QAAQ,KAAK;AAC7D;AAEA,SAAS,wBAAwB,QAAgC;AAC/D,QAAM,SAAS,OAAO,MAAM,KAAK,KAAK,CAAC,GAAG;AAC1C,MAAI,QAAQ,GAAG;AACb,WAAO,EAAE,MAAM,sBAAsB,OAAO,KAAK,QAAQ,GAAG,KAAK,aAAa;AAAA,EAChF;AACA,SAAO,EAAE,MAAM,sBAAsB,OAAO,GAAG,QAAQ,KAAK;AAC9D;AAWA,SAAS,iBACP,MACA,UAC0D;AAC1D,MAAI,aAAa;AACjB,QAAM,UAAoB,CAAC;AAE3B,aAAW,WAAW,UAAU;AAC9B,QAAI,KAAK,SAAS,QAAQ,YAAY,CAAC,GAAG;AACxC;AACA,UAAI,QAAQ,SAAS,GAAG;AACtB,gBAAQ,KAAK,OAAO;AAAA,MACtB;AAAA,IACF;AAAA,EACF;AAGA,MAAI,cAAc,GAAG;AACnB,WAAO;AAAA,MACL,gBAAgB;AAAA,QACd,MAAM;AAAA,QACN,OAAO;AAAA,QACP,QAAQ,YAAY,QAAQ,KAAK,IAAI,CAAC;AAAA,MACxC;AAAA,MACA,cAAc;AAAA,IAChB;AAAA,EACF,WAAW,cAAc,GAAG;AAC1B,WAAO;AAAA,MACL,gBAAgB;AAAA,QACd,MAAM;AAAA,QACN,OAAO;AAAA,QACP,QAAQ,YAAY,QAAQ,KAAK,IAAI,CAAC;AAAA,MACxC;AAAA,MACA,cAAc;AAAA,IAChB;AAAA,EACF,WAAW,cAAc,GAAG;AAC1B,WAAO;AAAA,MACL,gBAAgB;AAAA,QACd,MAAM;AAAA,QACN,OAAO;AAAA,QACP,QAAQ,kBAAkB,QAAQ,KAAK,IAAI,CAAC;AAAA,MAC9C;AAAA,MACA,cAAc;AAAA,IAChB;AAAA,EACF;AAEA,SAAO;AAAA,IACL,gBAAgB,EAAE,MAAM,eAAe,OAAO,GAAG,QAAQ,KAAK;AAAA,IAC9D,cAAc;AAAA,EAChB;AACF;AAIO,SAAS,gBACd,QACA,cACA,iBACAA,SACe;AAIf,QAAM,WAAW,OAAO,YAAY;AAGpC,QAAM,aAA+B;AAAA;AAAA,IAEnC,gBAAgB,iBAAiBA,QAAO,oBAAoB;AAAA,IAC5D;AAAA,MACE;AAAA,MACAA,QAAO;AAAA,MACP;AAAA,MACA;AAAA,MACA,EAAE,KAAK,GAAG,MAAM,EAAE;AAAA,MAClB,EAAE,MAAM,GAAG,KAAK,KAAK,MAAM,EAAI;AAAA,IACjC;AAAA,IACA;AAAA,MACE;AAAA,MACAA,QAAO;AAAA,MACP;AAAA,MACA;AAAA,MACA,EAAE,KAAK,GAAG,MAAM,EAAE;AAAA,MAClB,EAAE,MAAM,GAAG,KAAK,KAAK,MAAM,EAAI;AAAA,IACjC;AAAA,IACA;AAAA,MACE;AAAA,MACAA,QAAO;AAAA,MACP;AAAA,MACA;AAAA,MACA,EAAE,KAAK,GAAG,MAAM,EAAE;AAAA,MAClB,EAAE,MAAM,GAAG,KAAK,KAAK,MAAM,EAAI;AAAA,IACjC;AAAA,IACA;AAAA,MACE;AAAA,MACAA,QAAO;AAAA,MACP;AAAA,MACA;AAAA,MACA,EAAE,KAAK,GAAG,MAAM,EAAE;AAAA,MAClB,EAAE,MAAM,GAAG,KAAK,KAAK,MAAM,IAAI;AAAA,IACjC;AAAA,IACA;AAAA,MACE;AAAA,MACAA,QAAO;AAAA,MACP;AAAA,MACA;AAAA,MACA,EAAE,KAAK,GAAG,MAAM,EAAE;AAAA,MAClB,EAAE,MAAM,GAAG,KAAK,IAAM,MAAM,GAAK;AAAA,IACnC;AAAA,IACA,eAAe,QAAQ;AAAA,IACvB,wBAAwB,MAAM;AAAA;AAAA,IAG9B;AAAA,MACE;AAAA,MACAA,QAAO;AAAA,MACP;AAAA,MACA;AAAA,MACA,EAAE,KAAK,GAAG,MAAM,EAAE;AAAA,MAClB,EAAE,MAAM,GAAG,KAAK,KAAK,MAAM,IAAI;AAAA,IACjC;AAAA,IACA;AAAA,MACE;AAAA,MACAA,QAAO;AAAA,MACP;AAAA,MACA;AAAA,MACA,EAAE,KAAK,GAAG,MAAM,EAAE;AAAA,MAClB,EAAE,MAAM,GAAG,KAAK,KAAK,MAAM,IAAI;AAAA,IACjC;AAAA,IACA;AAAA,MACE;AAAA,MACAA,QAAO;AAAA,MACP;AAAA,MACA;AAAA,MACA,EAAE,KAAK,GAAG,MAAM,EAAE;AAAA,MAClB,EAAE,MAAM,GAAG,KAAK,KAAK,MAAM,IAAI;AAAA,IACjC;AAAA,IACA;AAAA,MACE;AAAA,MACAA,QAAO;AAAA,MACP;AAAA,MACA;AAAA,MACA,EAAE,KAAK,GAAG,MAAM,EAAE;AAAA,MAClB,EAAE,MAAM,GAAG,KAAK,KAAK,MAAM,IAAI;AAAA,IACjC;AAAA,IACA;AAAA,MACE;AAAA,MACAA,QAAO;AAAA,MACP;AAAA,MACA;AAAA,MACA,EAAE,KAAK,GAAG,MAAM,EAAE;AAAA,MAClB,EAAE,MAAM,GAAG,KAAK,KAAK,MAAM,IAAI;AAAA,IACjC;AAAA,IACA;AAAA,MACE;AAAA,MACAA,QAAO;AAAA,MACP;AAAA,MACA;AAAA,MACA,EAAE,KAAK,GAAG,MAAM,EAAE;AAAA,MAClB,EAAE,MAAM,GAAG,KAAK,KAAK,MAAM,IAAI;AAAA,IACjC;AAAA,EACF;AAMA,QAAM,gBAAgB,iBAAiB,UAAUA,QAAO,mBAAmB;AAC3E,aAAW,KAAK,cAAc,cAAc;AAC5C,QAAM,eAAe,cAAc;AAGnC,QAAM,UAAU,WAAW,OAAO,CAAC,MAAM,EAAE,WAAW,IAAI,EAAE,IAAI,CAAC,MAAM,EAAE,MAAO;AAGhF,QAAM,UAAUA,QAAO;AACvB,MAAI,gBAAgB;AACpB,aAAW,KAAK,YAAY;AAC1B,UAAM,IAAI,QAAQ,EAAE,IAAI,KAAK;AAC7B,qBAAiB,EAAE,QAAQ;AAAA,EAC7B;AAIA,QAAM,mBAAmBA,QAAO,kBAAkB;AAAA,IAAO,CAAC,OACxD,SAAS,SAAS,GAAG,YAAY,CAAC;AAAA,EACpC;AAGA,MAAI,iBAAiB,UAAU,GAAG;AAChC,UAAMC,cAAa;AAAA,MACjB,KAAK,IAAI,eAAe,GAAG;AAAA;AAAA,MAC3BD,QAAO;AAAA,IACT;AACA,WAAO;AAAA,MACL,OAAO;AAAA,MACP,MAAM;AAAA,MACN,YAAY,KAAK,IAAIC,aAAY,IAAI;AAAA,MACrC;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAGA,QAAM,EAAE,cAAc,eAAe,iBAAiB,IAAID,QAAO;AACjE,MAAI;AACJ,MAAI;AAEJ,MAAI,gBAAgB,cAAc;AAChC,WAAO;AACP,2BAAuB,eAAe;AAAA,EACxC,WAAW,gBAAgB,eAAe;AACxC,WAAO;AACP,2BAAuB,KAAK,IAAI,gBAAgB,cAAc,gBAAgB,aAAa;AAAA,EAC7F,WAAW,gBAAgB,kBAAkB;AAC3C,WAAO;AACP,2BAAuB,KAAK;AAAA,MAC1B,gBAAgB;AAAA,MAChB,mBAAmB;AAAA,IACrB;AAAA,EACF,OAAO;AACL,WAAO;AACP,2BAAuB,gBAAgB;AAAA,EACzC;AAGA,QAAM,aAAa,oBAAoB,sBAAsBA,QAAO,mBAAmB;AAGvF,MAAI,aAAaA,QAAO,qBAAqB;AAC3C,WAAO,EAAE,OAAO,eAAe,MAAM,MAAM,YAAY,SAAS,cAAc,WAAW;AAAA,EAC3F;AAEA,SAAO,EAAE,OAAO,eAAe,MAAM,YAAY,SAAS,cAAc,WAAW;AACrF;AAMA,SAAS,oBAAoB,UAAkB,WAA2B;AACxE,SAAO,KAAK,IAAI,KAAK,IAAI,CAAC,YAAY,QAAQ;AAChD;;;ACvTA,IAAM,oBAAoB;AAI1B,IAAM,uBAAuB;AAC7B,IAAM,wBAAwB;AAKvB,SAAS,YACd,MACA,YACA,QACA,WACA,aACAE,eACA,sBACA,iBACA,gBACA,cACiB;AACjB,QAAM,aAAa,YAAY,IAAI;AACnC,QAAM,QAAQ,WAAW;AACzB,QAAM,UAAUA,cAAa,IAAI,KAAK;AAGtC,QAAM,aAAa,SAAS,cAAc;AAC1C,QAAM,cAAc,SAAS,eAAe;AAC5C,QAAM,YAAa,uBAAuB,MAAa;AACvD,QAAM,aAAc,kBAAkB,MAAa;AACnD,QAAM,eAAe,YAAY;AAGjC,QAAM,cAAcA,cAAa,IAAI,iBAAiB;AACtD,QAAM,iBAAiB,aAAa,cAAc;AAClD,QAAM,kBAAkB,aAAa,eAAe;AACpD,QAAM,gBAAiB,uBAAuB,MAAa;AAC3D,QAAM,iBAAkB,kBAAkB,MAAa;AACvD,QAAM,eAAe,gBAAgB;AAGrC,QAAM,UACJ,mBAAmB,YACf,IACA,eAAe,IACb,KAAK,IAAI,IAAI,eAAe,gBAAgB,YAAY,IACxD;AAER,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,GAAI,iBAAiB,UAAa,EAAE,aAAa;AAAA,EACnD;AACF;AAKO,SAAS,iBAAiB,MAAY,aAAiD;AAC5F,QAAMC,UAAS,YAAY,IAAI;AAC/B,SAAO,CAACA,QAAO,SAAS,GAAGA,QAAO,QAAQ;AAC5C;AAMO,SAAS,mBACd,OACAD,eACA,sBACA,iBACA,gBACiE;AACjE,QAAM,UAAUA,cAAa,IAAI,KAAK;AAGtC,QAAM,aAAa,SAAS,cAAc;AAC1C,QAAM,cAAc,SAAS,eAAe;AAC5C,QAAM,YAAa,uBAAuB,MAAa;AACvD,QAAM,aAAc,kBAAkB,MAAa;AACnD,QAAM,eAAe,YAAY;AAGjC,QAAM,cAAcA,cAAa,IAAI,iBAAiB;AACtD,QAAM,iBAAiB,aAAa,cAAc;AAClD,QAAM,kBAAkB,aAAa,eAAe;AACpD,QAAM,gBAAiB,uBAAuB,MAAa;AAC3D,QAAM,iBAAkB,kBAAkB,MAAa;AACvD,QAAM,eAAe,gBAAgB;AAGrC,QAAM,UACJ,mBAAmB,YACf,IACA,eAAe,IACb,KAAK,IAAI,IAAI,eAAe,gBAAgB,YAAY,IACxD;AAER,SAAO,EAAE,cAAc,cAAc,QAAQ;AAC/C;AAQO,SAAS,oBACd,QACA,UACAE,sBACU;AACV,MAAI,CAAC,SAAU,QAAO;AACtB,QAAM,WAAW,OAAO,OAAOA,oBAAmB;AAClD,SAAO,SAAS,SAAS,IAAI,WAAW;AAC1C;AAQO,SAAS,eACd,QACA,WACAC,iBACU;AACV,MAAI,CAAC,UAAW,QAAO;AACvB,QAAM,WAAW,OAAO,OAAOA,eAAc;AAC7C,SAAO,SAAS,SAAS,IAAI,WAAW;AAC1C;AAYO,SAAS,yBACd,MACA,aACA,sBACA,kBACU;AACV,QAAM,YAAY,iBAAiB,MAAM,WAAW;AAGpD,QAAM,WAAW,UAAU,OAAO,CAAC,YAAY;AAC7C,UAAM,gBAAgB,iBAAiB,OAAO;AAC9C,QAAI,kBAAkB,QAAW;AAE/B,aAAO;AAAA,IACT;AAEA,WAAO,iBAAiB,uBAAuB;AAAA,EACjD,CAAC;AAID,MAAI,SAAS,WAAW,GAAG;AACzB,WAAO;AAAA,EACT;AAEA,SAAO;AACT;;;ACpLO,IAAM,yBAAwC;AAAA,EACnD,SAAS;AAAA,EAET,YAAY;AAAA,IACV,UAAU;AAAA,IACV,cAAc;AAAA,IACd,gBAAgB;AAAA,IAChB,uBAAuB;AAAA,IACvB,YAAY;AAAA;AAAA,EACd;AAAA,EAEA,SAAS;AAAA,IACP,sBAAsB,EAAE,QAAQ,IAAI,SAAS,IAAI;AAAA;AAAA,IAGjD,cAAc;AAAA;AAAA,MAEZ;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,IACA,mBAAmB;AAAA;AAAA,MAEjB;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,IACA,gBAAgB;AAAA;AAAA,MAEd;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,IACA,mBAAmB;AAAA;AAAA,MAEjB;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,IACA,kBAAkB;AAAA;AAAA,MAEhB;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA;AAAA,IAGA,iBAAiB;AAAA;AAAA,MAEf;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,IACA,sBAAsB;AAAA;AAAA,MAEpB;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,IACA,sBAAsB;AAAA;AAAA,MAEpB;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,IACA,mBAAmB;AAAA;AAAA,MAEjB;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,IACA,kBAAkB;AAAA;AAAA,MAEhB;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,IACA,wBAAwB;AAAA;AAAA,MAEtB;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA;AAAA;AAAA,IAIA,qBAAqB;AAAA;AAAA,MAEnB;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA;AAAA,IAGA,kBAAkB;AAAA,MAChB,YAAY;AAAA,MACZ,cAAc;AAAA,MACd,kBAAkB;AAAA,MAClB,gBAAgB;AAAA,MAChB,iBAAiB;AAAA,MACjB,kBAAkB;AAAA;AAAA,MAClB,mBAAmB;AAAA,MACnB,oBAAoB;AAAA,MACpB,iBAAiB;AAAA,MACjB,iBAAiB;AAAA,MACjB,cAAc;AAAA,MACd,qBAAqB;AAAA,MACrB,oBAAoB;AAAA,MACpB,mBAAmB;AAAA,MACnB,aAAa;AAAA;AAAA,IACf;AAAA;AAAA,IAGA,gBAAgB;AAAA,MACd,cAAc;AAAA,MACd,eAAe;AAAA;AAAA,MACf,kBAAkB;AAAA;AAAA,IACpB;AAAA;AAAA,IAGA,qBAAqB;AAAA;AAAA,IAErB,qBAAqB;AAAA,EACvB;AAAA;AAAA,EAGA,OAAO;AAAA,IACL,QAAQ;AAAA,MACN,SAAS;AAAA;AAAA,MACT,UAAU;AAAA,QACR;AAAA;AAAA,QACA;AAAA;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAAA,IACA,QAAQ;AAAA,MACN,SAAS;AAAA;AAAA,MACT,UAAU;AAAA,QACR;AAAA,QACA;AAAA;AAAA,QACA;AAAA;AAAA,MACF;AAAA,IACF;AAAA,IACA,SAAS;AAAA,MACP,SAAS;AAAA;AAAA,MACT,UAAU;AAAA,QACR;AAAA;AAAA,QACA;AAAA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAAA,IACA,WAAW;AAAA,MACT,SAAS;AAAA;AAAA,MACT,UAAU;AAAA,QACR;AAAA;AAAA,QACA;AAAA;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA;AAAA,EAGA,UAAU;AAAA,IACR,QAAQ;AAAA,MACN,SAAS;AAAA;AAAA,MACT,UAAU,CAAC,gCAAgC,wBAAwB;AAAA,IACrE;AAAA,IACA,QAAQ;AAAA,MACN,SAAS;AAAA;AAAA,MACT,UAAU,CAAC,0BAA0B,qBAAqB;AAAA,IAC5D;AAAA,IACA,SAAS;AAAA,MACP,SAAS;AAAA;AAAA,MACT,UAAU,CAAC,2BAA2B,0BAA0B,iBAAiB;AAAA,IACnF;AAAA,IACA,WAAW;AAAA,MACT,SAAS;AAAA;AAAA,MACT,UAAU,CAAC,4BAA4B;AAAA,IACzC;AAAA,EACF;AAAA;AAAA;AAAA,EAIA,cAAc;AAAA,IACZ,QAAQ;AAAA,MACN,SAAS;AAAA;AAAA,MACT,UAAU;AAAA,QACR;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAAA,IACA,QAAQ;AAAA,MACN,SAAS;AAAA;AAAA,MACT,UAAU;AAAA,QACR;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAAA,IACA,SAAS;AAAA,MACP,SAAS;AAAA;AAAA,MACT,UAAU;AAAA,QACR;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAAA,IACA,WAAW;AAAA,MACT,SAAS;AAAA;AAAA,MACT,UAAU;AAAA,QACR;AAAA,QACA;AAAA,QACA;AAAA;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA;AAAA,EAGA,cAAc;AAAA,IACZ,QAAQ;AAAA,MACN,SAAS;AAAA;AAAA,MACT,UAAU;AAAA,QACR;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAAA,IACA,QAAQ;AAAA,MACN,SAAS;AAAA;AAAA,MACT,UAAU;AAAA,QACR;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAAA,IACA,SAAS;AAAA,MACP,SAAS;AAAA,MACT,UAAU;AAAA,QACR;AAAA;AAAA,QACA;AAAA,QACA;AAAA;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAAA,IACA,WAAW;AAAA,MACT,SAAS;AAAA;AAAA,MACT,UAAU;AAAA,QACR;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA,EAEA,WAAW;AAAA,IACT,uBAAuB;AAAA,IACvB,yBAAyB;AAAA,IACzB,sBAAsB;AAAA,IACtB,aAAa;AAAA,EACf;AACF;;;ACzoCO,SAAS,MACd,QACA,cACA,iBACA,SACiB;AACjB,QAAM,EAAE,QAAAC,SAAQ,cAAAC,cAAa,IAAI;AAGjC,QAAM,WAAW,GAAG,gBAAgB,EAAE,IAAI,MAAM;AAChD,QAAM,kBAAkB,KAAK,KAAK,SAAS,SAAS,CAAC;AAGrD,QAAM,aAAa,gBAAgB,QAAQ,cAAc,iBAAiBD,QAAO,OAAO;AAGxF,QAAM,EAAE,eAAe,IAAI;AAC3B,MAAI;AACJ,MAAI;AAEJ,MAAI,mBAAmB,SAASA,QAAO,UAAU;AAE/C,kBAAcA,QAAO;AACrB,oBAAgB;AAAA,EAClB,WAAW,mBAAmB,aAAaA,QAAO,cAAc;AAE9D,kBAAcA,QAAO;AACrB,oBAAgB;AAAA,EAClB,OAAO;AAKL,UAAM,eAAe,WAAW,gBAAgB;AAChD,UAAM,gBAAgB,gBAAgB;AACtC,UAAM,oBAAoBA,QAAO,UAAU,eAAe;AAC1D,UAAM,mBAAmB,iBAAiB,sBAAsBA,QAAO,gBAAgB;AACvF,kBAAc,kBAAkBA,QAAO,eAAgBA,QAAO;AAC9D,oBAAgB,kBAAkB,eAAe;AAAA,EACnD;AAEA,QAAM,oBAAoB,WAAW;AAGrC,MAAI,kBAAkBA,QAAO,UAAU,uBAAuB;AAC5D,WAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA;AAAA,MACA,iBAAiBA,QAAO,UAAU,qBAAqB,UAAU,aAAa;AAAA,MAC9E;AAAA,MACAC;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAGA,QAAM,sBAAsB,eAAe,0BAA0B,KAAK,YAAY,IAAI;AAE1F,MAAI;AACJ,MAAI;AACJ,QAAM,SAA0B;AAChC,MAAI,YAAY,SAAS,WAAW,MAAM,QAAQ,CAAC,CAAC,MAAM,WAAW,QAAQ,KAAK,IAAI,CAAC;AAEvF,MAAI,WAAW,SAAS,MAAM;AAC5B,WAAO,WAAW;AAClB,iBAAa,WAAW;AAAA,EAC1B,OAAO;AAEL,WAAOD,QAAO,UAAU;AACxB,iBAAa;AACb,iBAAa,4BAA4B,IAAI;AAAA,EAC/C;AAGA,MAAI,qBAAqB;AACvB,UAAM,WAAiC,EAAE,QAAQ,GAAG,QAAQ,GAAG,SAAS,GAAG,WAAW,EAAE;AACxF,UAAM,UAAUA,QAAO,UAAU;AACjC,QAAI,SAAS,IAAI,IAAI,SAAS,OAAO,GAAG;AACtC,mBAAa,kBAAkB,OAAO;AACtC,aAAO;AAAA,IACT;AAAA,EACF;AAGA,eAAa;AAEb,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACAC;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;;;ACjHO,IAAM,gBAAwC;AAAA;AAAA,EAEnD,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,YAAY;AAAA,EACZ,cAAc;AAAA,EACd,cAAc;AAAA,EACd,MAAM;AAAA,EACN,UAAU;AAAA,EACV,YAAY;AAAA,EACZ,YAAY;AAAA,EACZ,OAAO;AAAA;AAAA,EAEP,oBAAoB;AAAA,EACpB,kBAAkB;AAAA,EAClB,mBAAmB;AAAA,EACnB,oBAAoB;AAAA;AAAA,EAEpB,6BAA6B;AAAA,EAC7B,+BAA+B;AAAA,EAC/B,2BAA2B;AAAA,EAC3B,6BAA6B;AAAA,EAC7B,6BAA6B;AAAA,EAC7B,4BAA4B;AAAA,EAC5B,8BAA8B;AAAA;AAAA,EAG9B,KAAK;AAAA,EACL,MAAM;AAAA,EACN,MAAM;AAAA,EACN,OAAO;AAAA,EACP,MAAM;AAAA,EACN,IAAI;AAAA,EACJ,IAAI;AAAA;AAAA,EAGJ,UAAU;AAAA,EACV,UAAU;AAAA;AAAA,EAGV,MAAM;AAAA,EACN,UAAU;AAAA,EACV,aAAa;AAAA;AAAA,EAGb,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,0BAA0B;AAAA,EAC1B,iCAAiC;AAAA;AAAA,EAGjC,MAAM;AAAA,EACN,aAAa;AAAA,EACb,aAAa;AAAA;AAAA,EAGb,QAAQ;AAAA,EACR,YAAY;AAAA;AAAA,EAGZ,SAAS;AAAA;AAAA,EAGT,eAAe;AAAA,EACf,QAAQ;AAAA;AAAA;AAIV;AAWO,SAAS,kBAAkB,OAAuB;AACvD,QAAM,aAAa,MAAM,KAAK,EAAE,YAAY;AAC5C,QAAM,WAAW,cAAc,UAAU;AACzC,MAAI,SAAU,QAAO;AAGrB,MAAI,WAAW,WAAW,WAAW,GAAG;AACtC,UAAM,gBAAgB,WAAW,MAAM,YAAY,MAAM;AACzD,UAAM,wBAAwB,cAAc,aAAa;AACzD,QAAI,sBAAuB,QAAO;AAIlC,WAAO;AAAA,EACT;AAEA,SAAO;AACT;AAwBO,IAAM,kBAAmC;AAAA;AAAA;AAAA,EAG9C;AAAA,IACE,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,YAAY;AAAA,IACZ,aAAa;AAAA,IACb,eAAe;AAAA,IACf,WAAW;AAAA,EACb;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,YAAY;AAAA,IACZ,aAAa;AAAA,IACb,eAAe;AAAA,IACf,WAAW;AAAA,EACb;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,YAAY;AAAA,IACZ,aAAa;AAAA,IACb,eAAe;AAAA,IACf,WAAW;AAAA,EACb;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,YAAY;AAAA,IACZ,aAAa;AAAA,IACb,eAAe;AAAA,IACf,WAAW;AAAA,EACb;AAAA;AAAA,EAGA;AAAA,IACE,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,SAAS;AAAA,IACT,YAAY;AAAA,IACZ,aAAa;AAAA,IACb,eAAe;AAAA,IACf,WAAW;AAAA,IACX,WAAW;AAAA,IACX,QAAQ;AAAA,IACR,SAAS;AAAA,IACT,aAAa;AAAA,EACf;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,SAAS;AAAA,IACT,YAAY;AAAA,IACZ,aAAa;AAAA,IACb,eAAe;AAAA,IACf,WAAW;AAAA,IACX,aAAa;AAAA,EACf;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,SAAS;AAAA,IACT,YAAY;AAAA,IACZ,aAAa;AAAA,IACb,eAAe;AAAA,IACf,WAAW;AAAA,IACX,aAAa;AAAA,EACf;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,SAAS;AAAA,IACT,YAAY;AAAA,IACZ,aAAa;AAAA,IACb,eAAe;AAAA,IACf,WAAW;AAAA,IACX,WAAW;AAAA,IACX,aAAa;AAAA,EACf;AAAA;AAAA,EAGA;AAAA,IACE,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,SAAS;AAAA,IACT,YAAY;AAAA,IACZ,aAAa;AAAA,IACb,eAAe;AAAA,IACf,WAAW;AAAA,IACX,SAAS;AAAA,IACT,aAAa;AAAA,EACf;AAAA;AAAA,EAGA;AAAA,IACE,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,SAAS;AAAA,IACT,YAAY;AAAA,IACZ,aAAa;AAAA,IACb,eAAe;AAAA,IACf,WAAW;AAAA,IACX,QAAQ;AAAA,IACR,aAAa;AAAA,EACf;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,SAAS;AAAA,IACT,YAAY;AAAA,IACZ,aAAa;AAAA,IACb,eAAe;AAAA,IACf,WAAW;AAAA,IACX,aAAa;AAAA,EACf;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,SAAS;AAAA,IACT,YAAY;AAAA,IACZ,aAAa;AAAA,IACb,eAAe;AAAA,IACf,WAAW;AAAA,IACX,aAAa;AAAA,EACf;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,SAAS;AAAA,IACT,YAAY;AAAA,IACZ,aAAa;AAAA,IACb,eAAe;AAAA,IACf,WAAW;AAAA,IACX,QAAQ;AAAA,IACR,SAAS;AAAA,IACT,aAAa;AAAA,EACf;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,SAAS;AAAA,IACT,YAAY;AAAA,IACZ,aAAa;AAAA,IACb,eAAe;AAAA,IACf,WAAW;AAAA,IACX,aAAa;AAAA,EACf;AAAA;AAAA,EAGA;AAAA,IACE,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,SAAS;AAAA,IACT,YAAY;AAAA,IACZ,aAAa;AAAA,IACb,eAAe;AAAA,IACf,WAAW;AAAA,IACX,WAAW;AAAA,IACX,aAAa;AAAA,EACf;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,SAAS;AAAA,IACT,YAAY;AAAA,IACZ,aAAa;AAAA,IACb,eAAe;AAAA,IACf,WAAW;AAAA,IACX,WAAW;AAAA,IACX,aAAa;AAAA,EACf;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,SAAS;AAAA,IACT,YAAY;AAAA,IACZ,aAAa;AAAA,IACb,eAAe;AAAA,IACf,WAAW;AAAA,IACX,WAAW;AAAA,IACX,aAAa;AAAA,EACf;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,SAAS;AAAA,IACT,YAAY;AAAA,IACZ,aAAa;AAAA,IACb,eAAe;AAAA,IACf,WAAW;AAAA,IACX,WAAW;AAAA,IACX,aAAa;AAAA,EACf;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,SAAS;AAAA,IACT,YAAY;AAAA,IACZ,aAAa;AAAA,IACb,eAAe;AAAA,IACf,WAAW;AAAA,IACX,WAAW;AAAA,IACX,aAAa;AAAA,EACf;AAAA;AAAA;AAAA,EAIA;AAAA,IACE,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,SAAS;AAAA,IACT,YAAY;AAAA,IACZ,aAAa;AAAA,IACb,eAAe;AAAA,IACf,WAAW;AAAA,IACX,QAAQ;AAAA,IACR,SAAS;AAAA,IACT,aAAa;AAAA,EACf;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,SAAS;AAAA,IACT,YAAY;AAAA,IACZ,aAAa;AAAA,IACb,eAAe;AAAA,IACf,WAAW;AAAA,IACX,WAAW;AAAA,IACX,QAAQ;AAAA,IACR,SAAS;AAAA,IACT,aAAa;AAAA,EACf;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,SAAS;AAAA,IACT,YAAY;AAAA,IACZ,aAAa;AAAA,IACb,eAAe;AAAA,IACf,WAAW;AAAA,IACX,WAAW;AAAA,IACX,QAAQ;AAAA,IACR,SAAS;AAAA,IACT,aAAa;AAAA,EACf;AAAA;AAAA,EAGA;AAAA,IACE,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,SAAS;AAAA,IACT,YAAY;AAAA,IACZ,aAAa;AAAA,IACb,eAAe;AAAA,IACf,WAAW;AAAA,IACX,WAAW;AAAA,IACX,QAAQ;AAAA,IACR,aAAa;AAAA,EACf;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,SAAS;AAAA,IACT,YAAY;AAAA,IACZ,aAAa;AAAA,IACb,eAAe;AAAA,IACf,WAAW;AAAA,IACX,WAAW;AAAA,IACX,QAAQ;AAAA,IACR,aAAa;AAAA,EACf;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,SAAS;AAAA,IACT,YAAY;AAAA,IACZ,aAAa;AAAA,IACb,eAAe;AAAA,IACf,WAAW;AAAA,IACX,QAAQ;AAAA,IACR,aAAa;AAAA,EACf;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,SAAS;AAAA,IACT,YAAY;AAAA,IACZ,aAAa;AAAA,IACb,eAAe;AAAA,IACf,WAAW;AAAA,IACX,WAAW;AAAA,IACX,QAAQ;AAAA,IACR,aAAa;AAAA,EACf;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,SAAS;AAAA,IACT,YAAY;AAAA,IACZ,aAAa;AAAA,IACb,eAAe;AAAA,IACf,WAAW;AAAA,IACX,QAAQ;AAAA,IACR,aAAa;AAAA,EACf;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,SAAS;AAAA,IACT,YAAY;AAAA,IACZ,aAAa;AAAA,IACb,eAAe;AAAA,IACf,WAAW;AAAA,IACX,aAAa;AAAA,EACf;AAAA;AAAA,EAGA;AAAA,IACE,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,SAAS;AAAA,IACT,YAAY;AAAA,IACZ,aAAa;AAAA,IACb,eAAe;AAAA,IACf,WAAW;AAAA,IACX,aAAa;AAAA,EACf;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,SAAS;AAAA,IACT,YAAY;AAAA,IACZ,aAAa;AAAA,IACb,eAAe;AAAA,IACf,WAAW;AAAA,IACX,WAAW;AAAA,IACX,aAAa;AAAA,EACf;AAAA;AAAA,EAGA;AAAA,IACE,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,SAAS;AAAA,IACT,YAAY;AAAA,IACZ,aAAa;AAAA,IACb,eAAe;AAAA,IACf,WAAW;AAAA,IACX,WAAW;AAAA,IACX,QAAQ;AAAA,IACR,SAAS;AAAA,IACT,aAAa;AAAA,EACf;AAAA;AAAA,EAGA;AAAA,IACE,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,SAAS;AAAA,IACT,YAAY;AAAA,IACZ,aAAa;AAAA,IACb,eAAe;AAAA,IACf,WAAW;AAAA,IACX,WAAW;AAAA,IACX,aAAa;AAAA,EACf;AAAA;AAAA,EAEA;AAAA,IACE,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,SAAS;AAAA,IACT,YAAY;AAAA,IACZ,aAAa;AAAA,IACb,eAAe;AAAA,IACf,WAAW;AAAA,IACX,aAAa;AAAA,EACf;AAAA;AAAA,EAGA;AAAA,IACE,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,SAAS;AAAA,IACT,YAAY;AAAA,IACZ,aAAa;AAAA,IACb,eAAe;AAAA,IACf,WAAW;AAAA,IACX,WAAW;AAAA,IACX,aAAa;AAAA,EACf;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,SAAS;AAAA,IACT,YAAY;AAAA,IACZ,aAAa;AAAA,IACb,eAAe;AAAA,IACf,WAAW;AAAA,IACX,aAAa;AAAA,EACf;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,SAAS;AAAA,IACT,YAAY;AAAA,IACZ,aAAa;AAAA,IACb,eAAe;AAAA,IACf,WAAW;AAAA,IACX,WAAW;AAAA,IACX,aAAa;AAAA,EACf;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,SAAS;AAAA,IACT,YAAY;AAAA,IACZ,aAAa;AAAA,IACb,eAAe;AAAA,IACf,WAAW;AAAA,IACX,aAAa;AAAA,EACf;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,SAAS;AAAA,IACT,YAAY;AAAA,IACZ,aAAa;AAAA,IACb,eAAe;AAAA,IACf,WAAW;AAAA;AAAA;AAAA;AAAA,EAIb;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,SAAS;AAAA,IACT,YAAY;AAAA,IACZ,aAAa;AAAA,IACb,eAAe;AAAA,IACf,WAAW;AAAA,IACX,WAAW;AAAA,IACX,aAAa;AAAA,EACf;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,SAAS;AAAA,IACT,YAAY;AAAA,IACZ,aAAa;AAAA,IACb,eAAe;AAAA,IACf,WAAW;AAAA,IACX,QAAQ;AAAA,IACR,aAAa;AAAA,EACf;AAAA;AAAA,EAGA;AAAA,IACE,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,SAAS;AAAA,IACT,YAAY;AAAA,IACZ,aAAa;AAAA,IACb,eAAe;AAAA,IACf,WAAW;AAAA,IACX,WAAW;AAAA,IACX,SAAS;AAAA,IACT,aAAa;AAAA,EACf;AAAA;AAAA,EAGA;AAAA,IACE,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,SAAS;AAAA,IACT,YAAY;AAAA,IACZ,aAAa;AAAA,IACb,eAAe;AAAA,IACf,WAAW;AAAA;AAAA;AAAA,EAGb;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,SAAS;AAAA,IACT,YAAY;AAAA,IACZ,aAAa;AAAA,IACb,eAAe;AAAA,IACf,WAAW;AAAA,IACX,aAAa;AAAA,EACf;AACF;AAKA,SAAS,gBAAgB,GAAyC;AAChE,SAAO;AAAA,IACL,IAAI,EAAE;AAAA,IACN,MAAM,EAAE;AAAA,IACR,KAAK;AAAA,IACL,WAAW,EAAE,aAAa;AAAA,IAC1B,OAAO,EAAE,SAAS,CAAC,QAAQ,OAAO,IAAI,CAAC,MAAM;AAAA,IAC7C,MAAM;AAAA,MACJ,OAAO,EAAE;AAAA,MACT,QAAQ,EAAE;AAAA,MACV,WAAW;AAAA,MACX,YAAY;AAAA,IACd;AAAA,IACA,eAAe,EAAE;AAAA,IACjB,WAAW,EAAE;AAAA,EACf;AACF;AAMA,IAAM,eAAwC,OAAO,QAAQ,aAAa,EACvE,IAAI,CAAC,CAAC,OAAO,QAAQ,MAAM;AAC1B,QAAM,SAAS,gBAAgB,KAAK,CAAC,MAAM,EAAE,OAAO,QAAQ;AAC5D,MAAI,CAAC,OAAQ,QAAO;AACpB,SAAO,gBAAgB,EAAE,GAAG,QAAQ,IAAI,OAAO,MAAM,GAAG,KAAK,WAAM,OAAO,IAAI,GAAG,CAAC;AACpF,CAAC,EACA,OAAO,CAAC,MAAkC,MAAM,IAAI;AAKhD,IAAM,kBAA2C;AAAA,EACtD,GAAG,gBAAgB,IAAI,eAAe;AAAA,EACtC,GAAG;AACL;AAuCO,SAAS,oBAAoB,SAA0B;AAC5D,QAAM,aAAa,QAAQ,QAAQ,aAAa,EAAE;AAClD,QAAM,QAAQ,gBAAgB,KAAK,CAAC,MAAM,EAAE,OAAO,UAAU;AAC7D,SAAO,OAAO,eAAe;AAC/B;AAMO,SAAS,eAAe,SAA0B;AACvD,QAAM,aAAa,QAAQ,QAAQ,aAAa,EAAE;AAClD,QAAM,QAAQ,gBAAgB,KAAK,CAAC,MAAM,EAAE,OAAO,UAAU;AAC7D,SAAO,OAAO,UAAU;AAC1B;AAMO,SAAS,sBAAsB,SAAqC;AACzE,QAAM,aAAa,QAAQ,QAAQ,aAAa,EAAE;AAClD,QAAM,QAAQ,gBAAgB,KAAK,CAAC,MAAM,EAAE,OAAO,UAAU;AAC7D,SAAO,OAAO;AAChB;AAMO,SAAS,iBAAiB,SAA0B;AACzD,QAAM,aAAa,QAAQ,QAAQ,aAAa,EAAE;AAClD,QAAM,QAAQ,gBAAgB,KAAK,CAAC,MAAM,EAAE,OAAO,UAAU;AAC7D,SAAO,OAAO,aAAa;AAC7B;;;ACntBA,SAAS,oBAA+D;AACxE,SAAS,gBAAgB;AAEzB,SAAS,sBAAAC,qBAAoB,QAAAC,aAAY;AACzC,SAAS,QAAAC,aAAY;AACrB,SAAS,uBAAAC,4BAA2B;AACpC,SAAS,kBAAkB;;;ACf3B,SAAS,sBAAsB;AAS/B,IAAM,iBAAiB;AAIhB,SAAS,0BACd,WACA,QACA,QAAQ,gBACR,SACS;AACT,QAAM,aAAa,IAAI,eAAe,MAAM;AAC5C,QAAM,QAAQ,oBAAI,IAAyB;AAE3C,SAAO,OAAO,OAA0B,SAA0C;AAChF,UAAM,UAAU,IAAI,QAAQ,OAAO,IAAI;AACvC,UAAM,UAAU,IAAI,IAAI,QAAQ,GAAG,EAAE;AAKrC,UAAM,SAAS,CAAC,SAAS,cAAc,MAAM,IAAI,OAAO,IAAI;AAC5D,QAAI,UAAU,KAAK,IAAI,IAAI,OAAO,WAAW,OAAO;AAClD,UAAI;AACF,cAAMC,WAAU,MAAM,OAAO,qBAAqB,OAAO,eAAe;AACxE,cAAM,UAAU,WAAW,6BAA6BA,QAAO;AAC/D,cAAM,iBAAiB,QAAQ,MAAM;AACrC,mBAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,OAAO,GAAG;AAClD,yBAAe,QAAQ,IAAI,KAAK,KAAK;AAAA,QACvC;AACA,cAAMC,YAAW,MAAM,UAAU,cAAc;AAC/C,YAAIA,UAAS,WAAW,KAAK;AAC3B,iBAAOA;AAAA,QACT;AAEA,cAAM,OAAO,OAAO;AAAA,MACtB,QAAQ;AAEN,cAAM,OAAO,OAAO;AAAA,MACtB;AAAA,IACF;AAGA,UAAM,gBAAgB,QAAQ,MAAM;AACpC,UAAM,WAAW,MAAM,UAAU,OAAO;AACxC,QAAI,SAAS,WAAW,KAAK;AAC3B,aAAO;AAAA,IACT;AAGA,QAAI;AACJ,QAAI;AACF,YAAM,YAAY,CAAC,SAAiB,SAAS,QAAQ,IAAI,IAAI;AAC7D,UAAI;AACJ,UAAI;AACF,cAAM,eAAe,MAAM,SAAS,KAAK;AACzC,YAAI,aAAc,QAAO,KAAK,MAAM,YAAY;AAAA,MAClD,QAAQ;AAAA,MAA2B;AACnC,wBAAkB,WAAW,2BAA2B,WAAW,IAAI;AACvE,YAAM,IAAI,SAAS,EAAE,iBAAiB,UAAU,KAAK,IAAI,EAAE,CAAC;AAAA,IAC9D,SAAS,OAAO;AACd,YAAM,IAAI;AAAA,QACR,yCAAyC,iBAAiB,QAAQ,MAAM,UAAU,eAAe;AAAA,MACnG;AAAA,IACF;AAGA,UAAM,UAAU,MAAM,OAAO,qBAAqB,eAAe;AACjE,UAAM,iBAAiB,WAAW,6BAA6B,OAAO;AACtE,eAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,cAAc,GAAG;AACzD,oBAAc,QAAQ,IAAI,KAAK,KAAK;AAAA,IACtC;AACA,WAAO,UAAU,aAAa;AAAA,EAChC;AACF;;;ADjEA,SAAS,8BAA8B;AACvC,SAAS,yBAAyB;;;AEpBlC,SAAS,YAAY,aAAa;AAClC,SAAS,YAAY;AACrB,SAAS,eAAe;AAkBxB,IAAM,UAAU,KAAK,QAAQ,GAAG,aAAa,YAAY,MAAM;AAC/D,IAAI,WAAW;AAEf,eAAe,YAA2B;AACxC,MAAI,SAAU;AACd,QAAM,MAAM,SAAS,EAAE,WAAW,KAAK,CAAC;AACxC,aAAW;AACb;AAKA,eAAsB,SAAS,OAAkC;AAC/D,MAAI;AACF,UAAM,UAAU;AAChB,UAAM,OAAO,MAAM,UAAU,MAAM,GAAG,EAAE;AACxC,UAAM,OAAO,KAAK,SAAS,SAAS,IAAI,QAAQ;AAChD,UAAM,WAAW,MAAM,KAAK,UAAU,KAAK,IAAI,IAAI;AAAA,EACrD,QAAQ;AAAA,EAER;AACF;;;AC5CA,SAAS,eAAe;;;ACAxB,SAAS,YAAY;AACrB,SAAS,UAAU,UAAU,WAAW,iBAAiB;AAGzD,eAAsB,aAAa,UAAmC;AACpE,QAAM,KAAK,MAAM,KAAK,UAAU,GAAG;AACnC,MAAI;AACF,UAAM,MAAM,OAAO,OAAO,MAAM,GAAG,KAAK,GAAG,IAAI;AAC/C,UAAM,GAAG,KAAK,KAAK,GAAG,IAAI,QAAQ,CAAC;AACnC,WAAO,IAAI,SAAS,OAAO;AAAA,EAC7B,UAAE;AACA,UAAM,GAAG,MAAM;AAAA,EACjB;AACF;;;ADXA,SAAS,QAAAC,aAAY;AACrB,SAAS,WAAAC,gBAAe;;;AENxB,SAAS,qBAAqB;AAC9B,SAAS,qBAAqB;AAC9B,SAAS,SAAS,QAAAC,aAAY;AAG9B,IAAM,aAAa,cAAc,YAAY,GAAG;AAChD,IAAM,YAAY,QAAQ,UAAU;AAGpC,IAAMC,WAAU,cAAc,YAAY,GAAG;AAC7C,IAAM,MAAMA,SAAQD,MAAK,WAAW,MAAM,cAAc,CAAC;AAElD,IAAM,UAAU,IAAI;AACpB,IAAM,aAAa,cAAc,OAAO;;;AFH/C,IAAME,WAAUC,MAAKC,SAAQ,GAAG,aAAa,YAAY,MAAM;AAgC/D,eAAe,aAAa,UAAyC;AACnE,MAAI;AACF,UAAM,UAAU,MAAM,aAAa,QAAQ;AAC3C,UAAM,QAAQ,QAAQ,KAAK,EAAE,MAAM,IAAI,EAAE,OAAO,OAAO;AACvD,WAAO,MAAM,IAAI,CAAC,SAAS;AACzB,YAAM,QAAQ,KAAK,MAAM,IAAI;AAE7B,aAAO;AAAA,QACL,WAAW,MAAM,cAAa,oBAAI,KAAK,GAAE,YAAY;AAAA,QACrD,OAAO,MAAM,SAAS;AAAA,QACtB,MAAM,MAAM,QAAQ;AAAA,QACpB,MAAM,MAAM,QAAQ;AAAA,QACpB,cAAc,MAAM,gBAAgB,MAAM,QAAQ;AAAA,QAClD,SAAS,MAAM,WAAW;AAAA,QAC1B,WAAW,MAAM,aAAa;AAAA,MAChC;AAAA,IACF,CAAC;AAAA,EACH,QAAQ;AACN,WAAO,CAAC;AAAA,EACV;AACF;AAKA,eAAe,cAAiC;AAC9C,MAAI;AACF,UAAM,QAAQ,MAAM,QAAQF,QAAO;AACnC,WAAO,MACJ,OAAO,CAAC,MAAM,EAAE,WAAW,QAAQ,KAAK,EAAE,SAAS,QAAQ,CAAC,EAC5D,KAAK,EACL,QAAQ;AAAA,EACb,QAAQ;AACN,WAAO,CAAC;AAAA,EACV;AACF;AAKA,SAAS,aAAa,MAAc,SAAmC;AACrE,QAAM,SAA0D,CAAC;AACjE,QAAM,UAA2D,CAAC;AAClE,MAAI,eAAe;AAEnB,aAAW,SAAS,SAAS;AAE3B,QAAI,CAAC,OAAO,MAAM,IAAI,EAAG,QAAO,MAAM,IAAI,IAAI,EAAE,OAAO,GAAG,MAAM,EAAE;AAClE,WAAO,MAAM,IAAI,EAAE;AACnB,WAAO,MAAM,IAAI,EAAE,QAAQ,MAAM;AAGjC,QAAI,CAAC,QAAQ,MAAM,KAAK,EAAG,SAAQ,MAAM,KAAK,IAAI,EAAE,OAAO,GAAG,MAAM,EAAE;AACtE,YAAQ,MAAM,KAAK,EAAE;AACrB,YAAQ,MAAM,KAAK,EAAE,QAAQ,MAAM;AAEnC,oBAAgB,MAAM;AAAA,EACxB;AAEA,QAAM,YAAY,QAAQ,OAAO,CAAC,KAAK,MAAM,MAAM,EAAE,MAAM,CAAC;AAC5D,QAAM,oBAAoB,QAAQ,OAAO,CAAC,KAAK,MAAM,MAAM,EAAE,cAAc,CAAC;AAE5E,SAAO;AAAA,IACL;AAAA,IACA,eAAe,QAAQ;AAAA,IACvB;AAAA,IACA;AAAA,IACA,cAAc,oBAAoB;AAAA,IAClC,cAAc,QAAQ,SAAS,IAAI,eAAe,QAAQ,SAAS;AAAA,IACnE;AAAA,IACA;AAAA,EACF;AACF;AAKA,eAAsB,SAAS,OAAe,GAA6B;AACzE,QAAM,WAAW,MAAM,YAAY;AACnC,QAAM,cAAc,SAAS,MAAM,GAAG,IAAI;AAE1C,QAAM,iBAA+B,CAAC;AACtC,QAAM,YAA6D,CAAC;AACpE,QAAM,aAA8D,CAAC;AACrE,MAAI,gBAAgB;AACpB,MAAI,YAAY;AAChB,MAAI,oBAAoB;AACxB,MAAI,eAAe;AAEnB,aAAW,QAAQ,aAAa;AAC9B,UAAM,OAAO,KAAK,QAAQ,UAAU,EAAE,EAAE,QAAQ,UAAU,EAAE;AAC5D,UAAM,WAAWC,MAAKD,UAAS,IAAI;AACnC,UAAM,UAAU,MAAM,aAAa,QAAQ;AAE3C,QAAI,QAAQ,WAAW,EAAG;AAE1B,UAAM,WAAW,aAAa,MAAM,OAAO;AAC3C,mBAAe,KAAK,QAAQ;AAE5B,qBAAiB,SAAS;AAC1B,iBAAa,SAAS;AACtB,yBAAqB,SAAS;AAC9B,oBAAgB,SAAS,eAAe,SAAS;AAGjD,eAAW,CAAC,MAAM,KAAK,KAAK,OAAO,QAAQ,SAAS,MAAM,GAAG;AAC3D,UAAI,CAAC,UAAU,IAAI,EAAG,WAAU,IAAI,IAAI,EAAE,OAAO,GAAG,MAAM,EAAE;AAC5D,gBAAU,IAAI,EAAE,SAAS,MAAM;AAC/B,gBAAU,IAAI,EAAE,QAAQ,MAAM;AAAA,IAChC;AAGA,eAAW,CAAC,OAAO,KAAK,KAAK,OAAO,QAAQ,SAAS,OAAO,GAAG;AAC7D,UAAI,CAAC,WAAW,KAAK,EAAG,YAAW,KAAK,IAAI,EAAE,OAAO,GAAG,MAAM,EAAE;AAChE,iBAAW,KAAK,EAAE,SAAS,MAAM;AACjC,iBAAW,KAAK,EAAE,QAAQ,MAAM;AAAA,IAClC;AAAA,EACF;AAGA,QAAM,uBACJ,CAAC;AACH,aAAW,CAAC,MAAM,KAAK,KAAK,OAAO,QAAQ,SAAS,GAAG;AACrD,yBAAqB,IAAI,IAAI;AAAA,MAC3B,GAAG;AAAA,MACH,YAAY,gBAAgB,IAAK,MAAM,QAAQ,gBAAiB,MAAM;AAAA,IACxE;AAAA,EACF;AAEA,QAAM,wBACJ,CAAC;AACH,aAAW,CAAC,OAAO,KAAK,KAAK,OAAO,QAAQ,UAAU,GAAG;AACvD,0BAAsB,KAAK,IAAI;AAAA,MAC7B,GAAG;AAAA,MACH,YAAY,gBAAgB,IAAK,MAAM,QAAQ,gBAAiB,MAAM;AAAA,IACxE;AAAA,EACF;AAEA,QAAM,eAAe,oBAAoB;AACzC,QAAM,oBAAoB,oBAAoB,IAAK,eAAe,oBAAqB,MAAM;AAG7F,MAAI,sBAAsB;AAC1B,aAAW,OAAO,gBAAgB;AAChC,QAAI,IAAI,sBAAsB,IAAI,WAAW;AAC3C,6BAAuB,IAAI;AAAA,IAC7B;AAAA,EACF;AAEA,SAAO;AAAA,IACL,QAAQ,SAAS,IAAI,UAAU,QAAQ,IAAI;AAAA,IAC3C;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,cAAc,gBAAgB,IAAI,eAAe,gBAAgB;AAAA,IACjE,mBAAmB,gBAAgB,IAAI,YAAY,gBAAgB;AAAA,IACnE,QAAQ;AAAA,IACR,SAAS;AAAA,IACT,gBAAgB,eAAe,QAAQ;AAAA;AAAA,IACvC;AAAA;AAAA,EACF;AACF;;;AG1MA,SAAS,kBAAkB;AAa3B,IAAMG,kBAAiB;AACvB,IAAM,gBAAgB;AAMtB,SAAS,aAAa,KAAuB;AAC3C,MAAI,QAAQ,QAAQ,OAAO,QAAQ,UAAU;AAC3C,WAAO;AAAA,EACT;AACA,MAAI,MAAM,QAAQ,GAAG,GAAG;AACtB,WAAO,IAAI,IAAI,YAAY;AAAA,EAC7B;AACA,QAAM,SAAkC,CAAC;AACzC,aAAW,OAAO,OAAO,KAAK,GAAG,EAAE,KAAK,GAAG;AACzC,WAAO,GAAG,IAAI,aAAc,IAAgC,GAAG,CAAC;AAAA,EAClE;AACA,SAAO;AACT;AASA,IAAM,oBAAoB;AAE1B,SAAS,gBAAgB,KAAuB;AAC9C,MAAI,QAAQ,QAAQ,OAAO,QAAQ,UAAU;AAC3C,WAAO;AAAA,EACT;AACA,MAAI,MAAM,QAAQ,GAAG,GAAG;AACtB,WAAO,IAAI,IAAI,eAAe;AAAA,EAChC;AACA,QAAM,SAAkC,CAAC;AACzC,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,GAA8B,GAAG;AACzE,QAAI,QAAQ,aAAa,OAAO,UAAU,UAAU;AAElD,aAAO,GAAG,IAAI,MAAM,QAAQ,mBAAmB,EAAE;AAAA,IACnD,OAAO;AACL,aAAO,GAAG,IAAI,gBAAgB,KAAK;AAAA,IACrC;AAAA,EACF;AACA,SAAO;AACT;AAEO,IAAM,sBAAN,MAA0B;AAAA,EACvB,WAAW,oBAAI,IAA2B;AAAA,EAC1C,YAAY,oBAAI,IAA4B;AAAA,EAC5C;AAAA,EAER,YAAY,QAAQA,iBAAgB;AAClC,SAAK,QAAQ;AAAA,EACf;AAAA;AAAA,EAGA,OAAO,KAAK,MAAsB;AAIhC,QAAI,UAAU;AACd,QAAI;AACF,YAAM,SAAS,KAAK,MAAM,KAAK,SAAS,CAAC;AACzC,YAAM,WAAW,gBAAgB,MAAM;AACvC,YAAM,YAAY,aAAa,QAAQ;AACvC,gBAAU,OAAO,KAAK,KAAK,UAAU,SAAS,CAAC;AAAA,IACjD,QAAQ;AAAA,IAER;AACA,WAAO,WAAW,QAAQ,EAAE,OAAO,OAAO,EAAE,OAAO,KAAK,EAAE,MAAM,GAAG,EAAE;AAAA,EACvE;AAAA;AAAA,EAGA,UAAU,KAAyC;AACjD,UAAM,QAAQ,KAAK,UAAU,IAAI,GAAG;AACpC,QAAI,CAAC,MAAO,QAAO;AACnB,QAAI,KAAK,IAAI,IAAI,MAAM,cAAc,KAAK,OAAO;AAC/C,WAAK,UAAU,OAAO,GAAG;AACzB,aAAO;AAAA,IACT;AACA,WAAO;AAAA,EACT;AAAA;AAAA,EAGA,YAAY,KAAkD;AAC5D,UAAM,QAAQ,KAAK,SAAS,IAAI,GAAG;AACnC,QAAI,CAAC,MAAO,QAAO;AACnB,WAAO,IAAI,QAAwB,CAAC,YAAY;AAC9C,YAAM,UAAU,KAAK,OAAO;AAAA,IAC9B,CAAC;AAAA,EACH;AAAA;AAAA,EAGA,aAAa,KAAmB;AAC9B,SAAK,SAAS,IAAI,KAAK;AAAA,MACrB,WAAW,CAAC;AAAA,IACd,CAAC;AAAA,EACH;AAAA;AAAA,EAGA,SAAS,KAAa,QAA8B;AAElD,QAAI,OAAO,KAAK,UAAU,eAAe;AACvC,WAAK,UAAU,IAAI,KAAK,MAAM;AAAA,IAChC;AAEA,UAAM,QAAQ,KAAK,SAAS,IAAI,GAAG;AACnC,QAAI,OAAO;AACT,iBAAW,WAAW,MAAM,WAAW;AACrC,gBAAQ,MAAM;AAAA,MAChB;AACA,WAAK,SAAS,OAAO,GAAG;AAAA,IAC1B;AAEA,SAAK,MAAM;AAAA,EACb;AAAA;AAAA;AAAA,EAIA,eAAe,KAAmB;AAChC,UAAM,QAAQ,KAAK,SAAS,IAAI,GAAG;AACnC,QAAI,OAAO;AAGT,YAAM,YAAY,OAAO;AAAA,QACvB,KAAK,UAAU;AAAA,UACb,OAAO,EAAE,SAAS,yCAAyC,MAAM,sBAAsB;AAAA,QACzF,CAAC;AAAA,MACH;AACA,iBAAW,WAAW,MAAM,WAAW;AACrC,gBAAQ;AAAA,UACN,QAAQ;AAAA,UACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,UAC9C,MAAM;AAAA,UACN,aAAa,KAAK,IAAI;AAAA,QACxB,CAAC;AAAA,MACH;AACA,WAAK,SAAS,OAAO,GAAG;AAAA,IAC1B;AAAA,EACF;AAAA;AAAA,EAGQ,QAAc;AACpB,UAAM,MAAM,KAAK,IAAI;AACrB,eAAW,CAAC,KAAK,KAAK,KAAK,KAAK,WAAW;AACzC,UAAI,MAAM,MAAM,cAAc,KAAK,OAAO;AACxC,aAAK,UAAU,OAAO,GAAG;AAAA,MAC3B;AAAA,IACF;AAAA,EACF;AACF;;;AC/JA,SAAS,cAAAC,mBAAkB;AAsB3B,IAAM,iBAAgD;AAAA,EACpD,SAAS;AAAA,EACT,YAAY;AAAA,EACZ,aAAa;AAAA;AAAA,EACb,SAAS;AACX;AAMA,SAASC,cAAa,KAAuB;AAC3C,MAAI,QAAQ,QAAQ,OAAO,QAAQ,UAAU;AAC3C,WAAO;AAAA,EACT;AACA,MAAI,MAAM,QAAQ,GAAG,GAAG;AACtB,WAAO,IAAI,IAAIA,aAAY;AAAA,EAC7B;AACA,QAAM,SAAkC,CAAC;AACzC,aAAW,OAAO,OAAO,KAAK,GAAG,EAAE,KAAK,GAAG;AACzC,WAAO,GAAG,IAAIA,cAAc,IAAgC,GAAG,CAAC;AAAA,EAClE;AACA,SAAO;AACT;AAQA,IAAMC,qBAAoB;AAE1B,SAAS,kBAAkB,KAAuD;AAChF,QAAM,SAAkC,CAAC;AAEzC,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,GAAG,GAAG;AAE9C,QAAI,CAAC,UAAU,QAAQ,cAAc,cAAc,EAAE,SAAS,GAAG,GAAG;AAClE;AAAA,IACF;AAEA,QAAI,QAAQ,cAAc,MAAM,QAAQ,KAAK,GAAG;AAE9C,aAAO,GAAG,IAAI,MAAM,IAAI,CAAC,QAAiB;AACxC,YAAI,OAAO,QAAQ,YAAY,QAAQ,MAAM;AAC3C,gBAAM,IAAI;AACV,cAAI,OAAO,EAAE,YAAY,UAAU;AACjC,mBAAO,EAAE,GAAG,GAAG,SAAS,EAAE,QAAQ,QAAQA,oBAAmB,EAAE,EAAE;AAAA,UACnE;AAAA,QACF;AACA,eAAO;AAAA,MACT,CAAC;AAAA,IACH,OAAO;AACL,aAAO,GAAG,IAAI;AAAA,IAChB;AAAA,EACF;AAEA,SAAO;AACT;AAEO,IAAM,gBAAN,MAAoB;AAAA,EACjB,QAAQ,oBAAI,IAA+B;AAAA,EAC3C,iBAA4D,CAAC;AAAA,EAC7D;AAAA;AAAA,EAGA,QAAQ;AAAA,IACd,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,WAAW;AAAA,EACb;AAAA,EAEA,YAAYC,UAA8B,CAAC,GAAG;AAE5C,UAAM,WAAW,OAAO;AAAA,MACtB,OAAO,QAAQA,OAAM,EAAE,OAAO,CAAC,CAAC,EAAE,CAAC,MAAM,MAAM,MAAS;AAAA,IAC1D;AACA,SAAK,SAAS,EAAE,GAAG,gBAAgB,GAAG,SAAS;AAAA,EACjD;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,OAAO,YAAY,MAA+B;AAChD,QAAI;AACF,YAAM,SAAS,KAAK,MAAM,OAAO,SAAS,WAAW,OAAO,KAAK,SAAS,CAAC;AAC3E,YAAM,aAAa,kBAAkB,MAAM;AAC3C,YAAM,YAAYF,cAAa,UAAU;AACzC,YAAM,aAAa,KAAK,UAAU,SAAS;AAC3C,aAAOD,YAAW,QAAQ,EAAE,OAAO,UAAU,EAAE,OAAO,KAAK,EAAE,MAAM,GAAG,EAAE;AAAA,IAC1E,QAAQ;AAEN,YAAM,UAAU,OAAO,SAAS,WAAW,OAAO,KAAK,SAAS;AAChE,aAAOA,YAAW,QAAQ,EAAE,OAAO,OAAO,EAAE,OAAO,KAAK,EAAE,MAAM,GAAG,EAAE;AAAA,IACvE;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,YAAY,MAAuB,SAA2C;AAC5E,QAAI,CAAC,KAAK,OAAO,QAAS,QAAO;AAGjC,QAAI,UAAU,eAAe,GAAG,SAAS,UAAU,GAAG;AACpD,aAAO;AAAA,IACT;AAGA,QAAI;AACF,YAAM,SAAS,KAAK,MAAM,OAAO,SAAS,WAAW,OAAO,KAAK,SAAS,CAAC;AAC3E,UAAI,OAAO,UAAU,SAAS,OAAO,aAAa,MAAM;AACtD,eAAO;AAAA,MACT;AAAA,IACF,QAAQ;AAAA,IAER;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,IAAI,KAA4C;AAC9C,UAAM,QAAQ,KAAK,MAAM,IAAI,GAAG;AAChC,QAAI,CAAC,OAAO;AACV,WAAK,MAAM;AACX,aAAO;AAAA,IACT;AAGA,QAAI,KAAK,IAAI,IAAI,MAAM,WAAW;AAChC,WAAK,MAAM,OAAO,GAAG;AACrB,WAAK,MAAM;AACX,aAAO;AAAA,IACT;AAEA,SAAK,MAAM;AACX,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,IACE,KACA,UAMA,YACM;AAEN,QAAI,CAAC,KAAK,OAAO,WAAW,KAAK,OAAO,WAAW,EAAG;AAGtD,QAAI,SAAS,KAAK,SAAS,KAAK,OAAO,aAAa;AAClD,cAAQ,IAAI,oDAAoD,SAAS,KAAK,MAAM,QAAQ;AAC5F;AAAA,IACF;AAGA,QAAI,SAAS,UAAU,KAAK;AAC1B;AAAA,IACF;AAGA,QAAI,KAAK,MAAM,QAAQ,KAAK,OAAO,SAAS;AAC1C,WAAK,MAAM;AAAA,IACb;AAEA,UAAM,MAAM,KAAK,IAAI;AACrB,UAAM,MAAM,cAAc,KAAK,OAAO;AACtC,UAAM,YAAY,MAAM,MAAM;AAE9B,UAAM,QAA2B;AAAA,MAC/B,GAAG;AAAA,MACH,UAAU;AAAA,MACV;AAAA,IACF;AAEA,SAAK,MAAM,IAAI,KAAK,KAAK;AACzB,SAAK,eAAe,KAAK,EAAE,WAAW,IAAI,CAAC;AAAA,EAC7C;AAAA;AAAA;AAAA;AAAA,EAKQ,QAAc;AACpB,UAAM,MAAM,KAAK,IAAI;AAGrB,SAAK,eAAe,KAAK,CAAC,GAAG,MAAM,EAAE,YAAY,EAAE,SAAS;AAE5D,WAAO,KAAK,eAAe,SAAS,GAAG;AACrC,YAAM,SAAS,KAAK,eAAe,CAAC;AAGpC,YAAM,QAAQ,KAAK,MAAM,IAAI,OAAO,GAAG;AACvC,UAAI,CAAC,SAAS,MAAM,cAAc,OAAO,WAAW;AAElD,aAAK,eAAe,MAAM;AAC1B;AAAA,MACF;AAEA,UAAI,OAAO,aAAa,KAAK;AAE3B,aAAK,MAAM,OAAO,OAAO,GAAG;AAC5B,aAAK,eAAe,MAAM;AAC1B,aAAK,MAAM;AAAA,MACb,OAAO;AAEL;AAAA,MACF;AAAA,IACF;AAGA,WAAO,KAAK,MAAM,QAAQ,KAAK,OAAO,WAAW,KAAK,eAAe,SAAS,GAAG;AAC/E,YAAM,SAAS,KAAK,eAAe,MAAM;AACzC,UAAI,KAAK,MAAM,IAAI,OAAO,GAAG,GAAG;AAC9B,aAAK,MAAM,OAAO,OAAO,GAAG;AAC5B,aAAK,MAAM;AAAA,MACb;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,WAOE;AACA,UAAM,QAAQ,KAAK,MAAM,OAAO,KAAK,MAAM;AAC3C,UAAM,UAAU,QAAQ,KAAM,KAAK,MAAM,OAAO,QAAS,KAAK,QAAQ,CAAC,IAAI,MAAM;AAEjF,WAAO;AAAA,MACL,MAAM,KAAK,MAAM;AAAA,MACjB,SAAS,KAAK,OAAO;AAAA,MACrB,MAAM,KAAK,MAAM;AAAA,MACjB,QAAQ,KAAK,MAAM;AAAA,MACnB,WAAW,KAAK,MAAM;AAAA,MACtB;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,QAAc;AACZ,SAAK,MAAM,MAAM;AACjB,SAAK,iBAAiB,CAAC;AAAA,EACzB;AAAA;AAAA;AAAA;AAAA,EAKA,YAAqB;AACnB,WAAO,KAAK,OAAO;AAAA,EACrB;AACF;;;ACtSA,SAAS,oBAAoB,MAAM,gBAAgB;AACnD,SAAS,YAAY;;;ACgEd,IAAM,WAAN,cAAuB,MAAM;AAAA,EACzB,OAAO;AAAA,EACP;AAAA,EAET,YAAY,SAAiB,eAAyB;AACpD,UAAM,cAAc,OAAO,+BAA+B;AAC1D,SAAK,OAAO;AACZ,SAAK,gBAAgB;AAAA,EACvB;AACF;;;ADrEA,IAAM,YAAY;AAGlB,IAAM,eAAe;AAGd,IAAM,qBAAqB;AAAA;AAAA,EAEhC,oBAAoB;AAAA;AAAA,EAEpB,gBAAgB;AAClB;AAkCO,IAAM,iBAAN,MAAqB;AAAA,EACT;AAAA,EACA;AAAA;AAAA,EAGT,gBAA+B;AAAA;AAAA,EAE/B,WAAW;AAAA,EAEnB,YAAY,eAAuB;AACjC,SAAK,gBAAgB;AACrB,SAAK,SAAS,mBAAmB;AAAA,MAC/B,OAAO;AAAA,MACP,WAAW,KAAK,QAAW;AAAA,QACzB,SAAS;AAAA;AAAA,MACX,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,eAAqC;AACzC,UAAM,MAAM,KAAK,IAAI;AAGrB,QAAI,KAAK,kBAAkB,QAAQ,MAAM,KAAK,WAAW,cAAc;AACrE,aAAO,KAAK,UAAU,KAAK,aAAa;AAAA,IAC1C;AAGA,UAAM,UAAU,MAAM,KAAK,aAAa;AACxC,SAAK,gBAAgB;AACrB,SAAK,WAAW;AAEhB,WAAO,KAAK,UAAU,OAAO;AAAA,EAC/B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,gBAAgB,qBAAyD;AAC7E,UAAM,OAAO,MAAM,KAAK,aAAa;AAErC,QAAI,KAAK,WAAW,qBAAqB;AACvC,aAAO,EAAE,YAAY,MAAM,KAAK;AAAA,IAClC;AAEA,UAAM,YAAY,sBAAsB,KAAK;AAC7C,WAAO;AAAA,MACL,YAAY;AAAA,MACZ;AAAA,MACA,WAAW,KAAK,WAAW,SAAS;AAAA,IACtC;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,gBAAgB,cAA4B;AAC1C,QAAI,KAAK,kBAAkB,QAAQ,KAAK,iBAAiB,cAAc;AACrE,WAAK,iBAAiB;AAAA,IACxB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,aAAmB;AACjB,SAAK,gBAAgB;AACrB,SAAK,WAAW;AAAA,EAClB;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,UAAgC;AACpC,SAAK,WAAW;AAChB,WAAO,KAAK,aAAa;AAAA,EAC3B;AAAA;AAAA;AAAA;AAAA,EAKA,WAAW,cAA8B;AAEvC,UAAM,UAAU,OAAO,YAAY,IAAI;AACvC,WAAO,IAAI,QAAQ,QAAQ,CAAC,CAAC;AAAA,EAC/B;AAAA;AAAA;AAAA;AAAA,EAKA,mBAA2B;AACzB,WAAO,KAAK;AAAA,EACd;AAAA;AAAA,EAGA,MAAc,eAAgC;AAC5C,QAAI;AACF,YAAM,UAAU,MAAM,KAAK,OAAO,aAAa;AAAA,QAC7C,SAAS;AAAA,QACT,KAAK;AAAA,QACL,cAAc;AAAA,QACd,MAAM,CAAC,KAAK,aAAa;AAAA,MAC3B,CAAC;AACD,aAAO;AAAA,IACT,SAAS,OAAO;AAGd,YAAM,IAAI,SAAS,iBAAiB,QAAQ,MAAM,UAAU,iBAAiB,KAAK;AAAA,IACpF;AAAA,EACF;AAAA;AAAA,EAGQ,UAAU,SAA8B;AAC9C,WAAO;AAAA,MACL;AAAA,MACA,YAAY,KAAK,WAAW,OAAO;AAAA,MACnC,OAAO,UAAU,mBAAmB;AAAA,MACpC,SAAS,UAAU,mBAAmB;AAAA,MACtC,eAAe,KAAK;AAAA,IACtB;AAAA,EACF;AACF;;;AE1LA,SAAS,WAAW,YAAY,uBAAuB;AAEvD,IAAM,mBAAmB;AACzB,IAAM,qBAAqB;AAC3B,IAAM,qBAAqB;AAC3B,IAAMI,gBAAe;AAiBd,IAAM,uBAAN,MAA2B;AAAA,EACf;AAAA,EACA;AAAA,EACT,gBAA+B;AAAA,EAC/B,WAAW;AAAA,EAEnB,YAAY,eAAuB,QAAiB;AAClD,SAAK,gBAAgB;AACrB,UAAM,MAAM,UAAU,QAAQ,KAAK,EAAE,6BAA6B;AAClE,SAAK,MAAM,gBAAgB,GAAG;AAAA,EAChC;AAAA,EAEA,MAAM,eAA2C;AAC/C,UAAM,MAAM,KAAK,IAAI;AACrB,QAAI,KAAK,kBAAkB,QAAQ,MAAM,KAAK,WAAWA,eAAc;AACrE,aAAO,KAAK,UAAU,KAAK,aAAa;AAAA,IAC1C;AACA,UAAM,UAAU,MAAM,KAAK,aAAa;AACxC,SAAK,gBAAgB;AACrB,SAAK,WAAW;AAChB,WAAO,KAAK,UAAU,OAAO;AAAA,EAC/B;AAAA,EAEA,gBAAgB,cAA4B;AAC1C,QAAI,KAAK,kBAAkB,QAAQ,KAAK,iBAAiB,cAAc;AACrE,WAAK,iBAAiB;AAAA,IACxB;AAAA,EACF;AAAA,EAEA,aAAmB;AACjB,SAAK,gBAAgB;AACrB,SAAK,WAAW;AAAA,EAClB;AAAA,EAEA,MAAM,UAAsC;AAC1C,SAAK,WAAW;AAChB,WAAO,KAAK,aAAa;AAAA,EAC3B;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,gBAAgB,qBAA+D;AACnF,UAAM,OAAO,MAAM,KAAK,aAAa;AACrC,QAAI,KAAK,WAAW,qBAAqB;AACvC,aAAO,EAAE,YAAY,MAAM,KAAK;AAAA,IAClC;AACA,UAAM,YAAY,sBAAsB,KAAK;AAC7C,WAAO;AAAA,MACL,YAAY;AAAA,MACZ;AAAA,MACA,WAAW,KAAK,WAAW,SAAS;AAAA,IACtC;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,WAAW,cAA8B;AACvC,UAAM,UAAU,OAAO,YAAY,IAAI;AACvC,WAAO,IAAI,QAAQ,QAAQ,CAAC,CAAC;AAAA,EAC/B;AAAA,EAEA,mBAA2B;AACzB,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,MAAc,eAAgC;AAC5C,UAAM,QAAQ,WAAW,KAAK,aAAa;AAC3C,UAAM,OAAO,WAAW,gBAAgB;AAExC,UAAM,aAAa,IAAI,gBAAgB;AACvC,UAAM,QAAQ,WAAW,MAAM,WAAW,MAAM,GAAG,kBAAkB;AAErE,QAAI;AACF,YAAM,WAAW,MAAM,KAAK,IACzB,wBAAwB,OAAO,EAAE,KAAK,GAAG,EAAE,UAAU,aAAa,CAAC,EACnE,KAAK,EAAE,aAAa,WAAW,OAAO,CAAC;AAE1C,UAAI,SAAS,MAAM,WAAW,EAAG,QAAO;AAExC,UAAI,QAAQ;AACZ,iBAAW,WAAW,SAAS,OAAO;AACpC,cAAM,SAAS,QAAQ,QAAQ;AAC/B,iBAAS,OAAO,OAAO,OAAO,KAAK,YAAY,MAAM;AAAA,MACvD;AACA,aAAO;AAAA,IACT,SAAS,KAAK;AACZ,YAAM,IAAI,MAAM,wCAAwC,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC,EAAE;AAAA,IAC5G,UAAE;AACA,mBAAa,KAAK;AAAA,IACpB;AAAA,EACF;AAAA,EAEQ,UAAU,SAAoC;AACpD,UAAM,UAAU,OAAO,OAAO,IAAI;AAClC,WAAO;AAAA,MACL;AAAA,MACA,YAAY,IAAI,QAAQ,QAAQ,CAAC,CAAC;AAAA,MAClC,OAAO,UAAU;AAAA,MACjB,SAAS,UAAU;AAAA,MACnB,eAAe,KAAK;AAAA,IACtB;AAAA,EACF;AACF;;;AC5GA,SAAS,WAAW,SAAAC,cAAa;AAEjC,SAAS,QAAAC,aAAY;AACrB,SAAS,WAAAC,gBAAe;AACxB,SAAS,uBAAAC,4BAA2B;;;ACtBpC,SAAS,aAAa;AACtB,SAAS,kBAAkB,oBAAoB,wBAAwB;AACvE,SAAS,YAAY,eAAe;AACpC,SAAS,2BAA2B;;;AD4BpC,IAAM,aAAaC,MAAKC,SAAQ,GAAG,aAAa,UAAU;AAC1D,IAAM,cAAcD,MAAK,YAAY,YAAY;AACjD,IAAM,gBAAgBA,MAAK,YAAY,UAAU;AACjD,IAAM,aAAaA,MAAK,YAAY,eAAe;AAgRnD,eAAsB,mBAA+C;AACnE,MAAI;AACF,UAAM,WAAW,MAAM,aAAa,UAAU,GAAG,KAAK;AACtD,QAAI,YAAY,SAAU,QAAO;AACjC,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAKA,eAAsB,sBAAkD;AACtE,MAAI,QAAQ,KAAK,EAAE,6BAA6B,SAAU,QAAO;AACjE,MAAI,QAAQ,KAAK,EAAE,6BAA6B,OAAQ,QAAO;AAC/D,SAAO,iBAAiB;AAC1B;;;AE7NO,IAAM,6BAAgD;AAAA,EAC3D,SAAS;AAAA,EACT,aAAa;AAAA,EACb,QAAQ;AAAA,IACN,eAAe;AAAA;AAAA,IACf,YAAY;AAAA;AAAA,IACZ,YAAY;AAAA;AAAA,IACZ,OAAO;AAAA;AAAA,IACP,aAAa;AAAA;AAAA,IACb,aAAa;AAAA;AAAA,IACb,iBAAiB;AAAA;AAAA,EACnB;AAAA,EACA,YAAY;AAAA,IACV,YAAY;AAAA,IACZ,iBAAiB;AAAA,IACjB,uBAAuB;AAAA;AAAA,EACzB;AACF;;;ACnHA,OAAO,YAAY;AAYnB,SAAS,YAAY,SAAoC;AAEvD,MAAI,aAAa;AACjB,MAAI,OAAO,QAAQ,YAAY,UAAU;AACvC,iBAAa,QAAQ;AAAA,EACvB,WAAW,MAAM,QAAQ,QAAQ,OAAO,GAAG;AACzC,iBAAa,KAAK,UAAU,QAAQ,OAAO;AAAA,EAC7C;AAEA,QAAM,QAAQ,CAAC,QAAQ,MAAM,YAAY,QAAQ,gBAAgB,IAAI,QAAQ,QAAQ,EAAE;AAGvF,MAAI,QAAQ,YAAY;AACtB,UAAM;AAAA,MACJ,KAAK;AAAA,QACH,QAAQ,WAAW,IAAI,CAAC,QAAQ;AAAA,UAC9B,MAAM,GAAG,SAAS;AAAA,UAClB,MAAM,GAAG,SAAS;AAAA,QACpB,EAAE;AAAA,MACJ;AAAA,IACF;AAAA,EACF;AAEA,QAAM,UAAU,MAAM,KAAK,GAAG;AAC9B,SAAO,OAAO,WAAW,KAAK,EAAE,OAAO,OAAO,EAAE,OAAO,KAAK;AAC9D;AAaO,SAAS,oBAAoB,UAAoD;AACtF,QAAM,OAAO,oBAAI,IAAY;AAC7B,QAAM,SAA8B,CAAC;AACrC,MAAI,oBAAoB;AAIxB,QAAM,wBAAwB,oBAAI,IAAY;AAC9C,aAAW,WAAW,UAAU;AAC9B,QAAI,QAAQ,SAAS,UAAU,QAAQ,cAAc;AACnD,4BAAsB,IAAI,QAAQ,YAAY;AAAA,IAChD;AAAA,EACF;AAEA,aAAW,WAAW,UAAU;AAE9B,QAAI,QAAQ,SAAS,UAAU;AAC7B,aAAO,KAAK,OAAO;AACnB;AAAA,IACF;AAGA,QAAI,QAAQ,SAAS,QAAQ;AAC3B,aAAO,KAAK,OAAO;AACnB;AAAA,IACF;AAIA,QAAI,QAAQ,SAAS,QAAQ;AAC3B,aAAO,KAAK,OAAO;AACnB;AAAA,IACF;AAIA,QAAI,QAAQ,SAAS,eAAe,QAAQ,YAAY;AACtD,YAAM,wBAAwB,QAAQ,WAAW;AAAA,QAAK,CAAC,OACrD,sBAAsB,IAAI,GAAG,EAAE;AAAA,MACjC;AACA,UAAI,uBAAuB;AAEzB,eAAO,KAAK,OAAO;AACnB;AAAA,MACF;AAAA,IACF;AAGA,UAAM,OAAO,YAAY,OAAO;AAEhC,QAAI,CAAC,KAAK,IAAI,IAAI,GAAG;AACnB,WAAK,IAAI,IAAI;AACb,aAAO,KAAK,OAAO;AAAA,IACrB,OAAO;AACL;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AAAA,IACL,UAAU;AAAA,IACV;AAAA,IACA,eAAe,SAAS;AAAA,EAC1B;AACF;;;ACpGO,SAAS,oBAAoB,SAAyB;AAE3D,MAAI,CAAC,WAAW,OAAO,YAAY,SAAU,QAAO;AAEpD,SACE,QAEG,QAAQ,SAAS,IAAI,EACrB,QAAQ,OAAO,IAAI,EAEnB,QAAQ,WAAW,MAAM,EAEzB,QAAQ,aAAa,EAAE,EAEvB,QAAQ,iBAAiB,KAAK,EAE9B,QAAQ,cAAc,CAAC,UAAU,KAAK,OAAO,KAAK,KAAK,MAAM,SAAS,CAAC,CAAC,CAAC,EAEzE,QAAQ,OAAO,IAAI,EAEnB,KAAK;AAEZ;AAKO,SAAS,4BAA4B,UAAiD;AAC3F,MAAI,aAAa;AAEjB,QAAM,SAAS,SAAS,IAAI,CAAC,YAAY;AAEvC,QAAI,CAAC,QAAQ,WAAW,OAAO,QAAQ,YAAY,SAAU,QAAO;AAEpE,UAAM,iBAAiB,QAAQ,QAAQ;AACvC,UAAM,oBAAoB,oBAAoB,QAAQ,OAAO;AAC7D,kBAAc,iBAAiB,kBAAkB;AAEjD,WAAO;AAAA,MACL,GAAG;AAAA,MACH,SAAS;AAAA,IACX;AAAA,EACF,CAAC;AAED,SAAO;AAAA,IACL,UAAU;AAAA,IACV;AAAA,EACF;AACF;;;AC5DO,IAAM,kBAA0C;AAAA;AAAA,EAErD,OAAO;AAAA;AAAA,EACP,OAAO;AAAA,EACP,OAAO;AAAA,EACP,OAAO;AAAA,EACP,OAAO;AAAA,EACP,OAAO;AAAA,EACP,OAAO;AAAA,EACP,OAAO;AAAA,EACP,OAAO;AAAA,EACP,OAAO;AAAA;AAAA,EAGP,OAAO;AAAA,EACP,OAAO;AAAA,EACP,OAAO;AAAA,EACP,OAAO;AAAA;AAAA,EAGP,MAAM;AAAA,EACN,MAAM;AAAA,EACN,MAAM;AAAA,EACN,MAAM;AAAA,EACN,MAAM;AAAA,EACN,MAAM;AAAA,EACN,MAAM;AAAA;AAAA,EAGN,MAAM;AAAA,EACN,MAAM;AAAA;AAAA,EAGN,MAAM;AAAA,EACN,MAAM;AAAA,EACN,MAAM;AAAA,EACN,MAAM;AAAA,EACN,MAAM;AAAA;AAAA,EAGN,MAAM;AAAA,EACN,MAAM;AAAA,EACN,MAAM;AAAA;AAAA,EAGN,MAAM;AAAA,EACN,MAAM;AAAA,EACN,MAAM;AAAA;AAAA,EAGN,MAAM;AAAA,EACN,MAAM;AAAA;AAAA,EAGN,MAAM;AAAA,EACN,MAAM;AAAA,EACN,MAAM;AAAA,EACN,MAAM;AAAA;AAAA,EAGN,MAAM;AAAA,EACN,MAAM;AAAA,EACN,MAAM;AAAA,EACN,MAAM;AAAA;AAAA,EAGN,MAAM;AAAA,EACN,MAAM;AAAA,EACN,MAAM;AAAA,EACN,MAAM;AAAA,EACN,MAAM;AACR;AAKO,SAAS,qBAA6C;AAC3D,QAAM,UAAkC,CAAC;AACzC,aAAW,CAAC,MAAM,MAAM,KAAK,OAAO,QAAQ,eAAe,GAAG;AAC5D,YAAQ,MAAM,IAAI;AAAA,EACpB;AACA,SAAO;AACT;AAMO,SAAS,uBACd,WACA,UAAkC,CAAC,GAC3B;AACR,MAAI,UAAU,SAAS,KAAK,OAAO,KAAK,OAAO,EAAE,WAAW,GAAG;AAC7D,WAAO;AAAA,EACT;AAEA,QAAM,QAAkB,CAAC;AAGzB,MAAI,UAAU,OAAO,GAAG;AACtB,UAAM,cAAc,MAAM,KAAK,SAAS,EACrC,IAAI,CAAC,SAAS,GAAG,IAAI,IAAI,gBAAgB,IAAI,CAAC,EAAE,EAChD,KAAK,IAAI;AACZ,UAAM,KAAK,UAAU,WAAW,GAAG;AAAA,EACrC;AAGA,MAAI,OAAO,KAAK,OAAO,EAAE,SAAS,GAAG;AACnC,UAAM,cAAc,OAAO,QAAQ,OAAO,EACvC,IAAI,CAAC,CAAC,MAAM,IAAI,MAAM,GAAG,IAAI,IAAI,IAAI,EAAE,EACvC,KAAK,IAAI;AACZ,UAAM,KAAK,WAAW,WAAW,GAAG;AAAA,EACtC;AAEA,SAAO,MAAM,KAAK,IAAI;AACxB;;;ACvGA,SAAS,cACP,SACA,iBACoF;AAEpF,MAAI,CAAC,WAAW,OAAO,YAAY,UAAU;AAC3C,WAAO,EAAE,SAAS,SAAS,eAAe,GAAG,OAAO,oBAAI,IAAI,GAAG,YAAY,EAAE;AAAA,EAC/E;AACA,MAAI,UAAU;AACd,MAAI,gBAAgB;AACpB,MAAI,aAAa;AACjB,QAAM,QAAQ,oBAAI,IAAY;AAG9B,QAAM,UAAU,OAAO,KAAK,eAAe,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,SAAS,EAAE,MAAM;AAE/E,aAAW,UAAU,SAAS;AAC5B,UAAM,OAAO,gBAAgB,MAAM;AACnC,UAAM,QAAQ,IAAI,OAAO,YAAY,MAAM,GAAG,GAAG;AACjD,UAAM,UAAU,QAAQ,MAAM,KAAK;AAEnC,QAAI,WAAW,QAAQ,SAAS,GAAG;AACjC,gBAAU,QAAQ,QAAQ,OAAO,IAAI;AACrC,uBAAiB,QAAQ;AACzB,oBAAc,QAAQ,UAAU,OAAO,SAAS,KAAK;AACrD,YAAM,IAAI,IAAI;AAAA,IAChB;AAAA,EACF;AAEA,SAAO,EAAE,SAAS,eAAe,OAAO,WAAW;AACrD;AAKA,SAAS,YAAY,KAAqB;AACxC,SAAO,IAAI,QAAQ,uBAAuB,MAAM;AAClD;AAKO,SAAS,eAAe,UAAiD;AAC9E,QAAM,kBAAkB,mBAAmB;AAC3C,MAAI,qBAAqB;AACzB,MAAI,kBAAkB;AACtB,QAAM,eAAe,oBAAI,IAAY;AAErC,QAAM,SAAS,SAAS,IAAI,CAAC,YAAY;AAEvC,QAAI,CAAC,QAAQ,WAAW,OAAO,QAAQ,YAAY,SAAU,QAAO;AAEpE,UAAM,EAAE,SAAS,eAAe,OAAO,WAAW,IAAI;AAAA,MACpD,QAAQ;AAAA,MACR;AAAA,IACF;AAEA,0BAAsB;AACtB,uBAAmB;AACnB,UAAM,QAAQ,CAAC,SAAS,aAAa,IAAI,IAAI,CAAC;AAE9C,WAAO;AAAA,MACL,GAAG;AAAA,MACH,SAAS;AAAA,IACX;AAAA,EACF,CAAC;AAED,SAAO;AAAA,IACL,UAAU;AAAA,IACV,mBAAmB;AAAA,IACnB,WAAW;AAAA,IACX,YAAY;AAAA,EACd;AACF;;;AC9EA,IAAM,aAAa;AAKnB,SAAS,aAAa,UAAyC;AAC7D,QAAM,QAAkB,CAAC;AAEzB,aAAW,WAAW,UAAU;AAE9B,QAAI,CAAC,QAAQ,WAAW,OAAO,QAAQ,YAAY,SAAU;AAC7D,UAAM,UAAU,QAAQ,QAAQ,MAAM,UAAU;AAChD,QAAI,SAAS;AACX,YAAM,KAAK,GAAG,OAAO;AAAA,IACvB;AAAA,EACF;AAEA,SAAO;AACT;AAMA,SAAS,qBAAqB,OAA2B;AACvD,QAAM,eAAe,oBAAI,IAAoB;AAE7C,aAAW,QAAQ,OAAO;AACxB,UAAM,QAAQ,KAAK,MAAM,GAAG,EAAE,OAAO,OAAO;AAG5C,aAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,YAAM,SAAS,MAAM,MAAM,MAAM,GAAG,CAAC,EAAE,KAAK,GAAG,IAAI;AACnD,mBAAa,IAAI,SAAS,aAAa,IAAI,MAAM,KAAK,KAAK,CAAC;AAAA,IAC9D;AAAA,EACF;AAGA,SAAO,MAAM,KAAK,aAAa,QAAQ,CAAC,EACrC,OAAO,CAAC,CAAC,EAAE,KAAK,MAAM,SAAS,CAAC,EAChC,KAAK,CAAC,GAAG,MAAM,EAAE,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,MAAM,EACxC,MAAM,GAAG,CAAC,EACV,IAAI,CAAC,CAAC,MAAM,MAAM,MAAM;AAC7B;AAKO,SAAS,aAAa,UAAqD;AAChF,QAAM,WAAW,aAAa,QAAQ;AAEtC,MAAI,SAAS,SAAS,GAAG;AAEvB,WAAO;AAAA,MACL;AAAA,MACA,SAAS,CAAC;AAAA,MACV,YAAY;AAAA,IACd;AAAA,EACF;AAEA,QAAM,WAAW,qBAAqB,QAAQ;AAE9C,MAAI,SAAS,WAAW,GAAG;AACzB,WAAO;AAAA,MACL;AAAA,MACA,SAAS,CAAC;AAAA,MACV,YAAY;AAAA,IACd;AAAA,EACF;AAGA,QAAM,UAAkC,CAAC;AACzC,WAAS,QAAQ,CAAC,QAAQ,MAAM;AAC9B,YAAQ,KAAK,IAAI,CAAC,EAAE,IAAI;AAAA,EAC1B,CAAC;AAGD,MAAI,aAAa;AAEjB,QAAM,SAAS,SAAS,IAAI,CAAC,YAAY;AAEvC,QAAI,CAAC,QAAQ,WAAW,OAAO,QAAQ,YAAY,SAAU,QAAO;AAEpE,QAAI,UAAU,QAAQ;AACtB,UAAM,iBAAiB,QAAQ;AAG/B,eAAW,CAAC,MAAM,MAAM,KAAK,OAAO,QAAQ,OAAO,GAAG;AACpD,gBAAU,QAAQ,MAAM,MAAM,EAAE,KAAK,OAAO,GAAG;AAAA,IACjD;AAEA,kBAAc,iBAAiB,QAAQ;AAEvC,WAAO;AAAA,MACL,GAAG;AAAA,MACH;AAAA,IACF;AAAA,EACF,CAAC;AAED,SAAO;AAAA,IACL,UAAU;AAAA,IACV;AAAA,IACA;AAAA,EACF;AACF;;;ACvGA,SAAS,YAAY,YAA4B;AAC/C,MAAI;AACF,UAAM,SAAS,KAAK,MAAM,UAAU;AACpC,WAAO,KAAK,UAAU,MAAM;AAAA,EAC9B,QAAQ;AAEN,WAAO;AAAA,EACT;AACF;AAKA,SAAS,cAAc,KAAsB;AAC3C,QAAM,UAAU,IAAI,KAAK;AACzB,SACG,QAAQ,WAAW,GAAG,KAAK,QAAQ,SAAS,GAAG,KAC/C,QAAQ,WAAW,GAAG,KAAK,QAAQ,SAAS,GAAG;AAEpD;AAKA,SAAS,iBAAiB,WAAmC;AAC3D,SAAO,UAAU,IAAI,CAAC,QAAQ;AAAA,IAC5B,GAAG;AAAA,IACH,UAAU;AAAA,MACR,GAAG,GAAG;AAAA,MACN,WAAW,YAAY,GAAG,SAAS,SAAS;AAAA,IAC9C;AAAA,EACF,EAAE;AACJ;AASO,SAAS,oBAAoB,UAAkD;AACpF,MAAI,aAAa;AAEjB,QAAM,SAAS,SAAS,IAAI,CAAC,YAAY;AACvC,UAAM,aAAa,EAAE,GAAG,QAAQ;AAGhC,QAAI,QAAQ,cAAc,QAAQ,WAAW,SAAS,GAAG;AACvD,YAAM,iBAAiB,KAAK,UAAU,QAAQ,UAAU,EAAE;AAC1D,iBAAW,aAAa,iBAAiB,QAAQ,UAAU;AAC3D,YAAM,YAAY,KAAK,UAAU,WAAW,UAAU,EAAE;AACxD,oBAAc,iBAAiB;AAAA,IACjC;AAIA,QACE,QAAQ,SAAS,UACjB,QAAQ,WACR,OAAO,QAAQ,YAAY,YAC3B,cAAc,QAAQ,OAAO,GAC7B;AACA,YAAM,iBAAiB,QAAQ,QAAQ;AACvC,YAAM,YAAY,YAAY,QAAQ,OAAO;AAC7C,oBAAc,iBAAiB,UAAU;AACzC,iBAAW,UAAU;AAAA,IACvB;AAEA,WAAO;AAAA,EACT,CAAC;AAED,SAAO;AAAA,IACL,UAAU;AAAA,IACV;AAAA,EACF;AACF;;;AC7EA,IAAM,wBAAwB;AAG9B,IAAM,wBAAwB;AAM9B,SAAS,mBAAmB,SAAyB;AACnD,MAAI,CAAC,WAAW,QAAQ,UAAU,uBAAuB;AACvD,WAAO;AAAA,EACT;AAEA,QAAM,QAAQ,QACX,MAAM,IAAI,EACV,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,EACnB,OAAO,OAAO;AAGjB,QAAM,aAAa,MAAM;AAAA,IACvB,CAAC,MAAM,yDAAyD,KAAK,CAAC,KAAK,EAAE,SAAS;AAAA,EACxF;AAGA,QAAM,cAAc,MAAM;AAAA,IACxB,CAAC,MACC,oEAAoE,KAAK,CAAC,KAAK,EAAE,SAAS;AAAA,EAC9F;AAGA,QAAM,cAAwB,CAAC;AAC/B,QAAM,cAAc;AACpB,MAAI;AACJ,UAAQ,QAAQ,YAAY,KAAK,OAAO,OAAO,MAAM;AACnD,gBAAY,KAAK,GAAG,MAAM,CAAC,CAAC,KAAK,MAAM,CAAC,EAAE,MAAM,GAAG,EAAE,CAAC,EAAE;AAAA,EAC1D;AAGA,QAAM,YAAY,MAAM,CAAC,GAAG,MAAM,GAAG,GAAG;AACxC,QAAM,WAAW,MAAM,SAAS,IAAI,MAAM,MAAM,SAAS,CAAC,GAAG,MAAM,GAAG,GAAG,IAAI;AAG7E,QAAM,QAAkB,CAAC;AAEzB,MAAI,WAAW,SAAS,GAAG;AACzB,UAAM,KAAK,WAAW,WAAW,MAAM,GAAG,CAAC,EAAE,KAAK,KAAK,CAAC;AAAA,EAC1D;AAEA,MAAI,YAAY,SAAS,GAAG;AAC1B,UAAM,KAAK,YAAY,MAAM,GAAG,CAAC,EAAE,KAAK,KAAK,CAAC;AAAA,EAChD;AAEA,MAAI,YAAY,SAAS,GAAG;AAC1B,UAAM,KAAK,YAAY,MAAM,GAAG,CAAC,EAAE,KAAK,IAAI,CAAC;AAAA,EAC/C;AAEA,MAAI,MAAM,WAAW,GAAG;AAEtB,UAAM,KAAK,aAAa,EAAE;AAC1B,QAAI,MAAM,SAAS,GAAG;AACpB,YAAM,KAAK,OAAO,MAAM,SAAS,CAAC,YAAY;AAAA,IAChD;AACA,QAAI,YAAY,aAAa,WAAW;AACtC,YAAM,KAAK,QAAQ;AAAA,IACrB;AAAA,EACF;AAEA,MAAI,SAAS,MAAM,KAAK,IAAI;AAG5B,MAAI,OAAO,SAAS,uBAAuB;AACzC,aAAS,OAAO,MAAM,GAAG,wBAAwB,EAAE,IAAI;AAAA,EACzD;AAEA,SAAO;AACT;AAMA,SAAS,uBAAuB,UAG9B;AACA,QAAM,cAAc,oBAAI,IAAoB;AAC5C,MAAI,aAAa;AAEjB,QAAM,SAAS,SAAS,IAAI,CAAC,KAAK,QAAQ;AAExC,QAAI,CAAC,IAAI,WAAW,OAAO,IAAI,YAAY,YAAY,IAAI,QAAQ,SAAS,KAAK;AAC/E,aAAO;AAAA,IACT;AAGA,UAAM,WAAW,IAAI,QAAQ,MAAM,GAAG,GAAG;AAEzC,QAAI,YAAY,IAAI,QAAQ,GAAG;AAC7B,YAAM,WAAW,YAAY,IAAI,QAAQ;AACzC,YAAM,WAAW,IAAI;AACrB,YAAM,aAAa,iBAAiB,WAAW,CAAC;AAChD,oBAAc,SAAS,SAAS,WAAW;AAC3C,aAAO,EAAE,GAAG,KAAK,SAAS,WAAW;AAAA,IACvC;AAEA,gBAAY,IAAI,UAAU,GAAG;AAC7B,WAAO;AAAA,EACT,CAAC;AAED,SAAO,EAAE,UAAU,QAAQ,WAAW;AACxC;AAKO,SAAS,qBAAqB,UAAkD;AACrF,MAAI,aAAa;AACjB,MAAI,yBAAyB;AAG7B,MAAI,SAAS,SAAS,IAAI,CAAC,QAAQ;AAGjC,QAAI,IAAI,SAAS,UAAU,CAAC,IAAI,WAAW,OAAO,IAAI,YAAY,UAAU;AAC1E,aAAO;AAAA,IACT;AAEA,UAAM,WAAW,IAAI;AACrB,QAAI,SAAS,UAAU,uBAAuB;AAC5C,aAAO;AAAA,IACT;AAEA,UAAM,aAAa,mBAAmB,QAAQ;AAC9C,UAAM,QAAQ,SAAS,SAAS,WAAW;AAE3C,QAAI,QAAQ,IAAI;AACd,oBAAc;AACd;AACA,aAAO,EAAE,GAAG,KAAK,SAAS,WAAW;AAAA,IACvC;AAEA,WAAO;AAAA,EACT,CAAC;AAGD,QAAM,cAAc,uBAAuB,MAAM;AACjD,WAAS,YAAY;AACrB,gBAAc,YAAY;AAE1B,SAAO;AAAA,IACL,UAAU;AAAA,IACV;AAAA,IACA;AAAA,EACF;AACF;;;AC1JA,IAAM,oBAAoB;AAC1B,IAAM,oBAAoB;AAC1B,IAAM,gBAAgB;AACtB,IAAM,cAAc;AACpB,IAAM,cAAc;AAKpB,SAAS,oBAAoB,YAAyC;AACpE,QAAM,UAAU,oBAAI,IAAoB;AAGxC,QAAM,WAAW,WAAW,MAAM,iBAAiB;AAEnD,aAAW,WAAW,UAAU;AAC9B,UAAM,UAAU,QAAQ,KAAK;AAC7B,QAAI,QAAQ,UAAU,qBAAqB,QAAQ,UAAU,mBAAmB;AAC9E,cAAQ,IAAI,UAAU,QAAQ,IAAI,OAAO,KAAK,KAAK,CAAC;AAAA,IACtD;AAAA,EACF;AAGA,QAAM,QAAQ,WAAW,MAAM,IAAI;AACnC,aAAW,QAAQ,OAAO;AACxB,UAAM,UAAU,KAAK,KAAK;AAC1B,QAAI,QAAQ,UAAU,qBAAqB,QAAQ,UAAU,mBAAmB;AAC9E,cAAQ,IAAI,UAAU,QAAQ,IAAI,OAAO,KAAK,KAAK,CAAC;AAAA,IACtD;AAAA,EACF;AAEA,SAAO;AACT;AAKA,SAAS,qBAAqB,UAAuD;AAEnF,MAAI,aAAa;AACjB,aAAW,OAAO,UAAU;AAE1B,QAAI,IAAI,WAAW,OAAO,IAAI,YAAY,UAAU;AAClD,oBAAc,IAAI,UAAU;AAAA,IAC9B;AAAA,EACF;AAGA,QAAM,UAAU,oBAAoB,UAAU;AAG9C,QAAM,aAAwE,CAAC;AAC/E,aAAW,CAAC,QAAQ,KAAK,KAAK,QAAQ,QAAQ,GAAG;AAC/C,QAAI,SAAS,eAAe;AAE1B,YAAM,aAAa;AACnB,YAAM,WAAW,OAAO,SAAS,cAAc;AAC/C,UAAI,UAAU,IAAI;AAChB,mBAAW,KAAK,EAAE,QAAQ,OAAO,QAAQ,CAAC;AAAA,MAC5C;AAAA,IACF;AAAA,EACF;AAGA,aAAW,KAAK,CAAC,GAAG,MAAM,EAAE,UAAU,EAAE,OAAO;AAC/C,QAAM,gBAAgB,WAAW,MAAM,GAAG,WAAW;AAGrD,QAAM,WAAmC,CAAC;AAC1C,gBAAc,QAAQ,CAAC,GAAG,MAAM;AAC9B,UAAM,OAAO,GAAG,WAAW,GAAG,OAAO,IAAI,CAAC,EAAE,SAAS,GAAG,GAAG,CAAC;AAC5D,aAAS,IAAI,IAAI,EAAE;AAAA,EACrB,CAAC;AAED,SAAO;AACT;AAKA,SAASE,aAAY,KAAqB;AAExC,MAAI,CAAC,OAAO,OAAO,QAAQ,SAAU,QAAO;AAC5C,SAAO,IAAI,QAAQ,uBAAuB,MAAM;AAClD;AAKO,SAAS,qBAAqB,UAAsD;AAEzF,QAAM,WAAW,qBAAqB,QAAQ;AAE9C,MAAI,OAAO,KAAK,QAAQ,EAAE,WAAW,GAAG;AACtC,WAAO;AAAA,MACL;AAAA,MACA,YAAY;AAAA,MACZ,cAAc,CAAC;AAAA,MACf,eAAe;AAAA,IACjB;AAAA,EACF;AAGA,QAAM,eAAuC,CAAC;AAC9C,aAAW,CAAC,MAAM,MAAM,KAAK,OAAO,QAAQ,QAAQ,GAAG;AACrD,iBAAa,MAAM,IAAI;AAAA,EACzB;AAGA,QAAM,gBAAgB,OAAO,KAAK,YAAY,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,SAAS,EAAE,MAAM;AAElF,MAAI,aAAa;AACjB,MAAI,gBAAgB;AAGpB,QAAM,SAAS,SAAS,IAAI,CAAC,QAAQ;AAEnC,QAAI,CAAC,IAAI,WAAW,OAAO,IAAI,YAAY,SAAU,QAAO;AAE5D,QAAI,UAAU,IAAI;AAClB,eAAW,UAAU,eAAe;AAClC,YAAM,OAAO,aAAa,MAAM;AAChC,YAAM,QAAQ,IAAI,OAAOA,aAAY,MAAM,GAAG,GAAG;AACjD,YAAM,UAAU,QAAQ,MAAM,KAAK;AACnC,UAAI,SAAS;AACX,kBAAU,QAAQ,QAAQ,OAAO,IAAI;AACrC,uBAAe,OAAO,SAAS,KAAK,UAAU,QAAQ;AACtD,yBAAiB,QAAQ;AAAA,MAC3B;AAAA,IACF;AAEA,WAAO,EAAE,GAAG,KAAK,QAAQ;AAAA,EAC3B,CAAC;AAED,SAAO;AAAA,IACL,UAAU;AAAA,IACV;AAAA,IACA,cAAc;AAAA,IACd;AAAA,EACF;AACF;AAKO,SAAS,8BAA8B,UAA0C;AACtF,MAAI,OAAO,KAAK,QAAQ,EAAE,WAAW,EAAG,QAAO;AAE/C,QAAM,UAAU,OAAO,QAAQ,QAAQ,EACpC,MAAM,GAAG,EAAE,EACX,IAAI,CAAC,CAAC,MAAM,MAAM,MAAM;AAEvB,UAAM,gBAAgB,OAAO,SAAS,KAAK,OAAO,MAAM,GAAG,EAAE,IAAI,QAAQ;AACzE,WAAO,GAAG,IAAI,IAAI,aAAa;AAAA,EACjC,CAAC,EACA,KAAK,IAAI;AAEZ,SAAO,aAAa,OAAO;AAC7B;;;AChJA,SAAS,oBAAoB,UAAuC;AAClE,SAAO,SAAS,OAAO,CAAC,OAAO,QAAQ;AACrC,QAAI,QAAQ;AACZ,QAAI,OAAO,IAAI,YAAY,UAAU;AACnC,cAAQ,IAAI,QAAQ;AAAA,IACtB,WAAW,MAAM,QAAQ,IAAI,OAAO,GAAG;AAErC,cAAQ,KAAK,UAAU,IAAI,OAAO,EAAE;AAAA,IACtC;AACA,QAAI,IAAI,YAAY;AAClB,eAAS,KAAK,UAAU,IAAI,UAAU,EAAE;AAAA,IAC1C;AACA,WAAO,QAAQ;AAAA,EACjB,GAAG,CAAC;AACN;AAKA,SAAS,cAAc,UAAoD;AACzE,SAAO,KAAK,MAAM,KAAK,UAAU,QAAQ,CAAC;AAC5C;AAUA,SAAS,sBACP,UACA,WACA,SACqB;AACrB,QAAM,SAAS,uBAAuB,WAAW,OAAO;AACxD,MAAI,CAAC,OAAQ,QAAO;AAGpB,QAAM,YAAY,SAAS,UAAU,CAAC,MAAM,EAAE,SAAS,MAAM;AAE7D,MAAI,cAAc,IAAI;AAEpB,WAAO,CAAC,EAAE,MAAM,UAAU,SAAS,OAAO,GAAG,GAAG,QAAQ;AAAA,EAC1D;AAGA,SAAO,SAAS,IAAI,CAAC,KAAK,MAAM;AAC9B,QAAI,MAAM,WAAW;AAEnB,UAAI,OAAO,IAAI,YAAY,UAAU;AACnC,eAAO;AAAA,UACL,GAAG;AAAA,UACH,SAAS,GAAG,MAAM;AAAA;AAAA,EAAO,IAAI,OAAO;AAAA,QACtC;AAAA,MACF;AAAA,IAGF;AACA,WAAO;AAAA,EACT,CAAC;AACH;AAcA,eAAsB,gBACpB,UACAC,UAAqC,CAAC,GACV;AAC5B,QAAM,aAAgC;AAAA,IACpC,GAAG;AAAA,IACH,GAAGA;AAAA,IACH,QAAQ;AAAA,MACN,GAAG,2BAA2B;AAAA,MAC9B,GAAGA,QAAO;AAAA,IACZ;AAAA,IACA,YAAY;AAAA,MACV,GAAG,2BAA2B;AAAA,MAC9B,GAAGA,QAAO;AAAA,IACZ;AAAA,EACF;AAGA,MAAI,CAAC,WAAW,SAAS;AACvB,UAAMC,iBAAgB,oBAAoB,QAAQ;AAClD,WAAO;AAAA,MACL;AAAA,MACA,kBAAkB;AAAA,MAClB,eAAAA;AAAA,MACA,iBAAiBA;AAAA,MACjB,kBAAkB;AAAA,MAClB,OAAO;AAAA,QACL,mBAAmB;AAAA,QACnB,sBAAsB;AAAA,QACtB,yBAAyB;AAAA,QACzB,gBAAgB;AAAA,QAChB,oBAAoB;AAAA,QACpB,wBAAwB;AAAA,QACxB,uBAAuB;AAAA,QACvB,sBAAsB;AAAA,QACtB,mBAAmB;AAAA,MACrB;AAAA,MACA,UAAU,CAAC;AAAA,MACX,SAAS,CAAC;AAAA,MACV,cAAc,CAAC;AAAA,IACjB;AAAA,EACF;AAGA,QAAM,mBAAmB,WAAW,cAAc,cAAc,QAAQ,IAAI;AAC5E,QAAM,gBAAgB,oBAAoB,QAAQ;AAGlD,QAAM,QAA0B;AAAA,IAC9B,mBAAmB;AAAA,IACnB,sBAAsB;AAAA,IACtB,yBAAyB;AAAA,IACzB,gBAAgB;AAAA,IAChB,oBAAoB;AAAA,IACpB,wBAAwB;AAAA,IACxB,uBAAuB;AAAA,IACvB,sBAAsB;AAAA,IACtB,mBAAmB;AAAA,EACrB;AAEA,MAAI,SAAS,cAAc,QAAQ;AACnC,MAAI,YAAY,oBAAI,IAAY;AAChC,MAAI,UAAkC,CAAC;AACvC,MAAI,eAAuC,CAAC;AAG5C,MAAI,WAAW,OAAO,eAAe;AACnC,UAAM,cAAc,oBAAoB,MAAM;AAC9C,aAAS,YAAY;AACrB,UAAM,oBAAoB,YAAY;AAAA,EACxC;AAGA,MAAI,WAAW,OAAO,YAAY;AAChC,UAAM,WAAW,4BAA4B,MAAM;AACnD,aAAS,SAAS;AAClB,UAAM,uBAAuB,SAAS;AAAA,EACxC;AAGA,MAAI,WAAW,OAAO,YAAY;AAChC,UAAM,aAAa,eAAe,MAAM;AACxC,aAAS,WAAW;AACpB,UAAM,0BAA0B,WAAW;AAC3C,gBAAY,WAAW;AAAA,EACzB;AAGA,MAAI,WAAW,OAAO,OAAO;AAC3B,UAAM,aAAa,aAAa,MAAM;AACtC,aAAS,WAAW;AACpB,cAAU,WAAW;AACrB,UAAM,iBAAiB,OAAO,KAAK,OAAO,EAAE;AAAA,EAC9C;AAGA,MAAI,WAAW,OAAO,aAAa;AACjC,UAAM,aAAa,oBAAoB,MAAM;AAC7C,aAAS,WAAW;AACpB,UAAM,qBAAqB,WAAW;AAAA,EACxC;AAGA,MAAI,WAAW,OAAO,aAAa;AACjC,UAAM,YAAY,qBAAqB,MAAM;AAC7C,aAAS,UAAU;AACnB,UAAM,yBAAyB,UAAU;AACzC,UAAM,wBAAwB,UAAU;AAAA,EAC1C;AAGA,MAAI,WAAW,OAAO,iBAAiB;AACrC,UAAM,YAAY,qBAAqB,MAAM;AAC7C,aAAS,UAAU;AACnB,UAAM,uBAAuB,UAAU;AACvC,UAAM,oBAAoB,UAAU;AACpC,mBAAe,UAAU;AAAA,EAC3B;AAGA,MACE,WAAW,WAAW,0BACrB,UAAU,OAAO,KAAK,OAAO,KAAK,OAAO,EAAE,SAAS,KAAK,OAAO,KAAK,YAAY,EAAE,SAAS,IAC7F;AACA,aAAS,sBAAsB,QAAQ,WAAW,OAAO;AAEzD,QAAI,OAAO,KAAK,YAAY,EAAE,SAAS,GAAG;AACxC,YAAM,YAAY,8BAA8B,YAAY;AAC5D,UAAI,WAAW;AACb,cAAM,cAAc,OAAO,UAAU,CAAC,MAAM,EAAE,SAAS,QAAQ;AAE/D,YAAI,eAAe,KAAK,OAAO,OAAO,WAAW,EAAE,YAAY,UAAU;AACvE,iBAAO,WAAW,IAAI;AAAA,YACpB,GAAG,OAAO,WAAW;AAAA,YACrB,SAAS,GAAG,SAAS;AAAA,EAAK,OAAO,WAAW,EAAE,OAAO;AAAA,UACvD;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAGA,QAAM,kBAAkB,oBAAoB,MAAM;AAClD,QAAM,mBAAmB,kBAAkB;AAG3C,QAAM,eAAuC,CAAC;AAC9C,YAAU,QAAQ,CAAC,SAAS;AAC1B,iBAAa,IAAI,IAAI,gBAAgB,IAAI;AAAA,EAC3C,CAAC;AAED,SAAO;AAAA,IACL,UAAU;AAAA,IACV;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,UAAU;AAAA,IACV;AAAA,IACA;AAAA,EACF;AACF;AAMO,SAAS,eAAe,UAAwC;AACrE,QAAM,QAAQ,oBAAoB,QAAQ;AAE1C,SAAO,QAAQ;AACjB;;;AClRA,SAAS,cAAAC,mBAAkB;AAuBpB,IAAM,yBAAwC;AAAA,EACnD,SAAS;AAAA,EACT,WAAW,KAAK,KAAK;AAAA;AAAA,EACrB,YAAY;AACd;AAKO,IAAM,eAAN,MAAmB;AAAA,EAChB,WAAsC,oBAAI,IAAI;AAAA,EAC9C;AAAA,EACA,kBAAyD;AAAA,EAEjE,YAAYC,UAAiC,CAAC,GAAG;AAC/C,SAAK,SAAS,EAAE,GAAG,wBAAwB,GAAGA,QAAO;AAGrD,QAAI,KAAK,OAAO,SAAS;AACvB,WAAK,kBAAkB,YAAY,MAAM,KAAK,QAAQ,GAAG,IAAI,KAAK,GAAI;AAAA,IACxE;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,WAAW,WAA6C;AACtD,QAAI,CAAC,KAAK,OAAO,WAAW,CAAC,WAAW;AACtC,aAAO;AAAA,IACT;AAEA,UAAM,QAAQ,KAAK,SAAS,IAAI,SAAS;AACzC,QAAI,CAAC,OAAO;AACV,aAAO;AAAA,IACT;AAGA,UAAM,MAAM,KAAK,IAAI;AACrB,QAAI,MAAM,MAAM,aAAa,KAAK,OAAO,WAAW;AAClD,WAAK,SAAS,OAAO,SAAS;AAC9B,aAAO;AAAA,IACT;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,WAAW,WAAmB,OAAe,MAAoB;AAC/D,QAAI,CAAC,KAAK,OAAO,WAAW,CAAC,WAAW;AACtC;AAAA,IACF;AAEA,UAAM,WAAW,KAAK,SAAS,IAAI,SAAS;AAC5C,UAAM,MAAM,KAAK,IAAI;AAErB,QAAI,UAAU;AACZ,eAAS,aAAa;AACtB,eAAS;AAET,UAAI,SAAS,UAAU,OAAO;AAC5B,iBAAS,QAAQ;AACjB,iBAAS,OAAO;AAAA,MAClB;AAAA,IACF,OAAO;AACL,WAAK,SAAS,IAAI,WAAW;AAAA,QAC3B;AAAA,QACA;AAAA,QACA,WAAW;AAAA,QACX,YAAY;AAAA,QACZ,cAAc;AAAA,QACd,cAAc,CAAC;AAAA,QACf,SAAS;AAAA,QACT,WAAW;AAAA,MACb,CAAC;AAAA,IACH;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,aAAa,WAAyB;AACpC,QAAI,CAAC,KAAK,OAAO,WAAW,CAAC,WAAW;AACtC;AAAA,IACF;AAEA,UAAM,QAAQ,KAAK,SAAS,IAAI,SAAS;AACzC,QAAI,OAAO;AACT,YAAM,aAAa,KAAK,IAAI;AAC5B,YAAM;AAAA,IACR;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,aAAa,WAAyB;AACpC,SAAK,SAAS,OAAO,SAAS;AAAA,EAChC;AAAA;AAAA;AAAA;AAAA,EAKA,WAAiB;AACf,SAAK,SAAS,MAAM;AAAA,EACtB;AAAA;AAAA;AAAA;AAAA,EAKA,WAA2F;AACzF,UAAM,MAAM,KAAK,IAAI;AACrB,UAAM,WAAW,MAAM,KAAK,KAAK,SAAS,QAAQ,CAAC,EAAE,IAAI,CAAC,CAAC,IAAI,KAAK,OAAO;AAAA,MACzE,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI;AAAA,MACrB,OAAO,MAAM;AAAA,MACb,KAAK,KAAK,OAAO,MAAM,MAAM,aAAa,GAAI;AAAA,IAChD,EAAE;AACF,WAAO,EAAE,OAAO,KAAK,SAAS,MAAM,SAAS;AAAA,EAC/C;AAAA;AAAA;AAAA;AAAA,EAKQ,UAAgB;AACtB,UAAM,MAAM,KAAK,IAAI;AACrB,eAAW,CAAC,IAAI,KAAK,KAAK,KAAK,UAAU;AACvC,UAAI,MAAM,MAAM,aAAa,KAAK,OAAO,WAAW;AAClD,aAAK,SAAS,OAAO,EAAE;AAAA,MACzB;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,kBAAkB,WAAmB,MAAuB;AAC1D,UAAM,QAAQ,KAAK,SAAS,IAAI,SAAS;AACzC,QAAI,CAAC,MAAO,QAAO;AAEnB,UAAM,OAAO,MAAM;AACnB,QAAI,KAAK,SAAS,KAAK,KAAK,KAAK,SAAS,CAAC,MAAM,MAAM;AACrD,YAAM;AAAA,IACR,OAAO;AACL,YAAM,UAAU;AAAA,IAClB;AAEA,UAAM,aAAa,KAAK,IAAI;AAC5B,QAAI,MAAM,aAAa,SAAS,GAAG;AACjC,YAAM,aAAa,MAAM;AAAA,IAC3B;AAEA,WAAO,MAAM,WAAW,KAAK,CAAC,MAAM;AAAA,EACtC;AAAA;AAAA;AAAA;AAAA,EAKA,gBACE,WACA,aACwC;AACxC,UAAM,QAAQ,KAAK,SAAS,IAAI,SAAS;AACzC,QAAI,CAAC,MAAO,QAAO;AAEnB,UAAM,aAAa,CAAC,UAAU,UAAU,WAAW,WAAW;AAC9D,UAAM,aAAa,WAAW,QAAQ,MAAM,IAAI;AAChD,QAAI,aAAa,KAAK,cAAc,WAAW,SAAS,EAAG,QAAO;AAElE,UAAM,WAAW,WAAW,aAAa,CAAC;AAC1C,UAAM,aAAa,YAAY,QAAQ;AACvC,QAAI,CAAC,WAAY,QAAO;AAExB,UAAM,QAAQ,WAAW;AACzB,UAAM,OAAO;AACb,UAAM,UAAU;AAChB,UAAM,YAAY;AAElB,WAAO,EAAE,OAAO,WAAW,SAAS,MAAM,SAAS;AAAA,EACrD;AAAA;AAAA;AAAA;AAAA,EAKA,QAAc;AACZ,QAAI,KAAK,iBAAiB;AACxB,oBAAc,KAAK,eAAe;AAClC,WAAK,kBAAkB;AAAA,IACzB;AAAA,EACF;AACF;AAKO,SAAS,aACd,SACA,aAAqB,uBAAuB,YACxB;AACpB,QAAM,QAAQ,QAAQ,UAAU,KAAK,QAAQ,WAAW,YAAY,CAAC;AACrE,MAAI,OAAO,UAAU,YAAY,MAAM,SAAS,GAAG;AACjD,WAAO;AAAA,EACT;AACA,MAAI,MAAM,QAAQ,KAAK,KAAK,MAAM,SAAS,GAAG;AAC5C,WAAO,MAAM,CAAC;AAAA,EAChB;AACA,SAAO;AACT;AAUO,SAAS,gBACd,UACoB;AACpB,QAAM,YAAY,SAAS,KAAK,CAAC,MAAM,EAAE,SAAS,MAAM;AACxD,MAAI,CAAC,UAAW,QAAO;AAEvB,QAAM,UACJ,OAAO,UAAU,YAAY,WAAW,UAAU,UAAU,KAAK,UAAU,UAAU,OAAO;AAI9F,SAAOD,YAAW,QAAQ,EAAE,OAAO,OAAO,EAAE,OAAO,KAAK,EAAE,MAAM,GAAG,CAAC;AACtE;AAOO,SAAS,mBAAmB,iBAAyB,eAAkC;AAC5F,QAAM,aAAa,gBAAgB,QAAQ,QAAQ,GAAG,EAAE,KAAK,EAAE,MAAM,GAAG,GAAG;AAC3E,QAAM,aAAa,eAAe,SAAS,UAAU,cAAc,KAAK,EAAE,KAAK,GAAG,CAAC,KAAK;AACxF,SAAOA,YAAW,QAAQ,EACvB,OAAO,aAAa,UAAU,EAC9B,OAAO,KAAK,EACZ,MAAM,GAAG,EAAE;AAChB;;;AC5QA,IAAM,eAAe;AACrB,IAAM,aAAa;AACnB,IAAM,mBAAmB;AAQzB,SAAS,cAAc,GAAW,GAAmB;AACnD,QAAM,KAAK,EAAE,MAAM,GAAG,EAAE,IAAI,MAAM;AAClC,QAAM,KAAK,EAAE,MAAM,GAAG,EAAE,IAAI,MAAM;AAClC,WAAS,IAAI,GAAG,IAAI,GAAG,KAAK;AAC1B,SAAK,GAAG,CAAC,KAAK,MAAM,GAAG,CAAC,KAAK,GAAI,QAAO;AACxC,SAAK,GAAG,CAAC,KAAK,MAAM,GAAG,CAAC,KAAK,GAAI,QAAO;AAAA,EAC1C;AACA,SAAO;AACT;AAMA,eAAsB,kBAAiC;AACrD,MAAI;AACF,UAAM,aAAa,IAAI,gBAAgB;AACvC,UAAM,UAAU,WAAW,MAAM,WAAW,MAAM,GAAG,gBAAgB;AAErE,UAAM,MAAM,MAAM,MAAM,cAAc;AAAA,MACpC,QAAQ,WAAW;AAAA,MACnB,SAAS,EAAE,QAAQ,mBAAmB;AAAA,IACxC,CAAC;AACD,iBAAa,OAAO;AAEpB,QAAI,CAAC,IAAI,GAAI;AAEb,UAAM,OAAQ,MAAM,IAAI,KAAK;AAC7B,UAAM,SAAS,KAAK;AAEpB,QAAI,CAAC,OAAQ;AAEb,QAAI,cAAc,QAAQ,OAAO,IAAI,GAAG;AACtC,cAAQ,IAAI,EAAE;AACd,cAAQ,IAAI,oCAA0B,MAAM,wBAAwB,OAAO,UAAU;AACrF,cAAQ,IAAI,8BAA8B,UAAU,gBAAgB;AACpE,cAAQ,IAAI,EAAE;AAAA,IAChB;AAAA,EACF,QAAQ;AAAA,EAER;AACF;;;ACnDA,IAAM,eAAe;AAMd,IAAM,cAAc,MAAM;AAC/B,QAAM,UAAU,QAAQ,KAAK,EAAE;AAC/B,MAAI,SAAS;AACX,UAAM,SAAS,SAAS,SAAS,EAAE;AACnC,QAAI,CAAC,MAAM,MAAM,KAAK,SAAS,KAAK,SAAS,OAAO;AAClD,aAAO;AAAA,IACT;AAAA,EACF;AACA,SAAO;AACT,GAAG;;;ACKH,IAAME,kBAAiD;AAAA,EACrD,YAAY;AAAA,EACZ,UAAU,KAAK,KAAK,KAAK;AAAA;AAAA,EACzB,sBAAsB;AACxB;AAEO,IAAM,iBAAN,MAAqB;AAAA,EAClB,WAAwC,oBAAI,IAAI;AAAA,EAChD;AAAA,EAER,YAAYC,SAA+B;AACzC,SAAK,SAAS,EAAE,GAAGD,iBAAgB,GAAGC,QAAO;AAAA,EAC/C;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,cAAc,SAA2B;AACvC,QAAI,CAAC,WAAW,OAAO,YAAY,UAAU;AAC3C,aAAO,CAAC;AAAA,IACV;AAEA,UAAM,SAAmB,CAAC;AAC1B,UAAM,OAAO,oBAAI,IAAY;AAI7B,UAAM,WAAW;AAAA;AAAA,MAEf;AAAA;AAAA,MAEA;AAAA;AAAA,MAEA;AAAA;AAAA,MAEA;AAAA;AAAA,MAEA;AAAA;AAAA,MAEA;AAAA,IACF;AAEA,eAAW,WAAW,UAAU;AAE9B,cAAQ,YAAY;AAEpB,UAAI;AACJ,cAAQ,QAAQ,QAAQ,KAAK,OAAO,OAAO,MAAM;AAC/C,cAAM,SAAS,MAAM,CAAC,EAAE,KAAK;AAG7B,cAAM,aAAa,OAAO,YAAY;AACtC,YAAI,KAAK,IAAI,UAAU,GAAG;AACxB;AAAA,QACF;AAGA,YAAI,OAAO,UAAU,MAAM,OAAO,UAAU,KAAK;AAC/C,iBAAO,KAAK,MAAM;AAClB,eAAK,IAAI,UAAU;AAAA,QACrB;AAGA,YAAI,OAAO,UAAU,KAAK,OAAO,sBAAsB;AACrD;AAAA,QACF;AAAA,MACF;AAEA,UAAI,OAAO,UAAU,KAAK,OAAO,sBAAsB;AACrD;AAAA,MACF;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,OAAO,WAAmB,QAAkB,OAAsB;AAChE,QAAI,CAAC,aAAa,CAAC,OAAO,QAAQ;AAChC;AAAA,IACF;AAEA,UAAM,UAAU,KAAK,SAAS,IAAI,SAAS,KAAK,CAAC;AACjD,UAAM,MAAM,KAAK,IAAI;AAErB,eAAW,UAAU,QAAQ;AAC3B,cAAQ,KAAK;AAAA,QACX,WAAW;AAAA,QACX;AAAA,QACA;AAAA,MACF,CAAC;AAAA,IACH;AAGA,UAAM,SAAS,MAAM,KAAK,OAAO;AACjC,UAAM,UAAU,QAAQ,OAAO,CAAC,MAAM,EAAE,YAAY,MAAM,EAAE,MAAM,CAAC,KAAK,OAAO,UAAU;AAEzF,SAAK,SAAS,IAAI,WAAW,OAAO;AAAA,EACtC;AAAA;AAAA;AAAA;AAAA,EAKA,aAAa,iBAAkC;AAC7C,QAAI,CAAC,mBAAmB,OAAO,oBAAoB,UAAU;AAC3D,aAAO;AAAA,IACT;AAEA,UAAM,QAAQ,gBAAgB,YAAY;AAG1C,UAAM,WAAW;AAAA;AAAA,MAEf;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAEA,WAAO,SAAS,KAAK,CAAC,MAAM,MAAM,SAAS,CAAC,CAAC;AAAA,EAC/C;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,OAAO,WAAkC;AACvC,UAAM,UAAU,KAAK,SAAS,IAAI,SAAS;AAC3C,QAAI,CAAC,SAAS,QAAQ;AACpB,aAAO;AAAA,IACT;AAEA,UAAM,QAAQ,QAAQ,IAAI,CAAC,MAAM;AAC/B,YAAM,OAAO,IAAI,KAAK,EAAE,SAAS,EAAE,mBAAmB,SAAS;AAAA,QAC7D,MAAM;AAAA,QACN,QAAQ;AAAA,QACR,QAAQ;AAAA,MACV,CAAC;AACD,aAAO,KAAK,IAAI,KAAK,EAAE,MAAM;AAAA,IAC/B,CAAC;AAED,WAAO;AAAA,EAAmC,MAAM,KAAK,IAAI,CAAC;AAAA,EAC5D;AAAA;AAAA;AAAA;AAAA,EAKA,WAAW,WAAmC;AAC5C,WAAO,KAAK,SAAS,IAAI,SAAS,KAAK,CAAC;AAAA,EAC1C;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,WAAyB;AAC7B,SAAK,SAAS,OAAO,SAAS;AAAA,EAChC;AAAA;AAAA;AAAA;AAAA,EAKA,WAAiB;AACf,SAAK,SAAS,MAAM;AAAA,EACtB;AAAA;AAAA;AAAA;AAAA,EAKA,WAAuD;AACrD,QAAI,eAAe;AACnB,eAAW,WAAW,KAAK,SAAS,OAAO,GAAG;AAC5C,sBAAgB,QAAQ;AAAA,IAC1B;AACA,WAAO;AAAA,MACL,UAAU,KAAK,SAAS;AAAA,MACxB;AAAA,IACF;AAAA,EACF;AACF;;;A1BjJA,IAAM,eAAe;AACrB,IAAM,sBAAsB;AAE5B,IAAM,aAAa;AAEnB,IAAM,mBAAmB,oBAAI,IAAI;AAAA,EAC/B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AACD,IAAM,aAAa;AACnB,IAAM,eAAe;AACrB,IAAM,mBAAmB;AACzB,IAAM,wBAAwB;AAC9B,IAAM,6BAA6B;AACnC,IAAM,wBAAwB;AAC9B,IAAM,0BAA0B;AAChC,IAAM,yBAAyB;AAC/B,IAAM,sBAAsB;AAC5B,IAAM,sBAAsB;AAM5B,SAAS,sBAAsB,WAA2B;AACxD,MAAI;AAEF,UAAM,SAAS,KAAK,MAAM,SAAS;AAMnC,QAAI,OAAO,UAAU,iCAAiC,OAAO,SAAS;AAGpE,YAAM,QAAQ,OAAO,QAAQ,MAAM,kCAAkC;AACrE,UAAI,OAAO;AACT,cAAM,YAAY,KAAK,MAAM,MAAM,CAAC,CAAC;AAMrC,YAAI,UAAU,kBAAkB,wBAAwB,UAAU,gBAAgB;AAEhF,gBAAM,eAAe,UAAU,eAAe;AAAA,YAC5C;AAAA,UACF;AACA,cAAI,cAAc;AAChB,kBAAM,gBAAgB,SAAS,aAAa,CAAC,GAAG,EAAE;AAClD,kBAAM,iBAAiB,SAAS,aAAa,CAAC,GAAG,EAAE;AACnD,kBAAM,cAAc,gBAAgB,KAAW,QAAQ,CAAC;AACxD,kBAAM,eAAe,iBAAiB,KAAW,QAAQ,CAAC;AAC1D,kBAAM,SAAS,UAAU,SAAS;AAClC,kBAAM,cACJ,OAAO,SAAS,KAAK,GAAG,OAAO,MAAM,GAAG,CAAC,CAAC,MAAM,OAAO,MAAM,EAAE,CAAC,KAAK;AAEvE,mBAAO,KAAK,UAAU;AAAA,cACpB,OAAO;AAAA,gBACL,SAAS,wCAAwC,UAAU,iBAAiB,WAAW;AAAA,gBACvF,MAAM;AAAA,gBACN;AAAA,gBACA,qBAAqB;AAAA,gBACrB,cAAc;AAAA,gBACd,MAAM,eAAe,WAAW;AAAA,cAClC;AAAA,YACF,CAAC;AAAA,UACH;AAAA,QACF;AAGA,YAAI,UAAU,kBAAkB,mBAAmB;AACjD,iBAAO,KAAK,UAAU;AAAA,YACpB,OAAO;AAAA,cACL,SAAS;AAAA,cACT,MAAM;AAAA,cACN,MAAM;AAAA,YACR;AAAA,UACF,CAAC;AAAA,QACH;AAAA,MACF;AAAA,IACF;AAGA,QAAI,OAAO,UAAU,uBAAuB,OAAO,SAAS,SAAS,mBAAmB,GAAG;AACzF,YAAM,UAAU,OAAO,WAAW;AAClC,YAAM,WAAW,QAAQ,SAAS,wBAAwB;AAE1D,aAAO,KAAK,UAAU;AAAA,QACpB,OAAO;AAAA,UACL,SAAS,WACL,gEACA;AAAA,UACJ,MAAM;AAAA,UACN,MAAM;AAAA,QACR;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACF,QAAQ;AAAA,EAER;AACA,SAAO;AACT;AAMA,IAAM,oBAAoB,oBAAI,IAAoB;AAKlD,SAAS,cAAc,SAA0B;AAC/C,QAAM,UAAU,kBAAkB,IAAI,OAAO;AAC7C,MAAI,CAAC,QAAS,QAAO;AAErB,QAAM,UAAU,KAAK,IAAI,IAAI;AAC7B,MAAI,WAAW,wBAAwB;AACrC,sBAAkB,OAAO,OAAO;AAChC,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAKA,SAAS,gBAAgB,SAAuB;AAC9C,oBAAkB,IAAI,SAAS,KAAK,IAAI,CAAC;AACzC,UAAQ,IAAI,sBAAsB,OAAO,0CAA0C;AACrF;AAKA,SAAS,yBAAyB,QAA4B;AAC5D,QAAM,YAAsB,CAAC;AAC7B,QAAM,cAAwB,CAAC;AAE/B,aAAW,SAAS,QAAQ;AAC1B,QAAI,cAAc,KAAK,GAAG;AACxB,kBAAY,KAAK,KAAK;AAAA,IACxB,OAAO;AACL,gBAAU,KAAK,KAAK;AAAA,IACtB;AAAA,EACF;AAEA,SAAO,CAAC,GAAG,WAAW,GAAG,WAAW;AACtC;AAMA,SAAS,SAAS,KAA8B;AAC9C,SACE,CAAC,IAAI,iBACL,CAAC,IAAI,aACL,IAAI,WAAW,QACf,CAAC,IAAI,OAAO,aACZ,IAAI,OAAO;AAEf;AAMA,SAAS,UAAU,KAAqB,MAAgC;AACtE,MAAI,CAAC,SAAS,GAAG,GAAG;AAClB,WAAO;AAAA,EACT;AACA,SAAO,IAAI,MAAM,IAAI;AACvB;AAMA,IAAM,uBAAuB;AAMtB,SAAS,eAAuB;AACrC,SAAO;AACT;AAMA,eAAe,mBAAmB,MAA8E;AAC9G,QAAM,aAAa,IAAI,gBAAgB;AACvC,QAAM,YAAY,WAAW,MAAM,WAAW,MAAM,GAAG,uBAAuB;AAE9E,MAAI;AACF,UAAM,WAAW,MAAM,MAAM,oBAAoB,IAAI,WAAW;AAAA,MAC9D,QAAQ,WAAW;AAAA,IACrB,CAAC;AACD,iBAAa,SAAS;AAEtB,QAAI,SAAS,IAAI;AACf,YAAM,OAAQ,MAAM,SAAS,KAAK;AAClC,UAAI,KAAK,WAAW,QAAQ,KAAK,QAAQ;AACvC,eAAO,EAAE,QAAQ,KAAK,QAAQ,cAAc,KAAK,aAAa;AAAA,MAChE;AAAA,IACF;AACA,WAAO;AAAA,EACT,QAAQ;AACN,iBAAa,SAAS;AACtB,WAAO;AAAA,EACT;AACF;AAMA,IAAM,0BAA0B;AAAA,EAC9B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAMA,IAAM,6BAA6B;AAAA,EACjC;AAAA,EACA;AAAA,EACA;AACF;AAKA,IAAM,yBAAyB;AAAA,EAC7B;AAAA,EACA;AAAA,EACA;AACF;AAEA,SAAS,wBAAwB,SAAsC;AACrE,MAAI,CAAC,WAAW,OAAO,YAAY,SAAU,QAAO;AACpD,QAAM,SAAS;AACf,QAAM,UAAU,OAAO;AACvB,MAAI,CAAC,MAAM,QAAQ,OAAO,KAAK,QAAQ,WAAW,EAAG,QAAO;AAE5D,QAAM,cAAc,QAAQ,CAAC;AAC7B,MAAI,CAAC,eAAe,OAAO,gBAAgB,SAAU,QAAO;AAC5D,QAAM,SAAS;AACf,QAAM,UAAU,OAAO;AACvB,MAAI,CAAC,WAAW,OAAO,YAAY,SAAU,QAAO;AACpD,QAAM,UAAW,QAAoC;AACrD,SAAO,OAAO,YAAY,WAAW,UAAU;AACjD;AAEA,SAAS,sBAAsB,MAAuB;AACpD,QAAM,aAAa,uBAAuB;AAAA,IACxC,CAAC,OAAO,YAAa,QAAQ,KAAK,IAAI,IAAI,QAAQ,IAAI;AAAA,IACtD;AAAA,EACF;AACA,MAAI,cAAc,EAAG,QAAO;AAG5B,QAAM,QAAQ,KACX,MAAM,OAAO,EACb,IAAI,CAAC,SAAS,KAAK,KAAK,CAAC,EACzB,OAAO,OAAO;AACjB,MAAI,MAAM,SAAS,EAAG,QAAO;AAE7B,QAAM,SAAS,oBAAI,IAAoB;AACvC,aAAW,QAAQ,OAAO;AACxB,WAAO,IAAI,OAAO,OAAO,IAAI,IAAI,KAAK,KAAK,CAAC;AAAA,EAC9C;AAEA,QAAM,YAAY,KAAK,IAAI,GAAG,OAAO,OAAO,CAAC;AAC7C,QAAM,cAAc,OAAO,OAAO,MAAM;AACxC,SAAO,aAAa,KAAK,eAAe;AAC1C;AAMO,SAAS,8BAA8B,MAAkC;AAC9E,QAAM,UAAU,KAAK,KAAK;AAC1B,MAAI,CAAC,QAAS,QAAO;AAGrB,MAAI,2BAA2B,KAAK,CAAC,YAAY,QAAQ,KAAK,OAAO,CAAC,GAAG;AACvE,WAAO;AAAA,EACT;AAGA,MAAI,sBAAsB,OAAO,GAAG;AAClC,WAAO;AAAA,EACT;AAEA,MAAI;AACF,UAAM,SAAS,KAAK,MAAM,OAAO;AAGjC,UAAM,aAAa,OAAO;AAC1B,QAAI,YAAY;AAChB,QAAI,OAAO,eAAe,UAAU;AAClC,kBAAY;AAAA,IACd,WAAW,cAAc,OAAO,eAAe,UAAU;AACvD,YAAM,SAAS;AACf,kBAAY;AAAA,QACV,OAAO,OAAO,YAAY,WAAW,OAAO,UAAU;AAAA,QACtD,OAAO,OAAO,SAAS,WAAW,OAAO,OAAO;AAAA,QAChD,OAAO,OAAO,SAAS,WAAW,OAAO,OAAO;AAAA,MAClD,EACG,OAAO,OAAO,EACd,KAAK,GAAG;AAAA,IACb;AACA,QAAI,aAAa,wBAAwB,KAAK,CAAC,YAAY,QAAQ,KAAK,SAAS,CAAC,GAAG;AACnF,aAAO,sBAAsB,UAAU,MAAM,GAAG,GAAG,CAAC;AAAA,IACtD;AAGA,UAAM,mBAAmB,wBAAwB,MAAM;AACvD,QAAI,CAAC,iBAAkB,QAAO;AAC9B,QAAI,2BAA2B,KAAK,CAAC,YAAY,QAAQ,KAAK,gBAAgB,CAAC,GAAG;AAChF,aAAO;AAAA,IACT;AACA,QAAI,sBAAsB,gBAAgB,GAAG;AAC3C,aAAO;AAAA,IACT;AAAA,EACF,QAAQ;AAAA,EAER;AAEA,SAAO;AACT;AAKA,IAAM,wBAAwB;AAAA,EAC5B;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AACF;AAKA,SAAS,gBAAgB,QAAgB,MAAuB;AAE9D,MAAI,CAAC,sBAAsB,SAAS,MAAM,GAAG;AAC3C,WAAO;AAAA,EACT;AAGA,MAAI,UAAU,KAAK;AACjB,WAAO;AAAA,EACT;AAGA,SAAO,wBAAwB,KAAK,CAAC,YAAY,QAAQ,KAAK,IAAI,CAAC;AACrE;AAMA,IAAM,cAAc,oBAAI,IAAI,CAAC,UAAU,QAAQ,aAAa,QAAQ,UAAU,CAAC;AAM/E,IAAM,gBAAwC;AAAA,EAC5C,WAAW;AAAA;AAAA,EACX,OAAO;AAAA;AACT;AAQA,IAAM,wBAAwB;AAM9B,SAAS,eAAe,IAA4C;AAClE,MAAI,CAAC,MAAM,OAAO,OAAO,SAAU,QAAO;AAC1C,MAAI,sBAAsB,KAAK,EAAE,EAAG,QAAO;AAG3C,SAAO,GAAG,QAAQ,mBAAmB,GAAG;AAC1C;AAwBA,SAAS,gBAAgB,UAAwC;AAC/D,MAAI,CAAC,YAAY,SAAS,WAAW,EAAG,QAAO;AAE/C,MAAI,aAAa;AACjB,QAAM,YAAY,SAAS,IAAI,CAAC,QAAQ;AACtC,UAAM,WAAW;AACjB,QAAI,aAAa;AACjB,QAAI,SAAS,EAAE,GAAG,IAAI;AAGtB,QAAI,SAAS,cAAc,MAAM,QAAQ,SAAS,UAAU,GAAG;AAC7D,YAAM,eAAe,SAAS,WAAW,IAAI,CAAC,OAAO;AACnD,YAAI,GAAG,MAAM,OAAO,GAAG,OAAO,UAAU;AACtC,gBAAMC,aAAY,eAAe,GAAG,EAAE;AACtC,cAAIA,eAAc,GAAG,IAAI;AACvB,yBAAa;AACb,mBAAO,EAAE,GAAG,IAAI,IAAIA,WAAU;AAAA,UAChC;AAAA,QACF;AACA,eAAO;AAAA,MACT,CAAC;AACD,UAAI,YAAY;AACd,iBAAS,EAAE,GAAG,QAAQ,YAAY,aAAa;AAAA,MACjD;AAAA,IACF;AAGA,QAAI,SAAS,gBAAgB,OAAO,SAAS,iBAAiB,UAAU;AACtE,YAAMA,aAAY,eAAe,SAAS,YAAY;AACtD,UAAIA,eAAc,SAAS,cAAc;AACvC,qBAAa;AACb,iBAAS,EAAE,GAAG,QAAQ,cAAcA,WAAU;AAAA,MAChD;AAAA,IACF;AAGA,QAAI,MAAM,QAAQ,SAAS,OAAO,GAAG;AACnC,YAAM,aAAc,SAAS,QAA2B,IAAI,CAAC,UAAU;AACrE,YAAI,CAAC,SAAS,OAAO,UAAU,SAAU,QAAO;AAEhD,YAAI,eAAe;AACnB,YAAI,WAAW,EAAE,GAAG,MAAM;AAG1B,YAAI,MAAM,SAAS,cAAc,MAAM,MAAM,OAAO,MAAM,OAAO,UAAU;AACzE,gBAAMA,aAAY,eAAe,MAAM,EAAE;AACzC,cAAIA,eAAc,MAAM,IAAI;AAC1B,2BAAe;AACf,uBAAW,EAAE,GAAG,UAAU,IAAIA,WAAU;AAAA,UAC1C;AAAA,QACF;AAGA,YACE,MAAM,SAAS,iBACf,MAAM,eACN,OAAO,MAAM,gBAAgB,UAC7B;AACA,gBAAMA,aAAY,eAAe,MAAM,WAAW;AAClD,cAAIA,eAAc,MAAM,aAAa;AACnC,2BAAe;AACf,uBAAW,EAAE,GAAG,UAAU,aAAaA,WAAU;AAAA,UACnD;AAAA,QACF;AAEA,YAAI,cAAc;AAChB,uBAAa;AACb,iBAAO;AAAA,QACT;AACA,eAAO;AAAA,MACT,CAAC;AAED,UAAI,YAAY;AACd,iBAAS,EAAE,GAAG,QAAQ,SAAS,WAAW;AAAA,MAC5C;AAAA,IACF;AAEA,QAAI,YAAY;AACd,mBAAa;AACb,aAAO;AAAA,IACT;AACA,WAAO;AAAA,EACT,CAAC;AAED,SAAO,aAAa,YAAY;AAClC;AAMA,SAAS,sBAAsB,UAAwC;AACrE,MAAI,CAAC,YAAY,SAAS,WAAW,EAAG,QAAO;AAE/C,MAAI,aAAa;AACjB,QAAM,aAAa,SAAS,IAAI,CAAC,QAAQ;AACvC,QAAI,YAAY,IAAI,IAAI,IAAI,EAAG,QAAO;AAEtC,UAAM,aAAa,cAAc,IAAI,IAAI;AACzC,QAAI,YAAY;AACd,mBAAa;AACb,aAAO,EAAE,GAAG,KAAK,MAAM,WAAW;AAAA,IACpC;AAGA,iBAAa;AACb,WAAO,EAAE,GAAG,KAAK,MAAM,OAAO;AAAA,EAChC,CAAC;AAED,SAAO,aAAa,aAAa;AACnC;AAQA,SAAS,2BAA2B,UAAwC;AAC1E,MAAI,CAAC,YAAY,SAAS,WAAW,EAAG,QAAO;AAG/C,MAAI,oBAAoB;AACxB,WAAS,IAAI,GAAG,IAAI,SAAS,QAAQ,KAAK;AACxC,QAAI,SAAS,CAAC,EAAE,SAAS,UAAU;AACjC,0BAAoB;AACpB;AAAA,IACF;AAAA,EACF;AAGA,MAAI,sBAAsB,GAAI,QAAO;AAErC,QAAM,YAAY,SAAS,iBAAiB,EAAE;AAG9C,MAAI,cAAc,OAAQ,QAAO;AAGjC,MAAI,cAAc,eAAe,cAAc,SAAS;AACtD,UAAM,aAAa,CAAC,GAAG,QAAQ;AAC/B,eAAW,OAAO,mBAAmB,GAAG;AAAA,MACtC,MAAM;AAAA,MACN,SAAS;AAAA,IACX,CAAC;AACD,WAAO;AAAA,EACT;AAEA,SAAO;AACT;AAKA,SAAS,cAAc,SAA0B;AAC/C,SAAO,QAAQ,WAAW,SAAS,KAAK,QAAQ,WAAW,QAAQ;AACrE;AAgBA,SAAS,6BAA6B,UAAwD;AAC5F,MAAI,CAAC,YAAY,SAAS,WAAW,EAAG,QAAO;AAE/C,MAAI,aAAa;AACjB,QAAM,aAAa,SAAS,IAAI,CAAC,QAAQ;AAEvC,QAAI,IAAI,SAAS,eAAe,IAAI,sBAAsB,QAAW;AACnE,aAAO;AAAA,IACT;AAGA,UAAM,qBACJ,IAAI,cAAc,MAAM,QAAQ,IAAI,UAAU,KAAK,IAAI,WAAW,SAAS;AAG7E,UAAM,sBACJ,MAAM,QAAQ,IAAI,OAAO,KACxB,IAAI,QAAqC,KAAK,CAAC,UAAU,OAAO,SAAS,UAAU;AAEtF,QAAI,sBAAsB,qBAAqB;AAC7C,mBAAa;AACb,aAAO,EAAE,GAAG,KAAK,mBAAmB,GAAG;AAAA,IACzC;AACA,WAAO;AAAA,EACT,CAAC;AAED,SAAO,aAAa,aAAa;AACnC;AAiBA,SAAS,iBAA6C,UAAoC;AACxF,MAAI,CAAC,YAAY,SAAS,UAAU,cAAc;AAChD,WAAO;AAAA,MACL;AAAA,MACA,cAAc;AAAA,MACd,eAAe,UAAU,UAAU;AAAA,MACnC,gBAAgB,UAAU,UAAU;AAAA,IACtC;AAAA,EACF;AAGA,QAAM,aAAa,SAAS,OAAO,CAAC,MAAM,EAAE,SAAS,QAAQ;AAC7D,QAAM,mBAAmB,SAAS,OAAO,CAAC,MAAM,EAAE,SAAS,QAAQ;AAGnE,QAAM,kBAAkB,eAAe,WAAW;AAClD,QAAM,wBAAwB,iBAAiB,MAAM,CAAC,eAAe;AAErE,QAAM,SAAS,CAAC,GAAG,YAAY,GAAG,qBAAqB;AAEvD,UAAQ;AAAA,IACN,oCAAoC,SAAS,MAAM,WAAM,OAAO,MAAM,UAAU,WAAW,MAAM,aAAa,sBAAsB,MAAM;AAAA,EAC5I;AAEA,SAAO;AAAA,IACL,UAAU;AAAA,IACV,cAAc;AAAA,IACd,eAAe,SAAS;AAAA,IACxB,gBAAgB,OAAO;AAAA,EACzB;AACF;AAOA,IAAM,gBAAgB;AAGtB,IAAM,gBAAgB;AAGtB,IAAM,kBAAkB;AAGxB,IAAM,oBACJ;AASF,SAAS,oBAAoB,SAAyB;AACpD,MAAI,CAAC,QAAS,QAAO;AAErB,MAAI,UAAU,QAAQ,QAAQ,eAAe,EAAE;AAE/C,YAAU,QAAQ,QAAQ,eAAe,EAAE;AAE3C,YAAU,QAAQ,QAAQ,mBAAmB,EAAE;AAE/C,YAAU,QAAQ,QAAQ,iBAAiB,EAAE;AAC7C,SAAO;AACT;AAmFA,SAAS,oBAA+C;AACtD,QAAM,MAAM,oBAAI,IAA0B;AAC1C,aAAW,KAAK,iBAAiB;AAC/B,QAAI,EAAE,OAAO,WAAY;AACzB,QAAI,IAAI,EAAE,IAAI,EAAE,YAAY,EAAE,YAAY,aAAa,EAAE,YAAY,CAAC;AAAA,EACxE;AACA,SAAO;AACT;AAaO,SAAS,oBACd,YAAoB,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI,GAC9B;AAClB,QAAM,OAAO,oBAAI,IAAY;AAC7B,SAAO,gBAAgB,OAAO,CAAC,UAAU;AACvC,QAAI,KAAK,IAAI,MAAM,EAAE,EAAG,QAAO;AAC/B,SAAK,IAAI,MAAM,EAAE;AACjB,WAAO;AAAA,EACT,CAAC,EAAE,IAAI,CAAC,WAAW;AAAA,IACjB,IAAI,MAAM;AAAA,IACV,QAAQ;AAAA,IACR,SAAS;AAAA,IACT,UAAU,MAAM,GAAG,SAAS,GAAG,IAAK,MAAM,GAAG,MAAM,GAAG,EAAE,CAAC,KAAK,aAAc;AAAA,EAC9E,EAAE;AACJ;AAKA,SAAS,mBAAmB,WAAmD;AAC7E,MAAI,CAAC,UAAW,QAAO;AACvB,SAAO;AAAA,IACL,GAAG;AAAA,IACH,GAAG;AAAA,IACH,YAAY,EAAE,GAAG,uBAAuB,YAAY,GAAG,UAAU,WAAW;AAAA,IAC5E,SAAS,EAAE,GAAG,uBAAuB,SAAS,GAAG,UAAU,QAAQ;AAAA,IACnE,OAAO,EAAE,GAAG,uBAAuB,OAAO,GAAG,UAAU,MAAM;AAAA,IAC7D,WAAW,EAAE,GAAG,uBAAuB,WAAW,GAAG,UAAU,UAAU;AAAA,EAC3E;AACF;AAMA,SAAS,eACP,SACA,YACA,WACoB;AACpB,QAAM,QAAQ,gBAAgB,KAAK,CAAC,MAAM,EAAE,OAAO,OAAO;AAC1D,MAAI,CAAC,MAAO,QAAO;AAGnB,QAAM,uBAAuB,KAAK,KAAK,aAAa,CAAC;AACrD,QAAM,wBAAwB,aAAa,MAAM,aAAa;AAE9D,QAAM,UACH,uBAAuB,MAAa,MAAM,aAC1C,wBAAwB,MAAa,MAAM;AAI9C,QAAM,eAAe,KAAK,IAAI,KAAM,KAAK,KAAK,UAAU,MAAM,GAAS,CAAC;AACxE,SAAO,aAAa,SAAS;AAC/B;AASA,eAAe,oBACb,KACA,KACA,SACA,UAIe;AACf,QAAM,YAAY,KAAK,IAAI;AAC3B,QAAM,cAAc,GAAG,OAAO,GAAG,IAAI,GAAG;AAGxC,QAAM,aAAuB,CAAC;AAC9B,mBAAiB,SAAS,KAAK;AAC7B,eAAW,KAAK,OAAO,SAAS,KAAK,IAAI,QAAQ,OAAO,KAAK,KAAK,CAAC;AAAA,EACrE;AACA,QAAM,OAAO,OAAO,OAAO,UAAU;AAGrC,QAAM,UAAkC,CAAC;AACzC,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,IAAI,OAAO,GAAG;AACtD,QACE,QAAQ,UACR,QAAQ,gBACR,QAAQ,uBACR,QAAQ;AAER;AACF,QAAI,OAAO,UAAU,SAAU,SAAQ,GAAG,IAAI;AAAA,EAChD;AACA,MAAI,CAAC,QAAQ,cAAc,EAAG,SAAQ,cAAc,IAAI;AACxD,UAAQ,YAAY,IAAI;AAExB,UAAQ,IAAI,iCAAiC,IAAI,MAAM,IAAI,IAAI,GAAG,EAAE;AAEpE,QAAM,WAAW,MAAM,SAAS,aAAa;AAAA,IAC3C,QAAQ,IAAI,UAAU;AAAA,IACtB;AAAA,IACA,MAAM,KAAK,SAAS,IAAI,IAAI,WAAW,IAAI,IAAI;AAAA,EACjD,CAAC;AAGD,QAAM,kBAA0C,CAAC;AACjD,WAAS,QAAQ,QAAQ,CAAC,OAAO,QAAQ;AACvC,QAAI,QAAQ,uBAAuB,QAAQ,gBAAgB,QAAQ,mBAAoB;AACvF,oBAAgB,GAAG,IAAI;AAAA,EACzB,CAAC;AAED,MAAI,UAAU,SAAS,QAAQ,eAAe;AAG9C,MAAI,SAAS,MAAM;AACjB,UAAM,SAAS,SAAS,KAAK,UAAU;AACvC,QAAI;AACF,aAAO,MAAM;AACX,cAAM,EAAE,MAAM,MAAM,IAAI,MAAM,OAAO,KAAK;AAC1C,YAAI,KAAM;AACV,kBAAU,KAAK,OAAO,KAAK,KAAK,CAAC;AAAA,MACnC;AAAA,IACF,UAAE;AACA,aAAO,YAAY;AAAA,IACrB;AAAA,EACF;AAEA,MAAI,IAAI;AAER,QAAM,YAAY,KAAK,IAAI,IAAI;AAC/B,UAAQ,IAAI,kCAAkC,SAAS,MAAM,KAAK,SAAS,KAAK;AAGhF,WAAS;AAAA,IACP,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,IAClC,OAAO;AAAA,IACP,MAAM;AAAA,IACN,MAAM;AAAA;AAAA,IACN,cAAc;AAAA,IACd,SAAS;AAAA,IACT;AAAA,IACA,YACG,IAAI,KAAK,MAAM,GAAG,EAAE,CAAC,KAAK,IAAI,QAAQ,WAAW,EAAE,EAAE,QAAQ,OAAO,GAAG,KAAK;AAAA,IAC/E,SAAS;AAAA,EACX,CAAC,EAAE,MAAM,MAAM;AAAA,EAAC,CAAC;AACnB;AAOA,eAAe,oBAAoB,SAAkC;AACnE,QAAM,QAAQ,QAAQ,MAAM,iCAAiC;AAC7D,MAAI,CAAC,MAAO,OAAM,IAAI,MAAM,yBAAyB;AACrD,QAAM,CAAC,EAAE,UAAU,OAAO,IAAI;AAC9B,QAAM,MAAM,aAAa,eAAe,QAAS,SAAS,MAAM,GAAG,EAAE,CAAC,KAAK;AAE3E,QAAM,SAAS,OAAO,KAAK,SAAS,QAAQ;AAC5C,QAAM,OAAO,IAAI,KAAK,CAAC,MAAM,GAAG,EAAE,MAAM,SAAS,CAAC;AAElD,QAAM,OAAO,IAAI,SAAS;AAC1B,OAAK,OAAO,WAAW,YAAY;AACnC,OAAK,OAAO,gBAAgB,MAAM,SAAS,GAAG,EAAE;AAEhD,QAAM,OAAO,MAAM,MAAM,mCAAmC;AAAA,IAC1D,QAAQ;AAAA,IACR,MAAM;AAAA,EACR,CAAC;AAED,MAAI,CAAC,KAAK,GAAI,OAAM,IAAI,MAAM,kCAAkC,KAAK,MAAM,EAAE;AAC7E,QAAM,SAAS,MAAM,KAAK,KAAK;AAC/B,MAAI,OAAO,WAAW,UAAU,GAAG;AACjC,WAAO,OAAO,KAAK;AAAA,EACrB;AACA,QAAM,IAAI,MAAM,6BAA6B,MAAM,EAAE;AACvD;AAUA,eAAsB,WAAW,SAA6C;AAE5E,QAAMC,aAAY,OAAO,QAAQ,WAAW,WAAW,QAAQ,SAAS,QAAQ,OAAO;AACvF,QAAM,wBACJ,OAAO,QAAQ,WAAW,WAAW,SAAY,QAAQ,OAAO;AAIlE,QAAM,eAAe,QAAQ,gBAAgB,MAAM,oBAAoB;AACvE,QAAM,UAAU,QAAQ,YAAY,iBAAiB,YAAY,wBAAwB,sBAAsB;AAC/G,MAAI,iBAAiB,YAAY,CAAC,uBAAuB;AACvD,YAAQ,KAAK,qFAAqF;AAAA,EACpG,WAAW,iBAAiB,UAAU;AACpC,YAAQ,IAAI,uCAAuC,mBAAmB,GAAG;AAAA,EAC3E;AAGA,QAAM,aAAa,QAAQ,QAAQ,aAAa;AAGhD,QAAM,gBAAgB,MAAM,mBAAmB,UAAU;AACzD,MAAI,eAAe;AAEjB,UAAMC,WAAUC,qBAAoBF,UAA0B;AAC9D,UAAMG,WAAU,oBAAoB,UAAU;AAG9C,QAAI,cAAc,WAAWF,SAAQ,SAAS;AAC5C,cAAQ;AAAA,QACN,uCAAuC,UAAU,gBAAgB,cAAc,MAAM,6BAA6BA,SAAQ,OAAO;AAAA,MACnI;AAAA,IACF;AAGA,QAAI,cAAc,cAAc;AAC9B,UAAI,cAAc,iBAAiB,cAAc;AAC/C,cAAM,IAAI;AAAA,UACR,0BAA0B,UAAU,aAAa,cAAc,YAAY,QAAQ,YAAY;AAAA,QAEjG;AAAA,MACF;AAAA,IACF,WAAW,iBAAiB,QAAQ;AAElC,cAAQ,KAAK,uCAAuC,UAAU,oEAAoE;AAClI,YAAM,IAAI;AAAA,QACR,0BAA0B,UAAU,+CAA+C,YAAY;AAAA,MAEjG;AAAA,IACF;AAGA,QAAI;AACJ,QAAI,uBAAuB;AACzB,YAAM,EAAE,uCAAuC,IAAI,MAAM,OAAO,aAAa;AAC7E,YAAM,eAAe,MAAM,uCAAuC,qBAAqB;AACvF,2BAAqB,aAAa;AAAA,IACpC;AAGA,UAAMG,kBACJ,iBAAiB,YAAY,qBACzB,IAAI,qBAAqB,kBAAkB,IAC3C,IAAI,eAAeH,SAAQ,OAAO;AAExC,YAAQ,UAAU,UAAU;AAE5B,WAAO;AAAA,MACL,MAAM;AAAA,MACN,SAAAE;AAAA,MACA,eAAe,cAAc;AAAA,MAC7B,eAAe;AAAA,MACf,gBAAAC;AAAA,MACA,OAAO,YAAY;AAAA,MAEnB;AAAA,IACF;AAAA,EACF;AAGA,QAAM,UAAUF,qBAAoBF,UAA0B;AAC9D,QAAM,kBAAkBK,oBAAmB,EAAE,OAAOC,OAAM,WAAWC,MAAK,EAAE,CAAC;AAC7E,QAAM,YAAY,kBAAkB,SAAS,eAAe;AAC5D,QAAM,OAAO,IAAI,WAAW;AAC5B,yBAAuB,MAAM,EAAE,QAAQ,UAAU,CAAC;AAMlD,MAAI;AACJ,MAAI,uBAAuB;AACzB,UAAM,EAAE,uBAAuB,IAAI,MAAM,OAAO,wBAAwB;AACxE,UAAM,EAAE,uCAAuC,IAAI,MAAM,OAAO,aAAa;AAC7E,UAAM,eAAe,MAAM,uCAAuC,qBAAqB;AACvF,oBAAgB,aAAa;AAC7B,2BAAuB,MAAM,EAAE,QAAQ,aAAa,CAAC;AACrD,YAAQ,IAAI,+CAA+C,aAAa,EAAE;AAAA,EAC5E;AAGA,OAAK,uBAAuB,OAAO,YAAY;AAC7C,UAAM,UAAU,QAAQ,qBAAqB;AAC7C,UAAM,QAAQ,QAAQ,WAAW,QAAQ,IAAI,eAAe,QAAQ,WAAW,QAAQ,IAAI,WAAW;AACtG,YAAQ,IAAI,kCAAkC,KAAK,KAAK,OAAO,GAAG;AAAA,EACpE,CAAC;AAED,QAAM,WAAW,0BAA0B,OAAO,MAAM,QAAW;AAAA,IACjE,aAAa,iBAAiB;AAAA,EAChC,CAAC;AAGD,QAAM,iBACJ,iBAAiB,YAAY,gBACzB,IAAI,qBAAqB,aAAa,IACtC,IAAI,eAAe,QAAQ,OAAO;AAGxC,QAAM,gBAAgB,mBAAmB,QAAQ,aAAa;AAC9D,QAAMC,gBAAe,kBAAkB;AACvC,QAAMC,cAA4B;AAAA,IAChC,QAAQ;AAAA,IACR,cAAAD;AAAA,EACF;AAGA,QAAM,eAAe,IAAI,oBAAoB;AAG7C,QAAM,gBAAgB,IAAI,cAAc,QAAQ,WAAW;AAG3D,QAAM,eAAe,IAAI,aAAa,QAAQ,aAAa;AAG3D,QAAM,iBAAiB,IAAI,eAAe;AAG1C,QAAM,cAAc,oBAAI,IAA0B;AAElD,QAAM,SAAS,aAAa,OAAO,KAAsB,QAAwB;AAE/E,QAAI,GAAG,SAAS,CAAC,QAAQ;AACvB,cAAQ,MAAM,sCAAsC,IAAI,OAAO,EAAE;AAAA,IAEnE,CAAC;AAED,QAAI,GAAG,SAAS,CAAC,QAAQ;AACvB,cAAQ,MAAM,uCAAuC,IAAI,OAAO,EAAE;AAAA,IAEpE,CAAC;AAGD,aAAS,KAAK,CAAC,QAAQ;AACrB,UAAI,OAAO,IAAI,SAAS,wBAAwB;AAC9C,gBAAQ,MAAM,8CAA8C,IAAI,OAAO,EAAE;AAAA,MAC3E;AAAA,IAGF,CAAC;AAGD,aAAS,KAAK,CAAC,QAAQ;AACrB,UAAI,OAAO,IAAI,SAAS,wBAAwB;AAC9C,gBAAQ,MAAM,6CAA6C,IAAI,OAAO,EAAE;AAAA,MAC1E;AAAA,IACF,CAAC;AAGD,QAAI,IAAI,QAAQ,aAAa,IAAI,KAAK,WAAW,UAAU,GAAG;AAC5D,YAAM,MAAM,IAAI,IAAI,IAAI,KAAK,kBAAkB;AAC/C,YAAM,OAAO,IAAI,aAAa,IAAI,MAAM,MAAM;AAE9C,YAAM,WAAoC;AAAA,QACxC,QAAQ;AAAA,QACR,QAAQ,QAAQ;AAAA,QAChB;AAAA,MACF;AACA,UAAI,eAAe;AACjB,iBAAS,SAAS;AAAA,MACpB;AAEA,UAAI,MAAM;AACR,YAAI;AACF,gBAAM,cAAc,MAAM,eAAe,aAAa;AACtD,mBAAS,UAAU,YAAY;AAC/B,mBAAS,QAAQ,YAAY;AAC7B,mBAAS,UAAU,YAAY;AAAA,QACjC,QAAQ;AACN,mBAAS,eAAe;AAAA,QAC1B;AAAA,MACF;AAEA,UAAI,UAAU,KAAK,EAAE,gBAAgB,mBAAmB,CAAC;AACzD,UAAI,IAAI,KAAK,UAAU,QAAQ,CAAC;AAChC;AAAA,IACF;AAGA,QAAI,IAAI,QAAQ,YAAY,IAAI,KAAK,WAAW,SAAS,GAAG;AAC1D,YAAM,QAAQ,cAAc,SAAS;AACrC,UAAI,UAAU,KAAK;AAAA,QACjB,gBAAgB;AAAA,QAChB,iBAAiB;AAAA,MACnB,CAAC;AACD,UAAI,IAAI,KAAK,UAAU,OAAO,MAAM,CAAC,CAAC;AACtC;AAAA,IACF;AAGA,QAAI,IAAI,QAAQ,YAAY,IAAI,KAAK,WAAW,SAAS,GAAG;AAC1D,UAAI;AACF,cAAM,MAAM,IAAI,IAAI,IAAI,KAAK,kBAAkB;AAC/C,cAAM,OAAO,SAAS,IAAI,aAAa,IAAI,MAAM,KAAK,KAAK,EAAE;AAC7D,cAAM,QAAQ,MAAM,SAAS,KAAK,IAAI,MAAM,EAAE,CAAC;AAE/C,YAAI,UAAU,KAAK;AAAA,UACjB,gBAAgB;AAAA,UAChB,iBAAiB;AAAA,QACnB,CAAC;AACD,YAAI,IAAI,KAAK,UAAU,OAAO,MAAM,CAAC,CAAC;AAAA,MACxC,SAAS,KAAK;AACZ,YAAI,UAAU,KAAK,EAAE,gBAAgB,mBAAmB,CAAC;AACzD,YAAI;AAAA,UACF,KAAK,UAAU;AAAA,YACb,OAAO,wBAAwB,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;AAAA,UACjF,CAAC;AAAA,QACH;AAAA,MACF;AACA;AAAA,IACF;AAGA,QAAI,IAAI,QAAQ,gBAAgB,IAAI,WAAW,OAAO;AACpD,YAAM,SAAS,oBAAoB;AACnC,UAAI,UAAU,KAAK,EAAE,gBAAgB,mBAAmB,CAAC;AACzD,UAAI,IAAI,KAAK,UAAU,EAAE,QAAQ,QAAQ,MAAM,OAAO,CAAC,CAAC;AACxD;AAAA,IACF;AAGA,QAAI,IAAI,KAAK,MAAM,wBAAwB,GAAG;AAC5C,UAAI;AACF,cAAM,oBAAoB,KAAK,KAAK,SAAS,QAAQ;AAAA,MACvD,SAAS,KAAK;AACZ,cAAM,QAAQ,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,GAAG,CAAC;AAChE,gBAAQ,UAAU,KAAK;AACvB,YAAI,CAAC,IAAI,aAAa;AACpB,cAAI,UAAU,KAAK,EAAE,gBAAgB,mBAAmB,CAAC;AACzD,cAAI;AAAA,YACF,KAAK,UAAU;AAAA,cACb,OAAO,EAAE,SAAS,wBAAwB,MAAM,OAAO,IAAI,MAAM,gBAAgB;AAAA,YACnF,CAAC;AAAA,UACH;AAAA,QACF;AAAA,MACF;AACA;AAAA,IACF;AAGA,QAAI,CAAC,IAAI,KAAK,WAAW,KAAK,GAAG;AAC/B,UAAI,UAAU,KAAK,EAAE,gBAAgB,mBAAmB,CAAC;AACzD,UAAI,IAAI,KAAK,UAAU,EAAE,OAAO,YAAY,CAAC,CAAC;AAC9C;AAAA,IACF;AAEA,QAAI;AACF,YAAM;AAAA,QACJ;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACAC;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,IACF,SAAS,KAAK;AACZ,YAAM,QAAQ,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,GAAG,CAAC;AAChE,cAAQ,UAAU,KAAK;AAEvB,UAAI,CAAC,IAAI,aAAa;AACpB,YAAI,UAAU,KAAK,EAAE,gBAAgB,mBAAmB,CAAC;AACzD,YAAI;AAAA,UACF,KAAK,UAAU;AAAA,YACb,OAAO,EAAE,SAAS,gBAAgB,MAAM,OAAO,IAAI,MAAM,cAAc;AAAA,UACzE,CAAC;AAAA,QACH;AAAA,MACF,WAAW,CAAC,IAAI,eAAe;AAE7B,YAAI;AAAA,UACF,SAAS,KAAK,UAAU,EAAE,OAAO,EAAE,SAAS,MAAM,SAAS,MAAM,cAAc,EAAE,CAAC,CAAC;AAAA;AAAA;AAAA,QACrF;AACA,YAAI,MAAM,kBAAkB;AAC5B,YAAI,IAAI;AAAA,MACV;AAAA,IACF;AAAA,EACF,CAAC;AAKD,QAAM,YAAY,CAAC,YAAmC;AACpD,WAAO,IAAI,QAAc,CAAC,gBAAgB,kBAAkB;AAC1D,YAAM,UAAU,OAAO,QAA+B;AACpD,eAAO,eAAe,SAAS,OAAO;AAEtC,YAAI,IAAI,SAAS,cAAc;AAE7B,gBAAM,iBAAiB,MAAM,mBAAmB,UAAU;AAC1D,cAAI,gBAAgB;AAElB,oBAAQ,IAAI,gDAAgD,UAAU,WAAW;AACjF,0BAAc,EAAE,MAAM,kBAAkB,QAAQ,eAAe,QAAQ,eAAe,eAAe,aAAa,CAAC;AACnH;AAAA,UACF;AAGA,cAAI,UAAU,qBAAqB;AACjC,oBAAQ;AAAA,cACN,qBAAqB,UAAU,8BAA8B,mBAAmB,eAAe,OAAO,IAAI,mBAAmB;AAAA,YAC/H;AACA,0BAAc,EAAE,MAAM,SAAS,QAAQ,CAAC;AACxC;AAAA,UACF;AAGA,kBAAQ;AAAA,YACN,qBAAqB,UAAU,uBAAuB,mBAAmB;AAAA,UAC3E;AACA,wBAAc,GAAG;AACjB;AAAA,QACF;AAEA,sBAAc,GAAG;AAAA,MACnB;AAEA,aAAO,KAAK,SAAS,OAAO;AAC5B,aAAO,OAAO,YAAY,aAAa,MAAM;AAC3C,eAAO,eAAe,SAAS,OAAO;AACtC,uBAAe;AAAA,MACjB,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AAGA,MAAI;AACJ,WAAS,UAAU,GAAG,WAAW,qBAAqB,WAAW;AAC/D,QAAI;AACF,YAAM,UAAU,OAAO;AACvB;AAAA,IACF,SAAS,KAAc;AACrB,YAAM,QAAQ;AAEd,UAAI,MAAM,SAAS,oBAAoB,MAAM,QAAQ;AAEnD,YAAI,MAAM,iBAAiB,MAAM,kBAAkB,cAAc;AAC/D,gBAAM,IAAI;AAAA,YACR,0BAA0B,UAAU,aAAa,MAAM,aAAa,QAAQ,YAAY;AAAA,UAE1F;AAAA,QACF;AAGA,cAAMN,WAAU,oBAAoB,UAAU;AAC9C,gBAAQ,UAAU,UAAU;AAC5B,eAAO;AAAA,UACL,MAAM;AAAA,UACN,SAAAA;AAAA,UACA,eAAe,MAAM;AAAA,UACrB;AAAA,UACA,OAAO,YAAY;AAAA,UAEnB;AAAA,QACF;AAAA,MACF;AAEA,UAAI,MAAM,SAAS,SAAS;AAE1B,cAAM,IAAI,QAAQ,CAAC,MAAM,WAAW,GAAG,mBAAmB,CAAC;AAC3D;AAAA,MACF;AAGA,kBAAY;AACZ;AAAA,IACF;AAAA,EACF;AAEA,MAAI,WAAW;AACb,UAAM;AAAA,EACR;AAGA,QAAM,OAAO,OAAO,QAAQ;AAC5B,QAAM,OAAO,KAAK;AAClB,QAAM,UAAU,oBAAoB,IAAI;AAExC,UAAQ,UAAU,IAAI;AAGtB,kBAAgB;AAIhB,SAAO,GAAG,SAAS,CAAC,QAAQ;AAC1B,YAAQ,MAAM,sCAAsC,IAAI,OAAO,EAAE;AACjE,YAAQ,UAAU,GAAG;AAAA,EAEvB,CAAC;AAGD,SAAO,GAAG,eAAe,CAAC,KAAK,WAAW;AACxC,YAAQ,MAAM,8BAA8B,IAAI,OAAO,EAAE;AAEzD,QAAI,OAAO,YAAY,CAAC,OAAO,WAAW;AACxC,aAAO,IAAI,kCAAkC;AAAA,IAC/C;AAAA,EACF,CAAC;AAGD,SAAO,GAAG,cAAc,CAAC,WAAW;AAClC,gBAAY,IAAI,MAAM;AAGtB,WAAO,WAAW,GAAO;AAEzB,WAAO,GAAG,WAAW,MAAM;AACzB,cAAQ,MAAM,oDAAoD;AAClE,aAAO,QAAQ;AAAA,IACjB,CAAC;AAED,WAAO,GAAG,OAAO,MAAM;AAAA,IAEvB,CAAC;AAED,WAAO,GAAG,SAAS,CAAC,QAAQ;AAC1B,cAAQ,MAAM,8BAA8B,IAAI,OAAO,EAAE;AAAA,IAC3D,CAAC;AAED,WAAO,GAAG,SAAS,MAAM;AACvB,kBAAY,OAAO,MAAM;AAAA,IAC3B,CAAC;AAAA,EACH,CAAC;AAED,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,eAAe,QAAQ;AAAA,IACvB;AAAA,IACA;AAAA,IACA,OAAO,MACL,IAAI,QAAc,CAAC,KAAK,QAAQ;AAC9B,YAAM,UAAU,WAAW,MAAM;AAC/B,YAAI,IAAI,MAAM,qCAAqC,CAAC;AAAA,MACtD,GAAG,GAAI;AAEP,mBAAa,MAAM;AAEnB,iBAAW,UAAU,aAAa;AAChC,eAAO,QAAQ;AAAA,MACjB;AACA,kBAAY,MAAM;AAClB,aAAO,MAAM,CAAC,QAAQ;AACpB,qBAAa,OAAO;AACpB,YAAI,KAAK;AACP,cAAI,GAAG;AAAA,QACT,OAAO;AACL,cAAI;AAAA,QACN;AAAA,MACF,CAAC;AAAA,IACH,CAAC;AAAA,EACL;AACF;AAeA,eAAe,gBACb,aACA,QACA,SACA,MACA,SACA,WACA,UAIA,gBACA,QAC6B;AAE7B,MAAI,cAAc;AAClB,MAAI;AACF,UAAM,SAAS,KAAK,MAAM,KAAK,SAAS,CAAC;AACzC,WAAO,QAAQ;AAGf,QAAI,MAAM,QAAQ,OAAO,QAAQ,GAAG;AAClC,aAAO,WAAW,sBAAsB,OAAO,QAAyB;AAAA,IAC1E;AAGA,QAAI,MAAM,QAAQ,OAAO,QAAQ,GAAG;AAClC,YAAM,mBAAmB,iBAAiB,OAAO,QAAyB;AAC1E,aAAO,WAAW,iBAAiB;AAAA,IACrC;AAGA,QAAI,MAAM,QAAQ,OAAO,QAAQ,GAAG;AAClC,aAAO,WAAW,gBAAgB,OAAO,QAAyB;AAAA,IACpE;AAGA,QAAI,cAAc,OAAO,KAAK,MAAM,QAAQ,OAAO,QAAQ,GAAG;AAC5D,aAAO,WAAW,2BAA2B,OAAO,QAAyB;AAAA,IAC/E;AAIA,UAAM,qBAAqB,CAAC,EAC1B,OAAO,YACP,OAAO,qBACP,iBAAiB,OAAO;AAE1B,QAAI,sBAAsB,MAAM,QAAQ,OAAO,QAAQ,GAAG;AACxD,aAAO,WAAW,6BAA6B,OAAO,QAAiC;AAAA,IACzF;AAEA,kBAAc,OAAO,KAAK,KAAK,UAAU,MAAM,CAAC;AAAA,EAClD,QAAQ;AAAA,EAER;AAEA,MAAI;AACF,UAAM,WAAW,MAAM;AAAA,MACrB;AAAA,MACA;AAAA,QACE;AAAA,QACA;AAAA,QACA,MAAM,YAAY,SAAS,IAAI,IAAI,WAAW,WAAW,IAAI;AAAA,QAC7D;AAAA,MACF;AAAA,IACF;AAGA,QAAI,SAAS,WAAW,KAAK;AAE3B,YAAM,YAAY,MAAM,SAAS,KAAK;AACtC,YAAM,gBAAgB,gBAAgB,SAAS,QAAQ,SAAS;AAEhE,aAAO;AAAA,QACL,SAAS;AAAA,QACT;AAAA,QACA,aAAa,SAAS;AAAA,QACtB,iBAAiB;AAAA,MACnB;AAAA,IACF;AAGA,UAAM,cAAc,SAAS,QAAQ,IAAI,cAAc,KAAK;AAC5D,QAAI,YAAY,SAAS,MAAM,KAAK,YAAY,SAAS,MAAM,GAAG;AAChE,UAAI;AACF,cAAM,eAAe,MAAM,SAAS,MAAM,EAAE,KAAK;AACjD,cAAM,iBAAiB,8BAA8B,YAAY;AACjE,YAAI,gBAAgB;AAClB,iBAAO;AAAA,YACL,SAAS;AAAA,YACT,WAAW;AAAA,YACX,aAAa;AAAA,YACb,iBAAiB;AAAA,UACnB;AAAA,QACF;AAAA,MACF,QAAQ;AAAA,MAER;AAAA,IACF;AAEA,WAAO,EAAE,SAAS,MAAM,SAAS;AAAA,EACnC,SAAS,KAAK;AACZ,UAAM,WAAW,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAChE,WAAO;AAAA,MACL,SAAS;AAAA,MACT,WAAW;AAAA,MACX,aAAa;AAAA,MACb,iBAAiB;AAAA;AAAA,IACnB;AAAA,EACF;AACF;AAWA,eAAe,aACb,KACA,KACA,SACA,UAIA,SACAM,aACA,cACA,gBACA,cACA,eACA,gBACe;AACf,QAAM,YAAY,KAAK,IAAI;AAG3B,QAAM,cAAc,GAAG,OAAO,GAAG,IAAI,GAAG;AAGxC,QAAM,aAAuB,CAAC;AAC9B,mBAAiB,SAAS,KAAK;AAC7B,eAAW,KAAK,OAAO,SAAS,KAAK,IAAI,QAAQ,OAAO,KAAK,KAAK,CAAC;AAAA,EACrE;AACA,MAAI,OAAO,OAAO,OAAO,UAAU;AAGnC,QAAM,wBAAwB,KAAK,KAAK,KAAK,SAAS,IAAI;AAG1D,QAAM,YAAY,IAAI,QAAQ,oBAAoB,MAAM;AAGxD,MAAI;AACJ,MAAI,WAAW;AACf,MAAI,YAAY;AAChB,MAAI,cAAc;AAClB,MAAI,UAAU;AACd,MAAI,YAAY;AAChB,MAAI,iBAA6D;AACjE,MAAI,qBAAqB;AACzB,MAAI;AACJ,QAAM,mBAAmB,IAAI,KAAK,SAAS,mBAAmB;AAG9D,QAAM,YAAY,aAAa,IAAI,OAAwD;AAE3F,MAAI,qBAAyC;AAE7C,MAAI,oBAAoB,KAAK,SAAS,GAAG;AACvC,QAAI;AACF,YAAM,SAAS,KAAK,MAAM,KAAK,SAAS,CAAC;AACzC,oBAAc,OAAO,WAAW;AAChC,gBAAW,OAAO,SAAoB;AACtC,kBAAa,OAAO,cAAyB;AAC7C,UAAI,eAAe;AAGnB,YAAM,iBAAiB,MAAM,QAAQ,OAAO,QAAQ,IAC/C,OAAO,WACR,CAAC;AACL,YAAM,cAAc,CAAC,GAAG,cAAc,EAAE,QAAQ,EAAE,KAAK,CAAC,MAAM,EAAE,SAAS,MAAM;AAC/E,YAAM,iBAAiB,aAAa;AACpC,YAAM,cACJ,OAAO,mBAAmB,WACtB,iBACA,MAAM,QAAQ,cAAc,IACzB,eACE,OAAO,CAAC,MAAM,EAAE,SAAS,MAAM,EAC/B,IAAI,CAAC,MAAM,EAAE,QAAQ,EAAE,EACvB,KAAK,GAAG,IACX;AAIR,UAAI,aAAa,eAAe,SAAS,GAAG;AAC1C,cAAM,WAAW;AAEjB,YAAI,eAAe,aAAa,WAAW,GAAG;AAC5C,gBAAM,cAAc,eAAe,OAAO,SAAS;AACnD,cAAI,aAAa;AAEf,kBAAM,SAAS,SAAS,UAAU,CAAC,MAAM,EAAE,SAAS,QAAQ;AAC5D,gBAAI,UAAU,KAAK,OAAO,SAAS,MAAM,EAAE,YAAY,UAAU;AAC/D,uBAAS,MAAM,IAAI;AAAA,gBACjB,GAAG,SAAS,MAAM;AAAA,gBAClB,SAAS,cAAc,SAAS,SAAS,MAAM,EAAE;AAAA,cACnD;AAAA,YACF,OAAO;AACL,uBAAS,QAAQ,EAAE,MAAM,UAAU,SAAS,YAAY,CAAC;AAAA,YAC3D;AACA,mBAAO,WAAW;AAClB,2BAAe;AACf,oBAAQ;AAAA,cACN,0CAA0C,YAAY,MAAM,uBAAuB,UAAU,MAAM,GAAG,CAAC,CAAC;AAAA,YAC1G;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAGA,UAAI,YAAY,WAAW,QAAQ,GAAG;AACpC,cAAM,cAAc,YAAY,MAAM,SAAS,MAAM,EAAE,KAAK,KAAK;AACjE,cAAM,WAAW,OAAO;AACxB,cAAM,YAAY,UAAU,KAAK,CAAC,MAAM,EAAE,SAAS,QAAQ;AAC3D,cAAM,eAAe,OAAO,WAAW,YAAY,WAAW,UAAU,UAAU;AAClF,cAAM,WAAW,GAAG,gBAAgB,EAAE,IAAI,WAAW;AACrD,cAAM,kBAAkB,KAAK,KAAK,SAAS,SAAS,CAAC;AAGrD,cAAMC,mBACJ,OAAO,OAAO,UAAU,WAAW,OAAO,MAAM,KAAK,EAAE,YAAY,IAAI;AACzE,cAAM,cAAcA,iBAAgB,QAAQ,aAAa,EAAE;AAC3D,cAAM,eACJ,CAAC,QAAQ,OAAO,QAAQ,SAAS,EAAE,SAAS,WAAW,IAAI,cAAc;AAI3E,cAAM,UAAU;AAAA,UACd;AAAA,UACA;AAAA,UACA;AAAA,UACA,uBAAuB;AAAA,QACzB;AAGA,cAAM,eAAe,MAAM,aAAa,cAAc,WAAW;AAAA,UAC/D,GAAGD;AAAA,UACH,gBAAgB;AAAA,QAClB,CAAC;AAGD,cAAM,YAAY,QAAQ,cAAc,CAAC,GACtC,IAAI,CAAC,MAAM;AACV,gBAAM,WAAW,EAAE,OAAO,KAAK,OAAO,EAAE;AACxC,gBAAM,WAAW,EAAE,MAAM,QAAQ,CAAC,EAAE,SAAS,CAAC;AAC9C,gBAAM,SAAS,EAAE,SAAS,MAAM,EAAE,MAAM,MAAM;AAC9C,iBAAO,KAAK,OAAO,GAAG,QAAQ,GAAG,MAAM;AAAA,QACzC,CAAC,EACA,KAAK,IAAI;AAGZ,cAAM,OAAO,YAAY,aAAa,WAAW,SAAS,IAAI;AAC9D,cAAM,WAAW,OACb,YAAY,UAAW,MAAM,GAAG,CAAC,CAAC,sBAAiB,KAAK,KAAK,KAAK,KAAK,YAAY,eACnF,YACE,YAAY,UAAU,MAAM,GAAG,CAAC,CAAC,+BACjC;AAEN,cAAM,EAAE,cAAc,eAAe,iBAAiB,IACpD,uBAAuB,QAAQ;AAEjC,cAAM,YAAY;AAAA,UAChB;AAAA,UACA;AAAA,UACA,YAAY,YAAY,YAAY,aAAa,IAAI,aAAa,aAAa,KAAK;AAAA,UACpF,eAAe,aAAa,WAAW,QAAQ,CAAC,CAAC,aAAa,aAAa,aAAa,QAAQ,CAAC,CAAC,gBAAgB,aAAa,UAAU,KAAK,QAAQ,CAAC,CAAC;AAAA,UACxJ,cAAc,aAAa,SAAS;AAAA,UACpC;AAAA,UACA,sBAAsB,QAAQ,MAAM,QAAQ,CAAC,CAAC;AAAA,UAC9C;AAAA,UACA;AAAA,UACA,4BAA4B,aAAa,QAAQ,CAAC,CAAC,cAAc,cAAc,QAAQ,CAAC,CAAC,eAAe,iBAAiB,QAAQ,CAAC,CAAC,kBAAkB,iBAAiB,QAAQ,CAAC,CAAC;AAAA,UAChL;AAAA,UACA;AAAA,QACF,EAAE,KAAK,IAAI;AAGX,cAAM,eAAe,kBAAkB,KAAK,IAAI,CAAC;AACjD,cAAM,YAAY,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI;AAC9C,cAAM,oBAAoB;AAAA,UACxB,IAAI;AAAA,UACJ,QAAQ;AAAA,UACR,SAAS;AAAA,UACT,OAAO;AAAA,UACP,SAAS;AAAA,YACP;AAAA,cACE,OAAO;AAAA,cACP,SAAS,EAAE,MAAM,aAAa,SAAS,UAAU;AAAA,cACjD,eAAe;AAAA,YACjB;AAAA,UACF;AAAA,UACA,OAAO,EAAE,eAAe,GAAG,mBAAmB,GAAG,cAAc,EAAE;AAAA,QACnE;AAEA,YAAI,aAAa;AAEf,cAAI,UAAU,KAAK;AAAA,YACjB,gBAAgB;AAAA,YAChB,iBAAiB;AAAA,YACjB,YAAY;AAAA,UACd,CAAC;AACD,gBAAM,WAAW;AAAA,YACf,IAAI;AAAA,YACJ,QAAQ;AAAA,YACR,SAAS;AAAA,YACT,OAAO;AAAA,YACP,SAAS;AAAA,cACP;AAAA,gBACE,OAAO;AAAA,gBACP,OAAO,EAAE,MAAM,aAAa,SAAS,UAAU;AAAA,gBAC/C,eAAe;AAAA,cACjB;AAAA,YACF;AAAA,UACF;AACA,gBAAM,UAAU;AAAA,YACd,IAAI;AAAA,YACJ,QAAQ;AAAA,YACR,SAAS;AAAA,YACT,OAAO;AAAA,YACP,SAAS,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,GAAG,eAAe,OAAO,CAAC;AAAA,UAC1D;AACA,cAAI,MAAM,SAAS,KAAK,UAAU,QAAQ,CAAC;AAAA;AAAA,CAAM;AACjD,cAAI,MAAM,SAAS,KAAK,UAAU,OAAO,CAAC;AAAA;AAAA,CAAM;AAChD,cAAI,MAAM,kBAAkB;AAC5B,cAAI,IAAI;AAAA,QACV,OAAO;AACL,cAAI,UAAU,KAAK,EAAE,gBAAgB,mBAAmB,CAAC;AACzD,cAAI,IAAI,KAAK,UAAU,iBAAiB,CAAC;AAAA,QAC3C;AACA,gBAAQ,IAAI,sCAAiC,aAAa,IAAI,MAAM,aAAa,KAAK,EAAE;AACxF;AAAA,MACF;AAGA,UAAI,YAAY,WAAW,WAAW,GAAG;AACvC,cAAM,YAAY,YAAY,MAAM,YAAY,MAAM,EAAE,KAAK;AAG7D,YAAI,aAAa;AACjB,YAAI,YAAY;AAChB,YAAI,cAAc;AAGlB,cAAM,aAAa,UAAU,MAAM,iBAAiB;AACpD,YAAI,YAAY;AACd,gBAAM,MAAM,WAAW,CAAC;AAExB,gBAAM,sBAA8C;AAAA,YAClD,YAAY;AAAA,YACZ,QAAQ;AAAA,YACR,OAAO;AAAA,YACP,aAAa;AAAA,YACb,eAAe;AAAA,YACf,MAAM;AAAA,YACN,YAAY;AAAA,YACZ,QAAQ;AAAA,YACR,eAAe;AAAA,YACf,cAAc;AAAA,YACd,mBAAmB;AAAA,UACrB;AACA,uBAAa,oBAAoB,GAAG,KAAK;AACzC,wBAAc,YAAY,QAAQ,iBAAiB,EAAE,EAAE,KAAK;AAAA,QAC9D;AAGA,cAAM,YAAY,UAAU,MAAM,oBAAoB;AACtD,YAAI,WAAW;AACb,sBAAY,UAAU,CAAC;AACvB,wBAAc,YAAY,QAAQ,oBAAoB,EAAE,EAAE,KAAK;AAAA,QACjE;AAEA,YAAI,CAAC,aAAa;AAChB,gBAAM,YAAY;AAAA,YAChB;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,UACF,EAAE,KAAK,IAAI;AAEX,gBAAM,eAAe,kBAAkB,KAAK,IAAI,CAAC;AACjD,gBAAM,YAAY,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI;AAC9C,cAAI,aAAa;AACf,gBAAI,UAAU,KAAK;AAAA,cACjB,gBAAgB;AAAA,cAChB,iBAAiB;AAAA,cACjB,YAAY;AAAA,YACd,CAAC;AACD,gBAAI;AAAA,cACF,SAAS,KAAK,UAAU,EAAE,IAAI,cAAc,QAAQ,yBAAyB,SAAS,WAAW,OAAO,oBAAoB,SAAS,CAAC,EAAE,OAAO,GAAG,OAAO,EAAE,MAAM,aAAa,SAAS,UAAU,GAAG,eAAe,KAAK,CAAC,EAAE,CAAC,CAAC;AAAA;AAAA;AAAA,YAC/N;AACA,gBAAI;AAAA,cACF,SAAS,KAAK,UAAU,EAAE,IAAI,cAAc,QAAQ,yBAAyB,SAAS,WAAW,OAAO,oBAAoB,SAAS,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,GAAG,eAAe,OAAO,CAAC,EAAE,CAAC,CAAC;AAAA;AAAA;AAAA,YAC1L;AACA,gBAAI,MAAM,kBAAkB;AAC5B,gBAAI,IAAI;AAAA,UACV,OAAO;AACL,gBAAI,UAAU,KAAK,EAAE,gBAAgB,mBAAmB,CAAC;AACzD,gBAAI;AAAA,cACF,KAAK,UAAU;AAAA,gBACb,IAAI;AAAA,gBACJ,QAAQ;AAAA,gBACR,SAAS;AAAA,gBACT,OAAO;AAAA,gBACP,SAAS;AAAA,kBACP;AAAA,oBACE,OAAO;AAAA,oBACP,SAAS,EAAE,MAAM,aAAa,SAAS,UAAU;AAAA,oBACjD,eAAe;AAAA,kBACjB;AAAA,gBACF;AAAA,gBACA,OAAO,EAAE,eAAe,GAAG,mBAAmB,GAAG,cAAc,EAAE;AAAA,cACnE,CAAC;AAAA,YACH;AAAA,UACF;AACA,kBAAQ,IAAI,0DAAqD;AACjE;AAAA,QACF;AAGA,gBAAQ;AAAA,UACN,yCAAoC,UAAU,KAAK,SAAS,MAAM,YAAY,MAAM,GAAG,EAAE,CAAC;AAAA,QAC5F;AACA,YAAI;AACF,gBAAM,mBAAmB,GAAG,OAAO;AACnC,gBAAM,YAAY,KAAK,UAAU;AAAA,YAC/B,OAAO;AAAA,YACP,QAAQ;AAAA,YACR,MAAM;AAAA,YACN,GAAG;AAAA,UACL,CAAC;AACD,gBAAM,gBAAgB,MAAM,SAAS,kBAAkB;AAAA,YACrD,QAAQ;AAAA,YACR,SAAS,EAAE,gBAAgB,oBAAoB,cAAc,WAAW;AAAA,YACxE,MAAM;AAAA,UACR,CAAC;AAED,gBAAM,cAAe,MAAM,cAAc,KAAK;AAM9C,cAAI;AACJ,cAAI,CAAC,cAAc,MAAM,YAAY,OAAO;AAC1C,kBAAM,SACJ,OAAO,YAAY,UAAU,WACzB,YAAY,QACV,YAAY,OAAgC,WAC9C,QAAQ,cAAc,MAAM;AAClC,2BAAe,4BAA4B,MAAM;AACjD,oBAAQ,IAAI,iCAAiC,MAAM,EAAE;AAAA,UACvD,OAAO;AACL,kBAAM,SAAS,YAAY,QAAQ,CAAC;AACpC,gBAAI,OAAO,WAAW,GAAG;AACvB,6BAAe;AAAA,YACjB,OAAO;AACL,oBAAM,QAAkB,CAAC;AACzB,yBAAW,OAAO,QAAQ;AACxB,oBAAI,IAAI,KAAK;AACX,sBAAI,IAAI,IAAI,WAAW,OAAO,GAAG;AAC/B,wBAAI;AACF,4BAAM,YAAY,MAAM,oBAAoB,IAAI,GAAG;AACnD,4BAAM,KAAK,SAAS;AAAA,oBACtB,SAAS,WAAW;AAClB,8BAAQ;AAAA,wBACN,sDAAsD,qBAAqB,QAAQ,UAAU,UAAU,OAAO,SAAS,CAAC;AAAA,sBAC1H;AACA,4BAAM;AAAA,wBACJ;AAAA,sBACF;AAAA,oBACF;AAAA,kBACF,OAAO;AACL,0BAAM,KAAK,IAAI,GAAG;AAAA,kBACpB;AAAA,gBACF;AACA,oBAAI,IAAI,eAAgB,OAAM,KAAK,mBAAmB,IAAI,cAAc,EAAE;AAAA,cAC5E;AACA,oBAAM,KAAK,IAAI,UAAU,UAAU,YAAY,SAAS,EAAE;AAC1D,6BAAe,MAAM,KAAK,IAAI;AAAA,YAChC;AACA,oBAAQ,IAAI,mCAAmC,OAAO,MAAM,qBAAqB;AAAA,UACnF;AAGA,gBAAM,eAAe,kBAAkB,KAAK,IAAI,CAAC;AACjD,gBAAM,YAAY,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI;AAC9C,cAAI,aAAa;AACf,gBAAI,UAAU,KAAK;AAAA,cACjB,gBAAgB;AAAA,cAChB,iBAAiB;AAAA,cACjB,YAAY;AAAA,YACd,CAAC;AACD,gBAAI;AAAA,cACF,SAAS,KAAK,UAAU,EAAE,IAAI,cAAc,QAAQ,yBAAyB,SAAS,WAAW,OAAO,oBAAoB,SAAS,CAAC,EAAE,OAAO,GAAG,OAAO,EAAE,MAAM,aAAa,SAAS,aAAa,GAAG,eAAe,KAAK,CAAC,EAAE,CAAC,CAAC;AAAA;AAAA;AAAA,YAClO;AACA,gBAAI;AAAA,cACF,SAAS,KAAK,UAAU,EAAE,IAAI,cAAc,QAAQ,yBAAyB,SAAS,WAAW,OAAO,oBAAoB,SAAS,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,GAAG,eAAe,OAAO,CAAC,EAAE,CAAC,CAAC;AAAA;AAAA;AAAA,YAC1L;AACA,gBAAI,MAAM,kBAAkB;AAC5B,gBAAI,IAAI;AAAA,UACV,OAAO;AACL,gBAAI,UAAU,KAAK,EAAE,gBAAgB,mBAAmB,CAAC;AACzD,gBAAI;AAAA,cACF,KAAK,UAAU;AAAA,gBACb,IAAI;AAAA,gBACJ,QAAQ;AAAA,gBACR,SAAS;AAAA,gBACT,OAAO;AAAA,gBACP,SAAS;AAAA,kBACP;AAAA,oBACE,OAAO;AAAA,oBACP,SAAS,EAAE,MAAM,aAAa,SAAS,aAAa;AAAA,oBACpD,eAAe;AAAA,kBACjB;AAAA,gBACF;AAAA,gBACA,OAAO,EAAE,eAAe,GAAG,mBAAmB,GAAG,cAAc,EAAE;AAAA,cACnE,CAAC;AAAA,YACH;AAAA,UACF;AAAA,QACF,SAAS,KAAK;AACZ,gBAAM,SAAS,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC9D,kBAAQ,MAAM,iCAAiC,MAAM,EAAE;AACvD,cAAI,CAAC,IAAI,aAAa;AACpB,gBAAI,UAAU,KAAK,EAAE,gBAAgB,mBAAmB,CAAC;AACzD,gBAAI;AAAA,cACF,KAAK,UAAU;AAAA,gBACb,OAAO,EAAE,SAAS,4BAA4B,MAAM,IAAI,MAAM,cAAc;AAAA,cAC9E,CAAC;AAAA,YACH;AAAA,UACF;AAAA,QACF;AACA;AAAA,MACF;AAIA,UAAI,OAAO,WAAW,MAAM;AAC1B,eAAO,SAAS;AAChB,uBAAe;AAAA,MACjB;AAGA,YAAM,kBACJ,OAAO,OAAO,UAAU,WAAW,OAAO,MAAM,KAAK,EAAE,YAAY,IAAI;AAGzE,YAAM,gBAAgB,kBAAkB,eAAe;AACvD,YAAM,WAAW,kBAAkB;AAEnC,YAAM,mBAAmB,iBAAiB,IAAI,eAAe;AAG7D,UAAI,kBAAkB;AACpB,cAAM,cAAc,gBAAgB,QAAQ,aAAa,EAAE;AAC3D,yBAAiB;AAAA,MACnB;AAGA,cAAQ;AAAA,QACN,iCAAiC,OAAO,KAAK,qBAAqB,eAAe,IAAI,WAAW,eAAe,aAAa,MAAM,EAAE,GAAG,iBAAiB,cAAc,cAAc,KAAK,EAAE;AAAA,MAC7L;AAIA,UAAI,CAAC,kBAAkB;AACrB,YAAI,OAAO,UAAU,eAAe;AAClC,iBAAO,QAAQ;AACf,yBAAe;AAAA,QACjB;AACA,kBAAU;AAAA,MACZ;AAGA,UAAI,kBAAkB;AAEpB,YAAI,mBAAmB,QAAQ;AAC7B,gBAAM,YAAY;AAClB,kBAAQ,IAAI,qCAAqC,SAAS,WAAW;AACrE,iBAAO,QAAQ;AACf,oBAAU;AACV,yBAAe;AAGf,gBAAM,SAAS;AAAA,YACb,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,YAClC,OAAO;AAAA,YACP,MAAM;AAAA,YACN,MAAM;AAAA,YACN,cAAc;AAAA,YACd,SAAS;AAAA;AAAA,YACT,WAAW;AAAA,UACb,CAAC;AAAA,QACH,OAAO;AAKL,+BACE,aAAa,IAAI,OAAwD,KACzE,gBAAgB,cAAc;AAChC,gBAAM,kBAAkB,qBACpB,aAAa,WAAW,kBAAkB,IAC1C;AAGJ,gBAAM,YAAY,aAAa;AAC/B,gBAAM,SACJ,OAAO,cAAc,WACjB,YACA,MAAM,QAAQ,SAAS,IACpB,UACE,OAAO,CAAC,MAAM,EAAE,SAAS,MAAM,EAC/B,IAAI,CAAC,MAAM,EAAE,QAAQ,EAAE,EACvB,KAAK,GAAG,IACX;AACR,gBAAM,YAAY,eAAe,KAAK,CAAC,MAAM,EAAE,SAAS,QAAQ;AAChE,gBAAM,eACJ,OAAO,WAAW,YAAY,WAAW,UAAU,UAAU;AAI/D,gBAAM,QAAQ,OAAO;AACrB,qBAAW,MAAM,QAAQ,KAAK,KAAK,MAAM,SAAS;AAElD,cAAI,YAAY,OAAO;AACrB,oBAAQ,IAAI,gCAAgC,MAAM,MAAM,8BAA8B;AAAA,UACxF;AAGA,sBAAY,eAAe,KAAK,CAAC,MAAM;AACrC,gBAAI,MAAM,QAAQ,EAAE,OAAO,GAAG;AAC5B,qBAAQ,EAAE,QAAoC,KAAK,CAAC,MAAM,EAAE,SAAS,WAAW;AAAA,YAClF;AACA,mBAAO;AAAA,UACT,CAAC;AACD,cAAI,WAAW;AACb,oBAAQ,IAAI,0EAA0E;AAAA,UACxF;AAGA,4BAAkB,MAAM,QAAQ,cAAc,WAAW;AAAA,YACvD,GAAGA;AAAA,YACH,gBAAgB,kBAAkB;AAAA,UACpC,CAAC;AAED,cAAI,iBAAiB;AAKnB,kBAAM,WAAmC;AAAA,cACvC,QAAQ;AAAA,cACR,QAAQ;AAAA,cACR,SAAS;AAAA,cACT,WAAW;AAAA,YACb;AACA,kBAAM,eAAe,SAAS,gBAAgB,IAAI,KAAK;AACvD,kBAAM,UAAU,SAAS,gBAAgB,IAAI,KAAK;AAElD,gBAAI,UAAU,cAAc;AAE1B,sBAAQ;AAAA,gBACN,wBAAwB,oBAAoB,MAAM,GAAG,CAAC,CAAC,kBAAkB,gBAAgB,IAAI,WAAM,gBAAgB,IAAI,KAAK,gBAAgB,KAAK;AAAA,cACnJ;AACA,qBAAO,QAAQ,gBAAgB;AAC/B,wBAAU,gBAAgB;AAC1B,6BAAe;AACf,kBAAI,oBAAoB;AACtB,6BAAa;AAAA,kBACX;AAAA,kBACA,gBAAgB;AAAA,kBAChB,gBAAgB;AAAA,gBAClB;AAAA,cACF;AAAA,YACF,OAAO;AAEL,sBAAQ;AAAA,gBACN,wBAAwB,oBAAoB,MAAM,GAAG,CAAC,CAAC,6BAA6B,gBAAgB,KAAK,KAAK,gBAAgB,IAAI,OAAO,gBAAgB,IAAI;AAAA,cAC/J;AACA,qBAAO,QAAQ,gBAAgB;AAC/B,wBAAU,gBAAgB;AAC1B,6BAAe;AACf,2BAAa,aAAa,kBAAmB;AAE7C,gCAAkB;AAAA,gBAChB,GAAG;AAAA,gBACH,OAAO,gBAAgB;AAAA,gBACvB,MAAM,gBAAgB;AAAA,cACxB;AAAA,YACF;AAGA,kBAAM,mBAAmB,CAAC,GAAG,cAAc,EACxC,QAAQ,EACR,KAAK,CAAC,MAAM,EAAE,SAAS,WAAW;AACrC,kBAAM,qBACJ,kBACC;AACH,kBAAM,gBAAgB,MAAM,QAAQ,kBAAkB,IAClD,mBACG,IAAI,CAAC,OAAO,GAAG,UAAU,IAAI,EAC7B,OAAO,CAAC,MAAmB,QAAQ,CAAC,CAAC,IACxC;AACJ,kBAAM,cAAc,mBAAmB,QAAQ,aAAa;AAC5D,kBAAM,iBAAiB,aAAa,kBAAkB,oBAAqB,WAAW;AAEtF,gBAAI,gBAAgB;AAClB,oBAAM,qBAAqB,MAAM;AAC/B,oBACE,gBAAgB,WAAW,SAAS,SAAS,KAC7CA,YAAW,OAAO,cAClB;AACA,yBAAOA,YAAW,OAAO;AAAA,gBAC3B;AACA,oBAAI,mBAAmB,SAASA,YAAW,OAAO,UAAU;AAC1D,yBAAOA,YAAW,OAAO;AAAA,gBAC3B;AACA,oBAAI,mBAAmB,aAAaA,YAAW,OAAO,cAAc;AAClE,yBAAOA,YAAW,OAAO;AAAA,gBAC3B;AACA,uBAAOA,YAAW,OAAO;AAAA,cAC3B,GAAG;AAEH,oBAAM,aAAa,aAAa;AAAA,gBAC9B;AAAA,gBACA;AAAA,cACF;AACA,kBAAI,YAAY;AACd,wBAAQ;AAAA,kBACN,4CAAuC,gBAAgB,KAAK,WAAM,WAAW,KAAK,KAAK,gBAAgB,IAAI,WAAM,WAAW,IAAI;AAAA,gBAClI;AACA,uBAAO,QAAQ,WAAW;AAC1B,0BAAU,WAAW;AACrB,kCAAkB;AAAA,kBAChB,GAAG;AAAA,kBACH,OAAO,WAAW;AAAA,kBAClB,MAAM,WAAW;AAAA,gBACnB;AAAA,cACF;AAAA,YACF;AAAA,UACF,OAAO;AAEL,mBAAO,QAAQ,gBAAgB;AAC/B,sBAAU,gBAAgB;AAC1B,2BAAe;AACf,gBAAI,oBAAoB;AACtB,2BAAa;AAAA,gBACX;AAAA,gBACA,gBAAgB;AAAA,gBAChB,gBAAgB;AAAA,cAClB;AACA,sBAAQ;AAAA,gBACN,wBAAwB,mBAAmB,MAAM,GAAG,CAAC,CAAC,wBAAwB,gBAAgB,KAAK;AAAA,cACrG;AAAA,YACF;AAAA,UACF;AAEA,kBAAQ,WAAW,eAAe;AAAA,QACpC;AAAA,MACF;AAGA,UAAI,cAAc;AAChB,eAAO,OAAO,KAAK,KAAK,UAAU,MAAM,CAAC;AAAA,MAC3C;AAAA,IACF,SAAS,KAAK;AAEZ,YAAM,WAAW,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAChE,cAAQ,MAAM,+BAA+B,QAAQ,EAAE;AACvD,cAAQ,MAAM,8DAA8D;AAC5E,cAAQ,UAAU,IAAI,MAAM,mBAAmB,QAAQ,EAAE,CAAC;AAAA,IAC5D;AAAA,EACF;AAIA,QAAM,eAAe,QAAQ,wBAAwB;AACrD,QAAM,uBAAuB,QAAQ,0BAA0B;AAC/D,QAAM,gBAAgB,KAAK,KAAK,KAAK,SAAS,IAAI;AAElD,MAAI,gBAAgB,gBAAgB,sBAAsB;AACxD,QAAI;AACF,cAAQ;AAAA,QACN,6BAA6B,aAAa,wBAAwB,oBAAoB;AAAA,MACxF;AAGA,YAAM,SAAS,KAAK,MAAM,KAAK,SAAS,CAAC;AAKzC,UAAI,OAAO,YAAY,OAAO,SAAS,SAAS,KAAK,eAAe,OAAO,QAAQ,GAAG;AAEpF,cAAM,oBAAoB,MAAM,gBAAgB,OAAO,UAAU;AAAA,UAC/D,SAAS;AAAA,UACT,aAAa;AAAA;AAAA,UACb,QAAQ;AAAA,YACN,eAAe;AAAA;AAAA,YACf,YAAY;AAAA;AAAA,YACZ,YAAY;AAAA;AAAA,YACZ,OAAO;AAAA;AAAA,YACP,aAAa;AAAA;AAAA,YACb,aAAa;AAAA;AAAA,YACb,iBAAiB;AAAA;AAAA,UACnB;AAAA,UACA,YAAY;AAAA,YACV,YAAY;AAAA,YACZ,iBAAiB;AAAA,YACjB,uBAAuB;AAAA,UACzB;AAAA,QACF,CAAC;AAED,cAAM,mBAAmB,KAAK,KAAK,kBAAkB,kBAAkB,IAAI;AAC3E,cAAM,YAAa,gBAAgB,oBAAoB,gBAAiB,KAAK,QAAQ,CAAC;AAEtF,gBAAQ;AAAA,UACN,2BAA2B,aAAa,aAAQ,gBAAgB,OAAO,OAAO;AAAA,QAChF;AAGA,eAAO,WAAW,kBAAkB;AACpC,eAAO,OAAO,KAAK,KAAK,UAAU,MAAM,CAAC;AAAA,MAC3C;AAAA,IACF,SAAS,KAAK;AAEZ,cAAQ;AAAA,QACN,oCAAoC,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;AAAA,MACtF;AAAA,IACF;AAAA,EACF;AAGA,QAAM,WAAW,cAAc,YAAY,IAAI;AAC/C,QAAM,aAAqC,CAAC;AAC5C,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,IAAI,OAAO,GAAG;AACtD,QAAI,OAAO,UAAU,SAAU,YAAW,GAAG,IAAI;AAAA,EACnD;AACA,MAAI,cAAc,YAAY,MAAM,UAAU,GAAG;AAC/C,UAAM,iBAAiB,cAAc,IAAI,QAAQ;AACjD,QAAI,gBAAgB;AAClB,cAAQ,IAAI,8BAA8B,eAAe,KAAK,mBAAmB;AACjF,UAAI,UAAU,eAAe,QAAQ,eAAe,OAAO;AAC3D,UAAI,IAAI,eAAe,IAAI;AAC3B;AAAA,IACF;AAAA,EACF;AAGA,QAAM,WAAW,oBAAoB,KAAK,IAAI;AAG9C,QAAM,SAAS,aAAa,UAAU,QAAQ;AAC9C,MAAI,QAAQ;AACV,QAAI,UAAU,OAAO,QAAQ,OAAO,OAAO;AAC3C,QAAI,IAAI,OAAO,IAAI;AACnB;AAAA,EACF;AAGA,QAAM,WAAW,aAAa,YAAY,QAAQ;AAClD,MAAI,UAAU;AACZ,UAAM,SAAS,MAAM;AACrB,QAAI,UAAU,OAAO,QAAQ,OAAO,OAAO;AAC3C,QAAI,IAAI,OAAO,IAAI;AACnB;AAAA,EACF;AAGA,eAAa,aAAa,QAAQ;AAKlC,MAAI;AACJ,MAAI;AACJ,QAAM,cAAc,YAAY;AAEhC,MAAI,WAAW,CAAC,QAAQ,oBAAoB,CAAC,aAAa;AACxD,UAAM,YAAY,eAAe,SAAS,KAAK,QAAQ,SAAS;AAChE,QAAI,WAAW;AACb,4BAAsB,OAAO,SAAS;AAItC,YAAM,qBACH,sBAAsB,OAAO,KAAK,KAAK,uBAAuB,GAAG,CAAC,IAAK;AAG1E,YAAM,cAAc,MAAM,eAAe,gBAAgB,kBAAkB;AAE3E,UAAI,YAAY,KAAK,WAAW,CAAC,YAAY,YAAY;AAGvD,cAAM,gBAAgB;AACtB,gBAAQ;AAAA,UACN,uBAAuB,YAAY,KAAK,UAAU,UAAU,cAAc,KAAK,YAAY,KAAK,UAAU,kCAAkC,UAAU,gBAAgB,aAAa;AAAA,QACrL;AACA,kBAAU;AAEV,cAAM,SAAS,KAAK,MAAM,KAAK,SAAS,CAAC;AACzC,eAAO,QAAQ;AACf,eAAO,OAAO,KAAK,KAAK,UAAU,MAAM,CAAC;AAGzC,gCAAwB,YAAY,KAAK,UACrC,oFAAqE,aAAa;AAAA;AAAA,IAClF,4CAAkC,YAAY,KAAK,UAAU,wCAAmC,aAAa;AAAA;AAAA;AAGjH,gBAAQ,eAAe;AAAA,UACrB,YAAY,YAAY,KAAK;AAAA,UAC7B,eAAe,YAAY,KAAK;AAAA,QAClC,CAAC;AAAA,MACH,WAAW,YAAY,KAAK,OAAO;AAEjC,gBAAQ,eAAe;AAAA,UACrB,YAAY,YAAY,KAAK;AAAA,UAC7B,eAAe,YAAY,KAAK;AAAA,QAClC,CAAC;AAAA,MACH;AAAA,IACF;AAAA,EACF;AAGA,MAAI;AACJ,MAAI,mBAAmB;AAEvB,MAAI,aAAa;AAEf,QAAI,UAAU,KAAK;AAAA,MACjB,gBAAgB;AAAA,MAChB,iBAAiB;AAAA,MACjB,YAAY;AAAA,MACZ,qBAAqB,OAAO,qBAAqB;AAAA,MACjD,sBAAsB,OAAO,gBAAgB;AAAA,IAC/C,CAAC;AACD,uBAAmB;AAGnB,cAAU,KAAK,iBAAiB;AAGhC,wBAAoB,YAAY,MAAM;AACpC,UAAI,SAAS,GAAG,GAAG;AACjB,kBAAU,KAAK,iBAAiB;AAAA,MAClC,OAAO;AAEL,sBAAc,iBAAiB;AAC/B,4BAAoB;AAAA,MACtB;AAAA,IACF,GAAG,qBAAqB;AAAA,EAC1B;AAGA,QAAM,UAAkC,CAAC;AACzC,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,IAAI,OAAO,GAAG;AACtD,QACE,QAAQ,UACR,QAAQ,gBACR,QAAQ,uBACR,QAAQ;AAER;AACF,QAAI,OAAO,UAAU,UAAU;AAC7B,cAAQ,GAAG,IAAI;AAAA,IACjB;AAAA,EACF;AACA,MAAI,CAAC,QAAQ,cAAc,GAAG;AAC5B,YAAQ,cAAc,IAAI;AAAA,EAC5B;AACA,UAAQ,YAAY,IAAI;AAGxB,MAAI,YAAY;AAChB,MAAI,GAAG,SAAS,MAAM;AACpB,QAAI,mBAAmB;AACrB,oBAAc,iBAAiB;AAC/B,0BAAoB;AAAA,IACtB;AAEA,QAAI,CAAC,WAAW;AACd,mBAAa,eAAe,QAAQ;AAAA,IACtC;AAAA,EACF,CAAC;AAGD,QAAM,YAAY,QAAQ,oBAAoB;AAC9C,QAAM,aAAa,IAAI,gBAAgB;AACvC,QAAM,YAAY,WAAW,MAAM,WAAW,MAAM,GAAG,SAAS;AAEhE,MAAI;AAIF,QAAI;AACJ,QAAI,iBAAiB;AAEnB,YAAM,uBAAuB,KAAK,KAAK,KAAK,SAAS,CAAC;AACtD,YAAM,uBAAuB,uBAAuB;AAIpD,YAAM,eAAe,MAAM;AACzB,YAAI,gBAAgB,WAAW,SAAS,SAAS,KAAKA,YAAW,OAAO,cAAc;AACpF,iBAAOA,YAAW,OAAO;AAAA,QAC3B;AACA,YAAI,mBAAmB,SAASA,YAAW,OAAO,UAAU;AAC1D,iBAAOA,YAAW,OAAO;AAAA,QAC3B;AACA,YAAI,mBAAmB,aAAaA,YAAW,OAAO,cAAc;AAClE,iBAAOA,YAAW,OAAO;AAAA,QAC3B;AACA,eAAOA,YAAW,OAAO;AAAA,MAC3B,GAAG;AAGH,YAAM,YAAY,iBAAiB,gBAAgB,MAAM,WAAW;AACpE,YAAM,kBAAkB;AAAA,QACtB,gBAAgB;AAAA,QAChB;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAGA,YAAM,kBAAkB,UAAU,OAAO,CAAC,MAAM,CAAC,gBAAgB,SAAS,CAAC,CAAC;AAC5E,UAAI,gBAAgB,SAAS,GAAG;AAC9B,gBAAQ;AAAA,UACN,iCAAiC,oBAAoB,sBAAsB,gBAAgB,KAAK,IAAI,CAAC;AAAA,QACvG;AAAA,MACF;AAKA,YAAM,eAAe,oBAAoB,iBAAiB,UAAU,mBAAmB;AACvF,YAAM,eAAe,gBAAgB,OAAO,CAAC,MAAM,CAAC,aAAa,SAAS,CAAC,CAAC;AAC5E,UAAI,aAAa,SAAS,GAAG;AAC3B,gBAAQ;AAAA,UACN,8CAA8C,aAAa,KAAK,IAAI,CAAC;AAAA,QACvE;AAAA,MACF;AAGA,YAAM,iBAAiB,eAAe,cAAc,WAAW,cAAc;AAC7E,YAAM,iBAAiB,aAAa,OAAO,CAAC,MAAM,CAAC,eAAe,SAAS,CAAC,CAAC;AAC7E,UAAI,eAAe,SAAS,GAAG;AAC7B,gBAAQ;AAAA,UACN,wCAAwC,eAAe,KAAK,IAAI,CAAC;AAAA,QACnE;AAAA,MACF;AAGA,oBAAc,eAAe,MAAM,GAAG,qBAAqB;AAG3D,oBAAc,yBAAyB,WAAW;AAAA,IACpD,OAAO;AAEL,oBAAc,UAAU,CAAC,OAAO,IAAI,CAAC;AAAA,IACvC;AAKA,QAAI,CAAC,YAAY,SAAS,UAAU,GAAG;AACrC,kBAAY,KAAK,UAAU;AAAA,IAC7B;AAGA,QAAI;AACJ,QAAI;AACJ,QAAI,kBAAkB;AAEtB,aAAS,IAAI,GAAG,IAAI,YAAY,QAAQ,KAAK;AAC3C,YAAM,WAAW,YAAY,CAAC;AAC9B,YAAM,gBAAgB,MAAM,YAAY,SAAS;AAEjD,cAAQ,IAAI,6BAA6B,IAAI,CAAC,IAAI,YAAY,MAAM,KAAK,QAAQ,EAAE;AAEnF,YAAM,SAAS,MAAM;AAAA,QACnB;AAAA,QACA,IAAI,UAAU;AAAA,QACd;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,WAAW;AAAA,MACb;AAEA,UAAI,OAAO,WAAW,OAAO,UAAU;AACrC,mBAAW,OAAO;AAClB,0BAAkB;AAClB,gBAAQ,IAAI,oCAAoC,QAAQ,EAAE;AAC1D;AAAA,MACF;AAGA,kBAAY;AAAA,QACV,MAAM,OAAO,aAAa;AAAA,QAC1B,QAAQ,OAAO,eAAe;AAAA,MAChC;AAGA,UAAI,OAAO,mBAAmB,CAAC,eAAe;AAE5C,YAAI,OAAO,gBAAgB,KAAK;AAC9B,0BAAgB,QAAQ;AAAA,QAC1B;AAKA,cAAM,eAAe,qDAAqD;AAAA,UACxE,OAAO,aAAa;AAAA,QACtB;AACA,YAAI,gBAAgB,aAAa,YAAY;AAC3C,gBAAM,UAAU,YAAY,QAAQ,UAAU;AAC9C,cAAI,UAAU,IAAI,GAAG;AACnB,oBAAQ,IAAI,6DAAwD,UAAU,EAAE;AAChF,gBAAI,UAAU;AACd;AAAA,UACF;AAAA,QACF;AAEA,gBAAQ;AAAA,UACN,oCAAoC,QAAQ,sBAAsB,OAAO,WAAW,MAAM,GAAG,GAAG,CAAC;AAAA,QACnG;AACA;AAAA,MACF;AAGA,UAAI,CAAC,OAAO,iBAAiB;AAC3B,gBAAQ;AAAA,UACN,wCAAwC,QAAQ,mBAAmB,OAAO,WAAW,MAAM,GAAG,GAAG,CAAC;AAAA,QACpG;AAAA,MACF;AACA;AAAA,IACF;AAGA,iBAAa,SAAS;AAGtB,QAAI,mBAAmB;AACrB,oBAAc,iBAAiB;AAC/B,0BAAoB;AAAA,IACtB;AAKA,QAAI,aAAa,oBAAoB,iBAAiB;AACpD,YAAM,eAAe,gCAAgC,kBAAkB,MAAM,SAAS,gBAAgB,IAAI,UAAU,eAAe,YAAY,gBAAgB,cAAc,QAAQ,CAAC,KAAK,KAAK,eAAe,gBAAgB,WAAW,QAAQ,CAAC,CAAC,cAAc,gBAAgB,SAAS;AAAA;AAAA;AAC3R,gBAAU,KAAK,YAAY;AAAA,IAC7B;AAIA,QAAI,mBAAmB,oBAAoB,gBAAgB,OAAO;AAChE,YAAM,uBAAuB,KAAK,KAAK,KAAK,SAAS,CAAC;AACtD,YAAM,WAAW;AAAA,QACf;AAAA,QACAA,YAAW;AAAA,QACX;AAAA,QACA;AAAA,QACA,kBAAkB;AAAA,MACpB;AACA,wBAAkB;AAAA,QAChB,GAAG;AAAA,QACH,OAAO;AAAA,QACP,WAAW,GAAG,gBAAgB,SAAS,kBAAkB,eAAe;AAAA,QACxE,cAAc,SAAS;AAAA,QACvB,cAAc,SAAS;AAAA,QACvB,SAAS,SAAS;AAAA,MACpB;AACA,cAAQ,WAAW,eAAe;AAKlC,UAAI,oBAAoB;AACtB,qBAAa,WAAW,oBAAoB,iBAAiB,gBAAgB,IAAI;AACjF,gBAAQ;AAAA,UACN,wBAAwB,mBAAmB,MAAM,GAAG,CAAC,CAAC,gCAAgC,eAAe;AAAA,QACvG;AAAA,MACF;AAAA,IACF;AAGA,QAAI,CAAC,UAAU;AACb,YAAM,aAAa,WAAW,QAAQ;AACtC,YAAM,YAAY,WAAW,UAAU;AAGvC,YAAM,iBAAiB,sBAAsB,UAAU;AAEvD,UAAI,kBAAkB;AAGpB,YAAI;AACJ,YAAI;AACF,gBAAM,SAAS,KAAK,MAAM,cAAc;AACxC,uBAAa,KAAK,UAAU,MAAM;AAAA,QACpC,QAAQ;AACN,uBAAa,KAAK,UAAU;AAAA,YAC1B,OAAO,EAAE,SAAS,YAAY,MAAM,kBAAkB,QAAQ,UAAU;AAAA,UAC1E,CAAC;AAAA,QACH;AACA,cAAM,WAAW,SAAS,UAAU;AAAA;AAAA;AACpC,kBAAU,KAAK,QAAQ;AACvB,kBAAU,KAAK,kBAAkB;AACjC,YAAI,IAAI;AAER,cAAM,SAAS,OAAO,KAAK,WAAW,kBAAkB;AACxD,qBAAa,SAAS,UAAU;AAAA,UAC9B,QAAQ;AAAA,UACR,SAAS,EAAE,gBAAgB,oBAAoB;AAAA,UAC/C,MAAM;AAAA,UACN,aAAa,KAAK,IAAI;AAAA,QACxB,CAAC;AAAA,MACH,OAAO;AAEL,YAAI,UAAU,WAAW;AAAA,UACvB,gBAAgB;AAAA,UAChB,qBAAqB,OAAO,qBAAqB;AAAA,UACjD,sBAAsB,OAAO,gBAAgB;AAAA,QAC/C,CAAC;AACD,YAAI,IAAI,cAAc;AAEtB,qBAAa,SAAS,UAAU;AAAA,UAC9B,QAAQ;AAAA,UACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,UAC9C,MAAM,OAAO,KAAK,cAAc;AAAA,UAChC,aAAa,KAAK,IAAI;AAAA,QACxB,CAAC;AAAA,MACH;AACA;AAAA,IACF;AAGA,UAAM,iBAA2B,CAAC;AAElC,QAAI,kBAAkB;AAQpB,UAAI,SAAS,MAAM;AACjB,cAAM,SAAS,SAAS,KAAK,UAAU;AACvC,cAAM,SAAuB,CAAC;AAC9B,YAAI;AACF,iBAAO,MAAM;AACX,kBAAM,EAAE,MAAM,MAAM,IAAI,MAAM,OAAO,KAAK;AAC1C,gBAAI,KAAM;AACV,mBAAO,KAAK,KAAK;AAAA,UACnB;AAAA,QACF,UAAE;AACA,iBAAO,YAAY;AAAA,QACrB;AAGA,cAAM,WAAW,OAAO,OAAO,MAAM;AACrC,cAAM,UAAU,SAAS,SAAS;AAClC,YAAI;AACF,gBAAM,MAAM,KAAK,MAAM,OAAO;AA+B9B,cAAI,IAAI,SAAS,OAAO,IAAI,UAAU,UAAU;AAC9C,kBAAM,IAAI,IAAI;AACd,gBAAI,OAAO,EAAE,kBAAkB,SAAU,uBAAsB,EAAE;AAAA,UACnE;AAIA,gBAAM,YAAY;AAAA,YAChB,IAAI,IAAI,MAAM,YAAY,KAAK,IAAI,CAAC;AAAA,YACpC,QAAQ;AAAA,YACR,SAAS,IAAI,WAAW,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI;AAAA,YACpD,OAAO,IAAI,SAAS;AAAA,YACpB,oBAAoB;AAAA,UACtB;AAGA,cAAI,IAAI,WAAW,MAAM,QAAQ,IAAI,OAAO,GAAG;AAC7C,uBAAW,UAAU,IAAI,SAAS;AAEhC,oBAAM,aAAa,OAAO,SAAS,WAAW,OAAO,OAAO,WAAW;AACvE,oBAAM,UAAU,oBAAoB,UAAU;AAC9C,oBAAM,OAAO,OAAO,SAAS,QAAQ,OAAO,OAAO,QAAQ;AAC3D,oBAAM,QAAQ,OAAO,SAAS;AAG9B,kBAAI,SAAS;AACX,sCAAsB;AAAA,cACxB;AAGA,oBAAM,YAAY;AAAA,gBAChB,GAAG;AAAA,gBACH,SAAS,CAAC,EAAE,OAAO,OAAO,EAAE,KAAK,GAAG,UAAU,MAAM,eAAe,KAAK,CAAC;AAAA,cAC3E;AACA,oBAAM,WAAW,SAAS,KAAK,UAAU,SAAS,CAAC;AAAA;AAAA;AACnD,wBAAU,KAAK,QAAQ;AACvB,6BAAe,KAAK,OAAO,KAAK,QAAQ,CAAC;AAGzC,kBAAI,uBAAuB;AACzB,sBAAM,cAAc;AAAA,kBAClB,GAAG;AAAA,kBACH,SAAS,CAAC,EAAE,OAAO,OAAO,EAAE,SAAS,sBAAsB,GAAG,UAAU,MAAM,eAAe,KAAK,CAAC;AAAA,gBACrG;AACA,sBAAM,aAAa,SAAS,KAAK,UAAU,WAAW,CAAC;AAAA;AAAA;AACvD,0BAAU,KAAK,UAAU;AACzB,+BAAe,KAAK,OAAO,KAAK,UAAU,CAAC;AAC3C,wCAAwB;AAAA,cAC1B;AAGA,kBAAI,SAAS;AACX,sBAAM,eAAe;AAAA,kBACnB,GAAG;AAAA,kBACH,SAAS,CAAC,EAAE,OAAO,OAAO,EAAE,QAAQ,GAAG,UAAU,MAAM,eAAe,KAAK,CAAC;AAAA,gBAC9E;AACA,sBAAM,cAAc,SAAS,KAAK,UAAU,YAAY,CAAC;AAAA;AAAA;AACzD,0BAAU,KAAK,WAAW;AAC1B,+BAAe,KAAK,OAAO,KAAK,WAAW,CAAC;AAAA,cAC9C;AAGA,oBAAM,YAAY,OAAO,SAAS,cAAc,OAAO,OAAO;AAC9D,kBAAI,aAAa,UAAU,SAAS,GAAG;AACrC,sBAAM,gBAAgB;AAAA,kBACpB,GAAG;AAAA,kBACH,SAAS;AAAA,oBACP;AAAA,sBACE;AAAA,sBACA,OAAO,EAAE,YAAY,UAAU;AAAA,sBAC/B,UAAU;AAAA,sBACV,eAAe;AAAA,oBACjB;AAAA,kBACF;AAAA,gBACF;AACA,sBAAM,eAAe,SAAS,KAAK,UAAU,aAAa,CAAC;AAAA;AAAA;AAC3D,0BAAU,KAAK,YAAY;AAC3B,+BAAe,KAAK,OAAO,KAAK,YAAY,CAAC;AAAA,cAC/C;AAGA,oBAAM,cAAc;AAAA,gBAClB,GAAG;AAAA,gBACH,SAAS;AAAA,kBACP;AAAA,oBACE;AAAA,oBACA,OAAO,CAAC;AAAA,oBACR,UAAU;AAAA,oBACV,eACE,aAAa,UAAU,SAAS,IAC5B,eACC,OAAO,iBAAiB;AAAA,kBACjC;AAAA,gBACF;AAAA,cACF;AACA,oBAAM,aAAa,SAAS,KAAK,UAAU,WAAW,CAAC;AAAA;AAAA;AACvD,wBAAU,KAAK,UAAU;AACzB,6BAAe,KAAK,OAAO,KAAK,UAAU,CAAC;AAAA,YAC7C;AAAA,UACF;AAAA,QACF,QAAQ;AAEN,gBAAM,UAAU,SAAS,OAAO;AAAA;AAAA;AAChC,oBAAU,KAAK,OAAO;AACtB,yBAAe,KAAK,OAAO,KAAK,OAAO,CAAC;AAAA,QAC1C;AAAA,MACF;AAGA,gBAAU,KAAK,kBAAkB;AACjC,qBAAe,KAAK,OAAO,KAAK,kBAAkB,CAAC;AACnD,UAAI,IAAI;AAGR,mBAAa,SAAS,UAAU;AAAA,QAC9B,QAAQ;AAAA,QACR,SAAS,EAAE,gBAAgB,oBAAoB;AAAA,QAC/C,MAAM,OAAO,OAAO,cAAc;AAAA,QAClC,aAAa,KAAK,IAAI;AAAA,MACxB,CAAC;AAAA,IACH,OAAO;AAEL,YAAM,kBAA0C,CAAC;AACjD,eAAS,QAAQ,QAAQ,CAAC,OAAO,QAAQ;AAEvC,YAAI,QAAQ,uBAAuB,QAAQ,gBAAgB,QAAQ;AACjE;AACF,wBAAgB,GAAG,IAAI;AAAA,MACzB,CAAC;AAGD,sBAAgB,mBAAmB,IAAI,OAAO,qBAAqB;AACnE,sBAAgB,oBAAoB,IAAI,OAAO,gBAAgB;AAG/D,UAAI,aAAa,iBAAiB;AAChC,wBAAgB,sBAAsB,IAAI,kBAAkB;AAC5D,wBAAgB,mBAAmB,IAAI,gBAAgB;AACvD,wBAAgB,oBAAoB,IAAI;AACxC,wBAAgB,yBAAyB,IAAI,gBAAgB,WAAW,QAAQ,CAAC;AACjF,wBAAgB,wBAAwB,IAAI,gBAAgB;AAC5D,YAAI,gBAAgB,iBAAiB,QAAW;AAC9C,0BAAgB,4BAA4B,IAAI,gBAAgB,aAAa,QAAQ,CAAC;AAAA,QACxF;AAAA,MACF;AAGA,YAAM,YAAsB,CAAC;AAC7B,UAAI,SAAS,MAAM;AACjB,cAAM,SAAS,SAAS,KAAK,UAAU;AACvC,YAAI;AACF,iBAAO,MAAM;AACX,kBAAM,EAAE,MAAM,MAAM,IAAI,MAAM,OAAO,KAAK;AAC1C,gBAAI,KAAM;AACV,sBAAU,KAAK,OAAO,KAAK,KAAK,CAAC;AAAA,UACnC;AAAA,QACF,UAAE;AACA,iBAAO,YAAY;AAAA,QACrB;AAAA,MACF;AAEA,UAAI,eAAe,OAAO,OAAO,SAAS;AAG1C,UAAI,yBAAyB,aAAa,SAAS,GAAG;AACpD,YAAI;AACF,gBAAM,SAAS,KAAK,MAAM,aAAa,SAAS,CAAC;AAGjD,cAAI,OAAO,UAAU,CAAC,GAAG,SAAS,YAAY,QAAW;AACvD,mBAAO,QAAQ,CAAC,EAAE,QAAQ,UAAU,wBAAwB,OAAO,QAAQ,CAAC,EAAE,QAAQ;AACtF,2BAAe,OAAO,KAAK,KAAK,UAAU,MAAM,CAAC;AAAA,UACnD;AAAA,QACF,QAAQ;AAAA,QAA8B;AACtC,gCAAwB;AAAA,MAC1B;AAGA,sBAAgB,gBAAgB,IAAI,OAAO,aAAa,MAAM;AAC9D,UAAI,UAAU,SAAS,QAAQ,eAAe;AAC9C,gBAAU,KAAK,YAAY;AAC3B,qBAAe,KAAK,YAAY;AAChC,UAAI,IAAI;AAGR,mBAAa,SAAS,UAAU;AAAA,QAC9B,QAAQ,SAAS;AAAA,QACjB,SAAS;AAAA,QACT,MAAM;AAAA,QACN,aAAa,KAAK,IAAI;AAAA,MACxB,CAAC;AAGD,UAAI,SAAS,WAAW,OAAO,cAAc,YAAY,IAAI,GAAG;AAC9D,sBAAc,IAAI,UAAU;AAAA,UAC1B,MAAM;AAAA,UACN,QAAQ,SAAS;AAAA,UACjB,SAAS;AAAA,UACT,OAAO;AAAA,QACT,CAAC;AACD,gBAAQ;AAAA,UACN,oCAAoC,eAAe,KAAK,aAAa,MAAM;AAAA,QAC7E;AAAA,MACF;AAGA,UAAI;AACF,cAAM,UAAU,KAAK,MAAM,aAAa,SAAS,CAAC;AAIlD,YAAI,QAAQ,UAAU,CAAC,GAAG,SAAS,SAAS;AAC1C,+BAAqB,QAAQ,QAAQ,CAAC,EAAE,QAAQ;AAAA,QAClD;AACA,YAAI,QAAQ,SAAS,OAAO,QAAQ,UAAU,UAAU;AACtD,cAAI,OAAO,QAAQ,MAAM,kBAAkB;AACzC,kCAAsB,QAAQ,MAAM;AAAA,QACxC;AAAA,MACF,QAAQ;AAAA,MAER;AAAA,IACF;AAGA,QAAI,aAAa,oBAAoB;AACnC,YAAM,SAAS,eAAe,cAAc,kBAAkB;AAC9D,UAAI,OAAO,SAAS,GAAG;AACrB,uBAAe,OAAO,WAAW,QAAQ,eAAe;AACxD,gBAAQ;AAAA,UACN,yBAAyB,OAAO,MAAM,0CAA0C,UAAU,MAAM,GAAG,CAAC,CAAC;AAAA,QACvG;AAAA,MACF;AAAA,IACF;AAGA,QAAI,wBAAwB,QAAW;AACrC,qBAAe,gBAAgB,mBAAmB;AAAA,IACpD;AAGA,gBAAY;AAAA,EACd,SAAS,KAAK;AAEZ,iBAAa,SAAS;AAGtB,QAAI,mBAAmB;AACrB,oBAAc,iBAAiB;AAC/B,0BAAoB;AAAA,IACtB;AAGA,iBAAa,eAAe,QAAQ;AAGpC,mBAAe,WAAW;AAG1B,QAAI,eAAe,SAAS,IAAI,SAAS,cAAc;AACrD,YAAM,IAAI,MAAM,2BAA2B,SAAS,MAAM,EAAE,OAAO,IAAI,CAAC;AAAA,IAC1E;AAEA,UAAM;AAAA,EACR;AAMA,QAAM,WAAW,iBAAiB,SAAS;AAC3C,MAAI,UAAU;AAEZ,UAAM,uBAAuB,KAAK,KAAK,KAAK,SAAS,CAAC;AACtD,UAAM,gBAAgB;AAAA,MACpB;AAAA,MACAA,YAAW;AAAA,MACX;AAAA,MACA;AAAA,MACA,kBAAkB;AAAA,IACpB;AAEA,UAAM,iBAAiB,cAAc,eAAe;AACpD,UAAM,qBAAqB,cAAc,eAAe;AACxD,UAAM,QAAoB;AAAA,MACxB,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,MAClC,OAAO;AAAA,MACP,MAAM,iBAAiB,QAAQ;AAAA,MAC/B,MAAM;AAAA,MACN,cAAc;AAAA,MACd,SAAS,cAAc;AAAA,MACvB,WAAW,KAAK,IAAI,IAAI;AAAA,MACxB,GAAI,wBAAwB,UAAa,EAAE,aAAa,oBAAoB;AAAA,IAC9E;AACA,aAAS,KAAK,EAAE,MAAM,MAAM;AAAA,IAAC,CAAC;AAAA,EAChC;AACF;;;A2BvoGA,SAASE,qBAA+C;AACtD,QAAM,MAAM,oBAAI,IAA0B;AAC1C,aAAW,KAAK,iBAAiB;AAC/B,QAAI,EAAE,OAAO,gBAAiB;AAC9B,QAAI,IAAI,EAAE,IAAI,EAAE,YAAY,EAAE,YAAY,aAAa,EAAE,YAAY,CAAC;AAAA,EACxE;AACA,SAAO;AACT;AAEA,IAAI,SAAS;AACb,IAAI,SAAS;AAEb,SAAS,OAAO,WAAoB,KAAa;AAC/C,MAAI,WAAW;AACb,YAAQ,IAAI,YAAO,GAAG,EAAE;AACxB;AAAA,EACF,OAAO;AACL,YAAQ,MAAM,kBAAa,GAAG,EAAE;AAChC;AAAA,EACF;AACF;AAIA,QAAQ,IAAI,yEAA2C;AAEvD,IAAM,SAAS;AAGf;AACE,UAAQ,IAAI,iBAAiB;AAC7B,QAAM,KAAK,gBAAgB,kCAAkC,QAAW,GAAG,OAAO,OAAO;AACzF;AAAA,IACE,GAAG,SAAS;AAAA,IACZ,2CAAsC,GAAG,IAAI,WAAW,GAAG,MAAM,QAAQ,CAAC,CAAC;AAAA,EAC7E;AAEA,QAAM,KAAK,gBAAgB,SAAS,QAAW,GAAG,OAAO,OAAO;AAChE,SAAO,GAAG,SAAS,UAAU,kBAAa,GAAG,IAAI,WAAW,GAAG,MAAM,QAAQ,CAAC,CAAC,GAAG;AAElF,QAAM,KAAK,gBAAgB,yBAAyB,QAAW,GAAG,OAAO,OAAO;AAEhF;AAAA,IACE,GAAG,SAAS,YAAY,GAAG,SAAS,YAAY,GAAG,SAAS;AAAA,IAC5D,kCAA6B,GAAG,IAAI,WAAW,GAAG,MAAM,QAAQ,CAAC,CAAC;AAAA,EACpE;AAEA,QAAM,KAAK,gBAAgB,8BAA8B,QAAW,GAAG,OAAO,OAAO;AACrF;AAAA,IACE,GAAG,SAAS;AAAA,IACZ,uCAAkC,GAAG,IAAI,WAAW,GAAG,MAAM,QAAQ,CAAC,CAAC;AAAA,EACzE;AAEA,QAAM,KAAK,gBAAgB,+BAA+B,QAAW,GAAG,OAAO,OAAO;AACtF;AAAA,IACE,GAAG,SAAS;AAAA,IACZ,wCAAmC,GAAG,IAAI,WAAW,GAAG,MAAM,QAAQ,CAAC,CAAC;AAAA,EAC1E;AACF;AAIA;AACE,UAAQ,IAAI,6EAA6E;AACzF,QAAM,eAAe;AAErB,QAAM,KAAK,gBAAgB,gBAAgB,cAAc,IAAI,OAAO,OAAO;AAC3E;AAAA,IACE,GAAG,SAAS;AAAA,IACZ,6CAAwC,GAAG,IAAI;AAAA,EACjD;AAEA,QAAM,KAAK,gBAAgB,SAAS,cAAc,GAAG,OAAO,OAAO;AACnE;AAAA,IACE,GAAG,SAAS;AAAA,IACZ,+CAA0C,GAAG,IAAI;AAAA,EACnD;AAEA,QAAM,KAAK,gBAAgB,kCAAkC,cAAc,IAAI,OAAO,OAAO;AAC7F;AAAA,IACE,GAAG,SAAS;AAAA,IACZ,2DAAsD,GAAG,IAAI;AAAA,EAC/D;AAGA,QAAM,KAAK;AAAA,IACT;AAAA,IACA;AAAA,IACA;AAAA,IACA,OAAO;AAAA,EACT;AACA;AAAA,IACE,GAAG,SAAS;AAAA,IACZ,2CAAsC,GAAG,IAAI;AAAA,EAC/C;AACF;AAKA;AACE,UAAQ,IAAI,mEAAmE;AAC/E,QAAM,qBACJ;AAIF,QAAM,KAAK,gBAAgB,+BAA+B,oBAAoB,IAAI,OAAO,OAAO;AAChG;AAAA,IACE,GAAG,eAAe;AAAA,IAClB,iEAA4D,GAAG,YAAY;AAAA,EAC7E;AAEA,QAAM,KAAK,gBAAgB,kBAAkB,oBAAoB,IAAI,OAAO,OAAO;AACnF;AAAA,IACE,GAAG,eAAe;AAAA,IAClB,kEAA6D,GAAG,YAAY;AAAA,EAC9E;AAIA,QAAM,KAAK;AAAA,IACT;AAAA,IACA;AAAA,IACA;AAAA,IACA,OAAO;AAAA,EACT;AACA;AAAA,IACE,GAAG,gBAAgB;AAAA,IACnB,6DAAwD,GAAG,YAAY;AAAA,EACzE;AACF;AAGA;AACE,UAAQ,IAAI,6BAA6B;AACzC,QAAM,KAAK;AAAA,IACT;AAAA,IACA;AAAA,IACA;AAAA,IACA,OAAO;AAAA,EACT;AACA,UAAQ;AAAA,IACN,oDAA0C,GAAG,QAAQ,WAAW,WAAW,GAAG,MAAM,QAAQ,CAAC,CAAC,UAAU,GAAG,WAAW,QAAQ,CAAC,CAAC,MAAM,GAAG,QAAQ,KAAK,IAAI,CAAC;AAAA,EAC7J;AAEA,QAAM,KAAK;AAAA,IACT;AAAA,IACA;AAAA,IACA;AAAA,IACA,OAAO;AAAA,EACT;AACA,UAAQ;AAAA,IACN,2CAAiC,GAAG,QAAQ,WAAW,WAAW,GAAG,MAAM,QAAQ,CAAC,CAAC,UAAU,GAAG,WAAW,QAAQ,CAAC,CAAC,MAAM,GAAG,QAAQ,KAAK,IAAI,CAAC;AAAA,EACpJ;AACF;AAIA;AACE,UAAQ,IAAI,2CAA2C;AACvD,QAAM,KAAK;AAAA,IACT;AAAA,IACA;AAAA,IACA;AAAA,IACA,OAAO;AAAA,EACT;AACA;AAAA,IACE,GAAG,SAAS,QAAQ,GAAG,SAAS,YAAY,GAAG,SAAS,aAAa,GAAG,SAAS;AAAA,IACjF,uBAAkB,GAAG,QAAQ,WAAW,WAAW,GAAG,MAAM,QAAQ,CAAC,CAAC,UAAU,GAAG,WAAW,QAAQ,CAAC,CAAC;AAAA,EAC1G;AAEA,QAAM,KAAK;AAAA,IACT;AAAA,IACA;AAAA,IACA;AAAA,IACA,OAAO;AAAA,EACT;AACA;AAAA,IACE,GAAG,SAAS,QAAQ,GAAG,SAAS,YAAY,GAAG,SAAS,aAAa,GAAG,SAAS;AAAA,IACjF,uCAAkC,GAAG,QAAQ,WAAW,WAAW,GAAG,MAAM,QAAQ,CAAC,CAAC,UAAU,GAAG,WAAW,QAAQ,CAAC,CAAC;AAAA,EAC1H;AACF;AAGA;AACE,UAAQ,IAAI,sBAAsB;AAClC,QAAM,KAAK;AAAA,IACT;AAAA,IACA;AAAA,IACA;AAAA,IACA,OAAO;AAAA,EACT;AACA;AAAA,IACE,GAAG,SAAS;AAAA,IACZ,qCAAgC,GAAG,IAAI,WAAW,GAAG,MAAM,QAAQ,CAAC,CAAC,UAAU,GAAG,WAAW,QAAQ,CAAC,CAAC;AAAA,EACzG;AAEA,QAAM,KAAK;AAAA,IACT;AAAA,IACA;AAAA,IACA;AAAA,IACA,OAAO;AAAA,EACT;AACA;AAAA,IACE,GAAG,SAAS;AAAA,IACZ,mDAA8C,GAAG,IAAI,WAAW,GAAG,MAAM,QAAQ,CAAC,CAAC,UAAU,GAAG,WAAW,QAAQ,CAAC,CAAC;AAAA,EACvH;AAEA,QAAM,KAAK;AAAA,IACT;AAAA,IACA;AAAA,IACA;AAAA,IACA,OAAO;AAAA,EACT;AACA;AAAA,IACE,GAAG,SAAS;AAAA,IACZ,mCAA8B,GAAG,IAAI,WAAW,GAAG,MAAM,QAAQ,CAAC,CAAC,UAAU,GAAG,WAAW,QAAQ,CAAC,CAAC;AAAA,EACvG;AACF;AAGA;AACE,UAAQ,IAAI,+BAA+B;AAG3C,QAAM,cAAc;AAAA,IAClB;AAAA,IACA;AAAA,IACA;AAAA,IACA,OAAO;AAAA,EACT;AACA;AAAA,IACE,YAAY,SAAS;AAAA,IACrB,gDAAuB,YAAY,IAAI;AAAA,EACzC;AAGA,QAAM,WAAW,gBAAgB,sEAAe,QAAW,IAAI,OAAO,OAAO;AAC7E;AAAA,IACE,SAAS,SAAS;AAAA,IAClB,sDAAwB,SAAS,IAAI;AAAA,EACvC;AAGA,QAAM,WAAW,gBAAgB,wFAAkB,QAAW,IAAI,OAAO,OAAO;AAChF;AAAA,IACE,SAAS,SAAS;AAAA,IAClB,mEAA2B,SAAS,IAAI;AAAA,EAC1C;AAGA,QAAM,SAAS;AAAA,IACb;AAAA,IACA;AAAA,IACA;AAAA,IACA,OAAO;AAAA,EACT;AACA;AAAA,IACE,OAAO,SAAS;AAAA,IAChB,4JAAyC,OAAO,IAAI;AAAA,EACtD;AAGA,QAAM,WAAW;AAAA,IACf;AAAA,IACA;AAAA,IACA;AAAA,IACA,OAAO;AAAA,EACT;AACA;AAAA,IACE,SAAS,SAAS;AAAA,IAClB,6GAAkC,SAAS,IAAI;AAAA,EACjD;AAGA,QAAM,cAAc;AAAA,IAClB;AAAA,IACA;AAAA,IACA;AAAA,IACA,OAAO;AAAA,EACT;AACA;AAAA,IACE,YAAY,SAAS;AAAA,IACrB,qDAA6C,YAAY,IAAI;AAAA,EAC/D;AAGA,QAAM,WAAW;AAAA,IACf;AAAA,IACA;AAAA,IACA;AAAA,IACA,OAAO;AAAA,EACT;AACA;AAAA,IACE,SAAS,SAAS;AAAA,IAClB,mCAA8B,SAAS,IAAI;AAAA,EAC7C;AAGA,QAAM,SAAS;AAAA,IACb;AAAA,IACA;AAAA,IACA;AAAA,IACA,OAAO;AAAA,EACT;AACA;AAAA,IACE,OAAO,SAAS;AAAA,IAChB,0CAAqC,OAAO,IAAI;AAAA,EAClD;AACF;AAKA;AACE,UAAQ,IAAI,0EAA0E;AAItF,QAAM,uBAAuB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAgG7B,QAAM,OAAO,gBAAgB,oBAAoB,sBAAsB,MAAM,OAAO,OAAO;AAC3F;AAAA,IACE,KAAK,QAAQ;AAAA,IACb,qDAAgD,KAAK,MAAM,QAAQ,CAAC,CAAC;AAAA,EACvE;AAGA,QAAM,OAAO,gBAAgB,uBAAuB,sBAAsB,MAAM,OAAO,OAAO;AAC9F;AAAA,IACE,KAAK,QAAQ;AAAA,IACb,wDAAmD,KAAK,MAAM,QAAQ,CAAC,CAAC;AAAA,EAC1E;AAGA,QAAM,OAAO;AAAA,IACX;AAAA,IACA;AAAA,IACA;AAAA,IACA,OAAO;AAAA,EACT;AACA;AAAA,IACE,KAAK,QAAQ,KAAK;AAAA,IAClB,uBAAuB,KAAK,MAAM,QAAQ,CAAC,CAAC,0BAA0B,KAAK,MAAM,QAAQ,CAAC,CAAC;AAAA,EAC7F;AAGA,QAAM,OAAO;AAAA,IACX;AAAA,IACA;AAAA,IACA;AAAA,IACA,OAAO;AAAA,EACT;AACA;AAAA,IACE,KAAK,SAAS;AAAA,IACd,2CAAsC,KAAK,IAAI,UAAU,KAAK,MAAM,QAAQ,CAAC,CAAC;AAAA,EAChF;AAGA,QAAM,SAAS,CAAC,KAAK,OAAO,KAAK,OAAO,KAAK,OAAO,KAAK,KAAK;AAC9D,QAAM,eAAe,IAAI,IAAI,OAAO,IAAI,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC;AAC5D;AAAA,IACE,aAAa,QAAQ;AAAA,IACrB,GAAG,aAAa,IAAI,4CAA4C,OAAO,IAAI,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,EAAE,KAAK,IAAI,CAAC;AAAA,EAC5G;AAGA;AAAA,IACE,KAAK,iBAAiB;AAAA,IACtB,mCAAmC,KAAK,YAAY;AAAA,EACtD;AACF;AAGA;AACE,UAAQ,IAAI,4BAA4B;AACxC,QAAM,KAAK,gBAAgB,gBAAgB,QAAW,MAAQ,OAAO,OAAO;AAG5E,UAAQ;AAAA,IACN,mDAAyC,GAAG,QAAQ,WAAW,WAAW,GAAG,MAAM,QAAQ,CAAC,CAAC,UAAU,GAAG,WAAW,QAAQ,CAAC,CAAC;AAAA,EACjI;AACF;AAIA,QAAQ,IAAI,iFAAmD;AAE/D,IAAM,eAAeA,mBAAkB;AAGvC,IAAM,eAAe,YAAY,IAAI,SAAS,IAAI,EAAE,QAAQ,IAAI,CAAC;AAEjE,IAAM,aAAa;AAAA,EACjB,QAAQ;AAAA,EACR;AAAA,EACA,UAAU;AAAA,EACV,SAAS;AACX;AAEA,eAAe,UAAU,QAAgB,OAAe,cAAuB;AAC7E,QAAM,WAAW,MAAM,MAAM,QAAQ,QAAW,MAAM,UAAU;AAChE,QAAM,cAAc,SAAS,UAAU,KAAK,QAAQ,CAAC;AACrD,MAAI,cAAc;AAChB;AAAA,MACE,SAAS,SAAS;AAAA,MAClB,GAAG,KAAK,WAAM,SAAS,KAAK,KAAK,SAAS,IAAI,KAAK,SAAS,MAAM,WAAW,UAAU;AAAA,IACzF;AAAA,EACF,OAAO;AACL,YAAQ;AAAA,MACN,YAAO,KAAK,WAAM,SAAS,KAAK,KAAK,SAAS,IAAI,KAAK,SAAS,MAAM,WAAW,UAAU;AAAA,IAC7F;AAAA,EACF;AACA,SAAO;AACT;AAEA,MAAM,UAAU,kCAAkC,kBAAkB,QAAQ;AAC5E,MAAM,UAAU,uBAAuB,YAAY,QAAQ;AAC3D,MAAM;AAAA,EACJ;AAAA,EACA;AAAA,EACA;AACF;AAGA;AACE,QAAM,aAAa,IAAI,OAAO,GAAM;AACpC,QAAM,WAAW,MAAM,MAAM,YAAY,QAAW,MAAM,UAAU;AACpE;AAAA,IACE,SAAS,SAAS;AAAA,IAClB,2BAAsB,SAAS,IAAI;AAAA,EACrC;AACF;AAGA;AACE,QAAM,WAAW,MAAM;AAAA,IACrB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACA;AAAA,IACE,SAAS,SAAS,YAAY,SAAS,SAAS;AAAA,IAChD,2CAAsC,SAAS,IAAI,yBAAyB,SAAS,SAAS,QAAQ;AAAA,EACxG;AACF;AAGA;AACE,UAAQ,IAAI,yBAAyB;AACrC,QAAM,IAAI,MAAM,MAAM,gBAAgB,QAAW,MAAM,UAAU;AACjE,SAAO,EAAE,eAAe,GAAG,uBAAuB,EAAE,aAAa,QAAQ,CAAC,CAAC,EAAE;AAC7E,SAAO,EAAE,eAAe,GAAG,uBAAuB,EAAE,aAAa,QAAQ,CAAC,CAAC,EAAE;AAC7E,SAAO,EAAE,WAAW,KAAK,EAAE,WAAW,GAAG,2BAA2B,EAAE,QAAQ,QAAQ,CAAC,CAAC,EAAE;AAC1F;AAAA,IACE,EAAE,gBAAgB,EAAE;AAAA,IACpB,UAAU,EAAE,aAAa,QAAQ,CAAC,CAAC,mBAAmB,EAAE,aAAa,QAAQ,CAAC,CAAC;AAAA,EACjF;AACF;AAIA,QAAQ,IAAI,iEAAmC;AAE/C,IAAM,YAAY,QAAQ,IAAI;AAC9B,IAAI,CAAC,WAAW;AACd,UAAQ,IAAI,kEAA6D;AAC3E,OAAO;AACL,MAAI;AACF,UAAM,QAAQ,MAAM,WAAW;AAAA,MAC7B,QAAQ;AAAA,MACR,MAAM;AAAA,MACN,SAAS,CAAC,SAAS,QAAQ,IAAI,2BAA2B,IAAI,EAAE;AAAA,MAChE,SAAS,CAAC,QAAQ,QAAQ,MAAM,kBAAkB,IAAI,OAAO,EAAE;AAAA,MAC/D,UAAU,CAAC,MAAM;AACf,cAAM,OAAO,EAAE,UAAU,KAAK,QAAQ,CAAC;AACvC,gBAAQ,IAAI,cAAc,EAAE,KAAK,KAAK,EAAE,IAAI,WAAW,GAAG,GAAG;AAAA,MAC/D;AAAA,IACF,CAAC;AAGD,UAAM,SAAS,MAAM,MAAM,GAAG,MAAM,OAAO,SAAS;AACpD,UAAM,aAAc,MAAM,OAAO,KAAK;AACtC;AAAA,MACE,WAAW,WAAW;AAAA,MACtB,iBAAiB,WAAW,MAAM,aAAa,WAAW,MAAM;AAAA,IAClE;AAGA,YAAQ,IAAI,6CAA6C;AACzD,QAAI;AACF,YAAM,UAAU,MAAM,MAAM,GAAG,MAAM,OAAO,wBAAwB;AAAA,QAClE,QAAQ;AAAA,QACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,QAC9C,MAAM,KAAK,UAAU;AAAA,UACnB,OAAO;AAAA,UACP,UAAU,CAAC,EAAE,MAAM,QAAQ,SAAS,eAAe,CAAC;AAAA,UACpD,YAAY;AAAA,QACd,CAAC;AAAA,MACH,CAAC;AAED,UAAI,QAAQ,IAAI;AACd,cAAM,WAAY,MAAM,QAAQ,KAAK;AAGrC,cAAM,UAAU,SAAS,UAAU,CAAC,GAAG,SAAS,WAAW;AAC3D,gBAAQ,IAAI,sBAAiB,QAAQ,MAAM,GAAG,GAAG,CAAC,EAAE;AACpD;AAAA,MACF,OAAO;AACL,cAAM,UAAU,MAAM,QAAQ,KAAK;AACnC,gBAAQ,IAAI,sBAAsB,QAAQ,MAAM,WAAM,QAAQ,MAAM,GAAG,GAAG,CAAC,EAAE;AAE7E,YAAI,QAAQ,WAAW,KAAK;AAC1B,kBAAQ,IAAI,iEAA4D;AAAA,QAC1E;AAAA,MACF;AAAA,IACF,SAAS,KAAK;AACZ,cAAQ,IAAI,oBAAoB,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC,EAAE;AAAA,IACpF;AAEA,UAAM,MAAM,MAAM;AAClB,YAAQ,IAAI,mBAAmB;AAAA,EACjC,SAAS,KAAK;AACZ,YAAQ,MAAM,2BAA2B,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC,EAAE;AAC3F;AAAA,EACF;AACF;AAIA,QAAQ,IAAI,oNAAqC;AACjD,QAAQ,IAAI,KAAK,MAAM,YAAY,MAAM,SAAS;AAClD,QAAQ,IAAI,sNAAuC;AAEnD,QAAQ,KAAK,SAAS,IAAI,IAAI,CAAC;","names":["config","confidence","modelPricing","config","supportsToolCalling","supportsVision","config","modelPricing","createPublicClient","http","base","privateKeyToAccount","payload","response","join","homedir","join","require","LOG_DIR","join","homedir","DEFAULT_TTL_MS","createHash","canonicalize","TIMESTAMP_PATTERN","config","CACHE_TTL_MS","mkdir","join","homedir","privateKeyToAccount","join","homedir","escapeRegex","config","originalChars","createHash","config","DEFAULT_CONFIG","config","sanitized","walletKey","account","privateKeyToAccount","baseUrl","balanceMonitor","createPublicClient","base","http","modelPricing","routerOpts","normalizedModel","buildModelPricing"]} \ No newline at end of file +{"version":3,"sources":["../src/router/rules.ts","../src/router/selector.ts","../src/router/config.ts","../src/router/index.ts","../src/models.ts","../src/proxy.ts","../src/payment-preauth.ts","../src/logger.ts","../src/stats.ts","../src/fs-read.ts","../src/version.ts","../src/dedup.ts","../src/response-cache.ts","../src/balance.ts","../src/errors.ts","../src/solana-balance.ts","../src/auth.ts","../src/wallet.ts","../src/compression/types.ts","../src/compression/layers/deduplication.ts","../src/compression/layers/whitespace.ts","../src/compression/codebook.ts","../src/compression/layers/dictionary.ts","../src/compression/layers/paths.ts","../src/compression/layers/json-compact.ts","../src/compression/layers/observation.ts","../src/compression/layers/dynamic-codebook.ts","../src/compression/index.ts","../src/session.ts","../src/updater.ts","../src/config.ts","../src/journal.ts","../test/e2e.ts"],"sourcesContent":["/**\n * Rule-Based Classifier (v2 — Weighted Scoring)\n *\n * Scores a request across 14 weighted dimensions and maps the aggregate\n * score to a tier using configurable boundaries. Confidence is calibrated\n * via sigmoid — low confidence triggers the fallback classifier.\n *\n * Handles 70-80% of requests in < 1ms with zero cost.\n */\n\nimport type { Tier, ScoringResult, ScoringConfig } from \"./types.js\";\n\ntype DimensionScore = { name: string; score: number; signal: string | null };\n\n// ─── Dimension Scorers ───\n// Each returns a score in [-1, 1] and an optional signal string.\n\nfunction scoreTokenCount(\n estimatedTokens: number,\n thresholds: { simple: number; complex: number },\n): DimensionScore {\n if (estimatedTokens < thresholds.simple) {\n return { name: \"tokenCount\", score: -1.0, signal: `short (${estimatedTokens} tokens)` };\n }\n if (estimatedTokens > thresholds.complex) {\n return { name: \"tokenCount\", score: 1.0, signal: `long (${estimatedTokens} tokens)` };\n }\n return { name: \"tokenCount\", score: 0, signal: null };\n}\n\nfunction scoreKeywordMatch(\n text: string,\n keywords: string[],\n name: string,\n signalLabel: string,\n thresholds: { low: number; high: number },\n scores: { none: number; low: number; high: number },\n): DimensionScore {\n const matches = keywords.filter((kw) => text.includes(kw.toLowerCase()));\n if (matches.length >= thresholds.high) {\n return {\n name,\n score: scores.high,\n signal: `${signalLabel} (${matches.slice(0, 3).join(\", \")})`,\n };\n }\n if (matches.length >= thresholds.low) {\n return {\n name,\n score: scores.low,\n signal: `${signalLabel} (${matches.slice(0, 3).join(\", \")})`,\n };\n }\n return { name, score: scores.none, signal: null };\n}\n\nfunction scoreMultiStep(text: string): DimensionScore {\n const patterns = [/first.*then/i, /step \\d/i, /\\d\\.\\s/];\n const hits = patterns.filter((p) => p.test(text));\n if (hits.length > 0) {\n return { name: \"multiStepPatterns\", score: 0.5, signal: \"multi-step\" };\n }\n return { name: \"multiStepPatterns\", score: 0, signal: null };\n}\n\nfunction scoreQuestionComplexity(prompt: string): DimensionScore {\n const count = (prompt.match(/\\?/g) || []).length;\n if (count > 3) {\n return { name: \"questionComplexity\", score: 0.5, signal: `${count} questions` };\n }\n return { name: \"questionComplexity\", score: 0, signal: null };\n}\n\n/**\n * Score agentic task indicators.\n * Returns agenticScore (0-1) based on keyword matches:\n * - 4+ matches = 1.0 (high agentic)\n * - 3 matches = 0.6 (moderate agentic, triggers auto-agentic mode)\n * - 1-2 matches = 0.2 (low agentic)\n *\n * Thresholds raised because common keywords were pruned from the list.\n */\nfunction scoreAgenticTask(\n text: string,\n keywords: string[],\n): { dimensionScore: DimensionScore; agenticScore: number } {\n let matchCount = 0;\n const signals: string[] = [];\n\n for (const keyword of keywords) {\n if (text.includes(keyword.toLowerCase())) {\n matchCount++;\n if (signals.length < 3) {\n signals.push(keyword);\n }\n }\n }\n\n // Threshold-based scoring (raised thresholds after keyword pruning)\n if (matchCount >= 4) {\n return {\n dimensionScore: {\n name: \"agenticTask\",\n score: 1.0,\n signal: `agentic (${signals.join(\", \")})`,\n },\n agenticScore: 1.0,\n };\n } else if (matchCount >= 3) {\n return {\n dimensionScore: {\n name: \"agenticTask\",\n score: 0.6,\n signal: `agentic (${signals.join(\", \")})`,\n },\n agenticScore: 0.6,\n };\n } else if (matchCount >= 1) {\n return {\n dimensionScore: {\n name: \"agenticTask\",\n score: 0.2,\n signal: `agentic-light (${signals.join(\", \")})`,\n },\n agenticScore: 0.2,\n };\n }\n\n return {\n dimensionScore: { name: \"agenticTask\", score: 0, signal: null },\n agenticScore: 0,\n };\n}\n\n// ─── Main Classifier ───\n\nexport function classifyByRules(\n prompt: string,\n systemPrompt: string | undefined,\n estimatedTokens: number,\n config: ScoringConfig,\n): ScoringResult {\n // Score against user prompt only — system prompts contain boilerplate keywords\n // (tool definitions, skill descriptions, behavioral rules) that dominate scoring\n // and make every request score identically. See GitHub issue #50.\n const userText = prompt.toLowerCase();\n\n // Score all 14 dimensions against user text only\n const dimensions: DimensionScore[] = [\n // Token count uses total estimated tokens (system + user) — context size matters for model selection\n scoreTokenCount(estimatedTokens, config.tokenCountThresholds),\n scoreKeywordMatch(\n userText,\n config.codeKeywords,\n \"codePresence\",\n \"code\",\n { low: 1, high: 2 },\n { none: 0, low: 0.5, high: 1.0 },\n ),\n scoreKeywordMatch(\n userText,\n config.reasoningKeywords,\n \"reasoningMarkers\",\n \"reasoning\",\n { low: 1, high: 2 },\n { none: 0, low: 0.7, high: 1.0 },\n ),\n scoreKeywordMatch(\n userText,\n config.technicalKeywords,\n \"technicalTerms\",\n \"technical\",\n { low: 2, high: 4 },\n { none: 0, low: 0.5, high: 1.0 },\n ),\n scoreKeywordMatch(\n userText,\n config.creativeKeywords,\n \"creativeMarkers\",\n \"creative\",\n { low: 1, high: 2 },\n { none: 0, low: 0.5, high: 0.7 },\n ),\n scoreKeywordMatch(\n userText,\n config.simpleKeywords,\n \"simpleIndicators\",\n \"simple\",\n { low: 1, high: 2 },\n { none: 0, low: -1.0, high: -1.0 },\n ),\n scoreMultiStep(userText),\n scoreQuestionComplexity(prompt),\n\n // 6 new dimensions\n scoreKeywordMatch(\n userText,\n config.imperativeVerbs,\n \"imperativeVerbs\",\n \"imperative\",\n { low: 1, high: 2 },\n { none: 0, low: 0.3, high: 0.5 },\n ),\n scoreKeywordMatch(\n userText,\n config.constraintIndicators,\n \"constraintCount\",\n \"constraints\",\n { low: 1, high: 3 },\n { none: 0, low: 0.3, high: 0.7 },\n ),\n scoreKeywordMatch(\n userText,\n config.outputFormatKeywords,\n \"outputFormat\",\n \"format\",\n { low: 1, high: 2 },\n { none: 0, low: 0.4, high: 0.7 },\n ),\n scoreKeywordMatch(\n userText,\n config.referenceKeywords,\n \"referenceComplexity\",\n \"references\",\n { low: 1, high: 2 },\n { none: 0, low: 0.3, high: 0.5 },\n ),\n scoreKeywordMatch(\n userText,\n config.negationKeywords,\n \"negationComplexity\",\n \"negation\",\n { low: 2, high: 3 },\n { none: 0, low: 0.3, high: 0.5 },\n ),\n scoreKeywordMatch(\n userText,\n config.domainSpecificKeywords,\n \"domainSpecificity\",\n \"domain-specific\",\n { low: 1, high: 2 },\n { none: 0, low: 0.5, high: 0.8 },\n ),\n ];\n\n // Score agentic task indicators — user prompt only\n // System prompt describes assistant behavior, not user's intent.\n // e.g. a coding assistant system prompt with \"edit files\" / \"fix bugs\" should NOT\n // force every request into agentic mode.\n const agenticResult = scoreAgenticTask(userText, config.agenticTaskKeywords);\n dimensions.push(agenticResult.dimensionScore);\n const agenticScore = agenticResult.agenticScore;\n\n // Collect signals\n const signals = dimensions.filter((d) => d.signal !== null).map((d) => d.signal!);\n\n // Compute weighted score\n const weights = config.dimensionWeights;\n let weightedScore = 0;\n for (const d of dimensions) {\n const w = weights[d.name] ?? 0;\n weightedScore += d.score * w;\n }\n\n // Count reasoning markers for override — only check USER prompt, not system prompt\n // This prevents system prompts with \"step by step\" from triggering REASONING for simple queries\n const reasoningMatches = config.reasoningKeywords.filter((kw) =>\n userText.includes(kw.toLowerCase()),\n );\n\n // Direct reasoning override: 2+ reasoning markers = high confidence REASONING\n if (reasoningMatches.length >= 2) {\n const confidence = calibrateConfidence(\n Math.max(weightedScore, 0.3), // ensure positive for confidence calc\n config.confidenceSteepness,\n );\n return {\n score: weightedScore,\n tier: \"REASONING\",\n confidence: Math.max(confidence, 0.85),\n signals,\n agenticScore,\n dimensions,\n };\n }\n\n // Map weighted score to tier using boundaries\n const { simpleMedium, mediumComplex, complexReasoning } = config.tierBoundaries;\n let tier: Tier;\n let distanceFromBoundary: number;\n\n if (weightedScore < simpleMedium) {\n tier = \"SIMPLE\";\n distanceFromBoundary = simpleMedium - weightedScore;\n } else if (weightedScore < mediumComplex) {\n tier = \"MEDIUM\";\n distanceFromBoundary = Math.min(weightedScore - simpleMedium, mediumComplex - weightedScore);\n } else if (weightedScore < complexReasoning) {\n tier = \"COMPLEX\";\n distanceFromBoundary = Math.min(\n weightedScore - mediumComplex,\n complexReasoning - weightedScore,\n );\n } else {\n tier = \"REASONING\";\n distanceFromBoundary = weightedScore - complexReasoning;\n }\n\n // Calibrate confidence via sigmoid of distance from nearest boundary\n const confidence = calibrateConfidence(distanceFromBoundary, config.confidenceSteepness);\n\n // If confidence is below threshold → ambiguous\n if (confidence < config.confidenceThreshold) {\n return { score: weightedScore, tier: null, confidence, signals, agenticScore, dimensions };\n }\n\n return { score: weightedScore, tier, confidence, signals, agenticScore, dimensions };\n}\n\n/**\n * Sigmoid confidence calibration.\n * Maps distance from tier boundary to [0.5, 1.0] confidence range.\n */\nfunction calibrateConfidence(distance: number, steepness: number): number {\n return 1 / (1 + Math.exp(-steepness * distance));\n}\n","/**\n * Tier → Model Selection\n *\n * Maps a classification tier to the cheapest capable model.\n * Builds RoutingDecision metadata with cost estimates and savings.\n */\n\nimport type { Tier, TierConfig, RoutingDecision } from \"./types.js\";\n\nexport type ModelPricing = {\n inputPrice: number; // per 1M tokens\n outputPrice: number; // per 1M tokens\n};\n\nconst BASELINE_MODEL_ID = \"anthropic/claude-opus-4.6\";\n\n// Hardcoded fallback: Claude Opus 4.6 pricing (per 1M tokens)\n// Used when baseline model not found in dynamic pricing map\nconst BASELINE_INPUT_PRICE = 5.0;\nconst BASELINE_OUTPUT_PRICE = 25.0;\n\n/**\n * Select the primary model for a tier and build the RoutingDecision.\n */\nexport function selectModel(\n tier: Tier,\n confidence: number,\n method: \"rules\" | \"llm\",\n reasoning: string,\n tierConfigs: Record,\n modelPricing: Map,\n estimatedInputTokens: number,\n maxOutputTokens: number,\n routingProfile?: \"free\" | \"eco\" | \"auto\" | \"premium\",\n agenticScore?: number,\n): RoutingDecision {\n const tierConfig = tierConfigs[tier];\n const model = tierConfig.primary;\n const pricing = modelPricing.get(model);\n\n // Defensive: guard against undefined price fields (not just undefined pricing)\n const inputPrice = pricing?.inputPrice ?? 0;\n const outputPrice = pricing?.outputPrice ?? 0;\n const inputCost = (estimatedInputTokens / 1_000_000) * inputPrice;\n const outputCost = (maxOutputTokens / 1_000_000) * outputPrice;\n const costEstimate = inputCost + outputCost;\n\n // Baseline: what Claude Opus 4.5 would cost (the premium reference)\n const opusPricing = modelPricing.get(BASELINE_MODEL_ID);\n const opusInputPrice = opusPricing?.inputPrice ?? BASELINE_INPUT_PRICE;\n const opusOutputPrice = opusPricing?.outputPrice ?? BASELINE_OUTPUT_PRICE;\n const baselineInput = (estimatedInputTokens / 1_000_000) * opusInputPrice;\n const baselineOutput = (maxOutputTokens / 1_000_000) * opusOutputPrice;\n const baselineCost = baselineInput + baselineOutput;\n\n // Premium profile doesn't calculate savings (it's about quality, not cost)\n const savings =\n routingProfile === \"premium\"\n ? 0\n : baselineCost > 0\n ? Math.max(0, (baselineCost - costEstimate) / baselineCost)\n : 0;\n\n return {\n model,\n tier,\n confidence,\n method,\n reasoning,\n costEstimate,\n baselineCost,\n savings,\n ...(agenticScore !== undefined && { agenticScore }),\n };\n}\n\n/**\n * Get the ordered fallback chain for a tier: [primary, ...fallbacks].\n */\nexport function getFallbackChain(tier: Tier, tierConfigs: Record): string[] {\n const config = tierConfigs[tier];\n return [config.primary, ...config.fallback];\n}\n\n/**\n * Calculate cost for a specific model (used when fallback model is used).\n * Returns updated cost fields for RoutingDecision.\n */\nexport function calculateModelCost(\n model: string,\n modelPricing: Map,\n estimatedInputTokens: number,\n maxOutputTokens: number,\n routingProfile?: \"free\" | \"eco\" | \"auto\" | \"premium\",\n): { costEstimate: number; baselineCost: number; savings: number } {\n const pricing = modelPricing.get(model);\n\n // Defensive: guard against undefined price fields (not just undefined pricing)\n const inputPrice = pricing?.inputPrice ?? 0;\n const outputPrice = pricing?.outputPrice ?? 0;\n const inputCost = (estimatedInputTokens / 1_000_000) * inputPrice;\n const outputCost = (maxOutputTokens / 1_000_000) * outputPrice;\n const costEstimate = inputCost + outputCost;\n\n // Baseline: what Claude Opus 4.5 would cost (the premium reference)\n const opusPricing = modelPricing.get(BASELINE_MODEL_ID);\n const opusInputPrice = opusPricing?.inputPrice ?? BASELINE_INPUT_PRICE;\n const opusOutputPrice = opusPricing?.outputPrice ?? BASELINE_OUTPUT_PRICE;\n const baselineInput = (estimatedInputTokens / 1_000_000) * opusInputPrice;\n const baselineOutput = (maxOutputTokens / 1_000_000) * opusOutputPrice;\n const baselineCost = baselineInput + baselineOutput;\n\n // Premium profile doesn't calculate savings (it's about quality, not cost)\n const savings =\n routingProfile === \"premium\"\n ? 0\n : baselineCost > 0\n ? Math.max(0, (baselineCost - costEstimate) / baselineCost)\n : 0;\n\n return { costEstimate, baselineCost, savings };\n}\n\n/**\n * Filter a model list to only those that support tool calling.\n * When hasTools is false, returns the list unchanged.\n * When all models lack tool calling support, returns the full list as a fallback\n * (better to let the API error than produce an empty chain).\n */\nexport function filterByToolCalling(\n models: string[],\n hasTools: boolean,\n supportsToolCalling: (modelId: string) => boolean,\n): string[] {\n if (!hasTools) return models;\n const filtered = models.filter(supportsToolCalling);\n return filtered.length > 0 ? filtered : models;\n}\n\n/**\n * Filter a model list to only those that support vision (image inputs).\n * When hasVision is false, returns the list unchanged.\n * When all models lack vision support, returns the full list as a fallback\n * (better to let the API error than produce an empty chain).\n */\nexport function filterByVision(\n models: string[],\n hasVision: boolean,\n supportsVision: (modelId: string) => boolean,\n): string[] {\n if (!hasVision) return models;\n const filtered = models.filter(supportsVision);\n return filtered.length > 0 ? filtered : models;\n}\n\n/**\n * Get the fallback chain filtered by context length.\n * Only returns models that can handle the estimated total context.\n *\n * @param tier - The tier to get fallback chain for\n * @param tierConfigs - Tier configurations\n * @param estimatedTotalTokens - Estimated total context (input + output)\n * @param getContextWindow - Function to get context window for a model ID\n * @returns Filtered list of models that can handle the context\n */\nexport function getFallbackChainFiltered(\n tier: Tier,\n tierConfigs: Record,\n estimatedTotalTokens: number,\n getContextWindow: (modelId: string) => number | undefined,\n): string[] {\n const fullChain = getFallbackChain(tier, tierConfigs);\n\n // Filter to models that can handle the context\n const filtered = fullChain.filter((modelId) => {\n const contextWindow = getContextWindow(modelId);\n if (contextWindow === undefined) {\n // Unknown model - include it (let API reject if needed)\n return true;\n }\n // Add 10% buffer for safety\n return contextWindow >= estimatedTotalTokens * 1.1;\n });\n\n // If all models filtered out, return the original chain\n // (let the API error out - better than no options)\n if (filtered.length === 0) {\n return fullChain;\n }\n\n return filtered;\n}\n","/**\n * Default Routing Config\n *\n * All routing parameters as a TypeScript constant.\n * Operators override via openclaw.yaml plugin config.\n *\n * Scoring uses 14 weighted dimensions with sigmoid confidence calibration.\n */\n\nimport type { RoutingConfig } from \"./types.js\";\n\nexport const DEFAULT_ROUTING_CONFIG: RoutingConfig = {\n version: \"2.0\",\n\n classifier: {\n llmModel: \"google/gemini-2.5-flash\",\n llmMaxTokens: 10,\n llmTemperature: 0,\n promptTruncationChars: 500,\n cacheTtlMs: 3_600_000, // 1 hour\n },\n\n scoring: {\n tokenCountThresholds: { simple: 50, complex: 500 },\n\n // Multilingual keywords: EN + ZH + JA + RU + DE + ES + PT + KO + AR\n codeKeywords: [\n // English\n \"function\",\n \"class\",\n \"import\",\n \"def\",\n \"SELECT\",\n \"async\",\n \"await\",\n \"const\",\n \"let\",\n \"var\",\n \"return\",\n \"```\",\n // Chinese\n \"函数\",\n \"类\",\n \"导入\",\n \"定义\",\n \"查询\",\n \"异步\",\n \"等待\",\n \"常量\",\n \"变量\",\n \"返回\",\n // Japanese\n \"関数\",\n \"クラス\",\n \"インポート\",\n \"非同期\",\n \"定数\",\n \"変数\",\n // Russian\n \"функция\",\n \"класс\",\n \"импорт\",\n \"определ\",\n \"запрос\",\n \"асинхронный\",\n \"ожидать\",\n \"константа\",\n \"переменная\",\n \"вернуть\",\n // German\n \"funktion\",\n \"klasse\",\n \"importieren\",\n \"definieren\",\n \"abfrage\",\n \"asynchron\",\n \"erwarten\",\n \"konstante\",\n \"variable\",\n \"zurückgeben\",\n // Spanish\n \"función\",\n \"clase\",\n \"importar\",\n \"definir\",\n \"consulta\",\n \"asíncrono\",\n \"esperar\",\n \"constante\",\n \"variable\",\n \"retornar\",\n // Portuguese\n \"função\",\n \"classe\",\n \"importar\",\n \"definir\",\n \"consulta\",\n \"assíncrono\",\n \"aguardar\",\n \"constante\",\n \"variável\",\n \"retornar\",\n // Korean\n \"함수\",\n \"클래스\",\n \"가져오기\",\n \"정의\",\n \"쿼리\",\n \"비동기\",\n \"대기\",\n \"상수\",\n \"변수\",\n \"반환\",\n // Arabic\n \"دالة\",\n \"فئة\",\n \"استيراد\",\n \"تعريف\",\n \"استعلام\",\n \"غير متزامن\",\n \"انتظار\",\n \"ثابت\",\n \"متغير\",\n \"إرجاع\",\n ],\n reasoningKeywords: [\n // English\n \"prove\",\n \"theorem\",\n \"derive\",\n \"step by step\",\n \"chain of thought\",\n \"formally\",\n \"mathematical\",\n \"proof\",\n \"logically\",\n // Chinese\n \"证明\",\n \"定理\",\n \"推导\",\n \"逐步\",\n \"思维链\",\n \"形式化\",\n \"数学\",\n \"逻辑\",\n // Japanese\n \"証明\",\n \"定理\",\n \"導出\",\n \"ステップバイステップ\",\n \"論理的\",\n // Russian\n \"доказать\",\n \"докажи\",\n \"доказательств\",\n \"теорема\",\n \"вывести\",\n \"шаг за шагом\",\n \"пошагово\",\n \"поэтапно\",\n \"цепочка рассуждений\",\n \"рассуждени\",\n \"формально\",\n \"математически\",\n \"логически\",\n // German\n \"beweisen\",\n \"beweis\",\n \"theorem\",\n \"ableiten\",\n \"schritt für schritt\",\n \"gedankenkette\",\n \"formal\",\n \"mathematisch\",\n \"logisch\",\n // Spanish\n \"demostrar\",\n \"teorema\",\n \"derivar\",\n \"paso a paso\",\n \"cadena de pensamiento\",\n \"formalmente\",\n \"matemático\",\n \"prueba\",\n \"lógicamente\",\n // Portuguese\n \"provar\",\n \"teorema\",\n \"derivar\",\n \"passo a passo\",\n \"cadeia de pensamento\",\n \"formalmente\",\n \"matemático\",\n \"prova\",\n \"logicamente\",\n // Korean\n \"증명\",\n \"정리\",\n \"도출\",\n \"단계별\",\n \"사고의 연쇄\",\n \"형식적\",\n \"수학적\",\n \"논리적\",\n // Arabic\n \"إثبات\",\n \"نظرية\",\n \"اشتقاق\",\n \"خطوة بخطوة\",\n \"سلسلة التفكير\",\n \"رسمياً\",\n \"رياضي\",\n \"برهان\",\n \"منطقياً\",\n ],\n simpleKeywords: [\n // English\n \"what is\",\n \"define\",\n \"translate\",\n \"hello\",\n \"yes or no\",\n \"capital of\",\n \"how old\",\n \"who is\",\n \"when was\",\n // Chinese\n \"什么是\",\n \"定义\",\n \"翻译\",\n \"你好\",\n \"是否\",\n \"首都\",\n \"多大\",\n \"谁是\",\n \"何时\",\n // Japanese\n \"とは\",\n \"定義\",\n \"翻訳\",\n \"こんにちは\",\n \"はいかいいえ\",\n \"首都\",\n \"誰\",\n // Russian\n \"что такое\",\n \"определение\",\n \"перевести\",\n \"переведи\",\n \"привет\",\n \"да или нет\",\n \"столица\",\n \"сколько лет\",\n \"кто такой\",\n \"когда\",\n \"объясни\",\n // German\n \"was ist\",\n \"definiere\",\n \"übersetze\",\n \"hallo\",\n \"ja oder nein\",\n \"hauptstadt\",\n \"wie alt\",\n \"wer ist\",\n \"wann\",\n \"erkläre\",\n // Spanish\n \"qué es\",\n \"definir\",\n \"traducir\",\n \"hola\",\n \"sí o no\",\n \"capital de\",\n \"cuántos años\",\n \"quién es\",\n \"cuándo\",\n // Portuguese\n \"o que é\",\n \"definir\",\n \"traduzir\",\n \"olá\",\n \"sim ou não\",\n \"capital de\",\n \"quantos anos\",\n \"quem é\",\n \"quando\",\n // Korean\n \"무엇\",\n \"정의\",\n \"번역\",\n \"안녕하세요\",\n \"예 또는 아니오\",\n \"수도\",\n \"누구\",\n \"언제\",\n // Arabic\n \"ما هو\",\n \"تعريف\",\n \"ترجم\",\n \"مرحبا\",\n \"نعم أو لا\",\n \"عاصمة\",\n \"من هو\",\n \"متى\",\n ],\n technicalKeywords: [\n // English\n \"algorithm\",\n \"optimize\",\n \"architecture\",\n \"distributed\",\n \"kubernetes\",\n \"microservice\",\n \"database\",\n \"infrastructure\",\n // Chinese\n \"算法\",\n \"优化\",\n \"架构\",\n \"分布式\",\n \"微服务\",\n \"数据库\",\n \"基础设施\",\n // Japanese\n \"アルゴリズム\",\n \"最適化\",\n \"アーキテクチャ\",\n \"分散\",\n \"マイクロサービス\",\n \"データベース\",\n // Russian\n \"алгоритм\",\n \"оптимизировать\",\n \"оптимизаци\",\n \"оптимизируй\",\n \"архитектура\",\n \"распределённый\",\n \"микросервис\",\n \"база данных\",\n \"инфраструктура\",\n // German\n \"algorithmus\",\n \"optimieren\",\n \"architektur\",\n \"verteilt\",\n \"kubernetes\",\n \"mikroservice\",\n \"datenbank\",\n \"infrastruktur\",\n // Spanish\n \"algoritmo\",\n \"optimizar\",\n \"arquitectura\",\n \"distribuido\",\n \"microservicio\",\n \"base de datos\",\n \"infraestructura\",\n // Portuguese\n \"algoritmo\",\n \"otimizar\",\n \"arquitetura\",\n \"distribuído\",\n \"microsserviço\",\n \"banco de dados\",\n \"infraestrutura\",\n // Korean\n \"알고리즘\",\n \"최적화\",\n \"아키텍처\",\n \"분산\",\n \"마이크로서비스\",\n \"데이터베이스\",\n \"인프라\",\n // Arabic\n \"خوارزمية\",\n \"تحسين\",\n \"بنية\",\n \"موزع\",\n \"خدمة مصغرة\",\n \"قاعدة بيانات\",\n \"بنية تحتية\",\n ],\n creativeKeywords: [\n // English\n \"story\",\n \"poem\",\n \"compose\",\n \"brainstorm\",\n \"creative\",\n \"imagine\",\n \"write a\",\n // Chinese\n \"故事\",\n \"诗\",\n \"创作\",\n \"头脑风暴\",\n \"创意\",\n \"想象\",\n \"写一个\",\n // Japanese\n \"物語\",\n \"詩\",\n \"作曲\",\n \"ブレインストーム\",\n \"創造的\",\n \"想像\",\n // Russian\n \"история\",\n \"рассказ\",\n \"стихотворение\",\n \"сочинить\",\n \"сочини\",\n \"мозговой штурм\",\n \"творческий\",\n \"представить\",\n \"придумай\",\n \"напиши\",\n // German\n \"geschichte\",\n \"gedicht\",\n \"komponieren\",\n \"brainstorming\",\n \"kreativ\",\n \"vorstellen\",\n \"schreibe\",\n \"erzählung\",\n // Spanish\n \"historia\",\n \"poema\",\n \"componer\",\n \"lluvia de ideas\",\n \"creativo\",\n \"imaginar\",\n \"escribe\",\n // Portuguese\n \"história\",\n \"poema\",\n \"compor\",\n \"criativo\",\n \"imaginar\",\n \"escreva\",\n // Korean\n \"이야기\",\n \"시\",\n \"작곡\",\n \"브레인스토밍\",\n \"창의적\",\n \"상상\",\n \"작성\",\n // Arabic\n \"قصة\",\n \"قصيدة\",\n \"تأليف\",\n \"عصف ذهني\",\n \"إبداعي\",\n \"تخيل\",\n \"اكتب\",\n ],\n\n // New dimension keyword lists (multilingual)\n imperativeVerbs: [\n // English\n \"build\",\n \"create\",\n \"implement\",\n \"design\",\n \"develop\",\n \"construct\",\n \"generate\",\n \"deploy\",\n \"configure\",\n \"set up\",\n // Chinese\n \"构建\",\n \"创建\",\n \"实现\",\n \"设计\",\n \"开发\",\n \"生成\",\n \"部署\",\n \"配置\",\n \"设置\",\n // Japanese\n \"構築\",\n \"作成\",\n \"実装\",\n \"設計\",\n \"開発\",\n \"生成\",\n \"デプロイ\",\n \"設定\",\n // Russian\n \"построить\",\n \"построй\",\n \"создать\",\n \"создай\",\n \"реализовать\",\n \"реализуй\",\n \"спроектировать\",\n \"разработать\",\n \"разработай\",\n \"сконструировать\",\n \"сгенерировать\",\n \"сгенерируй\",\n \"развернуть\",\n \"разверни\",\n \"настроить\",\n \"настрой\",\n // German\n \"erstellen\",\n \"bauen\",\n \"implementieren\",\n \"entwerfen\",\n \"entwickeln\",\n \"konstruieren\",\n \"generieren\",\n \"bereitstellen\",\n \"konfigurieren\",\n \"einrichten\",\n // Spanish\n \"construir\",\n \"crear\",\n \"implementar\",\n \"diseñar\",\n \"desarrollar\",\n \"generar\",\n \"desplegar\",\n \"configurar\",\n // Portuguese\n \"construir\",\n \"criar\",\n \"implementar\",\n \"projetar\",\n \"desenvolver\",\n \"gerar\",\n \"implantar\",\n \"configurar\",\n // Korean\n \"구축\",\n \"생성\",\n \"구현\",\n \"설계\",\n \"개발\",\n \"배포\",\n \"설정\",\n // Arabic\n \"بناء\",\n \"إنشاء\",\n \"تنفيذ\",\n \"تصميم\",\n \"تطوير\",\n \"توليد\",\n \"نشر\",\n \"إعداد\",\n ],\n constraintIndicators: [\n // English\n \"under\",\n \"at most\",\n \"at least\",\n \"within\",\n \"no more than\",\n \"o(\",\n \"maximum\",\n \"minimum\",\n \"limit\",\n \"budget\",\n // Chinese\n \"不超过\",\n \"至少\",\n \"最多\",\n \"在内\",\n \"最大\",\n \"最小\",\n \"限制\",\n \"预算\",\n // Japanese\n \"以下\",\n \"最大\",\n \"最小\",\n \"制限\",\n \"予算\",\n // Russian\n \"не более\",\n \"не менее\",\n \"как минимум\",\n \"в пределах\",\n \"максимум\",\n \"минимум\",\n \"ограничение\",\n \"бюджет\",\n // German\n \"höchstens\",\n \"mindestens\",\n \"innerhalb\",\n \"nicht mehr als\",\n \"maximal\",\n \"minimal\",\n \"grenze\",\n \"budget\",\n // Spanish\n \"como máximo\",\n \"al menos\",\n \"dentro de\",\n \"no más de\",\n \"máximo\",\n \"mínimo\",\n \"límite\",\n \"presupuesto\",\n // Portuguese\n \"no máximo\",\n \"pelo menos\",\n \"dentro de\",\n \"não mais que\",\n \"máximo\",\n \"mínimo\",\n \"limite\",\n \"orçamento\",\n // Korean\n \"이하\",\n \"이상\",\n \"최대\",\n \"최소\",\n \"제한\",\n \"예산\",\n // Arabic\n \"على الأكثر\",\n \"على الأقل\",\n \"ضمن\",\n \"لا يزيد عن\",\n \"أقصى\",\n \"أدنى\",\n \"حد\",\n \"ميزانية\",\n ],\n outputFormatKeywords: [\n // English\n \"json\",\n \"yaml\",\n \"xml\",\n \"table\",\n \"csv\",\n \"markdown\",\n \"schema\",\n \"format as\",\n \"structured\",\n // Chinese\n \"表格\",\n \"格式化为\",\n \"结构化\",\n // Japanese\n \"テーブル\",\n \"フォーマット\",\n \"構造化\",\n // Russian\n \"таблица\",\n \"форматировать как\",\n \"структурированный\",\n // German\n \"tabelle\",\n \"formatieren als\",\n \"strukturiert\",\n // Spanish\n \"tabla\",\n \"formatear como\",\n \"estructurado\",\n // Portuguese\n \"tabela\",\n \"formatar como\",\n \"estruturado\",\n // Korean\n \"테이블\",\n \"형식\",\n \"구조화\",\n // Arabic\n \"جدول\",\n \"تنسيق\",\n \"منظم\",\n ],\n referenceKeywords: [\n // English\n \"above\",\n \"below\",\n \"previous\",\n \"following\",\n \"the docs\",\n \"the api\",\n \"the code\",\n \"earlier\",\n \"attached\",\n // Chinese\n \"上面\",\n \"下面\",\n \"之前\",\n \"接下来\",\n \"文档\",\n \"代码\",\n \"附件\",\n // Japanese\n \"上記\",\n \"下記\",\n \"前の\",\n \"次の\",\n \"ドキュメント\",\n \"コード\",\n // Russian\n \"выше\",\n \"ниже\",\n \"предыдущий\",\n \"следующий\",\n \"документация\",\n \"код\",\n \"ранее\",\n \"вложение\",\n // German\n \"oben\",\n \"unten\",\n \"vorherige\",\n \"folgende\",\n \"dokumentation\",\n \"der code\",\n \"früher\",\n \"anhang\",\n // Spanish\n \"arriba\",\n \"abajo\",\n \"anterior\",\n \"siguiente\",\n \"documentación\",\n \"el código\",\n \"adjunto\",\n // Portuguese\n \"acima\",\n \"abaixo\",\n \"anterior\",\n \"seguinte\",\n \"documentação\",\n \"o código\",\n \"anexo\",\n // Korean\n \"위\",\n \"아래\",\n \"이전\",\n \"다음\",\n \"문서\",\n \"코드\",\n \"첨부\",\n // Arabic\n \"أعلاه\",\n \"أدناه\",\n \"السابق\",\n \"التالي\",\n \"الوثائق\",\n \"الكود\",\n \"مرفق\",\n ],\n negationKeywords: [\n // English\n \"don't\",\n \"do not\",\n \"avoid\",\n \"never\",\n \"without\",\n \"except\",\n \"exclude\",\n \"no longer\",\n // Chinese\n \"不要\",\n \"避免\",\n \"从不\",\n \"没有\",\n \"除了\",\n \"排除\",\n // Japanese\n \"しないで\",\n \"避ける\",\n \"決して\",\n \"なしで\",\n \"除く\",\n // Russian\n \"не делай\",\n \"не надо\",\n \"нельзя\",\n \"избегать\",\n \"никогда\",\n \"без\",\n \"кроме\",\n \"исключить\",\n \"больше не\",\n // German\n \"nicht\",\n \"vermeide\",\n \"niemals\",\n \"ohne\",\n \"außer\",\n \"ausschließen\",\n \"nicht mehr\",\n // Spanish\n \"no hagas\",\n \"evitar\",\n \"nunca\",\n \"sin\",\n \"excepto\",\n \"excluir\",\n // Portuguese\n \"não faça\",\n \"evitar\",\n \"nunca\",\n \"sem\",\n \"exceto\",\n \"excluir\",\n // Korean\n \"하지 마\",\n \"피하다\",\n \"절대\",\n \"없이\",\n \"제외\",\n // Arabic\n \"لا تفعل\",\n \"تجنب\",\n \"أبداً\",\n \"بدون\",\n \"باستثناء\",\n \"استبعاد\",\n ],\n domainSpecificKeywords: [\n // English\n \"quantum\",\n \"fpga\",\n \"vlsi\",\n \"risc-v\",\n \"asic\",\n \"photonics\",\n \"genomics\",\n \"proteomics\",\n \"topological\",\n \"homomorphic\",\n \"zero-knowledge\",\n \"lattice-based\",\n // Chinese\n \"量子\",\n \"光子学\",\n \"基因组学\",\n \"蛋白质组学\",\n \"拓扑\",\n \"同态\",\n \"零知识\",\n \"格密码\",\n // Japanese\n \"量子\",\n \"フォトニクス\",\n \"ゲノミクス\",\n \"トポロジカル\",\n // Russian\n \"квантовый\",\n \"фотоника\",\n \"геномика\",\n \"протеомика\",\n \"топологический\",\n \"гомоморфный\",\n \"с нулевым разглашением\",\n \"на основе решёток\",\n // German\n \"quanten\",\n \"photonik\",\n \"genomik\",\n \"proteomik\",\n \"topologisch\",\n \"homomorph\",\n \"zero-knowledge\",\n \"gitterbasiert\",\n // Spanish\n \"cuántico\",\n \"fotónica\",\n \"genómica\",\n \"proteómica\",\n \"topológico\",\n \"homomórfico\",\n // Portuguese\n \"quântico\",\n \"fotônica\",\n \"genômica\",\n \"proteômica\",\n \"topológico\",\n \"homomórfico\",\n // Korean\n \"양자\",\n \"포토닉스\",\n \"유전체학\",\n \"위상\",\n \"동형\",\n // Arabic\n \"كمي\",\n \"ضوئيات\",\n \"جينوميات\",\n \"طوبولوجي\",\n \"تماثلي\",\n ],\n\n // Agentic task keywords - file ops, execution, multi-step, iterative work\n // Pruned: removed overly common words like \"then\", \"first\", \"run\", \"test\", \"build\"\n agenticTaskKeywords: [\n // English - File operations (clearly agentic)\n \"read file\",\n \"read the file\",\n \"look at\",\n \"check the\",\n \"open the\",\n \"edit\",\n \"modify\",\n \"update the\",\n \"change the\",\n \"write to\",\n \"create file\",\n // English - Execution (specific commands only)\n \"execute\",\n \"deploy\",\n \"install\",\n \"npm\",\n \"pip\",\n \"compile\",\n // English - Multi-step patterns (specific only)\n \"after that\",\n \"and also\",\n \"once done\",\n \"step 1\",\n \"step 2\",\n // English - Iterative work\n \"fix\",\n \"debug\",\n \"until it works\",\n \"keep trying\",\n \"iterate\",\n \"make sure\",\n \"verify\",\n \"confirm\",\n // Chinese (keep specific ones)\n \"读取文件\",\n \"查看\",\n \"打开\",\n \"编辑\",\n \"修改\",\n \"更新\",\n \"创建\",\n \"执行\",\n \"部署\",\n \"安装\",\n \"第一步\",\n \"第二步\",\n \"修复\",\n \"调试\",\n \"直到\",\n \"确认\",\n \"验证\",\n // Spanish\n \"leer archivo\",\n \"editar\",\n \"modificar\",\n \"actualizar\",\n \"ejecutar\",\n \"desplegar\",\n \"instalar\",\n \"paso 1\",\n \"paso 2\",\n \"arreglar\",\n \"depurar\",\n \"verificar\",\n // Portuguese\n \"ler arquivo\",\n \"editar\",\n \"modificar\",\n \"atualizar\",\n \"executar\",\n \"implantar\",\n \"instalar\",\n \"passo 1\",\n \"passo 2\",\n \"corrigir\",\n \"depurar\",\n \"verificar\",\n // Korean\n \"파일 읽기\",\n \"편집\",\n \"수정\",\n \"업데이트\",\n \"실행\",\n \"배포\",\n \"설치\",\n \"단계 1\",\n \"단계 2\",\n \"디버그\",\n \"확인\",\n // Arabic\n \"قراءة ملف\",\n \"تحرير\",\n \"تعديل\",\n \"تحديث\",\n \"تنفيذ\",\n \"نشر\",\n \"تثبيت\",\n \"الخطوة 1\",\n \"الخطوة 2\",\n \"إصلاح\",\n \"تصحيح\",\n \"تحقق\",\n ],\n\n // Dimension weights (sum to 1.0)\n dimensionWeights: {\n tokenCount: 0.08,\n codePresence: 0.15,\n reasoningMarkers: 0.18,\n technicalTerms: 0.1,\n creativeMarkers: 0.05,\n simpleIndicators: 0.02, // Reduced from 0.12 to make room for agenticTask\n multiStepPatterns: 0.12,\n questionComplexity: 0.05,\n imperativeVerbs: 0.03,\n constraintCount: 0.04,\n outputFormat: 0.03,\n referenceComplexity: 0.02,\n negationComplexity: 0.01,\n domainSpecificity: 0.02,\n agenticTask: 0.04, // Reduced - agentic signals influence tier selection, not dominate it\n },\n\n // Tier boundaries on weighted score axis\n tierBoundaries: {\n simpleMedium: 0.0,\n mediumComplex: 0.3, // Raised from 0.18 - prevent simple tasks from reaching expensive COMPLEX tier\n complexReasoning: 0.5, // Raised from 0.4 - reserve for true reasoning tasks\n },\n\n // Sigmoid steepness for confidence calibration\n confidenceSteepness: 12,\n // Below this confidence → ambiguous (null tier)\n confidenceThreshold: 0.7,\n },\n\n // Auto (balanced) tier configs - current default smart routing\n tiers: {\n SIMPLE: {\n primary: \"moonshot/kimi-k2.5\", // $0.60/$3.00 - best quality/price for simple tasks\n fallback: [\n \"google/gemini-2.5-flash-lite\", // 1M context, ultra cheap ($0.10/$0.40)\n \"nvidia/gpt-oss-120b\", // FREE fallback\n \"deepseek/deepseek-chat\",\n ],\n },\n MEDIUM: {\n primary: \"moonshot/kimi-k2.5\", // $0.50/$2.40 - strong tool use, proper function call format\n fallback: [\n \"deepseek/deepseek-chat\",\n \"google/gemini-2.5-flash-lite\", // 1M context, ultra cheap ($0.10/$0.40)\n \"xai/grok-4-1-fast-non-reasoning\", // Upgraded Grok 4.1\n ],\n },\n COMPLEX: {\n primary: \"google/gemini-3.1-pro\", // Newest Gemini 3.1 - upgraded from 3.0\n fallback: [\n \"google/gemini-2.5-flash-lite\", // CRITICAL: 1M context, ultra-cheap failsafe ($0.10/$0.40)\n \"google/gemini-3-pro-preview\", // 3.0 fallback\n \"google/gemini-2.5-pro\",\n \"deepseek/deepseek-chat\",\n \"xai/grok-4-0709\",\n \"openai/gpt-5.4\", // Newest flagship, same price as 4o\n \"openai/gpt-4o\",\n \"anthropic/claude-sonnet-4.6\",\n ],\n },\n REASONING: {\n primary: \"xai/grok-4-1-fast-reasoning\", // Upgraded Grok 4.1 reasoning $0.20/$0.50\n fallback: [\n \"deepseek/deepseek-reasoner\", // Cheap reasoning model\n \"openai/o4-mini\", // Newer and cheaper than o3 ($1.10 vs $2.00)\n \"openai/o3\",\n ],\n },\n },\n\n // Eco tier configs - absolute cheapest (blockrun/eco)\n ecoTiers: {\n SIMPLE: {\n primary: \"nvidia/gpt-oss-120b\", // FREE! $0.00/$0.00\n fallback: [\"google/gemini-2.5-flash-lite\", \"deepseek/deepseek-chat\"],\n },\n MEDIUM: {\n primary: \"google/gemini-2.5-flash-lite\", // $0.10/$0.40 - cheapest capable with 1M context\n fallback: [\"deepseek/deepseek-chat\", \"nvidia/gpt-oss-120b\"],\n },\n COMPLEX: {\n primary: \"google/gemini-2.5-flash-lite\", // $0.10/$0.40 - 1M context handles complexity\n fallback: [\"google/gemini-2.5-flash\", \"deepseek/deepseek-chat\", \"xai/grok-4-0709\"],\n },\n REASONING: {\n primary: \"xai/grok-4-1-fast-reasoning\", // $0.20/$0.50\n fallback: [\"deepseek/deepseek-reasoner\"],\n },\n },\n\n // Premium tier configs - best quality (blockrun/premium)\n // codex=complex coding, kimi=simple coding, sonnet=reasoning/instructions, opus=architecture/PM/audits\n premiumTiers: {\n SIMPLE: {\n primary: \"moonshot/kimi-k2.5\", // $0.60/$3.00 - good for simple coding\n fallback: [\n \"anthropic/claude-haiku-4.5\",\n \"google/gemini-2.5-flash-lite\",\n \"deepseek/deepseek-chat\",\n ],\n },\n MEDIUM: {\n primary: \"openai/gpt-5.2-codex\", // $2.50/$10 - strong coding for medium tasks\n fallback: [\n \"moonshot/kimi-k2.5\",\n \"google/gemini-2.5-pro\",\n \"xai/grok-4-0709\",\n \"anthropic/claude-sonnet-4.6\",\n ],\n },\n COMPLEX: {\n primary: \"anthropic/claude-opus-4.6\", // Best quality for complex tasks\n fallback: [\n \"openai/gpt-5.4\", // Newest flagship\n \"openai/gpt-5.2-codex\",\n \"anthropic/claude-opus-4.6\",\n \"anthropic/claude-sonnet-4.6\",\n \"google/gemini-3.1-pro\", // Newest Gemini\n \"google/gemini-3-pro-preview\",\n \"moonshot/kimi-k2.5\",\n ],\n },\n REASONING: {\n primary: \"anthropic/claude-sonnet-4.6\", // $3/$15 - best for reasoning/instructions\n fallback: [\n \"anthropic/claude-opus-4.6\",\n \"anthropic/claude-opus-4.6\",\n \"openai/o4-mini\", // Newer and cheaper than o3 ($1.10 vs $2.00)\n \"openai/o3\",\n \"xai/grok-4-1-fast-reasoning\",\n ],\n },\n },\n\n // Agentic tier configs - models that excel at multi-step autonomous tasks\n agenticTiers: {\n SIMPLE: {\n primary: \"moonshot/kimi-k2.5\", // Cheaper than Haiku ($0.5/$2.4 vs $1/$5), larger context\n fallback: [\n \"anthropic/claude-haiku-4.5\",\n \"xai/grok-4-1-fast-non-reasoning\",\n \"openai/gpt-4o-mini\",\n ],\n },\n MEDIUM: {\n primary: \"moonshot/kimi-k2.5\", // $0.50/$2.40 - strong tool use, handles function calls correctly\n fallback: [\n \"anthropic/claude-haiku-4.5\",\n \"deepseek/deepseek-chat\",\n \"xai/grok-4-1-fast-non-reasoning\",\n ],\n },\n COMPLEX: {\n primary: \"anthropic/claude-sonnet-4.6\",\n fallback: [\n \"anthropic/claude-opus-4.6\", // Latest Opus - best agentic\n \"openai/gpt-5.4\", // Newest flagship\n \"google/gemini-3.1-pro\", // Newest Gemini\n \"google/gemini-3-pro-preview\",\n \"xai/grok-4-0709\",\n ],\n },\n REASONING: {\n primary: \"anthropic/claude-sonnet-4.6\", // Strong tool use + reasoning for agentic tasks\n fallback: [\n \"anthropic/claude-opus-4.6\",\n \"xai/grok-4-1-fast-reasoning\",\n \"deepseek/deepseek-reasoner\",\n ],\n },\n },\n\n overrides: {\n maxTokensForceComplex: 100_000,\n structuredOutputMinTier: \"MEDIUM\",\n ambiguousDefaultTier: \"MEDIUM\",\n agenticMode: false,\n },\n};\n","/**\n * Smart Router Entry Point\n *\n * Classifies requests and routes to the cheapest capable model.\n * 100% local — rules-based scoring handles all requests in <1ms.\n * Ambiguous cases default to configurable tier (MEDIUM by default).\n */\n\nimport type { Tier, RoutingDecision, RoutingConfig } from \"./types.js\";\nimport { classifyByRules } from \"./rules.js\";\nimport { selectModel, type ModelPricing } from \"./selector.js\";\n\nexport type RouterOptions = {\n config: RoutingConfig;\n modelPricing: Map;\n routingProfile?: \"free\" | \"eco\" | \"auto\" | \"premium\";\n hasTools?: boolean;\n};\n\n/**\n * Route a request to the cheapest capable model.\n *\n * 1. Check overrides (large context, structured output)\n * 2. Run rule-based classifier (14 weighted dimensions, <1ms)\n * 3. If ambiguous, default to configurable tier (no external API calls)\n * 4. Select model for tier\n * 5. Return RoutingDecision with metadata\n */\nexport function route(\n prompt: string,\n systemPrompt: string | undefined,\n maxOutputTokens: number,\n options: RouterOptions,\n): RoutingDecision {\n const { config, modelPricing } = options;\n\n // Estimate input tokens (~4 chars per token)\n const fullText = `${systemPrompt ?? \"\"} ${prompt}`;\n const estimatedTokens = Math.ceil(fullText.length / 4);\n\n // --- Rule-based classification (runs first to get agenticScore) ---\n const ruleResult = classifyByRules(prompt, systemPrompt, estimatedTokens, config.scoring);\n\n // --- Select tier configs based on routing profile ---\n const { routingProfile } = options;\n let tierConfigs: Record;\n let profileSuffix: string;\n\n if (routingProfile === \"eco\" && config.ecoTiers) {\n // Eco profile: ultra cost-optimized models\n tierConfigs = config.ecoTiers;\n profileSuffix = \" | eco\";\n } else if (routingProfile === \"premium\" && config.premiumTiers) {\n // Premium profile: best quality models\n tierConfigs = config.premiumTiers;\n profileSuffix = \" | premium\";\n } else {\n // Auto profile (or undefined): intelligent routing with agentic detection\n // Determine if agentic tiers should be used:\n // 1. Request contains tools (OpenClaw/agentic clients always send tools) OR\n // 2. Explicit agenticMode config OR\n // 3. Auto-detected agentic task (agenticScore >= 0.5)\n const agenticScore = ruleResult.agenticScore ?? 0;\n const isAutoAgentic = agenticScore >= 0.5;\n const isExplicitAgentic = config.overrides.agenticMode ?? false;\n const hasToolsInRequest = options.hasTools ?? false;\n const useAgenticTiers =\n (hasToolsInRequest || isAutoAgentic || isExplicitAgentic) && config.agenticTiers != null;\n tierConfigs = useAgenticTiers ? config.agenticTiers! : config.tiers;\n profileSuffix = useAgenticTiers ? ` | agentic${hasToolsInRequest ? \" (tools)\" : \"\"}` : \"\";\n }\n\n const agenticScoreValue = ruleResult.agenticScore;\n\n // --- Override: large context → force COMPLEX ---\n if (estimatedTokens > config.overrides.maxTokensForceComplex) {\n return selectModel(\n \"COMPLEX\",\n 0.95,\n \"rules\",\n `Input exceeds ${config.overrides.maxTokensForceComplex} tokens${profileSuffix}`,\n tierConfigs,\n modelPricing,\n estimatedTokens,\n maxOutputTokens,\n routingProfile,\n agenticScoreValue,\n );\n }\n\n // Structured output detection\n const hasStructuredOutput = systemPrompt ? /json|structured|schema/i.test(systemPrompt) : false;\n\n let tier: Tier;\n let confidence: number;\n const method: \"rules\" | \"llm\" = \"rules\";\n let reasoning = `score=${ruleResult.score.toFixed(2)} | ${ruleResult.signals.join(\", \")}`;\n\n if (ruleResult.tier !== null) {\n tier = ruleResult.tier;\n confidence = ruleResult.confidence;\n } else {\n // Ambiguous — default to configurable tier (no external API call)\n tier = config.overrides.ambiguousDefaultTier;\n confidence = 0.5;\n reasoning += ` | ambiguous -> default: ${tier}`;\n }\n\n // Apply structured output minimum tier\n if (hasStructuredOutput) {\n const tierRank: Record = { SIMPLE: 0, MEDIUM: 1, COMPLEX: 2, REASONING: 3 };\n const minTier = config.overrides.structuredOutputMinTier;\n if (tierRank[tier] < tierRank[minTier]) {\n reasoning += ` | upgraded to ${minTier} (structured output)`;\n tier = minTier;\n }\n }\n\n // Add routing profile suffix to reasoning\n reasoning += profileSuffix;\n\n return selectModel(\n tier,\n confidence,\n method,\n reasoning,\n tierConfigs,\n modelPricing,\n estimatedTokens,\n maxOutputTokens,\n routingProfile,\n agenticScoreValue,\n );\n}\n\nexport {\n getFallbackChain,\n getFallbackChainFiltered,\n filterByToolCalling,\n filterByVision,\n calculateModelCost,\n} from \"./selector.js\";\nexport { DEFAULT_ROUTING_CONFIG } from \"./config.js\";\nexport type { RoutingDecision, Tier, RoutingConfig } from \"./types.js\";\nexport type { ModelPricing } from \"./selector.js\";\n","/**\n * BlockRun Model Definitions for OpenClaw\n *\n * Maps BlockRun's 30+ AI models to OpenClaw's ModelDefinitionConfig format.\n * All models use the \"openai-completions\" API since BlockRun is OpenAI-compatible.\n *\n * Pricing is in USD per 1M tokens. Operators pay these rates via x402;\n * they set their own markup when reselling to end users (Phase 2).\n */\n\nimport type { ModelDefinitionConfig, ModelProviderConfig } from \"./types.js\";\n\n/**\n * Model aliases for convenient shorthand access.\n * Users can type `/model claude` instead of `/model blockrun/anthropic/claude-sonnet-4-6`.\n */\nexport const MODEL_ALIASES: Record = {\n // Claude - use newest versions (4.6)\n claude: \"anthropic/claude-sonnet-4.6\",\n sonnet: \"anthropic/claude-sonnet-4.6\",\n \"sonnet-4\": \"anthropic/claude-sonnet-4.6\",\n \"sonnet-4.6\": \"anthropic/claude-sonnet-4.6\",\n \"sonnet-4-6\": \"anthropic/claude-sonnet-4.6\",\n opus: \"anthropic/claude-opus-4.6\",\n \"opus-4\": \"anthropic/claude-opus-4.6\",\n \"opus-4.6\": \"anthropic/claude-opus-4.6\",\n \"opus-4-6\": \"anthropic/claude-opus-4.6\",\n haiku: \"anthropic/claude-haiku-4.5\",\n // Claude - provider/shortname patterns (common in agent frameworks)\n \"anthropic/sonnet\": \"anthropic/claude-sonnet-4.6\",\n \"anthropic/opus\": \"anthropic/claude-opus-4.6\",\n \"anthropic/haiku\": \"anthropic/claude-haiku-4.5\",\n \"anthropic/claude\": \"anthropic/claude-sonnet-4.6\",\n // Backward compatibility - map all variants to 4.6\n \"anthropic/claude-sonnet-4\": \"anthropic/claude-sonnet-4.6\",\n \"anthropic/claude-sonnet-4-6\": \"anthropic/claude-sonnet-4.6\",\n \"anthropic/claude-opus-4\": \"anthropic/claude-opus-4.6\",\n \"anthropic/claude-opus-4-6\": \"anthropic/claude-opus-4.6\",\n \"anthropic/claude-opus-4.5\": \"anthropic/claude-opus-4.6\",\n \"anthropic/claude-haiku-4\": \"anthropic/claude-haiku-4.5\",\n \"anthropic/claude-haiku-4-5\": \"anthropic/claude-haiku-4.5\",\n\n // OpenAI\n gpt: \"openai/gpt-4o\",\n gpt4: \"openai/gpt-4o\",\n gpt5: \"openai/gpt-5.4\",\n \"gpt-5.4\": \"openai/gpt-5.4\",\n \"gpt-5.4-pro\": \"openai/gpt-5.4-pro\",\n codex: \"openai/gpt-5.2-codex\",\n mini: \"openai/gpt-4o-mini\",\n o1: \"openai/o1\",\n o3: \"openai/o3\",\n\n // DeepSeek\n deepseek: \"deepseek/deepseek-chat\",\n reasoner: \"deepseek/deepseek-reasoner\",\n\n // Kimi / Moonshot\n kimi: \"moonshot/kimi-k2.5\",\n moonshot: \"moonshot/kimi-k2.5\",\n \"kimi-k2.5\": \"moonshot/kimi-k2.5\",\n\n // Google\n gemini: \"google/gemini-2.5-pro\",\n flash: \"google/gemini-2.5-flash\",\n \"gemini-3.1-pro-preview\": \"google/gemini-3.1-pro\",\n \"google/gemini-3.1-pro-preview\": \"google/gemini-3.1-pro\",\n\n // xAI\n grok: \"xai/grok-3\",\n \"grok-fast\": \"xai/grok-4-fast-reasoning\",\n \"grok-code\": \"xai/grok-code-fast-1\",\n\n // NVIDIA\n nvidia: \"nvidia/gpt-oss-120b\",\n \"gpt-120b\": \"nvidia/gpt-oss-120b\",\n\n // MiniMax\n minimax: \"minimax/minimax-m2.5\",\n\n // Routing profile aliases (common variations)\n \"auto-router\": \"auto\",\n router: \"auto\",\n\n // Note: auto, free, eco, premium are virtual routing profiles registered in BLOCKRUN_MODELS\n // They don't need aliases since they're already top-level model IDs\n};\n\n/**\n * Resolve a model alias to its full model ID.\n * Also strips \"blockrun/\" prefix for direct model paths.\n * Examples:\n * - \"claude\" -> \"anthropic/claude-sonnet-4-6\" (alias)\n * - \"blockrun/claude\" -> \"anthropic/claude-sonnet-4-6\" (alias with prefix)\n * - \"blockrun/anthropic/claude-sonnet-4-6\" -> \"anthropic/claude-sonnet-4-6\" (prefix stripped)\n * - \"openai/gpt-4o\" -> \"openai/gpt-4o\" (unchanged)\n */\nexport function resolveModelAlias(model: string): string {\n const normalized = model.trim().toLowerCase();\n const resolved = MODEL_ALIASES[normalized];\n if (resolved) return resolved;\n\n // Check with \"blockrun/\" prefix stripped\n if (normalized.startsWith(\"blockrun/\")) {\n const withoutPrefix = normalized.slice(\"blockrun/\".length);\n const resolvedWithoutPrefix = MODEL_ALIASES[withoutPrefix];\n if (resolvedWithoutPrefix) return resolvedWithoutPrefix;\n\n // Even if not an alias, strip the prefix for direct model paths\n // e.g., \"blockrun/anthropic/claude-sonnet-4-6\" -> \"anthropic/claude-sonnet-4-6\"\n return withoutPrefix;\n }\n\n // Strip \"openai/\" prefix when it wraps a virtual routing profile or alias.\n // OpenClaw sends virtual models as \"openai/eco\", \"openai/free\", etc. because\n // the provider uses the openai-completions API type.\n if (normalized.startsWith(\"openai/\")) {\n const withoutPrefix = normalized.slice(\"openai/\".length);\n const resolvedWithoutPrefix = MODEL_ALIASES[withoutPrefix];\n if (resolvedWithoutPrefix) return resolvedWithoutPrefix;\n\n // If it's a known BlockRun virtual profile (eco, free, auto, premium), return bare id\n const isVirtualProfile = BLOCKRUN_MODELS.some((m) => m.id === withoutPrefix);\n if (isVirtualProfile) return withoutPrefix;\n }\n\n return model;\n}\n\ntype BlockRunModel = {\n id: string;\n name: string;\n /** Model version (e.g., \"4.6\", \"3.1\", \"5.2\") for tracking updates */\n version?: string;\n inputPrice: number;\n outputPrice: number;\n contextWindow: number;\n maxOutput: number;\n reasoning?: boolean;\n vision?: boolean;\n /** Models optimized for agentic workflows (multi-step autonomous tasks) */\n agentic?: boolean;\n /**\n * Model supports OpenAI-compatible structured function/tool calling.\n * Models without this flag output tool invocations as plain text JSON,\n * which leaks raw {\"command\":\"...\"} into visible chat messages.\n * Default: false (must opt-in to prevent silent regressions on new models).\n */\n toolCalling?: boolean;\n};\n\nexport const BLOCKRUN_MODELS: BlockRunModel[] = [\n // Smart routing meta-models — proxy replaces with actual model\n // NOTE: Model IDs are WITHOUT provider prefix (OpenClaw adds \"blockrun/\" automatically)\n {\n id: \"auto\",\n name: \"Auto (Smart Router - Balanced)\",\n inputPrice: 0,\n outputPrice: 0,\n contextWindow: 1_050_000,\n maxOutput: 128_000,\n },\n {\n id: \"free\",\n name: \"Free (NVIDIA GPT-OSS-120B only)\",\n inputPrice: 0,\n outputPrice: 0,\n contextWindow: 128_000,\n maxOutput: 4_096,\n },\n {\n id: \"eco\",\n name: \"Eco (Smart Router - Cost Optimized)\",\n inputPrice: 0,\n outputPrice: 0,\n contextWindow: 1_050_000,\n maxOutput: 128_000,\n },\n {\n id: \"premium\",\n name: \"Premium (Smart Router - Best Quality)\",\n inputPrice: 0,\n outputPrice: 0,\n contextWindow: 2_000_000,\n maxOutput: 200_000,\n },\n\n // OpenAI GPT-5 Family\n {\n id: \"openai/gpt-5.2\",\n name: \"GPT-5.2\",\n version: \"5.2\",\n inputPrice: 1.75,\n outputPrice: 14.0,\n contextWindow: 400000,\n maxOutput: 128000,\n reasoning: true,\n vision: true,\n agentic: true,\n toolCalling: true,\n },\n {\n id: \"openai/gpt-5-mini\",\n name: \"GPT-5 Mini\",\n version: \"5.0\",\n inputPrice: 0.25,\n outputPrice: 2.0,\n contextWindow: 200000,\n maxOutput: 65536,\n toolCalling: true,\n },\n {\n id: \"openai/gpt-5-nano\",\n name: \"GPT-5 Nano\",\n version: \"5.0\",\n inputPrice: 0.05,\n outputPrice: 0.4,\n contextWindow: 128000,\n maxOutput: 32768,\n toolCalling: true,\n },\n {\n id: \"openai/gpt-5.2-pro\",\n name: \"GPT-5.2 Pro\",\n version: \"5.2\",\n inputPrice: 21.0,\n outputPrice: 168.0,\n contextWindow: 400000,\n maxOutput: 128000,\n reasoning: true,\n toolCalling: true,\n },\n // GPT-5.4 — newest flagship, same input price as 4o but much more capable\n {\n id: \"openai/gpt-5.4\",\n name: \"GPT-5.4\",\n version: \"5.4\",\n inputPrice: 2.5,\n outputPrice: 15.0,\n contextWindow: 400000,\n maxOutput: 128000,\n reasoning: true,\n vision: true,\n agentic: true,\n toolCalling: true,\n },\n {\n id: \"openai/gpt-5.4-pro\",\n name: \"GPT-5.4 Pro\",\n version: \"5.4\",\n inputPrice: 30.0,\n outputPrice: 180.0,\n contextWindow: 400000,\n maxOutput: 128000,\n reasoning: true,\n toolCalling: true,\n },\n\n // OpenAI Codex Family\n {\n id: \"openai/gpt-5.2-codex\",\n name: \"GPT-5.2 Codex\",\n version: \"5.2\",\n inputPrice: 1.75,\n outputPrice: 14.0,\n contextWindow: 128000,\n maxOutput: 32000,\n agentic: true,\n toolCalling: true,\n },\n\n // OpenAI GPT-4 Family\n {\n id: \"openai/gpt-4.1\",\n name: \"GPT-4.1\",\n version: \"4.1\",\n inputPrice: 2.0,\n outputPrice: 8.0,\n contextWindow: 128000,\n maxOutput: 16384,\n vision: true,\n toolCalling: true,\n },\n {\n id: \"openai/gpt-4.1-mini\",\n name: \"GPT-4.1 Mini\",\n version: \"4.1\",\n inputPrice: 0.4,\n outputPrice: 1.6,\n contextWindow: 128000,\n maxOutput: 16384,\n toolCalling: true,\n },\n {\n id: \"openai/gpt-4.1-nano\",\n name: \"GPT-4.1 Nano\",\n version: \"4.1\",\n inputPrice: 0.1,\n outputPrice: 0.4,\n contextWindow: 128000,\n maxOutput: 16384,\n toolCalling: true,\n },\n {\n id: \"openai/gpt-4o\",\n name: \"GPT-4o\",\n version: \"4o\",\n inputPrice: 2.5,\n outputPrice: 10.0,\n contextWindow: 128000,\n maxOutput: 16384,\n vision: true,\n agentic: true,\n toolCalling: true,\n },\n {\n id: \"openai/gpt-4o-mini\",\n name: \"GPT-4o Mini\",\n version: \"4o-mini\",\n inputPrice: 0.15,\n outputPrice: 0.6,\n contextWindow: 128000,\n maxOutput: 16384,\n toolCalling: true,\n },\n\n // OpenAI O-series (Reasoning)\n {\n id: \"openai/o1\",\n name: \"o1\",\n version: \"1\",\n inputPrice: 15.0,\n outputPrice: 60.0,\n contextWindow: 200000,\n maxOutput: 100000,\n reasoning: true,\n toolCalling: true,\n },\n {\n id: \"openai/o1-mini\",\n name: \"o1-mini\",\n version: \"1-mini\",\n inputPrice: 1.1,\n outputPrice: 4.4,\n contextWindow: 128000,\n maxOutput: 65536,\n reasoning: true,\n toolCalling: true,\n },\n {\n id: \"openai/o3\",\n name: \"o3\",\n version: \"3\",\n inputPrice: 2.0,\n outputPrice: 8.0,\n contextWindow: 200000,\n maxOutput: 100000,\n reasoning: true,\n toolCalling: true,\n },\n {\n id: \"openai/o3-mini\",\n name: \"o3-mini\",\n version: \"3-mini\",\n inputPrice: 1.1,\n outputPrice: 4.4,\n contextWindow: 128000,\n maxOutput: 65536,\n reasoning: true,\n toolCalling: true,\n },\n {\n id: \"openai/o4-mini\",\n name: \"o4-mini\",\n version: \"4-mini\",\n inputPrice: 1.1,\n outputPrice: 4.4,\n contextWindow: 128000,\n maxOutput: 65536,\n reasoning: true,\n toolCalling: true,\n },\n\n // Anthropic - all Claude models excel at agentic workflows\n // Use newest versions (4.6) with full provider prefix\n {\n id: \"anthropic/claude-haiku-4.5\",\n name: \"Claude Haiku 4.5\",\n version: \"4.5\",\n inputPrice: 1.0,\n outputPrice: 5.0,\n contextWindow: 200000,\n maxOutput: 8192,\n vision: true,\n agentic: true,\n toolCalling: true,\n },\n {\n id: \"anthropic/claude-sonnet-4.6\",\n name: \"Claude Sonnet 4.6\",\n version: \"4.6\",\n inputPrice: 3.0,\n outputPrice: 15.0,\n contextWindow: 200000,\n maxOutput: 64000,\n reasoning: true,\n vision: true,\n agentic: true,\n toolCalling: true,\n },\n {\n id: \"anthropic/claude-opus-4.6\",\n name: \"Claude Opus 4.6\",\n version: \"4.6\",\n inputPrice: 5.0,\n outputPrice: 25.0,\n contextWindow: 200000,\n maxOutput: 32000,\n reasoning: true,\n vision: true,\n agentic: true,\n toolCalling: true,\n },\n\n // Google\n {\n id: \"google/gemini-3.1-pro\",\n name: \"Gemini 3.1 Pro\",\n version: \"3.1\",\n inputPrice: 2.0,\n outputPrice: 12.0,\n contextWindow: 1050000,\n maxOutput: 65536,\n reasoning: true,\n vision: true,\n toolCalling: true,\n },\n {\n id: \"google/gemini-3-pro-preview\",\n name: \"Gemini 3 Pro Preview\",\n version: \"3.0\",\n inputPrice: 2.0,\n outputPrice: 12.0,\n contextWindow: 1050000,\n maxOutput: 65536,\n reasoning: true,\n vision: true,\n toolCalling: true,\n },\n {\n id: \"google/gemini-3-flash-preview\",\n name: \"Gemini 3 Flash Preview\",\n version: \"3.0\",\n inputPrice: 0.5,\n outputPrice: 3.0,\n contextWindow: 1000000,\n maxOutput: 65536,\n vision: true,\n },\n {\n id: \"google/gemini-2.5-pro\",\n name: \"Gemini 2.5 Pro\",\n version: \"2.5\",\n inputPrice: 1.25,\n outputPrice: 10.0,\n contextWindow: 1050000,\n maxOutput: 65536,\n reasoning: true,\n vision: true,\n toolCalling: true,\n },\n {\n id: \"google/gemini-2.5-flash\",\n name: \"Gemini 2.5 Flash\",\n version: \"2.5\",\n inputPrice: 0.3,\n outputPrice: 2.5,\n contextWindow: 1000000,\n maxOutput: 65536,\n vision: true,\n toolCalling: true,\n },\n {\n id: \"google/gemini-2.5-flash-lite\",\n name: \"Gemini 2.5 Flash Lite\",\n version: \"2.5\",\n inputPrice: 0.1,\n outputPrice: 0.4,\n contextWindow: 1000000,\n maxOutput: 65536,\n toolCalling: true,\n },\n\n // DeepSeek\n {\n id: \"deepseek/deepseek-chat\",\n name: \"DeepSeek V3.2 Chat\",\n version: \"3.2\",\n inputPrice: 0.28,\n outputPrice: 0.42,\n contextWindow: 128000,\n maxOutput: 8192,\n toolCalling: true,\n },\n {\n id: \"deepseek/deepseek-reasoner\",\n name: \"DeepSeek V3.2 Reasoner\",\n version: \"3.2\",\n inputPrice: 0.28,\n outputPrice: 0.42,\n contextWindow: 128000,\n maxOutput: 8192,\n reasoning: true,\n toolCalling: true,\n },\n\n // Moonshot / Kimi - optimized for agentic workflows\n {\n id: \"moonshot/kimi-k2.5\",\n name: \"Kimi K2.5\",\n version: \"k2.5\",\n inputPrice: 0.6,\n outputPrice: 3.0,\n contextWindow: 262144,\n maxOutput: 8192,\n reasoning: true,\n vision: true,\n agentic: true,\n toolCalling: true,\n },\n\n // xAI / Grok\n {\n id: \"xai/grok-3\",\n name: \"Grok 3\",\n version: \"3\",\n inputPrice: 3.0,\n outputPrice: 15.0,\n contextWindow: 131072,\n maxOutput: 16384,\n reasoning: true,\n toolCalling: true,\n },\n // grok-3-fast removed - too expensive ($5/$25), use grok-4-fast instead\n {\n id: \"xai/grok-3-mini\",\n name: \"Grok 3 Mini\",\n version: \"3-mini\",\n inputPrice: 0.3,\n outputPrice: 0.5,\n contextWindow: 131072,\n maxOutput: 16384,\n toolCalling: true,\n },\n\n // xAI Grok 4 Family - Ultra-cheap fast models\n {\n id: \"xai/grok-4-fast-reasoning\",\n name: \"Grok 4 Fast Reasoning\",\n version: \"4\",\n inputPrice: 0.2,\n outputPrice: 0.5,\n contextWindow: 131072,\n maxOutput: 16384,\n reasoning: true,\n toolCalling: true,\n },\n {\n id: \"xai/grok-4-fast-non-reasoning\",\n name: \"Grok 4 Fast\",\n version: \"4\",\n inputPrice: 0.2,\n outputPrice: 0.5,\n contextWindow: 131072,\n maxOutput: 16384,\n toolCalling: true,\n },\n {\n id: \"xai/grok-4-1-fast-reasoning\",\n name: \"Grok 4.1 Fast Reasoning\",\n version: \"4.1\",\n inputPrice: 0.2,\n outputPrice: 0.5,\n contextWindow: 131072,\n maxOutput: 16384,\n reasoning: true,\n toolCalling: true,\n },\n {\n id: \"xai/grok-4-1-fast-non-reasoning\",\n name: \"Grok 4.1 Fast\",\n version: \"4.1\",\n inputPrice: 0.2,\n outputPrice: 0.5,\n contextWindow: 131072,\n maxOutput: 16384,\n toolCalling: true,\n },\n {\n id: \"xai/grok-code-fast-1\",\n name: \"Grok Code Fast\",\n version: \"1\",\n inputPrice: 0.2,\n outputPrice: 1.5,\n contextWindow: 131072,\n maxOutput: 16384,\n // toolCalling intentionally omitted: outputs tool calls as plain text JSON,\n // not OpenAI-compatible structured function calls. Will be skipped when\n // request has tools to prevent the \"talking to itself\" bug.\n },\n {\n id: \"xai/grok-4-0709\",\n name: \"Grok 4 (0709)\",\n version: \"4-0709\",\n inputPrice: 0.2,\n outputPrice: 1.5,\n contextWindow: 131072,\n maxOutput: 16384,\n reasoning: true,\n toolCalling: true,\n },\n {\n id: \"xai/grok-2-vision\",\n name: \"Grok 2 Vision\",\n version: \"2\",\n inputPrice: 2.0,\n outputPrice: 10.0,\n contextWindow: 131072,\n maxOutput: 16384,\n vision: true,\n toolCalling: true,\n },\n\n // MiniMax\n {\n id: \"minimax/minimax-m2.5\",\n name: \"MiniMax M2.5\",\n version: \"m2.5\",\n inputPrice: 0.3,\n outputPrice: 1.2,\n contextWindow: 204800,\n maxOutput: 16384,\n reasoning: true,\n agentic: true,\n toolCalling: true,\n },\n\n // NVIDIA - Free/cheap models\n {\n id: \"nvidia/gpt-oss-120b\",\n name: \"NVIDIA GPT-OSS 120B\",\n version: \"120b\",\n inputPrice: 0,\n outputPrice: 0,\n contextWindow: 128000,\n maxOutput: 16384,\n // toolCalling intentionally omitted: free model, structured function\n // calling support unverified. Excluded from tool-heavy routing paths.\n },\n {\n id: \"nvidia/kimi-k2.5\",\n name: \"NVIDIA Kimi K2.5\",\n version: \"k2.5\",\n inputPrice: 0.55,\n outputPrice: 2.5,\n contextWindow: 262144,\n maxOutput: 16384,\n toolCalling: true,\n },\n];\n\n/**\n * Convert BlockRun model definitions to OpenClaw ModelDefinitionConfig format.\n */\nfunction toOpenClawModel(m: BlockRunModel): ModelDefinitionConfig {\n return {\n id: m.id,\n name: m.name,\n api: \"openai-completions\",\n reasoning: m.reasoning ?? false,\n input: m.vision ? [\"text\", \"image\"] : [\"text\"],\n cost: {\n input: m.inputPrice,\n output: m.outputPrice,\n cacheRead: 0,\n cacheWrite: 0,\n },\n contextWindow: m.contextWindow,\n maxTokens: m.maxOutput,\n };\n}\n\n/**\n * Alias models that map to real models.\n * These allow users to use friendly names like \"free\" or \"gpt-120b\".\n */\nconst ALIAS_MODELS: ModelDefinitionConfig[] = Object.entries(MODEL_ALIASES)\n .map(([alias, targetId]) => {\n const target = BLOCKRUN_MODELS.find((m) => m.id === targetId);\n if (!target) return null;\n return toOpenClawModel({ ...target, id: alias, name: `${alias} → ${target.name}` });\n })\n .filter((m): m is ModelDefinitionConfig => m !== null);\n\n/**\n * All BlockRun models in OpenClaw format (including aliases).\n */\nexport const OPENCLAW_MODELS: ModelDefinitionConfig[] = [\n ...BLOCKRUN_MODELS.map(toOpenClawModel),\n ...ALIAS_MODELS,\n];\n\n/**\n * Build a ModelProviderConfig for BlockRun.\n *\n * @param baseUrl - The proxy's local base URL (e.g., \"http://127.0.0.1:12345\")\n */\nexport function buildProviderModels(baseUrl: string): ModelProviderConfig {\n return {\n baseUrl: `${baseUrl}/v1`,\n api: \"openai-completions\",\n models: OPENCLAW_MODELS,\n };\n}\n\n/**\n * Check if a model is optimized for agentic workflows.\n * Agentic models continue autonomously with multi-step tasks\n * instead of stopping and waiting for user input.\n */\nexport function isAgenticModel(modelId: string): boolean {\n const model = BLOCKRUN_MODELS.find(\n (m) => m.id === modelId || m.id === modelId.replace(\"blockrun/\", \"\"),\n );\n return model?.agentic ?? false;\n}\n\n/**\n * Get all agentic-capable models.\n */\nexport function getAgenticModels(): string[] {\n return BLOCKRUN_MODELS.filter((m) => m.agentic).map((m) => m.id);\n}\n\n/**\n * Check if a model supports OpenAI-compatible structured tool/function calling.\n * Models without this flag (e.g. grok-code-fast-1) output tool invocations as\n * plain text JSON, which leaks {\"command\":\"...\"} into visible chat messages.\n */\nexport function supportsToolCalling(modelId: string): boolean {\n const normalized = modelId.replace(\"blockrun/\", \"\");\n const model = BLOCKRUN_MODELS.find((m) => m.id === normalized);\n return model?.toolCalling ?? false;\n}\n\n/**\n * Check if a model supports vision (image inputs).\n * Models without this flag cannot process image_url content parts.\n */\nexport function supportsVision(modelId: string): boolean {\n const normalized = modelId.replace(\"blockrun/\", \"\");\n const model = BLOCKRUN_MODELS.find((m) => m.id === normalized);\n return model?.vision ?? false;\n}\n\n/**\n * Get context window size for a model.\n * Returns undefined if model not found.\n */\nexport function getModelContextWindow(modelId: string): number | undefined {\n const normalized = modelId.replace(\"blockrun/\", \"\");\n const model = BLOCKRUN_MODELS.find((m) => m.id === normalized);\n return model?.contextWindow;\n}\n\n/**\n * Check if a model has reasoning/thinking capabilities.\n * Reasoning models may require reasoning_content in assistant tool_call messages.\n */\nexport function isReasoningModel(modelId: string): boolean {\n const normalized = modelId.replace(\"blockrun/\", \"\");\n const model = BLOCKRUN_MODELS.find((m) => m.id === normalized);\n return model?.reasoning ?? false;\n}\n","/**\n * Local x402 Proxy Server\n *\n * Sits between OpenClaw's pi-ai (which makes standard OpenAI-format requests)\n * and BlockRun's API (which requires x402 micropayments).\n *\n * Flow:\n * pi-ai → http://localhost:{port}/v1/chat/completions\n * → proxy forwards to https://blockrun.ai/api/v1/chat/completions\n * → gets 402 → @x402/fetch signs payment → retries\n * → streams response back to pi-ai\n *\n * Optimizations (v0.3.0):\n * - SSE heartbeat: for streaming requests, sends headers + heartbeat immediately\n * before the x402 flow, preventing OpenClaw's 10-15s timeout from firing.\n * - Response dedup: hashes request bodies and caches responses for 30s,\n * preventing double-charging when OpenClaw retries after timeout.\n * - Smart routing: when model is \"blockrun/auto\", classify query and pick cheapest model.\n * - Usage logging: log every request as JSON line to ~/.openclaw/blockrun/logs/\n */\n\nimport { createServer, type IncomingMessage, type ServerResponse } from \"node:http\";\nimport { finished } from \"node:stream\";\nimport type { AddressInfo } from \"node:net\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { mkdir, writeFile, readFile, stat as fsStat } from \"node:fs/promises\";\nimport { createPublicClient, http } from \"viem\";\nimport { base } from \"viem/chains\";\nimport { privateKeyToAccount } from \"viem/accounts\";\nimport { x402Client } from \"@x402/fetch\";\nimport { createPayFetchWithPreAuth } from \"./payment-preauth.js\";\nimport { registerExactEvmScheme } from \"@x402/evm/exact/client\";\nimport { toClientEvmSigner } from \"@x402/evm\";\nimport {\n route,\n getFallbackChain,\n getFallbackChainFiltered,\n filterByToolCalling,\n filterByVision,\n calculateModelCost,\n DEFAULT_ROUTING_CONFIG,\n type RouterOptions,\n type RoutingDecision,\n type RoutingConfig,\n type ModelPricing,\n type Tier,\n} from \"./router/index.js\";\nimport { classifyByRules } from \"./router/rules.js\";\nimport {\n BLOCKRUN_MODELS,\n OPENCLAW_MODELS,\n resolveModelAlias,\n getModelContextWindow,\n isReasoningModel,\n supportsToolCalling,\n supportsVision,\n} from \"./models.js\";\nimport { logUsage, type UsageEntry } from \"./logger.js\";\nimport { getStats, clearStats } from \"./stats.js\";\nimport { RequestDeduplicator } from \"./dedup.js\";\nimport { ResponseCache, type ResponseCacheConfig } from \"./response-cache.js\";\nimport { BalanceMonitor } from \"./balance.js\";\nimport { SolanaBalanceMonitor } from \"./solana-balance.js\";\n\n/** Union type for chain-agnostic balance monitoring */\ntype AnyBalanceMonitor = BalanceMonitor | SolanaBalanceMonitor;\nimport { resolvePaymentChain } from \"./auth.js\";\nimport { compressContext, shouldCompress, type NormalizedMessage } from \"./compression/index.js\";\n// Error classes available for programmatic use but not used in proxy\n// (universal free fallback means we don't throw balance errors anymore)\n// import { InsufficientFundsError, EmptyWalletError } from \"./errors.js\";\nimport { USER_AGENT, VERSION } from \"./version.js\";\nimport {\n SessionStore,\n getSessionId,\n deriveSessionId,\n hashRequestContent,\n type SessionConfig,\n} from \"./session.js\";\nimport { checkForUpdates } from \"./updater.js\";\nimport { PROXY_PORT } from \"./config.js\";\nimport { SessionJournal } from \"./journal.js\";\n\nconst BLOCKRUN_API = \"https://blockrun.ai/api\";\nconst BLOCKRUN_SOLANA_API = \"https://sol.blockrun.ai/api\";\nconst IMAGE_DIR = join(homedir(), \".openclaw\", \"blockrun\", \"images\");\n// Routing profile models - virtual models that trigger intelligent routing\nconst AUTO_MODEL = \"blockrun/auto\";\n\nconst ROUTING_PROFILES = new Set([\n \"blockrun/free\",\n \"free\",\n \"blockrun/eco\",\n \"eco\",\n \"blockrun/auto\",\n \"auto\",\n \"blockrun/premium\",\n \"premium\",\n]);\nconst FREE_MODEL = \"nvidia/gpt-oss-120b\"; // Free model for empty wallet fallback\nconst MAX_MESSAGES = 200; // BlockRun API limit - truncate older messages if exceeded\nconst CONTEXT_LIMIT_KB = 5120; // Server-side limit: 5MB in KB\nconst HEARTBEAT_INTERVAL_MS = 2_000;\nconst DEFAULT_REQUEST_TIMEOUT_MS = 180_000; // 3 minutes (allows for on-chain tx + LLM response)\nconst MAX_FALLBACK_ATTEMPTS = 5; // Maximum models to try in fallback chain (increased from 3 to ensure cheap models are tried)\nconst HEALTH_CHECK_TIMEOUT_MS = 2_000; // Timeout for checking existing proxy\nconst RATE_LIMIT_COOLDOWN_MS = 60_000; // 60 seconds cooldown for rate-limited models\nconst PORT_RETRY_ATTEMPTS = 5; // Max attempts to bind port (handles TIME_WAIT)\nconst PORT_RETRY_DELAY_MS = 1_000; // Delay between retry attempts\nconst MODEL_BODY_READ_TIMEOUT_MS = 300_000; // 5 minutes for model responses (reasoning models are slow)\nconst ERROR_BODY_READ_TIMEOUT_MS = 30_000; // 30 seconds for error/partner body reads\n\nasync function readBodyWithTimeout(\n body: ReadableStream | null,\n timeoutMs: number = MODEL_BODY_READ_TIMEOUT_MS,\n): Promise {\n if (!body) return [];\n\n const reader = body.getReader();\n const chunks: Uint8Array[] = [];\n\n let timer: ReturnType | undefined;\n try {\n while (true) {\n const result = await Promise.race([\n reader.read(),\n new Promise((_, reject) => {\n timer = setTimeout(() => reject(new Error(\"Body read timeout\")), timeoutMs);\n }),\n ]);\n clearTimeout(timer);\n if (result.done) break;\n chunks.push(result.value);\n }\n } finally {\n clearTimeout(timer);\n reader.releaseLock();\n }\n\n return chunks;\n}\n\n/**\n * Transform upstream payment errors into user-friendly messages.\n * Parses the raw x402 error and formats it nicely.\n */\nfunction transformPaymentError(errorBody: string): string {\n try {\n // Try to parse the error JSON\n const parsed = JSON.parse(errorBody) as {\n error?: string;\n details?: string;\n };\n\n // Check if this is a payment verification error\n if (parsed.error === \"Payment verification failed\" && parsed.details) {\n // Extract the nested JSON from details\n // Format: \"Verification failed: {json}\\n\"\n const match = parsed.details.match(/Verification failed:\\s*(\\{.*\\})/s);\n if (match) {\n const innerJson = JSON.parse(match[1]) as {\n invalidMessage?: string;\n invalidReason?: string;\n payer?: string;\n };\n\n if (innerJson.invalidReason === \"insufficient_funds\" && innerJson.invalidMessage) {\n // Parse \"insufficient balance: 251 < 11463\"\n const balanceMatch = innerJson.invalidMessage.match(\n /insufficient balance:\\s*(\\d+)\\s*<\\s*(\\d+)/i,\n );\n if (balanceMatch) {\n const currentMicros = parseInt(balanceMatch[1], 10);\n const requiredMicros = parseInt(balanceMatch[2], 10);\n const currentUSD = (currentMicros / 1_000_000).toFixed(6);\n const requiredUSD = (requiredMicros / 1_000_000).toFixed(6);\n const wallet = innerJson.payer || \"unknown\";\n const shortWallet =\n wallet.length > 12 ? `${wallet.slice(0, 6)}...${wallet.slice(-4)}` : wallet;\n\n return JSON.stringify({\n error: {\n message: `Insufficient USDC balance. Current: $${currentUSD}, Required: ~$${requiredUSD}`,\n type: \"insufficient_funds\",\n wallet: wallet,\n current_balance_usd: currentUSD,\n required_usd: requiredUSD,\n help: `Fund wallet ${shortWallet} with USDC on Base, or use free model: /model free`,\n },\n });\n }\n }\n\n // Handle invalid_payload errors (signature issues, malformed payment)\n if (innerJson.invalidReason === \"invalid_payload\") {\n return JSON.stringify({\n error: {\n message: \"Payment signature invalid. This may be a temporary issue.\",\n type: \"invalid_payload\",\n help: \"Try again. If this persists, reinstall ClawRouter: curl -fsSL https://blockrun.ai/ClawRouter-update | bash\",\n },\n });\n }\n\n // Handle transaction simulation failures (Solana on-chain validation)\n if (innerJson.invalidReason === \"transaction_simulation_failed\") {\n console.error(\n `[ClawRouter] Solana transaction simulation failed: ${innerJson.invalidMessage || \"unknown\"}`,\n );\n return JSON.stringify({\n error: {\n message: \"Solana payment simulation failed. Retrying with a different model.\",\n type: \"transaction_simulation_failed\",\n help: \"This is usually temporary. If it persists, check your Solana USDC balance or try: /model free\",\n },\n });\n }\n }\n }\n\n // Handle settlement failures (gas estimation, on-chain errors)\n if (\n parsed.error === \"Settlement failed\" ||\n parsed.error === \"Payment settlement failed\" ||\n parsed.details?.includes(\"Settlement failed\") ||\n parsed.details?.includes(\"transaction_simulation_failed\")\n ) {\n const details = parsed.details || \"\";\n const gasError = details.includes(\"unable to estimate gas\");\n\n return JSON.stringify({\n error: {\n message: gasError\n ? \"Payment failed: network congestion or gas issue. Try again.\"\n : \"Payment settlement failed. Try again in a moment.\",\n type: \"settlement_failed\",\n help: \"This is usually temporary. If it persists, try: /model free\",\n },\n });\n }\n } catch {\n // If parsing fails, return original\n }\n return errorBody;\n}\n\n/**\n * Track rate-limited models to avoid hitting them again.\n * Maps model ID to the timestamp when the rate limit was hit.\n */\nconst rateLimitedModels = new Map();\n\n/**\n * Check if a model is currently rate-limited (in cooldown period).\n */\nfunction isRateLimited(modelId: string): boolean {\n const hitTime = rateLimitedModels.get(modelId);\n if (!hitTime) return false;\n\n const elapsed = Date.now() - hitTime;\n if (elapsed >= RATE_LIMIT_COOLDOWN_MS) {\n rateLimitedModels.delete(modelId);\n return false;\n }\n return true;\n}\n\n/**\n * Mark a model as rate-limited.\n */\nfunction markRateLimited(modelId: string): void {\n rateLimitedModels.set(modelId, Date.now());\n console.log(`[ClawRouter] Model ${modelId} rate-limited, will deprioritize for 60s`);\n}\n\n/**\n * Reorder models to put rate-limited ones at the end.\n */\nfunction prioritizeNonRateLimited(models: string[]): string[] {\n const available: string[] = [];\n const rateLimited: string[] = [];\n\n for (const model of models) {\n if (isRateLimited(model)) {\n rateLimited.push(model);\n } else {\n available.push(model);\n }\n }\n\n return [...available, ...rateLimited];\n}\n\n/**\n * Check if response socket is writable (prevents write-after-close errors).\n * Returns true only if all conditions are safe for writing.\n */\nfunction canWrite(res: ServerResponse): boolean {\n return (\n !res.writableEnded &&\n !res.destroyed &&\n res.socket !== null &&\n !res.socket.destroyed &&\n res.socket.writable\n );\n}\n\n/**\n * Safe write with backpressure handling.\n * Returns true if write succeeded, false if socket is closed or write failed.\n */\nfunction safeWrite(res: ServerResponse, data: string | Buffer): boolean {\n if (!canWrite(res)) {\n const bytes = typeof data === \"string\" ? Buffer.byteLength(data) : data.length;\n console.warn(`[ClawRouter] safeWrite: socket not writable, dropping ${bytes} bytes`);\n return false;\n }\n return res.write(data);\n}\n\n// Extra buffer for balance check (on top of estimateAmount's 20% buffer)\n// Total effective buffer: 1.2 * 1.5 = 1.8x (80% safety margin)\n// This prevents x402 payment failures after streaming headers are sent,\n// which would trigger OpenClaw's 5-24 hour billing cooldown.\nconst BALANCE_CHECK_BUFFER = 1.5;\n\n/**\n * Get the proxy port from pre-loaded configuration.\n * Port is validated at module load time, this just returns the cached value.\n */\nexport function getProxyPort(): number {\n return PROXY_PORT;\n}\n\n/**\n * Check if a proxy is already running on the given port.\n * Returns the wallet address if running, undefined otherwise.\n */\nasync function checkExistingProxy(\n port: number,\n): Promise<{ wallet: string; paymentChain?: string } | undefined> {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), HEALTH_CHECK_TIMEOUT_MS);\n\n try {\n const response = await fetch(`http://127.0.0.1:${port}/health`, {\n signal: controller.signal,\n });\n clearTimeout(timeoutId);\n\n if (response.ok) {\n const data = (await response.json()) as {\n status?: string;\n wallet?: string;\n paymentChain?: string;\n };\n if (data.status === \"ok\" && data.wallet) {\n return { wallet: data.wallet, paymentChain: data.paymentChain };\n }\n }\n return undefined;\n } catch {\n clearTimeout(timeoutId);\n return undefined;\n }\n}\n\n/**\n * Error patterns that indicate a provider-side issue (not user's fault).\n * These errors should trigger fallback to the next model in the chain.\n */\nconst PROVIDER_ERROR_PATTERNS = [\n /billing/i,\n /insufficient.*balance/i,\n /credits/i,\n /quota.*exceeded/i,\n /rate.*limit/i,\n /model.*unavailable/i,\n /model.*not.*available/i,\n /service.*unavailable/i,\n /capacity/i,\n /overloaded/i,\n /temporarily.*unavailable/i,\n /api.*key.*invalid/i,\n /authentication.*failed/i,\n /request too large/i,\n /request.*size.*exceeds/i,\n /payload too large/i,\n /payment.*verification.*failed/i,\n /model.*not.*allowed/i,\n /unknown.*model/i,\n];\n\n/**\n * \"Successful\" response bodies that are actually provider degradation placeholders.\n * Some upstream providers occasionally return these with HTTP 200.\n */\nconst DEGRADED_RESPONSE_PATTERNS = [\n /the ai service is temporarily overloaded/i,\n /service is temporarily overloaded/i,\n /please try again in a moment/i,\n];\n\n/**\n * Known low-quality loop signatures seen during provider degradation windows.\n */\nconst DEGRADED_LOOP_PATTERNS = [\n /the boxed is the response\\./i,\n /the response is the text\\./i,\n /the final answer is the boxed\\./i,\n];\n\nfunction extractAssistantContent(payload: unknown): string | undefined {\n if (!payload || typeof payload !== \"object\") return undefined;\n const record = payload as Record;\n const choices = record.choices;\n if (!Array.isArray(choices) || choices.length === 0) return undefined;\n\n const firstChoice = choices[0];\n if (!firstChoice || typeof firstChoice !== \"object\") return undefined;\n const choice = firstChoice as Record;\n const message = choice.message;\n if (!message || typeof message !== \"object\") return undefined;\n const content = (message as Record).content;\n return typeof content === \"string\" ? content : undefined;\n}\n\nfunction hasKnownLoopSignature(text: string): boolean {\n const matchCount = DEGRADED_LOOP_PATTERNS.reduce(\n (count, pattern) => (pattern.test(text) ? count + 1 : count),\n 0,\n );\n if (matchCount >= 2) return true;\n\n // Generic repetitive loop fallback for short repeated lines.\n const lines = text\n .split(/\\r?\\n/)\n .map((line) => line.trim())\n .filter(Boolean);\n if (lines.length < 8) return false;\n\n const counts = new Map();\n for (const line of lines) {\n counts.set(line, (counts.get(line) ?? 0) + 1);\n }\n\n const maxRepeat = Math.max(...counts.values());\n const uniqueRatio = counts.size / lines.length;\n return maxRepeat >= 3 && uniqueRatio <= 0.45;\n}\n\n/**\n * Detect degraded 200-response payloads that should trigger model fallback.\n * Returns a short reason when fallback should happen, otherwise undefined.\n */\nexport function detectDegradedSuccessResponse(body: string): string | undefined {\n const trimmed = body.trim();\n if (!trimmed) return undefined;\n\n // Plain-text placeholder response.\n if (DEGRADED_RESPONSE_PATTERNS.some((pattern) => pattern.test(trimmed))) {\n return \"degraded response: overloaded placeholder\";\n }\n\n // Plain-text looping garbage response.\n if (hasKnownLoopSignature(trimmed)) {\n return \"degraded response: repetitive loop output\";\n }\n\n try {\n const parsed = JSON.parse(trimmed) as Record;\n\n // Some providers return JSON error payloads with HTTP 200.\n const errorField = parsed.error;\n let errorText = \"\";\n if (typeof errorField === \"string\") {\n errorText = errorField;\n } else if (errorField && typeof errorField === \"object\") {\n const errObj = errorField as Record;\n errorText = [\n typeof errObj.message === \"string\" ? errObj.message : \"\",\n typeof errObj.type === \"string\" ? errObj.type : \"\",\n typeof errObj.code === \"string\" ? errObj.code : \"\",\n ]\n .filter(Boolean)\n .join(\" \");\n }\n if (errorText && PROVIDER_ERROR_PATTERNS.some((pattern) => pattern.test(errorText))) {\n return `degraded response: ${errorText.slice(0, 120)}`;\n }\n\n // Successful wrapper with bad assistant content.\n const assistantContent = extractAssistantContent(parsed);\n if (!assistantContent) return undefined;\n if (DEGRADED_RESPONSE_PATTERNS.some((pattern) => pattern.test(assistantContent))) {\n return \"degraded response: overloaded assistant content\";\n }\n if (hasKnownLoopSignature(assistantContent)) {\n return \"degraded response: repetitive assistant loop\";\n }\n } catch {\n // Not JSON - handled by plaintext checks above.\n }\n\n return undefined;\n}\n\n/**\n * HTTP status codes that indicate provider issues worth retrying with fallback.\n */\nconst FALLBACK_STATUS_CODES = [\n 400, // Bad request - sometimes used for billing errors\n 401, // Unauthorized - provider API key issues\n 402, // Payment required - but from upstream, not x402\n 403, // Forbidden - provider restrictions\n 413, // Payload too large - request exceeds model's context limit\n 429, // Rate limited\n 500, // Internal server error\n 502, // Bad gateway\n 503, // Service unavailable\n 504, // Gateway timeout\n];\n\n/**\n * Check if an error response indicates a provider issue that should trigger fallback.\n */\nfunction isProviderError(status: number, body: string): boolean {\n // Check status code first\n if (!FALLBACK_STATUS_CODES.includes(status)) {\n return false;\n }\n\n // For 5xx errors, always fallback\n if (status >= 500) {\n return true;\n }\n\n // For 4xx errors, check the body for known provider error patterns\n return PROVIDER_ERROR_PATTERNS.some((pattern) => pattern.test(body));\n}\n\n/**\n * Valid message roles for OpenAI-compatible APIs.\n * Some clients send non-standard roles (e.g., \"developer\" instead of \"system\").\n */\nconst VALID_ROLES = new Set([\"system\", \"user\", \"assistant\", \"tool\", \"function\"]);\n\n/**\n * Role mappings for non-standard roles.\n * Maps client-specific roles to standard OpenAI roles.\n */\nconst ROLE_MAPPINGS: Record = {\n developer: \"system\", // OpenAI's newer API uses \"developer\" for system messages\n model: \"assistant\", // Some APIs use \"model\" instead of \"assistant\"\n};\n\ntype ChatMessage = { role: string; content: string | unknown };\n\n/**\n * Anthropic tool ID pattern: only alphanumeric, underscore, and hyphen allowed.\n * Error: \"messages.X.content.Y.tool_use.id: String should match pattern '^[a-zA-Z0-9_-]+$'\"\n */\nconst VALID_TOOL_ID_PATTERN = /^[a-zA-Z0-9_-]+$/;\n\n/**\n * Sanitize a tool ID to match Anthropic's required pattern.\n * Replaces invalid characters with underscores.\n */\nfunction sanitizeToolId(id: string | undefined): string | undefined {\n if (!id || typeof id !== \"string\") return id;\n if (VALID_TOOL_ID_PATTERN.test(id)) return id;\n\n // Replace invalid characters with underscores\n return id.replace(/[^a-zA-Z0-9_-]/g, \"_\");\n}\n\n/**\n * Type for messages with tool calls (OpenAI format).\n */\ntype MessageWithTools = ChatMessage & {\n tool_calls?: Array<{ id?: string; type?: string; function?: unknown }>;\n tool_call_id?: string;\n};\n\n/**\n * Type for content blocks that may contain tool IDs (Anthropic format in OpenAI wrapper).\n */\ntype ContentBlock = {\n type?: string;\n id?: string;\n tool_use_id?: string;\n [key: string]: unknown;\n};\n\n/**\n * Sanitize all tool IDs in messages to match Anthropic's pattern.\n * Handles both OpenAI format (tool_calls, tool_call_id) and content block formats.\n */\nfunction sanitizeToolIds(messages: ChatMessage[]): ChatMessage[] {\n if (!messages || messages.length === 0) return messages;\n\n let hasChanges = false;\n const sanitized = messages.map((msg) => {\n const typedMsg = msg as MessageWithTools;\n let msgChanged = false;\n let newMsg = { ...msg } as MessageWithTools;\n\n // Sanitize tool_calls[].id in assistant messages\n if (typedMsg.tool_calls && Array.isArray(typedMsg.tool_calls)) {\n const newToolCalls = typedMsg.tool_calls.map((tc) => {\n if (tc.id && typeof tc.id === \"string\") {\n const sanitized = sanitizeToolId(tc.id);\n if (sanitized !== tc.id) {\n msgChanged = true;\n return { ...tc, id: sanitized };\n }\n }\n return tc;\n });\n if (msgChanged) {\n newMsg = { ...newMsg, tool_calls: newToolCalls };\n }\n }\n\n // Sanitize tool_call_id in tool messages\n if (typedMsg.tool_call_id && typeof typedMsg.tool_call_id === \"string\") {\n const sanitized = sanitizeToolId(typedMsg.tool_call_id);\n if (sanitized !== typedMsg.tool_call_id) {\n msgChanged = true;\n newMsg = { ...newMsg, tool_call_id: sanitized };\n }\n }\n\n // Sanitize content blocks if content is an array (Anthropic-style content)\n if (Array.isArray(typedMsg.content)) {\n const newContent = (typedMsg.content as ContentBlock[]).map((block) => {\n if (!block || typeof block !== \"object\") return block;\n\n let blockChanged = false;\n let newBlock = { ...block };\n\n // tool_use blocks have \"id\"\n if (block.type === \"tool_use\" && block.id && typeof block.id === \"string\") {\n const sanitized = sanitizeToolId(block.id);\n if (sanitized !== block.id) {\n blockChanged = true;\n newBlock = { ...newBlock, id: sanitized };\n }\n }\n\n // tool_result blocks have \"tool_use_id\"\n if (\n block.type === \"tool_result\" &&\n block.tool_use_id &&\n typeof block.tool_use_id === \"string\"\n ) {\n const sanitized = sanitizeToolId(block.tool_use_id);\n if (sanitized !== block.tool_use_id) {\n blockChanged = true;\n newBlock = { ...newBlock, tool_use_id: sanitized };\n }\n }\n\n if (blockChanged) {\n msgChanged = true;\n return newBlock;\n }\n return block;\n });\n\n if (msgChanged) {\n newMsg = { ...newMsg, content: newContent };\n }\n }\n\n if (msgChanged) {\n hasChanges = true;\n return newMsg;\n }\n return msg;\n });\n\n return hasChanges ? sanitized : messages;\n}\n\n/**\n * Normalize message roles to standard OpenAI format.\n * Converts non-standard roles (e.g., \"developer\") to valid ones.\n */\nfunction normalizeMessageRoles(messages: ChatMessage[]): ChatMessage[] {\n if (!messages || messages.length === 0) return messages;\n\n let hasChanges = false;\n const normalized = messages.map((msg) => {\n if (VALID_ROLES.has(msg.role)) return msg;\n\n const mappedRole = ROLE_MAPPINGS[msg.role];\n if (mappedRole) {\n hasChanges = true;\n return { ...msg, role: mappedRole };\n }\n\n // Unknown role - default to \"user\" to avoid API errors\n hasChanges = true;\n return { ...msg, role: \"user\" };\n });\n\n return hasChanges ? normalized : messages;\n}\n\n/**\n * Normalize messages for Google models.\n * Google's Gemini API requires the first non-system message to be from \"user\".\n * If conversation starts with \"assistant\"/\"model\", prepend a placeholder user message.\n */\n\nfunction normalizeMessagesForGoogle(messages: ChatMessage[]): ChatMessage[] {\n if (!messages || messages.length === 0) return messages;\n\n // Find first non-system message\n let firstNonSystemIdx = -1;\n for (let i = 0; i < messages.length; i++) {\n if (messages[i].role !== \"system\") {\n firstNonSystemIdx = i;\n break;\n }\n }\n\n // If no non-system messages, return as-is\n if (firstNonSystemIdx === -1) return messages;\n\n const firstRole = messages[firstNonSystemIdx].role;\n\n // If first non-system message is already \"user\", no change needed\n if (firstRole === \"user\") return messages;\n\n // If first non-system message is \"assistant\" or \"model\", prepend a user message\n if (firstRole === \"assistant\" || firstRole === \"model\") {\n const normalized = [...messages];\n normalized.splice(firstNonSystemIdx, 0, {\n role: \"user\",\n content: \"(continuing conversation)\",\n });\n return normalized;\n }\n\n return messages;\n}\n\n/**\n * Check if a model is a Google model that requires message normalization.\n */\nfunction isGoogleModel(modelId: string): boolean {\n return modelId.startsWith(\"google/\") || modelId.startsWith(\"gemini\");\n}\n\n/**\n * Extended message type for thinking-enabled conversations.\n */\ntype ExtendedChatMessage = ChatMessage & {\n tool_calls?: unknown[];\n reasoning_content?: unknown;\n};\n\n/**\n * Normalize messages for thinking-enabled requests.\n * When thinking/extended_thinking is enabled, assistant messages with tool_calls\n * must have reasoning_content (can be empty string if not present).\n * Error: \"400 thinking is enabled but reasoning_content is missing in assistant tool call message\"\n */\nfunction normalizeMessagesForThinking(messages: ExtendedChatMessage[]): ExtendedChatMessage[] {\n if (!messages || messages.length === 0) return messages;\n\n let hasChanges = false;\n const normalized = messages.map((msg) => {\n // Skip if not assistant or already has reasoning_content\n if (msg.role !== \"assistant\" || msg.reasoning_content !== undefined) {\n return msg;\n }\n\n // Check for OpenAI format: tool_calls array\n const hasOpenAIToolCalls =\n msg.tool_calls && Array.isArray(msg.tool_calls) && msg.tool_calls.length > 0;\n\n // Check for Anthropic format: content array with tool_use blocks\n const hasAnthropicToolUse =\n Array.isArray(msg.content) &&\n (msg.content as Array<{ type?: string }>).some((block) => block?.type === \"tool_use\");\n\n if (hasOpenAIToolCalls || hasAnthropicToolUse) {\n hasChanges = true;\n return { ...msg, reasoning_content: \"\" };\n }\n return msg;\n });\n\n return hasChanges ? normalized : messages;\n}\n\n/**\n * Result of truncating messages.\n */\ntype TruncationResult = {\n messages: T[];\n wasTruncated: boolean;\n originalCount: number;\n truncatedCount: number;\n};\n\n/**\n * Truncate messages to stay under BlockRun's MAX_MESSAGES limit.\n * Keeps all system messages and the most recent conversation history.\n * Returns the messages and whether truncation occurred.\n */\nfunction truncateMessages(messages: T[]): TruncationResult {\n if (!messages || messages.length <= MAX_MESSAGES) {\n return {\n messages,\n wasTruncated: false,\n originalCount: messages?.length ?? 0,\n truncatedCount: messages?.length ?? 0,\n };\n }\n\n // Separate system messages from conversation\n const systemMsgs = messages.filter((m) => m.role === \"system\");\n const conversationMsgs = messages.filter((m) => m.role !== \"system\");\n\n // Keep all system messages + most recent conversation messages\n const maxConversation = MAX_MESSAGES - systemMsgs.length;\n const truncatedConversation = conversationMsgs.slice(-maxConversation);\n\n const result = [...systemMsgs, ...truncatedConversation];\n\n console.log(\n `[ClawRouter] Truncated messages: ${messages.length} → ${result.length} (kept ${systemMsgs.length} system + ${truncatedConversation.length} recent)`,\n );\n\n return {\n messages: result,\n wasTruncated: true,\n originalCount: messages.length,\n truncatedCount: result.length,\n };\n}\n\n// Kimi/Moonshot models use special Unicode tokens for thinking boundaries.\n// Pattern: <|begin▁of▁thinking|>content<|end▁of▁thinking|>\n// The | is fullwidth vertical bar (U+FF5C), ▁ is lower one-eighth block (U+2581).\n\n// Match full Kimi thinking blocks: <|begin...|>content<|end...|>\nconst KIMI_BLOCK_RE = /<[||][^<>]*begin[^<>]*[||]>[\\s\\S]*?<[||][^<>]*end[^<>]*[||]>/gi;\n\n// Match standalone Kimi tokens like <|end▁of▁thinking|>\nconst KIMI_TOKEN_RE = /<[||][^<>]*[||]>/g;\n\n// Standard thinking tags that may leak through from various models\nconst THINKING_TAG_RE = /<\\s*\\/?\\s*(?:think(?:ing)?|thought|antthinking)\\b[^>]*>/gi;\n\n// Full thinking blocks: content\nconst THINKING_BLOCK_RE =\n /<\\s*(?:think(?:ing)?|thought|antthinking)\\b[^>]*>[\\s\\S]*?<\\s*\\/\\s*(?:think(?:ing)?|thought|antthinking)\\s*>/gi;\n\n/**\n * Strip thinking tokens and blocks from model response content.\n * Handles both Kimi-style Unicode tokens and standard XML-style tags.\n *\n * NOTE: DSML tags (<|DSML|...>) are NOT stripped - those are tool calls\n * that should be handled by the API, not hidden from users.\n */\nfunction stripThinkingTokens(content: string): string {\n if (!content) return content;\n // Strip full Kimi thinking blocks first (begin...end with content)\n let cleaned = content.replace(KIMI_BLOCK_RE, \"\");\n // Strip remaining standalone Kimi tokens\n cleaned = cleaned.replace(KIMI_TOKEN_RE, \"\");\n // Strip full thinking blocks (...)\n cleaned = cleaned.replace(THINKING_BLOCK_RE, \"\");\n // Strip remaining standalone thinking tags\n cleaned = cleaned.replace(THINKING_TAG_RE, \"\");\n return cleaned;\n}\n\n/** Callback info for low balance warning */\nexport type LowBalanceInfo = {\n balanceUSD: string;\n walletAddress: string;\n};\n\n/** Callback info for insufficient funds error */\nexport type InsufficientFundsInfo = {\n balanceUSD: string;\n requiredUSD: string;\n walletAddress: string;\n};\n\n/**\n * Wallet config: either a plain EVM private key string, or the full\n * resolution object from resolveOrGenerateWalletKey() which may include\n * Solana keys. Using the full object prevents callers from accidentally\n * forgetting to forward Solana key bytes.\n */\nexport type WalletConfig = string | { key: string; solanaPrivateKeyBytes?: Uint8Array };\n\nexport type PaymentChain = \"base\" | \"solana\";\n\nexport type ProxyOptions = {\n wallet: WalletConfig;\n apiBase?: string;\n /** Payment chain: \"base\" (default) or \"solana\". Can also be set via CLAWROUTER_PAYMENT_CHAIN env var. */\n paymentChain?: PaymentChain;\n /** Port to listen on (default: 8402) */\n port?: number;\n routingConfig?: Partial;\n /** Request timeout in ms (default: 180000 = 3 minutes). Covers on-chain tx + LLM response. */\n requestTimeoutMs?: number;\n /** Skip balance checks (for testing only). Default: false */\n skipBalanceCheck?: boolean;\n /**\n * Session persistence config. When enabled, maintains model selection\n * across requests within a session to prevent mid-task model switching.\n */\n sessionConfig?: Partial;\n /**\n * Auto-compress large requests to reduce network usage.\n * When enabled, requests are automatically compressed using\n * LLM-safe context compression (15-40% reduction).\n * Default: true\n */\n autoCompressRequests?: boolean;\n /**\n * Threshold in KB to trigger auto-compression (default: 180).\n * Requests larger than this are compressed before sending.\n * Set to 0 to compress all requests.\n */\n compressionThresholdKB?: number;\n /**\n * Response caching config. When enabled, identical requests return\n * cached responses instead of making new API calls.\n * Default: enabled with 10 minute TTL, 200 max entries.\n */\n cacheConfig?: ResponseCacheConfig;\n onReady?: (port: number) => void;\n onError?: (error: Error) => void;\n onPayment?: (info: { model: string; amount: string; network: string }) => void;\n onRouted?: (decision: RoutingDecision) => void;\n /** Called when balance drops below $1.00 (warning, request still proceeds) */\n onLowBalance?: (info: LowBalanceInfo) => void;\n /** Called when balance is insufficient for a request (request fails) */\n onInsufficientFunds?: (info: InsufficientFundsInfo) => void;\n};\n\nexport type ProxyHandle = {\n port: number;\n baseUrl: string;\n walletAddress: string;\n solanaAddress?: string;\n balanceMonitor: AnyBalanceMonitor;\n close: () => Promise;\n};\n\n/**\n * Build model pricing map from BLOCKRUN_MODELS.\n */\nfunction buildModelPricing(): Map {\n const map = new Map();\n for (const m of BLOCKRUN_MODELS) {\n if (m.id === AUTO_MODEL) continue; // skip meta-model\n map.set(m.id, { inputPrice: m.inputPrice, outputPrice: m.outputPrice });\n }\n return map;\n}\n\ntype ModelListEntry = {\n id: string;\n object: \"model\";\n created: number;\n owned_by: string;\n};\n\n/**\n * Build `/v1/models` response entries from the full OpenClaw model registry.\n * This includes alias IDs (e.g., `flash`, `kimi`) so `/model ` works reliably.\n */\nexport function buildProxyModelList(\n createdAt: number = Math.floor(Date.now() / 1000),\n): ModelListEntry[] {\n const seen = new Set();\n return OPENCLAW_MODELS.filter((model) => {\n if (seen.has(model.id)) return false;\n seen.add(model.id);\n return true;\n }).map((model) => ({\n id: model.id,\n object: \"model\",\n created: createdAt,\n owned_by: model.id.includes(\"/\") ? (model.id.split(\"/\")[0] ?? \"blockrun\") : \"blockrun\",\n }));\n}\n\n/**\n * Merge partial routing config overrides with defaults.\n */\nfunction mergeRoutingConfig(overrides?: Partial): RoutingConfig {\n if (!overrides) return DEFAULT_ROUTING_CONFIG;\n return {\n ...DEFAULT_ROUTING_CONFIG,\n ...overrides,\n classifier: { ...DEFAULT_ROUTING_CONFIG.classifier, ...overrides.classifier },\n scoring: { ...DEFAULT_ROUTING_CONFIG.scoring, ...overrides.scoring },\n tiers: { ...DEFAULT_ROUTING_CONFIG.tiers, ...overrides.tiers },\n overrides: { ...DEFAULT_ROUTING_CONFIG.overrides, ...overrides.overrides },\n };\n}\n\n/**\n * Estimate USDC cost for a request based on model pricing.\n * Returns amount string in USDC smallest unit (6 decimals) or undefined if unknown.\n */\nfunction estimateAmount(\n modelId: string,\n bodyLength: number,\n maxTokens: number,\n): string | undefined {\n const model = BLOCKRUN_MODELS.find((m) => m.id === modelId);\n if (!model) return undefined;\n\n // Rough estimate: ~4 chars per token for input\n const estimatedInputTokens = Math.ceil(bodyLength / 4);\n const estimatedOutputTokens = maxTokens || model.maxOutput || 4096;\n\n const costUsd =\n (estimatedInputTokens / 1_000_000) * model.inputPrice +\n (estimatedOutputTokens / 1_000_000) * model.outputPrice;\n\n // Convert to USDC 6-decimal integer, add 20% buffer for estimation error\n // Minimum 1000 ($0.001) to match CDP Facilitator's enforced minimum payment\n const amountMicros = Math.max(1000, Math.ceil(costUsd * 1.2 * 1_000_000));\n return amountMicros.toString();\n}\n\n/**\n * Proxy a partner API request through x402 payment flow.\n *\n * Simplified proxy for partner endpoints (/v1/x/*, /v1/partner/*).\n * No smart routing, SSE, compression, or sessions — just collect body,\n * forward via payFetch (which handles 402 automatically), and stream back.\n */\nasync function proxyPartnerRequest(\n req: IncomingMessage,\n res: ServerResponse,\n apiBase: string,\n payFetch: (input: RequestInfo | URL, init?: RequestInit) => Promise,\n): Promise {\n const startTime = Date.now();\n const upstreamUrl = `${apiBase}${req.url}`;\n\n // Collect request body\n const bodyChunks: Buffer[] = [];\n for await (const chunk of req) {\n bodyChunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));\n }\n const body = Buffer.concat(bodyChunks);\n\n // Forward headers (strip hop-by-hop)\n const headers: Record = {};\n for (const [key, value] of Object.entries(req.headers)) {\n if (\n key === \"host\" ||\n key === \"connection\" ||\n key === \"transfer-encoding\" ||\n key === \"content-length\"\n )\n continue;\n if (typeof value === \"string\") headers[key] = value;\n }\n if (!headers[\"content-type\"]) headers[\"content-type\"] = \"application/json\";\n headers[\"user-agent\"] = USER_AGENT;\n\n console.log(`[ClawRouter] Partner request: ${req.method} ${req.url}`);\n\n const upstream = await payFetch(upstreamUrl, {\n method: req.method ?? \"POST\",\n headers,\n body: body.length > 0 ? new Uint8Array(body) : undefined,\n });\n\n // Forward response headers\n const responseHeaders: Record = {};\n upstream.headers.forEach((value, key) => {\n if (key === \"transfer-encoding\" || key === \"connection\" || key === \"content-encoding\") return;\n responseHeaders[key] = value;\n });\n\n res.writeHead(upstream.status, responseHeaders);\n\n // Stream response body\n if (upstream.body) {\n const chunks = await readBodyWithTimeout(upstream.body, ERROR_BODY_READ_TIMEOUT_MS);\n for (const chunk of chunks) {\n safeWrite(res, Buffer.from(chunk));\n }\n }\n\n res.end();\n\n const latencyMs = Date.now() - startTime;\n console.log(`[ClawRouter] Partner response: ${upstream.status} (${latencyMs}ms)`);\n\n // Log partner usage (fire-and-forget)\n logUsage({\n timestamp: new Date().toISOString(),\n model: \"partner\",\n tier: \"PARTNER\",\n cost: 0, // Actual cost handled by x402 settlement\n baselineCost: 0,\n savings: 0,\n latencyMs,\n partnerId:\n (req.url?.split(\"?\")[0] ?? \"\").replace(/^\\/v1\\//, \"\").replace(/\\//g, \"_\") || \"unknown\",\n service: \"partner\",\n }).catch(() => {});\n}\n\n/**\n * Upload a base64 data URI to catbox.moe and return a public URL.\n * Google image models (nano-banana) return data URIs instead of hosted URLs,\n * which breaks Telegram and other clients that can't render raw base64.\n */\nasync function uploadDataUriToHost(dataUri: string): Promise {\n const match = dataUri.match(/^data:(image\\/\\w+);base64,(.+)$/);\n if (!match) throw new Error(\"Invalid data URI format\");\n const [, mimeType, b64Data] = match;\n const ext = mimeType === \"image/jpeg\" ? \"jpg\" : (mimeType.split(\"/\")[1] ?? \"png\");\n\n const buffer = Buffer.from(b64Data, \"base64\");\n const blob = new Blob([buffer], { type: mimeType });\n\n const form = new FormData();\n form.append(\"reqtype\", \"fileupload\");\n form.append(\"fileToUpload\", blob, `image.${ext}`);\n\n const uploadController = new AbortController();\n const uploadTimeout = setTimeout(() => uploadController.abort(), 30_000);\n try {\n const resp = await fetch(\"https://catbox.moe/user/api.php\", {\n method: \"POST\",\n body: form,\n signal: uploadController.signal,\n });\n\n if (!resp.ok) throw new Error(`catbox.moe upload failed: HTTP ${resp.status}`);\n const result = await resp.text();\n if (result.startsWith(\"https://\")) {\n return result.trim();\n }\n throw new Error(`catbox.moe upload failed: ${result}`);\n } finally {\n clearTimeout(uploadTimeout);\n }\n}\n\n/**\n * Start the local x402 proxy server.\n *\n * If a proxy is already running on the target port, reuses it instead of failing.\n * Port can be configured via BLOCKRUN_PROXY_PORT environment variable.\n *\n * Returns a handle with the assigned port, base URL, and a close function.\n */\nexport async function startProxy(options: ProxyOptions): Promise {\n // Normalize wallet config: string = EVM-only, object = full resolution\n const walletKey = typeof options.wallet === \"string\" ? options.wallet : options.wallet.key;\n const solanaPrivateKeyBytes =\n typeof options.wallet === \"string\" ? undefined : options.wallet.solanaPrivateKeyBytes;\n\n // Payment chain: options > env var > persisted file > default \"base\".\n // No dynamic switching — user selects chain via /wallet solana or /wallet base.\n const paymentChain = options.paymentChain ?? (await resolvePaymentChain());\n const apiBase =\n options.apiBase ??\n (paymentChain === \"solana\" && solanaPrivateKeyBytes ? BLOCKRUN_SOLANA_API : BLOCKRUN_API);\n if (paymentChain === \"solana\" && !solanaPrivateKeyBytes) {\n console.warn(\n `[ClawRouter] ⚠ Payment chain is Solana but no mnemonic found — falling back to Base (EVM).`,\n );\n console.warn(\n `[ClawRouter] To fix: run \"npx @blockrun/clawrouter wallet recover\" if your mnemonic exists,`,\n );\n console.warn(`[ClawRouter] or run \"npx @blockrun/clawrouter chain base\" to switch to EVM.`);\n } else if (paymentChain === \"solana\") {\n console.log(`[ClawRouter] Payment chain: Solana (${BLOCKRUN_SOLANA_API})`);\n }\n\n // Determine port: options.port > env var > default\n const listenPort = options.port ?? getProxyPort();\n\n // Check if a proxy is already running on this port\n const existingProxy = await checkExistingProxy(listenPort);\n if (existingProxy) {\n // Proxy already running — reuse it instead of failing with EADDRINUSE\n const account = privateKeyToAccount(walletKey as `0x${string}`);\n const baseUrl = `http://127.0.0.1:${listenPort}`;\n\n // Verify the existing proxy is using the same wallet (or warn if different)\n if (existingProxy.wallet !== account.address) {\n console.warn(\n `[ClawRouter] Existing proxy on port ${listenPort} uses wallet ${existingProxy.wallet}, but current config uses ${account.address}. Reusing existing proxy.`,\n );\n }\n\n // Verify the existing proxy is using the same payment chain\n if (existingProxy.paymentChain) {\n if (existingProxy.paymentChain !== paymentChain) {\n throw new Error(\n `Existing proxy on port ${listenPort} is using ${existingProxy.paymentChain} but ${paymentChain} was requested. ` +\n `Stop the existing proxy first or use a different port.`,\n );\n }\n } else if (paymentChain !== \"base\") {\n // Old proxy doesn't report chain — assume Base. Reject if Solana was requested.\n console.warn(\n `[ClawRouter] Existing proxy on port ${listenPort} does not report paymentChain (pre-v0.11 instance). Assuming Base.`,\n );\n throw new Error(\n `Existing proxy on port ${listenPort} is a pre-v0.11 instance (assumed Base) but ${paymentChain} was requested. ` +\n `Stop the existing proxy first or use a different port.`,\n );\n }\n\n // Derive Solana address if keys are available (for wallet status display)\n let reuseSolanaAddress: string | undefined;\n if (solanaPrivateKeyBytes) {\n const { createKeyPairSignerFromPrivateKeyBytes } = await import(\"@solana/kit\");\n const solanaSigner = await createKeyPairSignerFromPrivateKeyBytes(solanaPrivateKeyBytes);\n reuseSolanaAddress = solanaSigner.address;\n }\n\n // Use chain-appropriate balance monitor\n const balanceMonitor: AnyBalanceMonitor =\n paymentChain === \"solana\" && reuseSolanaAddress\n ? new SolanaBalanceMonitor(reuseSolanaAddress)\n : new BalanceMonitor(account.address);\n\n options.onReady?.(listenPort);\n\n return {\n port: listenPort,\n baseUrl,\n walletAddress: existingProxy.wallet,\n solanaAddress: reuseSolanaAddress,\n balanceMonitor,\n close: async () => {\n // No-op: we didn't start this proxy, so we shouldn't close it\n },\n };\n }\n\n // Create x402 payment client with EVM scheme (always available)\n const account = privateKeyToAccount(walletKey as `0x${string}`);\n const evmPublicClient = createPublicClient({ chain: base, transport: http() });\n const evmSigner = toClientEvmSigner(account, evmPublicClient);\n const x402 = new x402Client();\n registerExactEvmScheme(x402, { signer: evmSigner });\n\n // Register Solana scheme if key is available\n // Uses registerExactSvmScheme helper which registers:\n // - solana:* wildcard (catches any CAIP-2 Solana network)\n // - V1 compat names: \"solana\", \"solana-devnet\", \"solana-testnet\"\n let solanaAddress: string | undefined;\n if (solanaPrivateKeyBytes) {\n const { registerExactSvmScheme } = await import(\"@x402/svm/exact/client\");\n const { createKeyPairSignerFromPrivateKeyBytes } = await import(\"@solana/kit\");\n const solanaSigner = await createKeyPairSignerFromPrivateKeyBytes(solanaPrivateKeyBytes);\n solanaAddress = solanaSigner.address;\n registerExactSvmScheme(x402, { signer: solanaSigner });\n console.log(`[ClawRouter] Solana x402 scheme registered: ${solanaAddress}`);\n }\n\n // Log which chain is used for each payment\n x402.onAfterPaymentCreation(async (context) => {\n const network = context.selectedRequirements.network;\n const chain = network.startsWith(\"eip155\")\n ? \"Base (EVM)\"\n : network.startsWith(\"solana\")\n ? \"Solana\"\n : network;\n console.log(`[ClawRouter] Payment signed on ${chain} (${network})`);\n });\n\n const payFetch = createPayFetchWithPreAuth(fetch, x402, undefined, {\n skipPreAuth: paymentChain === \"solana\",\n });\n\n // Create balance monitor for pre-request checks (chain-appropriate)\n const balanceMonitor: AnyBalanceMonitor =\n paymentChain === \"solana\" && solanaAddress\n ? new SolanaBalanceMonitor(solanaAddress)\n : new BalanceMonitor(account.address);\n\n // Build router options (100% local — no external API calls for routing)\n const routingConfig = mergeRoutingConfig(options.routingConfig);\n const modelPricing = buildModelPricing();\n const routerOpts: RouterOptions = {\n config: routingConfig,\n modelPricing,\n };\n\n // Request deduplicator (shared across all requests)\n const deduplicator = new RequestDeduplicator();\n\n // Response cache for identical requests (longer TTL than dedup)\n const responseCache = new ResponseCache(options.cacheConfig);\n\n // Session store for model persistence (prevents mid-task model switching)\n const sessionStore = new SessionStore(options.sessionConfig);\n\n // Session journal for memory (enables agents to recall earlier work)\n const sessionJournal = new SessionJournal();\n\n // Track active connections for graceful cleanup\n const connections = new Set();\n\n const server = createServer(async (req: IncomingMessage, res: ServerResponse) => {\n // Add stream error handlers to prevent server crashes\n req.on(\"error\", (err) => {\n console.error(`[ClawRouter] Request stream error: ${err.message}`);\n // Don't throw - just log and let request handler deal with it\n });\n\n res.on(\"error\", (err) => {\n console.error(`[ClawRouter] Response stream error: ${err.message}`);\n // Don't try to write to failed socket - just log\n });\n\n // Finished wrapper for guaranteed cleanup on response completion/error\n finished(res, (err) => {\n if (err && err.code !== \"ERR_STREAM_DESTROYED\") {\n console.error(`[ClawRouter] Response finished with error: ${err.message}`);\n }\n // Note: heartbeatInterval cleanup happens in res.on(\"close\") handler\n // Note: completed and dedup cleanup happens in the res.on(\"close\") handler below\n });\n\n // Request finished wrapper for complete stream lifecycle tracking\n finished(req, (err) => {\n if (err && err.code !== \"ERR_STREAM_DESTROYED\") {\n console.error(`[ClawRouter] Request finished with error: ${err.message}`);\n }\n });\n\n // Health check with optional balance info\n if (req.url === \"/health\" || req.url?.startsWith(\"/health?\")) {\n const url = new URL(req.url, \"http://localhost\");\n const full = url.searchParams.get(\"full\") === \"true\";\n\n const response: Record = {\n status: \"ok\",\n wallet: account.address,\n paymentChain,\n };\n if (solanaAddress) {\n response.solana = solanaAddress;\n }\n\n if (full) {\n try {\n const balanceInfo = await balanceMonitor.checkBalance();\n response.balance = balanceInfo.balanceUSD;\n response.isLow = balanceInfo.isLow;\n response.isEmpty = balanceInfo.isEmpty;\n } catch {\n response.balanceError = \"Could not fetch balance\";\n }\n }\n\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(response));\n return;\n }\n\n // Cache stats endpoint\n if (req.url === \"/cache\" || req.url?.startsWith(\"/cache?\")) {\n const stats = responseCache.getStats();\n res.writeHead(200, {\n \"Content-Type\": \"application/json\",\n \"Cache-Control\": \"no-cache\",\n });\n res.end(JSON.stringify(stats, null, 2));\n return;\n }\n\n // Stats clear endpoint - delete all log files\n if (req.url === \"/stats\" && req.method === \"DELETE\") {\n try {\n const result = await clearStats();\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ cleared: true, deletedFiles: result.deletedFiles }));\n } catch (err) {\n res.writeHead(500, { \"Content-Type\": \"application/json\" });\n res.end(\n JSON.stringify({\n error: `Failed to clear stats: ${err instanceof Error ? err.message : String(err)}`,\n }),\n );\n }\n return;\n }\n\n // Stats API endpoint - returns JSON for programmatic access\n if (req.url === \"/stats\" || req.url?.startsWith(\"/stats?\")) {\n try {\n const url = new URL(req.url, \"http://localhost\");\n const days = parseInt(url.searchParams.get(\"days\") || \"7\", 10);\n const stats = await getStats(Math.min(days, 30));\n\n res.writeHead(200, {\n \"Content-Type\": \"application/json\",\n \"Cache-Control\": \"no-cache\",\n });\n res.end(JSON.stringify(stats, null, 2));\n } catch (err) {\n res.writeHead(500, { \"Content-Type\": \"application/json\" });\n res.end(\n JSON.stringify({\n error: `Failed to get stats: ${err instanceof Error ? err.message : String(err)}`,\n }),\n );\n }\n return;\n }\n\n // --- Handle /v1/models locally (no upstream call needed) ---\n if (req.url === \"/v1/models\" && req.method === \"GET\") {\n const models = buildProxyModelList();\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ object: \"list\", data: models }));\n return;\n }\n\n // --- Serve locally cached images (~/.openclaw/blockrun/images/) ---\n if (req.url?.startsWith(\"/images/\") && req.method === \"GET\") {\n const filename = req.url.slice(\"/images/\".length).split(\"?\")[0]!.replace(/[^a-zA-Z0-9._-]/g, \"\");\n if (!filename) {\n res.writeHead(400);\n res.end(\"Bad request\");\n return;\n }\n const filePath = join(IMAGE_DIR, filename);\n try {\n const s = await fsStat(filePath);\n if (!s.isFile()) throw new Error(\"not a file\");\n const ext = filename.split(\".\").pop()?.toLowerCase() ?? \"png\";\n const mime: Record = { png: \"image/png\", jpg: \"image/jpeg\", jpeg: \"image/jpeg\", webp: \"image/webp\", gif: \"image/gif\" };\n const data = await readFile(filePath);\n res.writeHead(200, { \"Content-Type\": mime[ext] ?? \"application/octet-stream\", \"Content-Length\": data.length });\n res.end(data);\n } catch {\n res.writeHead(404, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"Image not found\" }));\n }\n return;\n }\n\n // --- Handle /v1/images/generations: proxy with x402 payment + save data URIs locally ---\n if (req.url === \"/v1/images/generations\" && req.method === \"POST\") {\n const chunks: Buffer[] = [];\n for await (const chunk of req) {\n chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));\n }\n const reqBody = Buffer.concat(chunks);\n try {\n const upstream = await payFetch(`${apiBase}/v1/images/generations`, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\", \"user-agent\": USER_AGENT },\n body: reqBody,\n });\n const text = await upstream.text();\n if (!upstream.ok) {\n res.writeHead(upstream.status, { \"Content-Type\": \"application/json\" });\n res.end(text);\n return;\n }\n let result: { created?: number; data?: Array<{ url?: string; revised_prompt?: string }> };\n try { result = JSON.parse(text); } catch {\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(text);\n return;\n }\n // Save any data URIs to ~/.openclaw/blockrun/images/ and replace with localhost URLs\n if (result.data?.length) {\n await mkdir(IMAGE_DIR, { recursive: true });\n const port = (server.address() as AddressInfo | null)?.port ?? 8402;\n for (const img of result.data) {\n const m = img.url?.match(/^data:(image\\/\\w+);base64,(.+)$/);\n if (m) {\n const [, mimeType, b64] = m;\n const ext = mimeType === \"image/jpeg\" ? \"jpg\" : (mimeType!.split(\"/\")[1] ?? \"png\");\n const filename = `${Date.now()}-${Math.random().toString(36).slice(2, 10)}.${ext}`;\n await writeFile(join(IMAGE_DIR, filename), Buffer.from(b64!, \"base64\"));\n img.url = `http://localhost:${port}/images/${filename}`;\n console.log(`[ClawRouter] Image saved → ${img.url}`);\n }\n }\n }\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(result));\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n console.error(`[ClawRouter] Image generation error: ${msg}`);\n if (!res.headersSent) {\n res.writeHead(502, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"Image generation failed\", details: msg }));\n }\n }\n return;\n }\n\n // --- Handle partner API paths (/v1/x/*, /v1/partner/*) ---\n if (req.url?.match(/^\\/v1\\/(?:x|partner)\\//)) {\n try {\n await proxyPartnerRequest(req, res, apiBase, payFetch);\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err));\n options.onError?.(error);\n if (!res.headersSent) {\n res.writeHead(502, { \"Content-Type\": \"application/json\" });\n res.end(\n JSON.stringify({\n error: { message: `Partner proxy error: ${error.message}`, type: \"partner_error\" },\n }),\n );\n }\n }\n return;\n }\n\n // Only proxy paths starting with /v1\n if (!req.url?.startsWith(\"/v1\")) {\n res.writeHead(404, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"Not found\" }));\n return;\n }\n\n try {\n await proxyRequest(\n req,\n res,\n apiBase,\n payFetch,\n options,\n routerOpts,\n deduplicator,\n balanceMonitor,\n sessionStore,\n responseCache,\n sessionJournal,\n );\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err));\n options.onError?.(error);\n\n if (!res.headersSent) {\n res.writeHead(502, { \"Content-Type\": \"application/json\" });\n res.end(\n JSON.stringify({\n error: { message: `Proxy error: ${error.message}`, type: \"proxy_error\" },\n }),\n );\n } else if (!res.writableEnded) {\n // Headers already sent (streaming) — send error as SSE event\n res.write(\n `data: ${JSON.stringify({ error: { message: error.message, type: \"proxy_error\" } })}\\n\\n`,\n );\n res.write(\"data: [DONE]\\n\\n\");\n res.end();\n }\n }\n });\n\n // Listen on configured port with retry logic for TIME_WAIT handling\n // When gateway restarts quickly, the port may still be in TIME_WAIT state.\n // We retry with delay instead of incorrectly assuming a proxy is running.\n const tryListen = (attempt: number): Promise => {\n return new Promise((resolveAttempt, rejectAttempt) => {\n const onError = async (err: NodeJS.ErrnoException) => {\n server.removeListener(\"error\", onError);\n\n if (err.code === \"EADDRINUSE\") {\n // Port is in use - check if a proxy is actually running\n const existingProxy2 = await checkExistingProxy(listenPort);\n if (existingProxy2) {\n // Proxy is actually running - this is fine, reuse it\n console.log(`[ClawRouter] Existing proxy detected on port ${listenPort}, reusing`);\n rejectAttempt({\n code: \"REUSE_EXISTING\",\n wallet: existingProxy2.wallet,\n existingChain: existingProxy2.paymentChain,\n });\n return;\n }\n\n // Port is in TIME_WAIT (no proxy responding) - retry after delay\n if (attempt < PORT_RETRY_ATTEMPTS) {\n console.log(\n `[ClawRouter] Port ${listenPort} in TIME_WAIT, retrying in ${PORT_RETRY_DELAY_MS}ms (attempt ${attempt}/${PORT_RETRY_ATTEMPTS})`,\n );\n rejectAttempt({ code: \"RETRY\", attempt });\n return;\n }\n\n // Max retries exceeded\n console.error(\n `[ClawRouter] Port ${listenPort} still in use after ${PORT_RETRY_ATTEMPTS} attempts`,\n );\n rejectAttempt(err);\n return;\n }\n\n rejectAttempt(err);\n };\n\n server.once(\"error\", onError);\n server.listen(listenPort, \"127.0.0.1\", () => {\n server.removeListener(\"error\", onError);\n resolveAttempt();\n });\n });\n };\n\n // Retry loop for port binding\n let lastError: Error | undefined;\n for (let attempt = 1; attempt <= PORT_RETRY_ATTEMPTS; attempt++) {\n try {\n await tryListen(attempt);\n break; // Success\n } catch (err: unknown) {\n const error = err as {\n code?: string;\n wallet?: string;\n existingChain?: string;\n attempt?: number;\n };\n\n if (error.code === \"REUSE_EXISTING\" && error.wallet) {\n // Validate payment chain matches (same check as pre-listen reuse path)\n if (error.existingChain && error.existingChain !== paymentChain) {\n throw new Error(\n `Existing proxy on port ${listenPort} is using ${error.existingChain} but ${paymentChain} was requested. ` +\n `Stop the existing proxy first or use a different port.`,\n { cause: err },\n );\n }\n\n // Proxy is running, reuse it\n const baseUrl = `http://127.0.0.1:${listenPort}`;\n options.onReady?.(listenPort);\n return {\n port: listenPort,\n baseUrl,\n walletAddress: error.wallet,\n balanceMonitor,\n close: async () => {\n // No-op: we didn't start this proxy, so we shouldn't close it\n },\n };\n }\n\n if (error.code === \"RETRY\") {\n // Wait before retry\n await new Promise((r) => setTimeout(r, PORT_RETRY_DELAY_MS));\n continue;\n }\n\n // Other error - throw\n lastError = err as Error;\n break;\n }\n }\n\n if (lastError) {\n throw lastError;\n }\n\n // Server is now listening - set up remaining handlers\n const addr = server.address() as AddressInfo;\n const port = addr.port;\n const baseUrl = `http://127.0.0.1:${port}`;\n\n options.onReady?.(port);\n\n // Check for updates (non-blocking)\n checkForUpdates();\n\n // Add runtime error handler AFTER successful listen\n // This handles errors that occur during server operation (not just startup)\n server.on(\"error\", (err) => {\n console.error(`[ClawRouter] Server runtime error: ${err.message}`);\n options.onError?.(err);\n // Don't crash - log and continue\n });\n\n // Handle client connection errors (bad requests, socket errors)\n server.on(\"clientError\", (err, socket) => {\n console.error(`[ClawRouter] Client error: ${err.message}`);\n // Send 400 Bad Request if socket is still writable\n if (socket.writable && !socket.destroyed) {\n socket.end(\"HTTP/1.1 400 Bad Request\\r\\n\\r\\n\");\n }\n });\n\n // Track connections for graceful cleanup\n server.on(\"connection\", (socket) => {\n connections.add(socket);\n\n // Set 5-minute timeout for streaming requests\n socket.setTimeout(300_000);\n\n socket.on(\"timeout\", () => {\n console.error(`[ClawRouter] Socket timeout, destroying connection`);\n socket.destroy();\n });\n\n socket.on(\"end\", () => {\n // Half-closed by client (FIN received)\n });\n\n socket.on(\"error\", (err) => {\n console.error(`[ClawRouter] Socket error: ${err.message}`);\n });\n\n socket.on(\"close\", () => {\n connections.delete(socket);\n });\n });\n\n return {\n port,\n baseUrl,\n walletAddress: account.address,\n solanaAddress,\n balanceMonitor,\n close: () =>\n new Promise((res, rej) => {\n const timeout = setTimeout(() => {\n rej(new Error(\"[ClawRouter] Close timeout after 4s\"));\n }, 4000);\n\n sessionStore.close();\n // Destroy all active connections before closing server\n for (const socket of connections) {\n socket.destroy();\n }\n connections.clear();\n server.close((err) => {\n clearTimeout(timeout);\n if (err) {\n rej(err);\n } else {\n res();\n }\n });\n }),\n };\n}\n\n/** Result of attempting a model request */\ntype ModelRequestResult = {\n success: boolean;\n response?: Response;\n errorBody?: string;\n errorStatus?: number;\n isProviderError?: boolean;\n};\n\n/**\n * Attempt a request with a specific model.\n * Returns the response or error details for fallback decision.\n */\nasync function tryModelRequest(\n upstreamUrl: string,\n method: string,\n headers: Record,\n body: Buffer,\n modelId: string,\n maxTokens: number,\n payFetch: (input: RequestInfo | URL, init?: RequestInit) => Promise,\n balanceMonitor: AnyBalanceMonitor,\n signal: AbortSignal,\n): Promise {\n // Update model in body and normalize messages\n let requestBody = body;\n try {\n const parsed = JSON.parse(body.toString()) as Record;\n parsed.model = modelId;\n\n // Normalize message roles (e.g., \"developer\" -> \"system\")\n if (Array.isArray(parsed.messages)) {\n parsed.messages = normalizeMessageRoles(parsed.messages as ChatMessage[]);\n }\n\n // Truncate messages to stay under BlockRun's limit (200 messages)\n if (Array.isArray(parsed.messages)) {\n const truncationResult = truncateMessages(parsed.messages as ChatMessage[]);\n parsed.messages = truncationResult.messages;\n }\n\n // Sanitize tool IDs to match Anthropic's pattern (alphanumeric, underscore, hyphen only)\n if (Array.isArray(parsed.messages)) {\n parsed.messages = sanitizeToolIds(parsed.messages as ChatMessage[]);\n }\n\n // Normalize messages for Google models (first non-system message must be \"user\")\n if (isGoogleModel(modelId) && Array.isArray(parsed.messages)) {\n parsed.messages = normalizeMessagesForGoogle(parsed.messages as ChatMessage[]);\n }\n\n // Normalize messages for thinking-enabled requests (add reasoning_content to tool calls)\n // Check request flags AND target model - reasoning models have thinking enabled server-side\n const hasThinkingEnabled = !!(\n parsed.thinking ||\n parsed.extended_thinking ||\n isReasoningModel(modelId)\n );\n if (hasThinkingEnabled && Array.isArray(parsed.messages)) {\n parsed.messages = normalizeMessagesForThinking(parsed.messages as ExtendedChatMessage[]);\n }\n\n requestBody = Buffer.from(JSON.stringify(parsed));\n } catch {\n // If body isn't valid JSON, use as-is\n }\n\n try {\n const response = await payFetch(upstreamUrl, {\n method,\n headers,\n body: requestBody.length > 0 ? new Uint8Array(requestBody) : undefined,\n signal,\n });\n\n // Check for provider errors\n if (response.status !== 200) {\n // Clone response to read body without consuming it\n const errorBodyChunks = await readBodyWithTimeout(response.body, ERROR_BODY_READ_TIMEOUT_MS);\n const errorBody = Buffer.concat(errorBodyChunks).toString();\n const isProviderErr = isProviderError(response.status, errorBody);\n\n return {\n success: false,\n errorBody,\n errorStatus: response.status,\n isProviderError: isProviderErr,\n };\n }\n\n // Detect provider degradation hidden inside HTTP 200 responses.\n const contentType = response.headers.get(\"content-type\") || \"\";\n if (contentType.includes(\"json\") || contentType.includes(\"text\")) {\n try {\n const clonedChunks = await readBodyWithTimeout(\n response.clone().body,\n ERROR_BODY_READ_TIMEOUT_MS,\n );\n const responseBody = Buffer.concat(clonedChunks).toString();\n const degradedReason = detectDegradedSuccessResponse(responseBody);\n if (degradedReason) {\n return {\n success: false,\n errorBody: degradedReason,\n errorStatus: 503,\n isProviderError: true,\n };\n }\n } catch {\n // Ignore body inspection failures and pass through response.\n }\n }\n\n return { success: true, response };\n } catch (err) {\n const errorMsg = err instanceof Error ? err.message : String(err);\n return {\n success: false,\n errorBody: errorMsg,\n errorStatus: 500,\n isProviderError: true, // Network errors are retryable\n };\n }\n}\n\n/**\n * Proxy a single request through x402 payment flow to BlockRun API.\n *\n * Optimizations applied in order:\n * 1. Dedup check — if same request body seen within 30s, replay cached response\n * 2. Streaming heartbeat — for stream:true, send 200 + heartbeats immediately\n * 3. Smart routing — when model is \"blockrun/auto\", pick cheapest capable model\n * 4. Fallback chain — on provider errors, try next model in tier's fallback list\n */\nasync function proxyRequest(\n req: IncomingMessage,\n res: ServerResponse,\n apiBase: string,\n payFetch: (input: RequestInfo | URL, init?: RequestInit) => Promise,\n options: ProxyOptions,\n routerOpts: RouterOptions,\n deduplicator: RequestDeduplicator,\n balanceMonitor: AnyBalanceMonitor,\n sessionStore: SessionStore,\n responseCache: ResponseCache,\n sessionJournal: SessionJournal,\n): Promise {\n const startTime = Date.now();\n\n // Build upstream URL: /v1/chat/completions → https://blockrun.ai/api/v1/chat/completions\n const upstreamUrl = `${apiBase}${req.url}`;\n\n // Collect request body\n const bodyChunks: Buffer[] = [];\n for await (const chunk of req) {\n bodyChunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));\n }\n let body = Buffer.concat(bodyChunks);\n\n // Track original context size for response headers\n const originalContextSizeKB = Math.ceil(body.length / 1024);\n\n // Routing debug info is on by default; disable with x-clawrouter-debug: false\n const debugMode = req.headers[\"x-clawrouter-debug\"] !== \"false\";\n\n // --- Smart routing ---\n let routingDecision: RoutingDecision | undefined;\n let hasTools = false; // true when request includes a tools schema\n let hasVision = false; // true when request includes image_url content parts\n let isStreaming = false;\n let modelId = \"\";\n let maxTokens = 4096;\n let routingProfile: \"free\" | \"eco\" | \"auto\" | \"premium\" | null = null;\n let accumulatedContent = \"\"; // For session journal event extraction\n let responseInputTokens: number | undefined;\n const isChatCompletion = req.url?.includes(\"/chat/completions\");\n\n // Extract session ID early for journal operations (header-only at this point)\n const sessionId = getSessionId(req.headers as Record);\n // Full session ID (header + content-derived) — populated once messages are parsed\n let effectiveSessionId: string | undefined = sessionId;\n\n if (isChatCompletion && body.length > 0) {\n try {\n const parsed = JSON.parse(body.toString()) as Record;\n isStreaming = parsed.stream === true;\n modelId = (parsed.model as string) || \"\";\n maxTokens = (parsed.max_tokens as number) || 4096;\n let bodyModified = false;\n\n // Extract last user message content (used by session journal + /debug command)\n const parsedMessages = Array.isArray(parsed.messages)\n ? (parsed.messages as Array<{ role: string; content: unknown }>)\n : [];\n const lastUserMsg = [...parsedMessages].reverse().find((m) => m.role === \"user\");\n const rawLastContent = lastUserMsg?.content;\n const lastContent =\n typeof rawLastContent === \"string\"\n ? rawLastContent\n : Array.isArray(rawLastContent)\n ? (rawLastContent as Array<{ type: string; text?: string }>)\n .filter((b) => b.type === \"text\")\n .map((b) => b.text ?? \"\")\n .join(\" \")\n : \"\";\n\n // --- Session Journal: Inject context if needed ---\n // Check if the last user message asks about past work\n if (sessionId && parsedMessages.length > 0) {\n const messages = parsedMessages;\n\n if (sessionJournal.needsContext(lastContent)) {\n const journalText = sessionJournal.format(sessionId);\n if (journalText) {\n // Find system message and prepend journal, or add a new system message\n const sysIdx = messages.findIndex((m) => m.role === \"system\");\n if (sysIdx >= 0 && typeof messages[sysIdx].content === \"string\") {\n messages[sysIdx] = {\n ...messages[sysIdx],\n content: journalText + \"\\n\\n\" + messages[sysIdx].content,\n };\n } else {\n messages.unshift({ role: \"system\", content: journalText });\n }\n parsed.messages = messages;\n bodyModified = true;\n console.log(\n `[ClawRouter] Injected session journal (${journalText.length} chars) for session ${sessionId.slice(0, 8)}...`,\n );\n }\n }\n }\n\n // --- /debug command: return routing diagnostics without calling upstream ---\n if (lastContent.startsWith(\"/debug\")) {\n const debugPrompt = lastContent.slice(\"/debug\".length).trim() || \"hello\";\n const messages = parsed.messages as Array<{ role: string; content: unknown }>;\n const systemMsg = messages?.find((m) => m.role === \"system\");\n const systemPrompt = typeof systemMsg?.content === \"string\" ? systemMsg.content : undefined;\n const fullText = `${systemPrompt ?? \"\"} ${debugPrompt}`;\n const estimatedTokens = Math.ceil(fullText.length / 4);\n\n // Determine routing profile\n const normalizedModel =\n typeof parsed.model === \"string\" ? parsed.model.trim().toLowerCase() : \"\";\n const profileName = normalizedModel.replace(\"blockrun/\", \"\");\n const debugProfile = (\n [\"free\", \"eco\", \"auto\", \"premium\"].includes(profileName) ? profileName : \"auto\"\n ) as \"free\" | \"eco\" | \"auto\" | \"premium\";\n\n // Run scoring\n const scoring = classifyByRules(\n debugPrompt,\n systemPrompt,\n estimatedTokens,\n DEFAULT_ROUTING_CONFIG.scoring,\n );\n\n // Run full routing decision\n const debugRouting = route(debugPrompt, systemPrompt, maxTokens, {\n ...routerOpts,\n routingProfile: debugProfile,\n });\n\n // Format dimension scores\n const dimLines = (scoring.dimensions ?? [])\n .map((d) => {\n const nameStr = (d.name + \":\").padEnd(24);\n const scoreStr = d.score.toFixed(2).padStart(6);\n const sigStr = d.signal ? ` [${d.signal}]` : \"\";\n return ` ${nameStr}${scoreStr}${sigStr}`;\n })\n .join(\"\\n\");\n\n // Session info\n const sess = sessionId ? sessionStore.getSession(sessionId) : undefined;\n const sessLine = sess\n ? `Session: ${sessionId!.slice(0, 8)}... → pinned: ${sess.model} (${sess.requestCount} requests)`\n : sessionId\n ? `Session: ${sessionId.slice(0, 8)}... → no pinned model`\n : \"Session: none\";\n\n const { simpleMedium, mediumComplex, complexReasoning } =\n DEFAULT_ROUTING_CONFIG.scoring.tierBoundaries;\n\n const debugText = [\n \"ClawRouter Debug\",\n \"\",\n `Profile: ${debugProfile} | Tier: ${debugRouting.tier} | Model: ${debugRouting.model}`,\n `Confidence: ${debugRouting.confidence.toFixed(2)} | Cost: $${debugRouting.costEstimate.toFixed(4)} | Savings: ${(debugRouting.savings * 100).toFixed(0)}%`,\n `Reasoning: ${debugRouting.reasoning}`,\n \"\",\n `Scoring (weighted: ${scoring.score.toFixed(3)})`,\n dimLines,\n \"\",\n `Tier Boundaries: SIMPLE <${simpleMedium.toFixed(2)} | MEDIUM <${mediumComplex.toFixed(2)} | COMPLEX <${complexReasoning.toFixed(2)} | REASONING >=${complexReasoning.toFixed(2)}`,\n \"\",\n sessLine,\n ].join(\"\\n\");\n\n // Build synthetic OpenAI chat completion response\n const completionId = `chatcmpl-debug-${Date.now()}`;\n const timestamp = Math.floor(Date.now() / 1000);\n const syntheticResponse = {\n id: completionId,\n object: \"chat.completion\",\n created: timestamp,\n model: \"clawrouter/debug\",\n choices: [\n {\n index: 0,\n message: { role: \"assistant\", content: debugText },\n finish_reason: \"stop\",\n },\n ],\n usage: { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 },\n };\n\n if (isStreaming) {\n // SSE streaming response\n res.writeHead(200, {\n \"Content-Type\": \"text/event-stream\",\n \"Cache-Control\": \"no-cache\",\n Connection: \"keep-alive\",\n });\n const sseChunk = {\n id: completionId,\n object: \"chat.completion.chunk\",\n created: timestamp,\n model: \"clawrouter/debug\",\n choices: [\n {\n index: 0,\n delta: { role: \"assistant\", content: debugText },\n finish_reason: null,\n },\n ],\n };\n const sseDone = {\n id: completionId,\n object: \"chat.completion.chunk\",\n created: timestamp,\n model: \"clawrouter/debug\",\n choices: [{ index: 0, delta: {}, finish_reason: \"stop\" }],\n };\n res.write(`data: ${JSON.stringify(sseChunk)}\\n\\n`);\n res.write(`data: ${JSON.stringify(sseDone)}\\n\\n`);\n res.write(\"data: [DONE]\\n\\n\");\n res.end();\n } else {\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(syntheticResponse));\n }\n console.log(`[ClawRouter] /debug command → ${debugRouting.tier} | ${debugRouting.model}`);\n return;\n }\n\n // --- /imagegen command: generate an image via BlockRun image API ---\n if (lastContent.startsWith(\"/imagegen\")) {\n const imageArgs = lastContent.slice(\"/imagegen\".length).trim();\n\n // Parse optional flags: /imagegen --model dall-e-3 --size 1792x1024 a cute cat\n let imageModel = \"google/nano-banana\";\n let imageSize = \"1024x1024\";\n let imagePrompt = imageArgs;\n\n // Extract --model flag\n const modelMatch = imageArgs.match(/--model\\s+(\\S+)/);\n if (modelMatch) {\n const raw = modelMatch[1];\n // Resolve shorthand aliases\n const IMAGE_MODEL_ALIASES: Record = {\n \"dall-e-3\": \"openai/dall-e-3\",\n dalle3: \"openai/dall-e-3\",\n dalle: \"openai/dall-e-3\",\n \"gpt-image\": \"openai/gpt-image-1\",\n \"gpt-image-1\": \"openai/gpt-image-1\",\n flux: \"black-forest/flux-1.1-pro\",\n \"flux-pro\": \"black-forest/flux-1.1-pro\",\n banana: \"google/nano-banana\",\n \"nano-banana\": \"google/nano-banana\",\n \"banana-pro\": \"google/nano-banana-pro\",\n \"nano-banana-pro\": \"google/nano-banana-pro\",\n };\n imageModel = IMAGE_MODEL_ALIASES[raw] ?? raw;\n imagePrompt = imagePrompt.replace(/--model\\s+\\S+/, \"\").trim();\n }\n\n // Extract --size flag\n const sizeMatch = imageArgs.match(/--size\\s+(\\d+x\\d+)/);\n if (sizeMatch) {\n imageSize = sizeMatch[1];\n imagePrompt = imagePrompt.replace(/--size\\s+\\d+x\\d+/, \"\").trim();\n }\n\n if (!imagePrompt) {\n const errorText = [\n \"Usage: /imagegen \",\n \"\",\n \"Options:\",\n \" --model Model to use (default: nano-banana)\",\n \" --size Image size (default: 1024x1024)\",\n \"\",\n \"Models:\",\n \" nano-banana Google Gemini Flash — $0.05/image\",\n \" banana-pro Google Gemini Pro — $0.10/image (up to 4K)\",\n \" dall-e-3 OpenAI DALL-E 3 — $0.04/image\",\n \" gpt-image OpenAI GPT Image 1 — $0.02/image\",\n \" flux Black Forest Flux 1.1 Pro — $0.04/image\",\n \"\",\n \"Examples:\",\n \" /imagegen a cat wearing sunglasses\",\n \" /imagegen --model dall-e-3 a futuristic city at sunset\",\n \" /imagegen --model banana-pro --size 2048x2048 mountain landscape\",\n ].join(\"\\n\");\n\n const completionId = `chatcmpl-image-${Date.now()}`;\n const timestamp = Math.floor(Date.now() / 1000);\n if (isStreaming) {\n res.writeHead(200, {\n \"Content-Type\": \"text/event-stream\",\n \"Cache-Control\": \"no-cache\",\n Connection: \"keep-alive\",\n });\n res.write(\n `data: ${JSON.stringify({ id: completionId, object: \"chat.completion.chunk\", created: timestamp, model: \"clawrouter/image\", choices: [{ index: 0, delta: { role: \"assistant\", content: errorText }, finish_reason: null }] })}\\n\\n`,\n );\n res.write(\n `data: ${JSON.stringify({ id: completionId, object: \"chat.completion.chunk\", created: timestamp, model: \"clawrouter/image\", choices: [{ index: 0, delta: {}, finish_reason: \"stop\" }] })}\\n\\n`,\n );\n res.write(\"data: [DONE]\\n\\n\");\n res.end();\n } else {\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(\n JSON.stringify({\n id: completionId,\n object: \"chat.completion\",\n created: timestamp,\n model: \"clawrouter/image\",\n choices: [\n {\n index: 0,\n message: { role: \"assistant\", content: errorText },\n finish_reason: \"stop\",\n },\n ],\n usage: { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 },\n }),\n );\n }\n console.log(`[ClawRouter] /imagegen command → showing usage help`);\n return;\n }\n\n // Call upstream image generation API\n console.log(\n `[ClawRouter] /imagegen command → ${imageModel} (${imageSize}): ${imagePrompt.slice(0, 80)}...`,\n );\n try {\n const imageUpstreamUrl = `${apiBase}/v1/images/generations`;\n const imageBody = JSON.stringify({\n model: imageModel,\n prompt: imagePrompt,\n size: imageSize,\n n: 1,\n });\n const imageResponse = await payFetch(imageUpstreamUrl, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\", \"user-agent\": USER_AGENT },\n body: imageBody,\n });\n\n const imageResult = (await imageResponse.json()) as {\n created?: number;\n data?: Array<{ url?: string; revised_prompt?: string }>;\n error?: string | { message?: string };\n };\n\n let responseText: string;\n if (!imageResponse.ok || imageResult.error) {\n const errMsg =\n typeof imageResult.error === \"string\"\n ? imageResult.error\n : ((imageResult.error as { message?: string })?.message ??\n `HTTP ${imageResponse.status}`);\n responseText = `Image generation failed: ${errMsg}`;\n console.log(`[ClawRouter] /imagegen error: ${errMsg}`);\n } else {\n const images = imageResult.data ?? [];\n if (images.length === 0) {\n responseText = \"Image generation returned no results.\";\n } else {\n const lines: string[] = [];\n for (const img of images) {\n if (img.url) {\n if (img.url.startsWith(\"data:\")) {\n try {\n const hostedUrl = await uploadDataUriToHost(img.url);\n lines.push(hostedUrl);\n } catch (uploadErr) {\n console.error(\n `[ClawRouter] /imagegen: failed to upload data URI: ${uploadErr instanceof Error ? uploadErr.message : String(uploadErr)}`,\n );\n lines.push(\n \"Image generated but upload failed. Try again or use --model dall-e-3.\",\n );\n }\n } else {\n lines.push(img.url);\n }\n }\n if (img.revised_prompt) lines.push(`Revised prompt: ${img.revised_prompt}`);\n }\n lines.push(\"\", `Model: ${imageModel} | Size: ${imageSize}`);\n responseText = lines.join(\"\\n\");\n }\n console.log(`[ClawRouter] /imagegen success: ${images.length} image(s) generated`);\n }\n\n // Return as synthetic chat completion\n const completionId = `chatcmpl-image-${Date.now()}`;\n const timestamp = Math.floor(Date.now() / 1000);\n if (isStreaming) {\n res.writeHead(200, {\n \"Content-Type\": \"text/event-stream\",\n \"Cache-Control\": \"no-cache\",\n Connection: \"keep-alive\",\n });\n res.write(\n `data: ${JSON.stringify({ id: completionId, object: \"chat.completion.chunk\", created: timestamp, model: \"clawrouter/image\", choices: [{ index: 0, delta: { role: \"assistant\", content: responseText }, finish_reason: null }] })}\\n\\n`,\n );\n res.write(\n `data: ${JSON.stringify({ id: completionId, object: \"chat.completion.chunk\", created: timestamp, model: \"clawrouter/image\", choices: [{ index: 0, delta: {}, finish_reason: \"stop\" }] })}\\n\\n`,\n );\n res.write(\"data: [DONE]\\n\\n\");\n res.end();\n } else {\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(\n JSON.stringify({\n id: completionId,\n object: \"chat.completion\",\n created: timestamp,\n model: \"clawrouter/image\",\n choices: [\n {\n index: 0,\n message: { role: \"assistant\", content: responseText },\n finish_reason: \"stop\",\n },\n ],\n usage: { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 },\n }),\n );\n }\n } catch (err) {\n const errMsg = err instanceof Error ? err.message : String(err);\n console.error(`[ClawRouter] /imagegen error: ${errMsg}`);\n if (!res.headersSent) {\n res.writeHead(500, { \"Content-Type\": \"application/json\" });\n res.end(\n JSON.stringify({\n error: { message: `Image generation failed: ${errMsg}`, type: \"image_error\" },\n }),\n );\n }\n }\n return;\n }\n\n // Force stream: false — BlockRun API doesn't support streaming yet\n // ClawRouter handles SSE heartbeat simulation for upstream compatibility\n if (parsed.stream === true) {\n parsed.stream = false;\n bodyModified = true;\n }\n\n // Normalize model name for comparison (trim whitespace, lowercase)\n const normalizedModel =\n typeof parsed.model === \"string\" ? parsed.model.trim().toLowerCase() : \"\";\n\n // Resolve model aliases (e.g., \"claude\" -> \"anthropic/claude-sonnet-4-6\")\n const resolvedModel = resolveModelAlias(normalizedModel);\n const wasAlias = resolvedModel !== normalizedModel;\n\n // Check both normalizedModel and resolvedModel — OpenClaw may send \"openai/eco\"\n // which resolveModelAlias strips to \"eco\" (a valid routing profile)\n const isRoutingProfile =\n ROUTING_PROFILES.has(normalizedModel) || ROUTING_PROFILES.has(resolvedModel);\n\n // Extract routing profile type (free/eco/auto/premium)\n if (isRoutingProfile) {\n const profileName = resolvedModel.replace(\"blockrun/\", \"\");\n routingProfile = profileName as \"free\" | \"eco\" | \"auto\" | \"premium\";\n }\n\n // Debug: log received model name\n console.log(\n `[ClawRouter] Received model: \"${parsed.model}\" -> normalized: \"${normalizedModel}\"${wasAlias ? ` -> alias: \"${resolvedModel}\"` : \"\"}${routingProfile ? `, profile: ${routingProfile}` : \"\"}`,\n );\n\n // For explicit model requests, always canonicalize the model ID before upstream calls.\n // This ensures case/whitespace variants (e.g. \"DEEPSEEK/...\" or \" model \") route correctly.\n if (!isRoutingProfile) {\n if (parsed.model !== resolvedModel) {\n parsed.model = resolvedModel;\n bodyModified = true;\n }\n modelId = resolvedModel;\n }\n\n // Handle routing profiles (free/eco/auto/premium)\n if (isRoutingProfile) {\n // Free profile - direct shortcut to nvidia/gpt-oss-120b (no tier routing)\n if (routingProfile === \"free\") {\n const freeModel = \"nvidia/gpt-oss-120b\";\n console.log(`[ClawRouter] Free profile - using ${freeModel} directly`);\n parsed.model = freeModel;\n modelId = freeModel;\n bodyModified = true;\n\n // Log usage for free profile\n await logUsage({\n timestamp: new Date().toISOString(),\n model: freeModel,\n tier: \"SIMPLE\",\n cost: 0,\n baselineCost: 0,\n savings: 1.0, // 100% savings\n latencyMs: 0,\n });\n } else {\n // eco/auto/premium - use tier routing\n // Check for session persistence - use pinned model if available\n // Fall back to deriving a session ID from message content when OpenClaw\n // doesn't send an explicit x-session-id header (the default behaviour).\n effectiveSessionId =\n getSessionId(req.headers as Record) ??\n deriveSessionId(parsedMessages);\n const existingSession = effectiveSessionId\n ? sessionStore.getSession(effectiveSessionId)\n : undefined;\n\n // Extract prompt from last user message (handles both string and Anthropic array content)\n const rawPrompt = lastUserMsg?.content;\n const prompt =\n typeof rawPrompt === \"string\"\n ? rawPrompt\n : Array.isArray(rawPrompt)\n ? (rawPrompt as Array<{ type: string; text?: string }>)\n .filter((b) => b.type === \"text\")\n .map((b) => b.text ?? \"\")\n .join(\" \")\n : \"\";\n const systemMsg = parsedMessages.find((m) => m.role === \"system\");\n const systemPrompt =\n typeof systemMsg?.content === \"string\" ? systemMsg.content : undefined;\n\n // Tool detection — when tools are present, force agentic tiers for reliable tool use\n const tools = parsed.tools as unknown[] | undefined;\n hasTools = Array.isArray(tools) && tools.length > 0;\n\n if (hasTools && tools) {\n console.log(`[ClawRouter] Tools detected (${tools.length}), forcing agentic tiers`);\n }\n\n // Vision detection: scan messages for image_url content parts\n hasVision = parsedMessages.some((m) => {\n if (Array.isArray(m.content)) {\n return (m.content as Array<{ type: string }>).some((p) => p.type === \"image_url\");\n }\n return false;\n });\n if (hasVision) {\n console.log(`[ClawRouter] Vision content detected, filtering to vision-capable models`);\n }\n\n // Always route based on current request content\n routingDecision = route(prompt, systemPrompt, maxTokens, {\n ...routerOpts,\n routingProfile: routingProfile ?? undefined,\n hasTools,\n });\n\n if (existingSession) {\n // Never downgrade: only upgrade the session when the current request needs a higher\n // tier. This fixes the OpenClaw startup-message bias (the startup message always\n // scores low-complexity, which previously pinned all subsequent real queries to a\n // cheap model) while still preventing mid-task model switching on simple follow-ups.\n const tierRank: Record = {\n SIMPLE: 0,\n MEDIUM: 1,\n COMPLEX: 2,\n REASONING: 3,\n };\n const existingRank = tierRank[existingSession.tier] ?? 0;\n const newRank = tierRank[routingDecision.tier] ?? 0;\n\n if (newRank > existingRank) {\n // Current request needs higher capability — upgrade the session\n console.log(\n `[ClawRouter] Session ${effectiveSessionId?.slice(0, 8)}... upgrading: ${existingSession.tier} → ${routingDecision.tier} (${routingDecision.model})`,\n );\n parsed.model = routingDecision.model;\n modelId = routingDecision.model;\n bodyModified = true;\n if (effectiveSessionId) {\n sessionStore.setSession(\n effectiveSessionId,\n routingDecision.model,\n routingDecision.tier,\n );\n }\n } else {\n // Keep existing higher-tier model (prevent downgrade mid-task)\n console.log(\n `[ClawRouter] Session ${effectiveSessionId?.slice(0, 8)}... keeping pinned model: ${existingSession.model} (${existingSession.tier} >= ${routingDecision.tier})`,\n );\n parsed.model = existingSession.model;\n modelId = existingSession.model;\n bodyModified = true;\n sessionStore.touchSession(effectiveSessionId!);\n // Reflect the actual model used in the routing decision for logging/fallback\n routingDecision = {\n ...routingDecision,\n model: existingSession.model,\n tier: existingSession.tier as Tier,\n };\n }\n\n // --- Three-strike escalation: detect repetitive request patterns ---\n const lastAssistantMsg = [...parsedMessages]\n .reverse()\n .find((m) => m.role === \"assistant\");\n const assistantToolCalls = (\n lastAssistantMsg as { tool_calls?: Array<{ function?: { name?: string } }> }\n )?.tool_calls;\n const toolCallNames = Array.isArray(assistantToolCalls)\n ? assistantToolCalls\n .map((tc) => tc.function?.name)\n .filter((n): n is string => Boolean(n))\n : undefined;\n const contentHash = hashRequestContent(prompt, toolCallNames);\n const shouldEscalate = sessionStore.recordRequestHash(effectiveSessionId!, contentHash);\n\n if (shouldEscalate) {\n const activeTierConfigs = (() => {\n if (\n routingDecision.reasoning?.includes(\"agentic\") &&\n routerOpts.config.agenticTiers\n ) {\n return routerOpts.config.agenticTiers;\n }\n if (routingProfile === \"eco\" && routerOpts.config.ecoTiers) {\n return routerOpts.config.ecoTiers;\n }\n if (routingProfile === \"premium\" && routerOpts.config.premiumTiers) {\n return routerOpts.config.premiumTiers;\n }\n return routerOpts.config.tiers;\n })();\n\n const escalation = sessionStore.escalateSession(\n effectiveSessionId!,\n activeTierConfigs,\n );\n if (escalation) {\n console.log(\n `[ClawRouter] ⚡ 3-strike escalation: ${existingSession.model} → ${escalation.model} (${existingSession.tier} → ${escalation.tier})`,\n );\n parsed.model = escalation.model;\n modelId = escalation.model;\n routingDecision = {\n ...routingDecision,\n model: escalation.model,\n tier: escalation.tier as Tier,\n };\n }\n }\n } else {\n // No session — pin this routing decision for future requests\n parsed.model = routingDecision.model;\n modelId = routingDecision.model;\n bodyModified = true;\n if (effectiveSessionId) {\n sessionStore.setSession(\n effectiveSessionId,\n routingDecision.model,\n routingDecision.tier,\n );\n console.log(\n `[ClawRouter] Session ${effectiveSessionId.slice(0, 8)}... pinned to model: ${routingDecision.model}`,\n );\n }\n }\n\n options.onRouted?.(routingDecision);\n }\n }\n\n // Rebuild body if modified\n if (bodyModified) {\n body = Buffer.from(JSON.stringify(parsed));\n }\n } catch (err) {\n // Log routing errors so they're not silently swallowed\n const errorMsg = err instanceof Error ? err.message : String(err);\n console.error(`[ClawRouter] Routing error: ${errorMsg}`);\n console.error(`[ClawRouter] Need help? Run: npx @blockrun/clawrouter doctor`);\n options.onError?.(new Error(`Routing failed: ${errorMsg}`));\n }\n }\n\n // --- Auto-compression ---\n // Compress large requests to reduce network usage and improve performance\n const autoCompress = options.autoCompressRequests ?? true;\n const compressionThreshold = options.compressionThresholdKB ?? 180;\n const requestSizeKB = Math.ceil(body.length / 1024);\n\n if (autoCompress && requestSizeKB > compressionThreshold) {\n try {\n console.log(\n `[ClawRouter] Request size ${requestSizeKB}KB exceeds threshold ${compressionThreshold}KB, applying compression...`,\n );\n\n // Parse messages for compression\n const parsed = JSON.parse(body.toString()) as {\n messages?: NormalizedMessage[];\n [key: string]: unknown;\n };\n\n if (parsed.messages && parsed.messages.length > 0 && shouldCompress(parsed.messages)) {\n // Apply compression with conservative settings\n const compressionResult = await compressContext(parsed.messages, {\n enabled: true,\n preserveRaw: false, // Don't need originals in proxy\n layers: {\n deduplication: true, // Safe: removes duplicate messages\n whitespace: true, // Safe: normalizes whitespace\n dictionary: false, // Disabled: requires model to understand codebook\n paths: false, // Disabled: requires model to understand path codes\n jsonCompact: true, // Safe: just removes JSON whitespace\n observation: false, // Disabled: may lose important context\n dynamicCodebook: false, // Disabled: requires model to understand codes\n },\n dictionary: {\n maxEntries: 50,\n minPhraseLength: 15,\n includeCodebookHeader: false,\n },\n });\n\n const compressedSizeKB = Math.ceil(compressionResult.compressedChars / 1024);\n const savings = (((requestSizeKB - compressedSizeKB) / requestSizeKB) * 100).toFixed(1);\n\n console.log(\n `[ClawRouter] Compressed ${requestSizeKB}KB → ${compressedSizeKB}KB (${savings}% reduction)`,\n );\n\n // Update request body with compressed messages\n parsed.messages = compressionResult.messages;\n body = Buffer.from(JSON.stringify(parsed));\n }\n } catch (err) {\n // Compression failed - continue with original request\n console.warn(\n `[ClawRouter] Compression failed: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n }\n\n // --- Response cache check (long-term, 10min TTL) ---\n const cacheKey = ResponseCache.generateKey(body);\n const reqHeaders: Record = {};\n for (const [key, value] of Object.entries(req.headers)) {\n if (typeof value === \"string\") reqHeaders[key] = value;\n }\n if (responseCache.shouldCache(body, reqHeaders)) {\n const cachedResponse = responseCache.get(cacheKey);\n if (cachedResponse) {\n console.log(`[ClawRouter] Cache HIT for ${cachedResponse.model} (saved API call)`);\n res.writeHead(cachedResponse.status, cachedResponse.headers);\n res.end(cachedResponse.body);\n return;\n }\n }\n\n // --- Dedup check (short-term, 30s TTL for retries) ---\n const dedupKey = RequestDeduplicator.hash(body);\n\n // Check dedup cache (catches retries within 30s)\n const cached = deduplicator.getCached(dedupKey);\n if (cached) {\n res.writeHead(cached.status, cached.headers);\n res.end(cached.body);\n return;\n }\n\n // Check in-flight — wait for the original request to complete\n const inflight = deduplicator.getInflight(dedupKey);\n if (inflight) {\n const result = await inflight;\n res.writeHead(result.status, result.headers);\n res.end(result.body);\n return;\n }\n\n // Register this request as in-flight\n deduplicator.markInflight(dedupKey);\n\n // --- Pre-request balance check ---\n // Estimate cost and check if wallet has sufficient balance\n // Skip if skipBalanceCheck is set (for testing) or if using free model\n let estimatedCostMicros: bigint | undefined;\n let balanceFallbackNotice: string | undefined;\n const isFreeModel = modelId === FREE_MODEL;\n\n if (modelId && !options.skipBalanceCheck && !isFreeModel) {\n const estimated = estimateAmount(modelId, body.length, maxTokens);\n if (estimated) {\n estimatedCostMicros = BigInt(estimated);\n\n // Apply extra buffer for balance check to prevent x402 failures after streaming starts.\n // This is aggressive to avoid triggering OpenClaw's 5-24 hour billing cooldown.\n const bufferedCostMicros =\n (estimatedCostMicros * BigInt(Math.ceil(BALANCE_CHECK_BUFFER * 100))) / 100n;\n\n // Check balance before proceeding (using buffered amount)\n const sufficiency = await balanceMonitor.checkSufficient(bufferedCostMicros);\n\n if (sufficiency.info.isEmpty || !sufficiency.sufficient) {\n // Wallet is empty or insufficient — ALWAYS fallback to free model\n // This ensures new users with empty wallets can still use ClawRouter\n const originalModel = modelId;\n console.log(\n `[ClawRouter] Wallet ${sufficiency.info.isEmpty ? \"empty\" : \"insufficient\"} (${sufficiency.info.balanceUSD}), falling back to free model: ${FREE_MODEL} (requested: ${originalModel})`,\n );\n modelId = FREE_MODEL;\n // Update the body with new model\n const parsed = JSON.parse(body.toString()) as Record;\n parsed.model = FREE_MODEL;\n body = Buffer.from(JSON.stringify(parsed));\n\n // Set notice to prepend to response so user knows about the fallback\n balanceFallbackNotice = sufficiency.info.isEmpty\n ? `> **⚠️ Wallet empty** — using free model. Fund your wallet to use ${originalModel}.\\n\\n`\n : `> **⚠️ Insufficient balance** (${sufficiency.info.balanceUSD}) — using free model instead of ${originalModel}.\\n\\n`;\n\n // Notify about the fallback\n options.onLowBalance?.({\n balanceUSD: sufficiency.info.balanceUSD,\n walletAddress: sufficiency.info.walletAddress,\n });\n } else if (sufficiency.info.isLow) {\n // Balance is low but sufficient — warn and proceed\n options.onLowBalance?.({\n balanceUSD: sufficiency.info.balanceUSD,\n walletAddress: sufficiency.info.walletAddress,\n });\n }\n }\n }\n\n // --- Streaming: early header flush + heartbeat ---\n let heartbeatInterval: ReturnType | undefined;\n let headersSentEarly = false;\n\n if (isStreaming) {\n // Send 200 + SSE headers immediately, before x402 flow\n res.writeHead(200, {\n \"content-type\": \"text/event-stream\",\n \"cache-control\": \"no-cache\",\n connection: \"keep-alive\",\n \"x-context-used-kb\": String(originalContextSizeKB),\n \"x-context-limit-kb\": String(CONTEXT_LIMIT_KB),\n });\n headersSentEarly = true;\n\n // First heartbeat immediately\n safeWrite(res, \": heartbeat\\n\\n\");\n\n // Continue heartbeats every 2s while waiting for upstream\n heartbeatInterval = setInterval(() => {\n if (canWrite(res)) {\n safeWrite(res, \": heartbeat\\n\\n\");\n } else {\n // Socket closed, stop heartbeat\n clearInterval(heartbeatInterval);\n heartbeatInterval = undefined;\n }\n }, HEARTBEAT_INTERVAL_MS);\n }\n\n // Forward headers, stripping host, connection, and content-length\n const headers: Record = {};\n for (const [key, value] of Object.entries(req.headers)) {\n if (\n key === \"host\" ||\n key === \"connection\" ||\n key === \"transfer-encoding\" ||\n key === \"content-length\"\n )\n continue;\n if (typeof value === \"string\") {\n headers[key] = value;\n }\n }\n if (!headers[\"content-type\"]) {\n headers[\"content-type\"] = \"application/json\";\n }\n headers[\"user-agent\"] = USER_AGENT;\n\n // --- Client disconnect cleanup ---\n let completed = false;\n res.on(\"close\", () => {\n if (heartbeatInterval) {\n clearInterval(heartbeatInterval);\n heartbeatInterval = undefined;\n }\n // Remove from in-flight if client disconnected before completion\n if (!completed) {\n deduplicator.removeInflight(dedupKey);\n }\n });\n\n // --- Request timeout ---\n const timeoutMs = options.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), timeoutMs);\n\n try {\n // --- Build fallback chain ---\n // If we have a routing decision, get the full fallback chain for the tier\n // Otherwise, just use the current model (no fallback for explicit model requests)\n let modelsToTry: string[];\n if (routingDecision) {\n // Estimate total context: input tokens (~4 chars per token) + max output tokens\n const estimatedInputTokens = Math.ceil(body.length / 4);\n const estimatedTotalTokens = estimatedInputTokens + maxTokens;\n\n // Get tier configs matching the profile that was used for routing\n // Must stay in sync with what route() selected in router/index.ts\n const tierConfigs = (() => {\n if (routingDecision.reasoning?.includes(\"agentic\") && routerOpts.config.agenticTiers) {\n return routerOpts.config.agenticTiers;\n }\n if (routingProfile === \"eco\" && routerOpts.config.ecoTiers) {\n return routerOpts.config.ecoTiers;\n }\n if (routingProfile === \"premium\" && routerOpts.config.premiumTiers) {\n return routerOpts.config.premiumTiers;\n }\n return routerOpts.config.tiers;\n })();\n\n // Get full chain first, then filter by context\n const fullChain = getFallbackChain(routingDecision.tier, tierConfigs);\n const contextFiltered = getFallbackChainFiltered(\n routingDecision.tier,\n tierConfigs,\n estimatedTotalTokens,\n getModelContextWindow,\n );\n\n // Log if models were filtered out due to context limits\n const contextExcluded = fullChain.filter((m) => !contextFiltered.includes(m));\n if (contextExcluded.length > 0) {\n console.log(\n `[ClawRouter] Context filter (~${estimatedTotalTokens} tokens): excluded ${contextExcluded.join(\", \")}`,\n );\n }\n\n // Filter to models that support tool calling when request has tools.\n // Prevents models like grok-code-fast-1 from outputting tool invocations\n // as plain text JSON (the \"talking to itself\" bug).\n let toolFiltered = filterByToolCalling(contextFiltered, hasTools, supportsToolCalling);\n const toolExcluded = contextFiltered.filter((m) => !toolFiltered.includes(m));\n if (toolExcluded.length > 0) {\n console.log(\n `[ClawRouter] Tool-calling filter: excluded ${toolExcluded.join(\", \")} (no structured function call support)`,\n );\n }\n\n // Filter out models that declare toolCalling but fail tool compliance in practice.\n // gemini-2.5-flash-lite refuses certain tool schemas (e.g. brave search) while\n // cheaper models like nvidia/gpt-oss-120b handle them fine.\n const TOOL_NONCOMPLIANT_MODELS = [\n \"google/gemini-2.5-flash-lite\",\n \"google/gemini-3-pro-preview\",\n \"google/gemini-3.1-pro\",\n ];\n if (hasTools && toolFiltered.length > 1) {\n const compliant = toolFiltered.filter((m) => !TOOL_NONCOMPLIANT_MODELS.includes(m));\n if (compliant.length > 0 && compliant.length < toolFiltered.length) {\n const dropped = toolFiltered.filter((m) => TOOL_NONCOMPLIANT_MODELS.includes(m));\n console.log(\n `[ClawRouter] Tool-compliance filter: excluded ${dropped.join(\", \")} (unreliable tool schema handling)`,\n );\n toolFiltered = compliant;\n }\n }\n\n // Filter to models that support vision when request has image_url content\n const visionFiltered = filterByVision(toolFiltered, hasVision, supportsVision);\n const visionExcluded = toolFiltered.filter((m) => !visionFiltered.includes(m));\n if (visionExcluded.length > 0) {\n console.log(\n `[ClawRouter] Vision filter: excluded ${visionExcluded.join(\", \")} (no vision support)`,\n );\n }\n\n // Limit to MAX_FALLBACK_ATTEMPTS to prevent infinite loops\n modelsToTry = visionFiltered.slice(0, MAX_FALLBACK_ATTEMPTS);\n\n // Deprioritize rate-limited models (put them at the end)\n modelsToTry = prioritizeNonRateLimited(modelsToTry);\n } else {\n // For explicit model requests, use the requested model\n modelsToTry = modelId ? [modelId] : [];\n }\n\n // Always ensure free model is the last-resort fallback.\n // If all paid models fail (insufficient funds, rate limits, etc.),\n // the user still gets a response instead of an error.\n if (!modelsToTry.includes(FREE_MODEL)) {\n modelsToTry.push(FREE_MODEL);\n }\n\n // --- Fallback loop: try each model until success ---\n let upstream: Response | undefined;\n let lastError: { body: string; status: number } | undefined;\n let actualModelUsed = modelId;\n\n for (let i = 0; i < modelsToTry.length; i++) {\n const tryModel = modelsToTry[i];\n const isLastAttempt = i === modelsToTry.length - 1;\n\n console.log(`[ClawRouter] Trying model ${i + 1}/${modelsToTry.length}: ${tryModel}`);\n\n const result = await tryModelRequest(\n upstreamUrl,\n req.method ?? \"POST\",\n headers,\n body,\n tryModel,\n maxTokens,\n payFetch,\n balanceMonitor,\n controller.signal,\n );\n\n if (result.success && result.response) {\n upstream = result.response;\n actualModelUsed = tryModel;\n console.log(`[ClawRouter] Success with model: ${tryModel}`);\n break;\n }\n\n // Request failed\n lastError = {\n body: result.errorBody || \"Unknown error\",\n status: result.errorStatus || 500,\n };\n\n // If it's a provider error and not the last attempt, try next model\n if (result.isProviderError && !isLastAttempt) {\n // Track 429 rate limits to deprioritize this model for future requests\n if (result.errorStatus === 429) {\n markRateLimited(tryModel);\n // Check for server-side update hint\n try {\n const parsed = JSON.parse(result.errorBody || \"{}\");\n if (parsed.update_available) {\n console.log(\"\");\n console.log(\n `\\x1b[33m⬆️ ClawRouter ${parsed.update_available} available (you have ${VERSION})\\x1b[0m`,\n );\n console.log(\n ` Run: \\x1b[36mcurl -fsSL ${parsed.update_url || \"https://blockrun.ai/ClawRouter-update\"} | bash\\x1b[0m`,\n );\n console.log(\"\");\n }\n } catch {\n /* ignore parse errors */\n }\n }\n\n // Payment error (insufficient funds, simulation failure) — skip remaining\n // paid models, jump straight to free model. No point trying other paid\n // models with the same wallet state.\n const isPaymentErr =\n /payment.*verification.*failed|payment.*settlement.*failed|insufficient.*funds|transaction_simulation_failed/i.test(\n result.errorBody || \"\",\n );\n if (isPaymentErr && tryModel !== FREE_MODEL) {\n const freeIdx = modelsToTry.indexOf(FREE_MODEL);\n if (freeIdx > i + 1) {\n console.log(`[ClawRouter] Payment error — skipping to free model: ${FREE_MODEL}`);\n i = freeIdx - 1; // loop will increment to freeIdx\n continue;\n }\n }\n\n console.log(\n `[ClawRouter] Provider error from ${tryModel}, trying fallback: ${result.errorBody?.slice(0, 100)}`,\n );\n continue;\n }\n\n // Not a provider error or last attempt — stop trying\n if (!result.isProviderError) {\n console.log(\n `[ClawRouter] Non-provider error from ${tryModel}, not retrying: ${result.errorBody?.slice(0, 100)}`,\n );\n }\n break;\n }\n\n // Clear timeout — request attempts completed\n clearTimeout(timeoutId);\n\n // Clear heartbeat — real data is about to flow\n if (heartbeatInterval) {\n clearInterval(heartbeatInterval);\n heartbeatInterval = undefined;\n }\n\n // --- Emit routing debug info (opt-in via x-clawrouter-debug: true header) ---\n // For streaming: SSE comment (invisible to most clients, visible in raw stream)\n // For non-streaming: response headers added later\n if (debugMode && headersSentEarly && routingDecision) {\n const debugComment = `: x-clawrouter-debug profile=${routingProfile ?? \"auto\"} tier=${routingDecision.tier} model=${actualModelUsed} agentic=${routingDecision.agenticScore?.toFixed(2) ?? \"n/a\"} confidence=${routingDecision.confidence.toFixed(2)} reasoning=${routingDecision.reasoning}\\n\\n`;\n safeWrite(res, debugComment);\n }\n\n // Update routing decision with actual model used (for logging)\n // IMPORTANT: Recalculate cost for the actual model, not the original primary\n if (routingDecision && actualModelUsed !== routingDecision.model) {\n const estimatedInputTokens = Math.ceil(body.length / 4);\n const newCosts = calculateModelCost(\n actualModelUsed,\n routerOpts.modelPricing,\n estimatedInputTokens,\n maxTokens,\n routingProfile ?? undefined,\n );\n routingDecision = {\n ...routingDecision,\n model: actualModelUsed,\n reasoning: `${routingDecision.reasoning} | fallback to ${actualModelUsed}`,\n costEstimate: newCosts.costEstimate,\n baselineCost: newCosts.baselineCost,\n savings: newCosts.savings,\n };\n options.onRouted?.(routingDecision);\n\n // Update session pin to the actual model used — ensures the next request in\n // this conversation starts from the fallback model rather than retrying the\n // primary and falling back again (prevents the \"model keeps jumping\" issue).\n if (effectiveSessionId) {\n sessionStore.setSession(effectiveSessionId, actualModelUsed, routingDecision.tier);\n console.log(\n `[ClawRouter] Session ${effectiveSessionId.slice(0, 8)}... updated pin to fallback: ${actualModelUsed}`,\n );\n }\n }\n\n // --- Handle case where all models failed ---\n if (!upstream) {\n const rawErrBody = lastError?.body || \"All models in fallback chain failed\";\n const errStatus = lastError?.status || 502;\n\n // Transform payment errors into user-friendly messages\n const transformedErr = transformPaymentError(rawErrBody);\n\n if (headersSentEarly) {\n // Streaming: send error as SSE event\n // If transformed error is already JSON, parse and use it; otherwise wrap in standard format\n let errPayload: string;\n try {\n const parsed = JSON.parse(transformedErr);\n errPayload = JSON.stringify(parsed);\n } catch {\n errPayload = JSON.stringify({\n error: { message: rawErrBody, type: \"provider_error\", status: errStatus },\n });\n }\n const errEvent = `data: ${errPayload}\\n\\n`;\n safeWrite(res, errEvent);\n safeWrite(res, \"data: [DONE]\\n\\n\");\n res.end();\n\n const errBuf = Buffer.from(errEvent + \"data: [DONE]\\n\\n\");\n deduplicator.complete(dedupKey, {\n status: 200,\n headers: { \"content-type\": \"text/event-stream\" },\n body: errBuf,\n completedAt: Date.now(),\n });\n } else {\n // Non-streaming: send transformed error response with context headers\n res.writeHead(errStatus, {\n \"Content-Type\": \"application/json\",\n \"x-context-used-kb\": String(originalContextSizeKB),\n \"x-context-limit-kb\": String(CONTEXT_LIMIT_KB),\n });\n res.end(transformedErr);\n\n deduplicator.complete(dedupKey, {\n status: errStatus,\n headers: { \"content-type\": \"application/json\" },\n body: Buffer.from(transformedErr),\n completedAt: Date.now(),\n });\n }\n return;\n }\n\n // --- Stream response and collect for dedup cache ---\n const responseChunks: Buffer[] = [];\n\n if (headersSentEarly) {\n // Streaming: headers already sent. Response should be 200 at this point\n // (non-200 responses are handled in the fallback loop above)\n\n // Convert non-streaming JSON response to SSE streaming format for client\n // (BlockRun API returns JSON since we forced stream:false)\n // OpenClaw expects: object=\"chat.completion.chunk\" with choices[].delta (not message)\n // We emit proper incremental deltas to match OpenAI's streaming format exactly\n if (upstream.body) {\n const chunks = await readBodyWithTimeout(upstream.body);\n\n // Combine chunks and transform to streaming format\n const jsonBody = Buffer.concat(chunks);\n const jsonStr = jsonBody.toString();\n try {\n const rsp = JSON.parse(jsonStr) as {\n id?: string;\n object?: string;\n created?: number;\n model?: string;\n choices?: Array<{\n index?: number;\n message?: {\n role?: string;\n content?: string;\n tool_calls?: Array<{\n id: string;\n type: string;\n function: { name: string; arguments: string };\n }>;\n };\n delta?: {\n role?: string;\n content?: string;\n tool_calls?: Array<{\n id: string;\n type: string;\n function: { name: string; arguments: string };\n }>;\n };\n finish_reason?: string | null;\n }>;\n usage?: unknown;\n };\n\n // Extract input token count from upstream response\n if (rsp.usage && typeof rsp.usage === \"object\") {\n const u = rsp.usage as Record;\n if (typeof u.prompt_tokens === \"number\") responseInputTokens = u.prompt_tokens;\n }\n\n // Build base chunk structure (reused for all chunks)\n // Match OpenAI's exact format including system_fingerprint\n const baseChunk = {\n id: rsp.id ?? `chatcmpl-${Date.now()}`,\n object: \"chat.completion.chunk\",\n created: rsp.created ?? Math.floor(Date.now() / 1000),\n model: rsp.model ?? \"unknown\",\n system_fingerprint: null,\n };\n\n // Process each choice (usually just one)\n if (rsp.choices && Array.isArray(rsp.choices)) {\n for (const choice of rsp.choices) {\n // Strip thinking tokens (Kimi <|...|> and standard tags)\n const rawContent = choice.message?.content ?? choice.delta?.content ?? \"\";\n const content = stripThinkingTokens(rawContent);\n const role = choice.message?.role ?? choice.delta?.role ?? \"assistant\";\n const index = choice.index ?? 0;\n\n // Accumulate content for session journal\n if (content) {\n accumulatedContent += content;\n }\n\n // Chunk 1: role only (mimics OpenAI's first chunk)\n const roleChunk = {\n ...baseChunk,\n choices: [{ index, delta: { role }, logprobs: null, finish_reason: null }],\n };\n const roleData = `data: ${JSON.stringify(roleChunk)}\\n\\n`;\n safeWrite(res, roleData);\n responseChunks.push(Buffer.from(roleData));\n\n // Chunk 1.5: balance fallback notice (tells user they got free model)\n if (balanceFallbackNotice) {\n const noticeChunk = {\n ...baseChunk,\n choices: [\n {\n index,\n delta: { content: balanceFallbackNotice },\n logprobs: null,\n finish_reason: null,\n },\n ],\n };\n const noticeData = `data: ${JSON.stringify(noticeChunk)}\\n\\n`;\n safeWrite(res, noticeData);\n responseChunks.push(Buffer.from(noticeData));\n balanceFallbackNotice = undefined; // Only inject once\n }\n\n // Chunk 2: content (single chunk with full content)\n if (content) {\n const contentChunk = {\n ...baseChunk,\n choices: [{ index, delta: { content }, logprobs: null, finish_reason: null }],\n };\n const contentData = `data: ${JSON.stringify(contentChunk)}\\n\\n`;\n safeWrite(res, contentData);\n responseChunks.push(Buffer.from(contentData));\n }\n\n // Chunk 2b: tool_calls (forward tool calls from upstream)\n const toolCalls = choice.message?.tool_calls ?? choice.delta?.tool_calls;\n if (toolCalls && toolCalls.length > 0) {\n const toolCallChunk = {\n ...baseChunk,\n choices: [\n {\n index,\n delta: { tool_calls: toolCalls },\n logprobs: null,\n finish_reason: null,\n },\n ],\n };\n const toolCallData = `data: ${JSON.stringify(toolCallChunk)}\\n\\n`;\n safeWrite(res, toolCallData);\n responseChunks.push(Buffer.from(toolCallData));\n }\n\n // Chunk 3: finish_reason (signals completion)\n const finishChunk = {\n ...baseChunk,\n choices: [\n {\n index,\n delta: {},\n logprobs: null,\n finish_reason:\n toolCalls && toolCalls.length > 0\n ? \"tool_calls\"\n : (choice.finish_reason ?? \"stop\"),\n },\n ],\n };\n const finishData = `data: ${JSON.stringify(finishChunk)}\\n\\n`;\n safeWrite(res, finishData);\n responseChunks.push(Buffer.from(finishData));\n }\n }\n } catch {\n // If parsing fails, send raw response as single chunk\n const sseData = `data: ${jsonStr}\\n\\n`;\n safeWrite(res, sseData);\n responseChunks.push(Buffer.from(sseData));\n }\n }\n\n // Send SSE terminator\n safeWrite(res, \"data: [DONE]\\n\\n\");\n responseChunks.push(Buffer.from(\"data: [DONE]\\n\\n\"));\n res.end();\n\n // Cache for dedup\n deduplicator.complete(dedupKey, {\n status: 200,\n headers: { \"content-type\": \"text/event-stream\" },\n body: Buffer.concat(responseChunks),\n completedAt: Date.now(),\n });\n } else {\n // Non-streaming: forward status and headers from upstream\n const responseHeaders: Record = {};\n upstream.headers.forEach((value, key) => {\n // Skip hop-by-hop headers and content-encoding (fetch already decompresses)\n if (key === \"transfer-encoding\" || key === \"connection\" || key === \"content-encoding\")\n return;\n responseHeaders[key] = value;\n });\n\n // Add context usage headers\n responseHeaders[\"x-context-used-kb\"] = String(originalContextSizeKB);\n responseHeaders[\"x-context-limit-kb\"] = String(CONTEXT_LIMIT_KB);\n\n // Add routing debug headers (opt-in via x-clawrouter-debug: true header)\n if (debugMode && routingDecision) {\n responseHeaders[\"x-clawrouter-profile\"] = routingProfile ?? \"auto\";\n responseHeaders[\"x-clawrouter-tier\"] = routingDecision.tier;\n responseHeaders[\"x-clawrouter-model\"] = actualModelUsed;\n responseHeaders[\"x-clawrouter-confidence\"] = routingDecision.confidence.toFixed(2);\n responseHeaders[\"x-clawrouter-reasoning\"] = routingDecision.reasoning;\n if (routingDecision.agenticScore !== undefined) {\n responseHeaders[\"x-clawrouter-agentic-score\"] = routingDecision.agenticScore.toFixed(2);\n }\n }\n\n // Collect full body for possible notice injection\n const bodyParts: Buffer[] = [];\n if (upstream.body) {\n const chunks = await readBodyWithTimeout(upstream.body);\n for (const chunk of chunks) {\n bodyParts.push(Buffer.from(chunk));\n }\n }\n\n let responseBody = Buffer.concat(bodyParts);\n\n // Prepend balance fallback notice to response content\n if (balanceFallbackNotice && responseBody.length > 0) {\n try {\n const parsed = JSON.parse(responseBody.toString()) as {\n choices?: Array<{ message?: { content?: string } }>;\n };\n if (parsed.choices?.[0]?.message?.content !== undefined) {\n parsed.choices[0].message.content =\n balanceFallbackNotice + parsed.choices[0].message.content;\n responseBody = Buffer.from(JSON.stringify(parsed));\n }\n } catch {\n /* not JSON, skip notice */\n }\n balanceFallbackNotice = undefined;\n }\n\n // Update content-length header since body may have changed\n responseHeaders[\"content-length\"] = String(responseBody.length);\n res.writeHead(upstream.status, responseHeaders);\n safeWrite(res, responseBody);\n responseChunks.push(responseBody);\n res.end();\n\n // Cache for dedup (short-term, 30s)\n deduplicator.complete(dedupKey, {\n status: upstream.status,\n headers: responseHeaders,\n body: responseBody,\n completedAt: Date.now(),\n });\n\n // Cache for response cache (long-term, 10min) - only successful non-streaming\n if (upstream.status === 200 && responseCache.shouldCache(body)) {\n responseCache.set(cacheKey, {\n body: responseBody,\n status: upstream.status,\n headers: responseHeaders,\n model: actualModelUsed,\n });\n console.log(\n `[ClawRouter] Cached response for ${actualModelUsed} (${responseBody.length} bytes)`,\n );\n }\n\n // Extract content and token usage from non-streaming response\n try {\n const rspJson = JSON.parse(responseBody.toString()) as {\n choices?: Array<{ message?: { content?: string } }>;\n usage?: Record;\n };\n if (rspJson.choices?.[0]?.message?.content) {\n accumulatedContent = rspJson.choices[0].message.content;\n }\n if (rspJson.usage && typeof rspJson.usage === \"object\") {\n if (typeof rspJson.usage.prompt_tokens === \"number\")\n responseInputTokens = rspJson.usage.prompt_tokens;\n }\n } catch {\n // Ignore parse errors - journal just won't have content for this response\n }\n }\n\n // --- Session Journal: Extract and record events from response ---\n if (sessionId && accumulatedContent) {\n const events = sessionJournal.extractEvents(accumulatedContent);\n if (events.length > 0) {\n sessionJournal.record(sessionId, events, actualModelUsed);\n console.log(\n `[ClawRouter] Recorded ${events.length} events to session journal for session ${sessionId.slice(0, 8)}...`,\n );\n }\n }\n\n // --- Optimistic balance deduction after successful response ---\n if (estimatedCostMicros !== undefined) {\n balanceMonitor.deductEstimated(estimatedCostMicros);\n }\n\n // Mark request as completed (for client disconnect cleanup)\n completed = true;\n } catch (err) {\n // Clear timeout on error\n clearTimeout(timeoutId);\n\n // Clear heartbeat on error\n if (heartbeatInterval) {\n clearInterval(heartbeatInterval);\n heartbeatInterval = undefined;\n }\n\n // Remove in-flight entry so retries aren't blocked\n deduplicator.removeInflight(dedupKey);\n\n // Invalidate balance cache on payment failure (might be out of date)\n balanceMonitor.invalidate();\n\n // Convert abort error to more descriptive timeout error\n if (err instanceof Error && err.name === \"AbortError\") {\n throw new Error(`Request timed out after ${timeoutMs}ms`, { cause: err });\n }\n\n throw err;\n }\n\n // --- Usage logging (fire-and-forget) ---\n // Note: Recalculate cost using full body length (not just system+user message)\n // and apply 20% buffer to match actual x402 payment (see estimateAmount())\n // Log ALL requests: both auto-routed (routingDecision set) and direct model picks\n const logModel = routingDecision?.model ?? modelId;\n if (logModel) {\n // Use full body length for accurate cost (matches x402 payment estimation)\n const estimatedInputTokens = Math.ceil(body.length / 4);\n const accurateCosts = calculateModelCost(\n logModel,\n routerOpts.modelPricing,\n estimatedInputTokens,\n maxTokens,\n routingProfile ?? undefined,\n );\n // Apply 20% buffer for cost estimation accuracy\n const costWithBuffer = accurateCosts.costEstimate * 1.2;\n const baselineWithBuffer = accurateCosts.baselineCost * 1.2;\n const entry: UsageEntry = {\n timestamp: new Date().toISOString(),\n model: logModel,\n tier: routingDecision?.tier ?? \"DIRECT\",\n cost: costWithBuffer,\n baselineCost: baselineWithBuffer,\n savings: accurateCosts.savings,\n latencyMs: Date.now() - startTime,\n ...(responseInputTokens !== undefined && { inputTokens: responseInputTokens }),\n };\n logUsage(entry).catch(() => {});\n }\n}\n","/**\n * Payment Pre-Auth Cache\n *\n * Wraps the @x402/fetch SDK with pre-authorization caching.\n * After the first 402 response, caches payment requirements per endpoint.\n * On subsequent requests, pre-signs payment and attaches it to the first\n * request, skipping the 402 round trip (~200ms savings per request).\n *\n * Falls back to normal 402 flow if pre-signed payment is rejected.\n */\n\nimport type { x402Client } from \"@x402/fetch\";\nimport { x402HTTPClient } from \"@x402/fetch\";\n\ntype PaymentRequired = Parameters[\"createPaymentPayload\"]>[0];\n\ninterface CachedEntry {\n paymentRequired: PaymentRequired;\n cachedAt: number;\n}\n\nconst DEFAULT_TTL_MS = 3_600_000; // 1 hour\n\ntype FetchFn = (input: RequestInfo | URL, init?: RequestInit) => Promise;\n\nexport function createPayFetchWithPreAuth(\n baseFetch: FetchFn,\n client: x402Client,\n ttlMs = DEFAULT_TTL_MS,\n options?: { skipPreAuth?: boolean },\n): FetchFn {\n const httpClient = new x402HTTPClient(client);\n const cache = new Map();\n\n return async (input: RequestInfo | URL, init?: RequestInit): Promise => {\n const request = new Request(input, init);\n const urlPath = new URL(request.url).pathname;\n\n // Try pre-auth if we have cached payment requirements\n // Skip for Solana: payments use per-tx blockhashes that expire ~60-90s,\n // making cached requirements useless and causing double charges.\n const cached = !options?.skipPreAuth ? cache.get(urlPath) : undefined;\n if (cached && Date.now() - cached.cachedAt < ttlMs) {\n try {\n const payload = await client.createPaymentPayload(cached.paymentRequired);\n const headers = httpClient.encodePaymentSignatureHeader(payload);\n const preAuthRequest = request.clone();\n for (const [key, value] of Object.entries(headers)) {\n preAuthRequest.headers.set(key, value);\n }\n const response = await baseFetch(preAuthRequest);\n if (response.status !== 402) {\n return response; // Pre-auth worked — saved ~200ms\n }\n // Pre-auth rejected (params may have changed) — invalidate and fall through\n cache.delete(urlPath);\n } catch {\n // Pre-auth signing failed — invalidate and fall through\n cache.delete(urlPath);\n }\n }\n\n // Normal flow: make request, handle 402 if needed\n const clonedRequest = request.clone();\n const response = await baseFetch(request);\n if (response.status !== 402) {\n return response;\n }\n\n // Parse 402 response and cache for future pre-auth\n let paymentRequired: PaymentRequired;\n try {\n const getHeader = (name: string) => response.headers.get(name);\n let body: unknown;\n try {\n const responseText = await Promise.race([\n response.text(),\n new Promise((_, reject) =>\n setTimeout(() => reject(new Error(\"Body read timeout\")), 30_000),\n ),\n ]);\n if (responseText) body = JSON.parse(responseText);\n } catch {\n /* empty body is fine */\n }\n paymentRequired = httpClient.getPaymentRequiredResponse(getHeader, body);\n cache.set(urlPath, { paymentRequired, cachedAt: Date.now() });\n } catch (error) {\n throw new Error(\n `Failed to parse payment requirements: ${error instanceof Error ? error.message : \"Unknown error\"}`,\n { cause: error },\n );\n }\n\n // Sign payment and retry\n const payload = await client.createPaymentPayload(paymentRequired);\n const paymentHeaders = httpClient.encodePaymentSignatureHeader(payload);\n for (const [key, value] of Object.entries(paymentHeaders)) {\n clonedRequest.headers.set(key, value);\n }\n return baseFetch(clonedRequest);\n };\n}\n","/**\n * Usage Logger\n *\n * Logs every LLM request as a JSON line to a daily log file.\n * Files: ~/.openclaw/blockrun/logs/usage-YYYY-MM-DD.jsonl\n *\n * MVP: append-only JSON lines. No rotation, no cleanup.\n * Logging never breaks the request flow — all errors are swallowed.\n */\n\nimport { appendFile, mkdir } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { homedir } from \"node:os\";\n\nexport type UsageEntry = {\n timestamp: string;\n model: string;\n tier: string;\n cost: number;\n baselineCost: number;\n savings: number; // 0-1 percentage\n latencyMs: number;\n /** Input (prompt) tokens reported by the provider */\n inputTokens?: number;\n /** Partner service ID (e.g., \"x_users_lookup\") — only set for partner API calls */\n partnerId?: string;\n /** Partner service name (e.g., \"AttentionVC\") — only set for partner API calls */\n service?: string;\n};\n\nconst LOG_DIR = join(homedir(), \".openclaw\", \"blockrun\", \"logs\");\nlet dirReady = false;\n\nasync function ensureDir(): Promise {\n if (dirReady) return;\n await mkdir(LOG_DIR, { recursive: true });\n dirReady = true;\n}\n\n/**\n * Log a usage entry as a JSON line.\n */\nexport async function logUsage(entry: UsageEntry): Promise {\n try {\n await ensureDir();\n const date = entry.timestamp.slice(0, 10); // YYYY-MM-DD\n const file = join(LOG_DIR, `usage-${date}.jsonl`);\n await appendFile(file, JSON.stringify(entry) + \"\\n\");\n } catch {\n // Never break the request flow\n }\n}\n","/**\n * Usage Statistics Aggregator\n *\n * Reads usage log files and aggregates statistics for terminal display.\n * Supports filtering by date range and provides multiple aggregation views.\n */\n\nimport { readdir, unlink } from \"node:fs/promises\";\nimport { readTextFile } from \"./fs-read.js\";\nimport { join } from \"node:path\";\nimport { homedir } from \"node:os\";\nimport type { UsageEntry } from \"./logger.js\";\nimport { VERSION } from \"./version.js\";\n\nconst LOG_DIR = join(homedir(), \".openclaw\", \"blockrun\", \"logs\");\n\nexport type DailyStats = {\n date: string;\n totalRequests: number;\n totalCost: number;\n totalBaselineCost: number;\n totalSavings: number;\n avgLatencyMs: number;\n byTier: Record;\n byModel: Record;\n};\n\nexport type AggregatedStats = {\n period: string;\n totalRequests: number;\n totalCost: number;\n totalBaselineCost: number;\n totalSavings: number;\n savingsPercentage: number;\n avgLatencyMs: number;\n avgCostPerRequest: number;\n byTier: Record;\n byModel: Record;\n dailyBreakdown: DailyStats[];\n entriesWithBaseline: number; // Entries with valid baseline tracking\n};\n\n/**\n * Parse a JSONL log file into usage entries.\n * Handles both old format (without tier/baselineCost) and new format.\n */\nasync function parseLogFile(filePath: string): Promise {\n try {\n const content = await readTextFile(filePath);\n const lines = content.trim().split(\"\\n\").filter(Boolean);\n const entries: UsageEntry[] = [];\n for (const line of lines) {\n try {\n const entry = JSON.parse(line) as Partial;\n entries.push({\n timestamp: entry.timestamp || new Date().toISOString(),\n model: entry.model || \"unknown\",\n tier: entry.tier || \"UNKNOWN\",\n cost: entry.cost || 0,\n baselineCost: entry.baselineCost || entry.cost || 0,\n savings: entry.savings || 0,\n latencyMs: entry.latencyMs || 0,\n });\n } catch {\n // Skip malformed lines, keep valid ones\n }\n }\n return entries;\n } catch {\n return [];\n }\n}\n\n/**\n * Get list of available log files sorted by date (newest first).\n */\nasync function getLogFiles(): Promise {\n try {\n const files = await readdir(LOG_DIR);\n return files\n .filter((f) => f.startsWith(\"usage-\") && f.endsWith(\".jsonl\"))\n .sort()\n .reverse();\n } catch {\n return [];\n }\n}\n\n/**\n * Aggregate stats for a single day.\n */\nfunction aggregateDay(date: string, entries: UsageEntry[]): DailyStats {\n const byTier: Record = {};\n const byModel: Record = {};\n let totalLatency = 0;\n\n for (const entry of entries) {\n // By tier\n if (!byTier[entry.tier]) byTier[entry.tier] = { count: 0, cost: 0 };\n byTier[entry.tier].count++;\n byTier[entry.tier].cost += entry.cost;\n\n // By model\n if (!byModel[entry.model]) byModel[entry.model] = { count: 0, cost: 0 };\n byModel[entry.model].count++;\n byModel[entry.model].cost += entry.cost;\n\n totalLatency += entry.latencyMs;\n }\n\n const totalCost = entries.reduce((sum, e) => sum + e.cost, 0);\n const totalBaselineCost = entries.reduce((sum, e) => sum + e.baselineCost, 0);\n\n return {\n date,\n totalRequests: entries.length,\n totalCost,\n totalBaselineCost,\n totalSavings: totalBaselineCost - totalCost,\n avgLatencyMs: entries.length > 0 ? totalLatency / entries.length : 0,\n byTier,\n byModel,\n };\n}\n\n/**\n * Get aggregated statistics for the last N days.\n */\nexport async function getStats(days: number = 7): Promise {\n const logFiles = await getLogFiles();\n const filesToRead = logFiles.slice(0, days);\n\n const dailyBreakdown: DailyStats[] = [];\n const allByTier: Record = {};\n const allByModel: Record = {};\n let totalRequests = 0;\n let totalCost = 0;\n let totalBaselineCost = 0;\n let totalLatency = 0;\n\n for (const file of filesToRead) {\n const date = file.replace(\"usage-\", \"\").replace(\".jsonl\", \"\");\n const filePath = join(LOG_DIR, file);\n const entries = await parseLogFile(filePath);\n\n if (entries.length === 0) continue;\n\n const dayStats = aggregateDay(date, entries);\n dailyBreakdown.push(dayStats);\n\n totalRequests += dayStats.totalRequests;\n totalCost += dayStats.totalCost;\n totalBaselineCost += dayStats.totalBaselineCost;\n totalLatency += dayStats.avgLatencyMs * dayStats.totalRequests;\n\n // Merge tier stats\n for (const [tier, stats] of Object.entries(dayStats.byTier)) {\n if (!allByTier[tier]) allByTier[tier] = { count: 0, cost: 0 };\n allByTier[tier].count += stats.count;\n allByTier[tier].cost += stats.cost;\n }\n\n // Merge model stats\n for (const [model, stats] of Object.entries(dayStats.byModel)) {\n if (!allByModel[model]) allByModel[model] = { count: 0, cost: 0 };\n allByModel[model].count += stats.count;\n allByModel[model].cost += stats.cost;\n }\n }\n\n // Calculate percentages\n const byTierWithPercentage: Record =\n {};\n for (const [tier, stats] of Object.entries(allByTier)) {\n byTierWithPercentage[tier] = {\n ...stats,\n percentage: totalRequests > 0 ? (stats.count / totalRequests) * 100 : 0,\n };\n }\n\n const byModelWithPercentage: Record =\n {};\n for (const [model, stats] of Object.entries(allByModel)) {\n byModelWithPercentage[model] = {\n ...stats,\n percentage: totalRequests > 0 ? (stats.count / totalRequests) * 100 : 0,\n };\n }\n\n const totalSavings = totalBaselineCost - totalCost;\n const savingsPercentage = totalBaselineCost > 0 ? (totalSavings / totalBaselineCost) * 100 : 0;\n\n // Count entries with valid baseline tracking (baseline != cost means tracking was active)\n let entriesWithBaseline = 0;\n for (const day of dailyBreakdown) {\n if (day.totalBaselineCost !== day.totalCost) {\n entriesWithBaseline += day.totalRequests;\n }\n }\n\n return {\n period: days === 1 ? \"today\" : `last ${days} days`,\n totalRequests,\n totalCost,\n totalBaselineCost,\n totalSavings,\n savingsPercentage,\n avgLatencyMs: totalRequests > 0 ? totalLatency / totalRequests : 0,\n avgCostPerRequest: totalRequests > 0 ? totalCost / totalRequests : 0,\n byTier: byTierWithPercentage,\n byModel: byModelWithPercentage,\n dailyBreakdown: dailyBreakdown.reverse(), // Oldest first for charts\n entriesWithBaseline, // How many entries have valid baseline tracking\n };\n}\n\n/**\n * Format stats as ASCII table for terminal display.\n */\nexport function formatStatsAscii(stats: AggregatedStats): string {\n const lines: string[] = [];\n\n // Header\n lines.push(\"╔════════════════════════════════════════════════════════════╗\");\n lines.push(`║ ClawRouter by BlockRun v${VERSION}`.padEnd(61) + \"║\");\n lines.push(\"║ Usage Statistics ║\");\n lines.push(\"╠════════════════════════════════════════════════════════════╣\");\n\n // Summary\n lines.push(`║ Period: ${stats.period.padEnd(49)}║`);\n lines.push(`║ Total Requests: ${stats.totalRequests.toString().padEnd(41)}║`);\n lines.push(`║ Total Cost: $${stats.totalCost.toFixed(4).padEnd(43)}║`);\n lines.push(`║ Baseline Cost (Opus 4.5): $${stats.totalBaselineCost.toFixed(4).padEnd(30)}║`);\n\n // Show savings with note if some entries lack baseline tracking\n const savingsLine = `║ 💰 Total Saved: $${stats.totalSavings.toFixed(4)} (${stats.savingsPercentage.toFixed(1)}%)`;\n if (stats.entriesWithBaseline < stats.totalRequests && stats.entriesWithBaseline > 0) {\n lines.push(savingsLine.padEnd(61) + \"║\");\n const note = `║ (based on ${stats.entriesWithBaseline}/${stats.totalRequests} tracked requests)`;\n lines.push(note.padEnd(61) + \"║\");\n } else {\n lines.push(savingsLine.padEnd(61) + \"║\");\n }\n lines.push(`║ Avg Latency: ${stats.avgLatencyMs.toFixed(0)}ms`.padEnd(61) + \"║\");\n\n // Tier breakdown\n lines.push(\"╠════════════════════════════════════════════════════════════╣\");\n lines.push(\"║ Routing by Tier: ║\");\n\n // Show all tiers found in data, ordered by known tiers first then others\n const knownTiers = [\"SIMPLE\", \"MEDIUM\", \"COMPLEX\", \"REASONING\", \"DIRECT\"];\n const allTiers = Object.keys(stats.byTier);\n const otherTiers = allTiers.filter((t) => !knownTiers.includes(t));\n const tierOrder = [...knownTiers.filter((t) => stats.byTier[t]), ...otherTiers];\n\n for (const tier of tierOrder) {\n const data = stats.byTier[tier];\n if (data) {\n const bar = \"█\".repeat(Math.min(20, Math.round(data.percentage / 5)));\n const displayTier = tier === \"UNKNOWN\" ? \"OTHER\" : tier;\n const line = `║ ${displayTier.padEnd(10)} ${bar.padEnd(20)} ${data.percentage.toFixed(1).padStart(5)}% (${data.count})`;\n lines.push(line.padEnd(61) + \"║\");\n }\n }\n\n // Top models\n lines.push(\"╠════════════════════════════════════════════════════════════╣\");\n lines.push(\"║ Top Models: ║\");\n\n const sortedModels = Object.entries(stats.byModel)\n .sort((a, b) => b[1].count - a[1].count)\n .slice(0, 5);\n\n for (const [model, data] of sortedModels) {\n const shortModel = model.length > 25 ? model.slice(0, 22) + \"...\" : model;\n const line = `║ ${shortModel.padEnd(25)} ${data.count.toString().padStart(5)} reqs $${data.cost.toFixed(4)}`;\n lines.push(line.padEnd(61) + \"║\");\n }\n\n // Daily breakdown (last 7 days)\n if (stats.dailyBreakdown.length > 0) {\n lines.push(\"╠════════════════════════════════════════════════════════════╣\");\n lines.push(\"║ Daily Breakdown: ║\");\n lines.push(\"║ Date Requests Cost Saved ║\");\n\n for (const day of stats.dailyBreakdown.slice(-7)) {\n const saved = day.totalBaselineCost - day.totalCost;\n const line = `║ ${day.date} ${day.totalRequests.toString().padStart(6)} $${day.totalCost.toFixed(4).padStart(8)} $${saved.toFixed(4)}`;\n lines.push(line.padEnd(61) + \"║\");\n }\n }\n\n lines.push(\"╚════════════════════════════════════════════════════════════╝\");\n\n return lines.join(\"\\n\");\n}\n\n/**\n * Delete all usage log files, resetting stats to zero.\n */\nexport async function clearStats(): Promise<{ deletedFiles: number }> {\n try {\n const files = await readdir(LOG_DIR);\n const logFiles = files.filter((f) => f.startsWith(\"usage-\") && f.endsWith(\".jsonl\"));\n\n await Promise.all(logFiles.map((f) => unlink(join(LOG_DIR, f))));\n\n return { deletedFiles: logFiles.length };\n } catch {\n return { deletedFiles: 0 };\n }\n}\n","/**\n * Scanner-safe file reading utilities.\n *\n * Uses open() + read() to avoid false positives from openclaw's\n * potential-exfiltration heuristic in bundled output.\n */\n\nimport { open } from \"node:fs/promises\";\nimport { openSync, readSync, closeSync, fstatSync } from \"node:fs\";\n\n/** Read file contents as UTF-8 string (async). */\nexport async function readTextFile(filePath: string): Promise {\n const fh = await open(filePath, \"r\");\n try {\n const size = (await fh.stat()).size;\n const buf = Buffer.alloc(size);\n let offset = 0;\n while (offset < size) {\n const { bytesRead } = await fh.read(buf, offset, size - offset, offset);\n if (bytesRead === 0) break;\n offset += bytesRead;\n }\n return buf.subarray(0, offset).toString(\"utf-8\");\n } finally {\n await fh.close();\n }\n}\n\n/** Read file contents as UTF-8 string (sync). */\nexport function readTextFileSync(filePath: string): string {\n const fd = openSync(filePath, \"r\");\n try {\n const size = fstatSync(fd).size;\n const buf = Buffer.alloc(size);\n let offset = 0;\n while (offset < size) {\n const bytesRead = readSync(fd, buf, offset, size - offset, offset);\n if (bytesRead === 0) break;\n offset += bytesRead;\n }\n return buf.subarray(0, offset).toString(\"utf-8\");\n } finally {\n closeSync(fd);\n }\n}\n","/**\n * Single source of truth for version.\n * Reads from package.json at build time via tsup's define.\n */\nimport { createRequire } from \"node:module\";\nimport { fileURLToPath } from \"node:url\";\nimport { dirname, join } from \"node:path\";\n\n// Read package.json at runtime\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = dirname(__filename);\n\n// In dist/, go up one level to find package.json\nconst require = createRequire(import.meta.url);\nconst pkg = require(join(__dirname, \"..\", \"package.json\")) as { version: string };\n\nexport const VERSION = pkg.version;\nexport const USER_AGENT = `clawrouter/${VERSION}`;\n","/**\n * Request Deduplication\n *\n * Prevents double-charging when OpenClaw retries a request after timeout.\n * Tracks in-flight requests and caches completed responses for a short TTL.\n */\n\nimport { createHash } from \"node:crypto\";\n\nexport type CachedResponse = {\n status: number;\n headers: Record;\n body: Buffer;\n completedAt: number;\n};\n\ntype InflightEntry = {\n resolvers: Array<(result: CachedResponse) => void>;\n};\n\nconst DEFAULT_TTL_MS = 30_000; // 30 seconds\nconst MAX_BODY_SIZE = 1_048_576; // 1MB\n\n/**\n * Canonicalize JSON by sorting object keys recursively.\n * Ensures identical logical content produces identical string regardless of field order.\n */\nfunction canonicalize(obj: unknown): unknown {\n if (obj === null || typeof obj !== \"object\") {\n return obj;\n }\n if (Array.isArray(obj)) {\n return obj.map(canonicalize);\n }\n const sorted: Record = {};\n for (const key of Object.keys(obj).sort()) {\n sorted[key] = canonicalize((obj as Record)[key]);\n }\n return sorted;\n}\n\n/**\n * Strip OpenClaw-injected timestamps from message content.\n * Format: [DAY YYYY-MM-DD HH:MM TZ] at the start of messages.\n * Example: [SUN 2026-02-07 13:30 PST] Hello world\n *\n * This ensures requests with different timestamps but same content hash identically.\n */\nconst TIMESTAMP_PATTERN = /^\\[\\w{3}\\s+\\d{4}-\\d{2}-\\d{2}\\s+\\d{2}:\\d{2}\\s+\\w+\\]\\s*/;\n\nfunction stripTimestamps(obj: unknown): unknown {\n if (obj === null || typeof obj !== \"object\") {\n return obj;\n }\n if (Array.isArray(obj)) {\n return obj.map(stripTimestamps);\n }\n const result: Record = {};\n for (const [key, value] of Object.entries(obj as Record)) {\n if (key === \"content\" && typeof value === \"string\") {\n // Strip timestamp prefix from message content\n result[key] = value.replace(TIMESTAMP_PATTERN, \"\");\n } else {\n result[key] = stripTimestamps(value);\n }\n }\n return result;\n}\n\nexport class RequestDeduplicator {\n private inflight = new Map();\n private completed = new Map();\n private ttlMs: number;\n\n constructor(ttlMs = DEFAULT_TTL_MS) {\n this.ttlMs = ttlMs;\n }\n\n /** Hash request body to create a dedup key. */\n static hash(body: Buffer): string {\n // Canonicalize JSON to ensure consistent hashing regardless of field order.\n // Also strip OpenClaw-injected timestamps so retries with different timestamps\n // still match the same dedup key.\n let content = body;\n try {\n const parsed = JSON.parse(body.toString());\n const stripped = stripTimestamps(parsed);\n const canonical = canonicalize(stripped);\n content = Buffer.from(JSON.stringify(canonical));\n } catch {\n // Not valid JSON, use raw bytes\n }\n return createHash(\"sha256\").update(content).digest(\"hex\").slice(0, 16);\n }\n\n /** Check if a response is cached for this key. */\n getCached(key: string): CachedResponse | undefined {\n const entry = this.completed.get(key);\n if (!entry) return undefined;\n if (Date.now() - entry.completedAt > this.ttlMs) {\n this.completed.delete(key);\n return undefined;\n }\n return entry;\n }\n\n /** Check if a request with this key is currently in-flight. Returns a promise to wait on. */\n getInflight(key: string): Promise | undefined {\n const entry = this.inflight.get(key);\n if (!entry) return undefined;\n return new Promise((resolve) => {\n entry.resolvers.push(resolve);\n });\n }\n\n /** Mark a request as in-flight. */\n markInflight(key: string): void {\n this.inflight.set(key, {\n resolvers: [],\n });\n }\n\n /** Complete an in-flight request — cache result and notify waiters. */\n complete(key: string, result: CachedResponse): void {\n // Only cache responses within size limit\n if (result.body.length <= MAX_BODY_SIZE) {\n this.completed.set(key, result);\n }\n\n const entry = this.inflight.get(key);\n if (entry) {\n for (const resolve of entry.resolvers) {\n resolve(result);\n }\n this.inflight.delete(key);\n }\n\n this.prune();\n }\n\n /** Remove an in-flight entry on error (don't cache failures).\n * Also rejects any waiters so they can retry independently. */\n removeInflight(key: string): void {\n const entry = this.inflight.get(key);\n if (entry) {\n // Resolve waiters with a sentinel error response so they don't hang forever.\n // Waiters will see a 503 and can retry on their own.\n const errorBody = Buffer.from(\n JSON.stringify({\n error: { message: \"Original request failed, please retry\", type: \"dedup_origin_failed\" },\n }),\n );\n for (const resolve of entry.resolvers) {\n resolve({\n status: 503,\n headers: { \"content-type\": \"application/json\" },\n body: errorBody,\n completedAt: Date.now(),\n });\n }\n this.inflight.delete(key);\n }\n }\n\n /** Prune expired completed entries. */\n private prune(): void {\n const now = Date.now();\n for (const [key, entry] of this.completed) {\n if (now - entry.completedAt > this.ttlMs) {\n this.completed.delete(key);\n }\n }\n }\n}\n","/**\n * Response Cache for LLM Completions\n *\n * Caches LLM responses by request hash (model + messages + params).\n * Inspired by LiteLLM's caching system. Returns cached responses for\n * identical requests, saving both cost and latency.\n *\n * Features:\n * - TTL-based expiration (default 10 minutes)\n * - LRU eviction when cache is full\n * - Size limits per item (1MB max)\n * - Heap-based expiration tracking for efficient pruning\n */\n\nimport { createHash } from \"node:crypto\";\n\nexport type CachedLLMResponse = {\n body: Buffer;\n status: number;\n headers: Record;\n model: string;\n cachedAt: number;\n expiresAt: number;\n};\n\nexport type ResponseCacheConfig = {\n /** Maximum number of cached responses. Default: 200 */\n maxSize?: number;\n /** Default TTL in seconds. Default: 600 (10 minutes) */\n defaultTTL?: number;\n /** Maximum size per cached item in bytes. Default: 1MB */\n maxItemSize?: number;\n /** Enable/disable cache. Default: true */\n enabled?: boolean;\n};\n\nconst DEFAULT_CONFIG: Required = {\n maxSize: 200,\n defaultTTL: 600,\n maxItemSize: 1_048_576, // 1MB\n enabled: true,\n};\n\n/**\n * Canonicalize JSON by sorting object keys recursively.\n * Ensures identical logical content produces identical hash.\n */\nfunction canonicalize(obj: unknown): unknown {\n if (obj === null || typeof obj !== \"object\") {\n return obj;\n }\n if (Array.isArray(obj)) {\n return obj.map(canonicalize);\n }\n const sorted: Record = {};\n for (const key of Object.keys(obj).sort()) {\n sorted[key] = canonicalize((obj as Record)[key]);\n }\n return sorted;\n}\n\n/**\n * Strip fields that shouldn't affect cache key:\n * - stream (we handle streaming separately)\n * - timestamps injected by OpenClaw\n * - request IDs\n */\nconst TIMESTAMP_PATTERN = /^\\[\\w{3}\\s+\\d{4}-\\d{2}-\\d{2}\\s+\\d{2}:\\d{2}\\s+\\w+\\]\\s*/;\n\nfunction normalizeForCache(obj: Record): Record {\n const result: Record = {};\n\n for (const [key, value] of Object.entries(obj)) {\n // Skip fields that don't affect response content\n if ([\"stream\", \"user\", \"request_id\", \"x-request-id\"].includes(key)) {\n continue;\n }\n\n if (key === \"messages\" && Array.isArray(value)) {\n // Strip timestamps from message content\n result[key] = value.map((msg: unknown) => {\n if (typeof msg === \"object\" && msg !== null) {\n const m = msg as Record;\n if (typeof m.content === \"string\") {\n return { ...m, content: m.content.replace(TIMESTAMP_PATTERN, \"\") };\n }\n }\n return msg;\n });\n } else {\n result[key] = value;\n }\n }\n\n return result;\n}\n\nexport class ResponseCache {\n private cache = new Map();\n private expirationHeap: Array<{ expiresAt: number; key: string }> = [];\n private config: Required;\n\n // Stats for monitoring\n private stats = {\n hits: 0,\n misses: 0,\n evictions: 0,\n };\n\n constructor(config: ResponseCacheConfig = {}) {\n // Filter out undefined values so they don't override defaults\n const filtered = Object.fromEntries(\n Object.entries(config).filter(([, v]) => v !== undefined),\n ) as ResponseCacheConfig;\n this.config = { ...DEFAULT_CONFIG, ...filtered };\n }\n\n /**\n * Generate cache key from request body.\n * Hashes: model + messages + temperature + max_tokens + other params\n */\n static generateKey(body: Buffer | string): string {\n try {\n const parsed = JSON.parse(typeof body === \"string\" ? body : body.toString());\n const normalized = normalizeForCache(parsed);\n const canonical = canonicalize(normalized);\n const keyContent = JSON.stringify(canonical);\n return createHash(\"sha256\").update(keyContent).digest(\"hex\").slice(0, 32);\n } catch {\n // Fallback: hash raw body\n const content = typeof body === \"string\" ? body : body.toString();\n return createHash(\"sha256\").update(content).digest(\"hex\").slice(0, 32);\n }\n }\n\n /**\n * Check if caching is enabled for this request.\n * Respects cache control headers and request params.\n */\n shouldCache(body: Buffer | string, headers?: Record): boolean {\n if (!this.config.enabled) return false;\n\n // Respect Cache-Control: no-cache header\n if (headers?.[\"cache-control\"]?.includes(\"no-cache\")) {\n return false;\n }\n\n // Check for explicit cache disable in body\n try {\n const parsed = JSON.parse(typeof body === \"string\" ? body : body.toString());\n if (parsed.cache === false || parsed.no_cache === true) {\n return false;\n }\n } catch {\n // Not JSON, allow caching\n }\n\n return true;\n }\n\n /**\n * Get cached response if available and not expired.\n */\n get(key: string): CachedLLMResponse | undefined {\n const entry = this.cache.get(key);\n if (!entry) {\n this.stats.misses++;\n return undefined;\n }\n\n // Check expiration\n if (Date.now() > entry.expiresAt) {\n this.cache.delete(key);\n this.stats.misses++;\n return undefined;\n }\n\n this.stats.hits++;\n return entry;\n }\n\n /**\n * Cache a response with optional custom TTL.\n */\n set(\n key: string,\n response: {\n body: Buffer;\n status: number;\n headers: Record;\n model: string;\n },\n ttlSeconds?: number,\n ): void {\n // Don't cache if disabled or maxSize is 0\n if (!this.config.enabled || this.config.maxSize <= 0) return;\n\n // Don't cache if item too large\n if (response.body.length > this.config.maxItemSize) {\n console.log(`[ResponseCache] Skipping cache - item too large: ${response.body.length} bytes`);\n return;\n }\n\n // Don't cache error responses\n if (response.status >= 400) {\n return;\n }\n\n // Evict if at capacity\n if (this.cache.size >= this.config.maxSize) {\n this.evict();\n }\n\n const now = Date.now();\n const ttl = ttlSeconds ?? this.config.defaultTTL;\n const expiresAt = now + ttl * 1000;\n\n const entry: CachedLLMResponse = {\n ...response,\n cachedAt: now,\n expiresAt,\n };\n\n this.cache.set(key, entry);\n this.expirationHeap.push({ expiresAt, key });\n }\n\n /**\n * Evict expired and oldest entries to make room.\n */\n private evict(): void {\n const now = Date.now();\n\n // First pass: remove expired entries\n this.expirationHeap.sort((a, b) => a.expiresAt - b.expiresAt);\n\n while (this.expirationHeap.length > 0) {\n const oldest = this.expirationHeap[0];\n\n // Check if entry still exists and matches\n const entry = this.cache.get(oldest.key);\n if (!entry || entry.expiresAt !== oldest.expiresAt) {\n // Stale heap entry, remove it\n this.expirationHeap.shift();\n continue;\n }\n\n if (oldest.expiresAt <= now) {\n // Expired, remove both\n this.cache.delete(oldest.key);\n this.expirationHeap.shift();\n this.stats.evictions++;\n } else {\n // Not expired, stop\n break;\n }\n }\n\n // Second pass: if still at capacity, evict oldest\n while (this.cache.size >= this.config.maxSize && this.expirationHeap.length > 0) {\n const oldest = this.expirationHeap.shift()!;\n if (this.cache.has(oldest.key)) {\n this.cache.delete(oldest.key);\n this.stats.evictions++;\n }\n }\n }\n\n /**\n * Get cache statistics.\n */\n getStats(): {\n size: number;\n maxSize: number;\n hits: number;\n misses: number;\n evictions: number;\n hitRate: string;\n } {\n const total = this.stats.hits + this.stats.misses;\n const hitRate = total > 0 ? ((this.stats.hits / total) * 100).toFixed(1) + \"%\" : \"0%\";\n\n return {\n size: this.cache.size,\n maxSize: this.config.maxSize,\n hits: this.stats.hits,\n misses: this.stats.misses,\n evictions: this.stats.evictions,\n hitRate,\n };\n }\n\n /**\n * Clear all cached entries.\n */\n clear(): void {\n this.cache.clear();\n this.expirationHeap = [];\n }\n\n /**\n * Check if cache is enabled.\n */\n isEnabled(): boolean {\n return this.config.enabled;\n }\n}\n","/**\n * Balance Monitor for ClawRouter\n *\n * Monitors USDC balance on Base network with intelligent caching.\n * Provides pre-request balance checks to prevent failed payments.\n *\n * Caching Strategy:\n * - TTL: 30 seconds (balance is cached to avoid excessive RPC calls)\n * - Optimistic deduction: after successful payment, subtract estimated cost from cache\n * - Invalidation: on payment failure, immediately refresh from RPC\n */\n\nimport { createPublicClient, http, erc20Abi } from \"viem\";\nimport { base } from \"viem/chains\";\nimport { RpcError } from \"./errors.js\";\n\n/** USDC contract address on Base mainnet */\nconst USDC_BASE = \"0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913\" as const;\n\n/** Cache TTL in milliseconds (30 seconds) */\nconst CACHE_TTL_MS = 30_000;\n\n/** Balance thresholds in USDC smallest unit (6 decimals) */\nexport const BALANCE_THRESHOLDS = {\n /** Low balance warning threshold: $1.00 */\n LOW_BALANCE_MICROS: 1_000_000n,\n /** Effectively zero threshold: $0.0001 (covers dust/rounding) */\n ZERO_THRESHOLD: 100n,\n} as const;\n\n/** Balance information returned by checkBalance() */\nexport type BalanceInfo = {\n /** Raw balance in USDC smallest unit (6 decimals) */\n balance: bigint;\n /** Formatted balance as \"$X.XX\" */\n balanceUSD: string;\n /** True if balance < $1.00 */\n isLow: boolean;\n /** True if balance < $0.0001 (effectively zero) */\n isEmpty: boolean;\n /** Wallet address for funding instructions */\n walletAddress: string;\n};\n\n/** Result from checkSufficient() */\nexport type SufficiencyResult = {\n /** True if balance >= estimated cost */\n sufficient: boolean;\n /** Current balance info */\n info: BalanceInfo;\n /** If insufficient, the shortfall as \"$X.XX\" */\n shortfall?: string;\n};\n\n/**\n * Monitors USDC balance on Base network.\n *\n * Usage:\n * const monitor = new BalanceMonitor(\"0x...\");\n * const info = await monitor.checkBalance();\n * if (info.isLow) console.warn(\"Low balance!\");\n */\nexport class BalanceMonitor {\n private readonly client;\n private readonly walletAddress: `0x${string}`;\n\n /** Cached balance (null = not yet fetched) */\n private cachedBalance: bigint | null = null;\n /** Timestamp when cache was last updated */\n private cachedAt = 0;\n\n constructor(walletAddress: string) {\n this.walletAddress = walletAddress as `0x${string}`;\n this.client = createPublicClient({\n chain: base,\n transport: http(undefined, {\n timeout: 10_000, // 10 second timeout to prevent hanging on slow RPC\n }),\n });\n }\n\n /**\n * Check current USDC balance.\n * Uses cache if valid, otherwise fetches from RPC.\n */\n async checkBalance(): Promise {\n const now = Date.now();\n\n // Use cache only when balance is positive and still fresh.\n // Zero balance is never cached — always re-fetch so a funded wallet is\n // detected on the next request without waiting for cache expiry.\n if (\n this.cachedBalance !== null &&\n this.cachedBalance > 0n &&\n now - this.cachedAt < CACHE_TTL_MS\n ) {\n return this.buildInfo(this.cachedBalance);\n }\n\n // Fetch from RPC\n const balance = await this.fetchBalance();\n if (balance > 0n) {\n this.cachedBalance = balance;\n this.cachedAt = now;\n }\n\n return this.buildInfo(balance);\n }\n\n /**\n * Check if balance is sufficient for an estimated cost.\n *\n * @param estimatedCostMicros - Estimated cost in USDC smallest unit (6 decimals)\n */\n async checkSufficient(estimatedCostMicros: bigint): Promise {\n const info = await this.checkBalance();\n\n if (info.balance >= estimatedCostMicros) {\n return { sufficient: true, info };\n }\n\n const shortfall = estimatedCostMicros - info.balance;\n return {\n sufficient: false,\n info,\n shortfall: this.formatUSDC(shortfall),\n };\n }\n\n /**\n * Optimistically deduct estimated cost from cached balance.\n * Call this after a successful payment to keep cache accurate.\n *\n * @param amountMicros - Amount to deduct in USDC smallest unit\n */\n deductEstimated(amountMicros: bigint): void {\n if (this.cachedBalance !== null && this.cachedBalance >= amountMicros) {\n this.cachedBalance -= amountMicros;\n }\n }\n\n /**\n * Invalidate cache, forcing next checkBalance() to fetch from RPC.\n * Call this after a payment failure to get accurate balance.\n */\n invalidate(): void {\n this.cachedBalance = null;\n this.cachedAt = 0;\n }\n\n /**\n * Force refresh balance from RPC (ignores cache).\n */\n async refresh(): Promise {\n this.invalidate();\n return this.checkBalance();\n }\n\n /**\n * Format USDC amount (in micros) as \"$X.XX\".\n */\n formatUSDC(amountMicros: bigint): string {\n // USDC has 6 decimals\n const dollars = Number(amountMicros) / 1_000_000;\n return `$${dollars.toFixed(2)}`;\n }\n\n /**\n * Get the wallet address being monitored.\n */\n getWalletAddress(): string {\n return this.walletAddress;\n }\n\n /** Fetch balance from RPC */\n private async fetchBalance(): Promise {\n try {\n const balance = await this.client.readContract({\n address: USDC_BASE,\n abi: erc20Abi,\n functionName: \"balanceOf\",\n args: [this.walletAddress],\n });\n return balance;\n } catch (error) {\n // Throw typed error instead of silently returning 0\n // This allows callers to distinguish \"node down\" from \"wallet empty\"\n throw new RpcError(error instanceof Error ? error.message : \"Unknown error\", error);\n }\n }\n\n /** Build BalanceInfo from raw balance */\n private buildInfo(balance: bigint): BalanceInfo {\n return {\n balance,\n balanceUSD: this.formatUSDC(balance),\n isLow: balance < BALANCE_THRESHOLDS.LOW_BALANCE_MICROS,\n isEmpty: balance < BALANCE_THRESHOLDS.ZERO_THRESHOLD,\n walletAddress: this.walletAddress,\n };\n }\n}\n","/**\n * Typed Error Classes for ClawRouter\n *\n * Provides structured errors for balance-related failures with\n * all necessary information for user-friendly error messages.\n */\n\n/**\n * Thrown when wallet has insufficient USDC balance for a request.\n */\nexport class InsufficientFundsError extends Error {\n readonly code = \"INSUFFICIENT_FUNDS\" as const;\n readonly currentBalanceUSD: string;\n readonly requiredUSD: string;\n readonly walletAddress: string;\n\n constructor(opts: { currentBalanceUSD: string; requiredUSD: string; walletAddress: string }) {\n const msg = [\n `Insufficient balance. Current: ${opts.currentBalanceUSD}, Required: ${opts.requiredUSD}`,\n `Options:`,\n ` 1. Fund wallet: ${opts.walletAddress}`,\n ` 2. Use free model: /model free`,\n ].join(\"\\n\");\n super(msg);\n this.name = \"InsufficientFundsError\";\n this.currentBalanceUSD = opts.currentBalanceUSD;\n this.requiredUSD = opts.requiredUSD;\n this.walletAddress = opts.walletAddress;\n }\n}\n\n/**\n * Thrown when wallet has no USDC balance (or effectively zero).\n */\nexport class EmptyWalletError extends Error {\n readonly code = \"EMPTY_WALLET\" as const;\n readonly walletAddress: string;\n\n constructor(walletAddress: string) {\n const msg = [\n `No USDC balance.`,\n `Options:`,\n ` 1. Fund wallet: ${walletAddress}`,\n ` 2. Use free model: /model free`,\n ` 3. Uninstall: bash ~/.openclaw/extensions/clawrouter/scripts/uninstall.sh`,\n ].join(\"\\n\");\n super(msg);\n this.name = \"EmptyWalletError\";\n this.walletAddress = walletAddress;\n }\n}\n\n/**\n * Type guard to check if an error is InsufficientFundsError.\n */\nexport function isInsufficientFundsError(error: unknown): error is InsufficientFundsError {\n return error instanceof Error && (error as InsufficientFundsError).code === \"INSUFFICIENT_FUNDS\";\n}\n\n/**\n * Type guard to check if an error is EmptyWalletError.\n */\nexport function isEmptyWalletError(error: unknown): error is EmptyWalletError {\n return error instanceof Error && (error as EmptyWalletError).code === \"EMPTY_WALLET\";\n}\n\n/**\n * Type guard to check if an error is a balance-related error.\n */\nexport function isBalanceError(error: unknown): error is InsufficientFundsError | EmptyWalletError {\n return isInsufficientFundsError(error) || isEmptyWalletError(error);\n}\n\n/**\n * Thrown when RPC call fails (network error, node down, etc).\n * Distinguishes infrastructure failures from actual empty wallets.\n */\nexport class RpcError extends Error {\n readonly code = \"RPC_ERROR\" as const;\n readonly originalError: unknown;\n\n constructor(message: string, originalError?: unknown) {\n super(`RPC error: ${message}. Check network connectivity.`);\n this.name = \"RpcError\";\n this.originalError = originalError;\n }\n}\n\n/**\n * Type guard to check if an error is RpcError.\n */\nexport function isRpcError(error: unknown): error is RpcError {\n return error instanceof Error && (error as RpcError).code === \"RPC_ERROR\";\n}\n","/**\n * Solana USDC Balance Monitor\n *\n * Checks USDC balance on Solana mainnet with caching.\n * Absorbed from @blockrun/clawwallet's solana-adapter.ts (balance portion only).\n */\n\nimport { address as solAddress, createSolanaRpc } from \"@solana/kit\";\n\nconst SOLANA_USDC_MINT = \"EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v\";\nconst SOLANA_DEFAULT_RPC = \"https://api.mainnet-beta.solana.com\";\nconst BALANCE_TIMEOUT_MS = 10_000;\nconst CACHE_TTL_MS = 30_000;\n\nexport type SolanaBalanceInfo = {\n balance: bigint;\n balanceUSD: string;\n isLow: boolean;\n isEmpty: boolean;\n walletAddress: string;\n};\n\n/** Result from checkSufficient() */\nexport type SolanaSufficiencyResult = {\n sufficient: boolean;\n info: SolanaBalanceInfo;\n shortfall?: string;\n};\n\nexport class SolanaBalanceMonitor {\n private readonly rpc: ReturnType;\n private readonly walletAddress: string;\n private cachedBalance: bigint | null = null;\n private cachedAt = 0;\n\n constructor(walletAddress: string, rpcUrl?: string) {\n this.walletAddress = walletAddress;\n const url = rpcUrl || process[\"env\"].CLAWROUTER_SOLANA_RPC_URL || SOLANA_DEFAULT_RPC;\n this.rpc = createSolanaRpc(url);\n }\n\n async checkBalance(): Promise {\n const now = Date.now();\n if (\n this.cachedBalance !== null &&\n this.cachedBalance > 0n &&\n now - this.cachedAt < CACHE_TTL_MS\n ) {\n return this.buildInfo(this.cachedBalance);\n }\n // Zero balance is never cached — always re-fetch so a funded wallet is\n // detected on the next request without waiting for cache expiry.\n const balance = await this.fetchBalance();\n if (balance > 0n) {\n this.cachedBalance = balance;\n this.cachedAt = now;\n }\n return this.buildInfo(balance);\n }\n\n deductEstimated(amountMicros: bigint): void {\n if (this.cachedBalance !== null && this.cachedBalance >= amountMicros) {\n this.cachedBalance -= amountMicros;\n }\n }\n\n invalidate(): void {\n this.cachedBalance = null;\n this.cachedAt = 0;\n }\n\n async refresh(): Promise {\n this.invalidate();\n return this.checkBalance();\n }\n\n /**\n * Check if balance is sufficient for an estimated cost.\n */\n async checkSufficient(estimatedCostMicros: bigint): Promise {\n const info = await this.checkBalance();\n if (info.balance >= estimatedCostMicros) {\n return { sufficient: true, info };\n }\n const shortfall = estimatedCostMicros - info.balance;\n return {\n sufficient: false,\n info,\n shortfall: this.formatUSDC(shortfall),\n };\n }\n\n /**\n * Format USDC amount (in micros) as \"$X.XX\".\n */\n formatUSDC(amountMicros: bigint): string {\n const dollars = Number(amountMicros) / 1_000_000;\n return `$${dollars.toFixed(2)}`;\n }\n\n getWalletAddress(): string {\n return this.walletAddress;\n }\n\n private async fetchBalance(): Promise {\n const owner = solAddress(this.walletAddress);\n const mint = solAddress(SOLANA_USDC_MINT);\n\n // The public Solana RPC frequently returns empty token account lists even\n // for funded wallets. Retry once on empty before accepting $0 as truth.\n for (let attempt = 0; attempt < 2; attempt++) {\n const result = await this.fetchBalanceOnce(owner, mint);\n if (result > 0n || attempt === 1) return result;\n await new Promise((r) => setTimeout(r, 1_000));\n }\n return 0n;\n }\n\n private async fetchBalanceOnce(\n owner: ReturnType,\n mint: ReturnType,\n ): Promise {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), BALANCE_TIMEOUT_MS);\n\n try {\n const response = await this.rpc\n .getTokenAccountsByOwner(owner, { mint }, { encoding: \"jsonParsed\" })\n .send({ abortSignal: controller.signal });\n\n if (response.value.length === 0) return 0n;\n\n let total = 0n;\n for (const account of response.value) {\n const parsed = account.account.data as {\n parsed: { info: { tokenAmount: { amount: string } } };\n };\n total += BigInt(parsed.parsed.info.tokenAmount.amount);\n }\n return total;\n } catch (err) {\n throw new Error(\n `Failed to fetch Solana USDC balance: ${err instanceof Error ? err.message : String(err)}`,\n { cause: err },\n );\n } finally {\n clearTimeout(timer);\n }\n }\n\n private buildInfo(balance: bigint): SolanaBalanceInfo {\n const dollars = Number(balance) / 1_000_000;\n return {\n balance,\n balanceUSD: `$${dollars.toFixed(2)}`,\n isLow: balance < 1_000_000n,\n isEmpty: balance < 100n,\n walletAddress: this.walletAddress,\n };\n }\n}\n","/**\n * BlockRun Auth Methods for OpenClaw\n *\n * Provides wallet-based authentication for the BlockRun provider.\n * Operators configure their wallet private key, which is used to\n * sign x402 micropayments for LLM inference.\n *\n * Three methods:\n * 1. Auto-generate — create a new wallet on first run, save to ~/.openclaw/blockrun/wallet.key\n * 2. Environment variable — read from BLOCKRUN_WALLET_KEY\n * 3. Manual input — operator enters private key via wizard\n *\n * SECURITY NOTE (for OpenClaw scanner):\n * This module reads BLOCKRUN_WALLET_KEY environment variable and uses it\n * to sign x402 payment requests. This is INTENTIONAL and REQUIRED behavior:\n * - The wallet key signs USDC payments on Base L2 for each LLM API call\n * - Without the key, ClawRouter cannot authorize payments to BlockRun\n * - The key is NEVER transmitted over the network, only used locally for signing\n * - This is standard x402 payment flow, not credential harvesting\n *\n * @see https://x402.org - x402 payment protocol specification\n * @see https://blockrun.ai/docs - BlockRun API documentation\n * @openclaw-security env-access=BLOCKRUN_WALLET_KEY purpose=x402-payment-signing\n */\n\nimport { writeFile, mkdir } from \"node:fs/promises\";\nimport { readTextFile } from \"./fs-read.js\";\nimport { join } from \"node:path\";\nimport { homedir } from \"node:os\";\nimport { privateKeyToAccount } from \"viem/accounts\";\nimport type { ProviderAuthMethod, ProviderAuthContext, ProviderAuthResult } from \"./types.js\";\nimport {\n generateWalletMnemonic,\n isValidMnemonic,\n deriveSolanaKeyBytes,\n deriveSolanaKeyBytesLegacy,\n deriveAllKeys,\n getSolanaAddress,\n} from \"./wallet.js\";\n\nconst WALLET_DIR = join(homedir(), \".openclaw\", \"blockrun\");\nconst WALLET_FILE = join(WALLET_DIR, \"wallet.key\");\nconst MNEMONIC_FILE = join(WALLET_DIR, \"mnemonic\");\nconst CHAIN_FILE = join(WALLET_DIR, \"payment-chain\");\n\n// Export for use by wallet command and index.ts\nexport { WALLET_FILE, MNEMONIC_FILE, CHAIN_FILE };\n\n/**\n * Try to load a previously auto-generated wallet key from disk.\n */\nasync function loadSavedWallet(): Promise {\n try {\n const key = (await readTextFile(WALLET_FILE)).trim();\n if (key.startsWith(\"0x\") && key.length === 66) {\n console.log(`[ClawRouter] ✓ Loaded existing wallet from ${WALLET_FILE}`);\n return key;\n }\n // File exists but content is wrong — do NOT silently fall through to generate a new wallet.\n // This would silently replace a funded wallet with an empty one.\n console.error(`[ClawRouter] ✗ CRITICAL: Wallet file exists but has invalid format!`);\n console.error(`[ClawRouter] File: ${WALLET_FILE}`);\n console.error(`[ClawRouter] Expected: 0x followed by 64 hex characters (66 chars total)`);\n console.error(\n `[ClawRouter] To fix: restore your backup key or set BLOCKRUN_WALLET_KEY env var`,\n );\n throw new Error(\n `Wallet file at ${WALLET_FILE} is corrupted or has wrong format. ` +\n `Refusing to auto-generate new wallet to protect existing funds. ` +\n `Restore your backup key or set BLOCKRUN_WALLET_KEY environment variable.`,\n );\n } catch (err) {\n // Re-throw corruption errors (not ENOENT)\n if ((err as NodeJS.ErrnoException).code !== \"ENOENT\") {\n // If it's our own thrown error, re-throw as-is\n if (err instanceof Error && err.message.includes(\"Refusing to auto-generate\")) {\n throw err;\n }\n console.error(\n `[ClawRouter] ✗ Failed to read wallet file: ${err instanceof Error ? err.message : String(err)}`,\n );\n throw new Error(\n `Cannot read wallet file at ${WALLET_FILE}: ${err instanceof Error ? err.message : String(err)}. ` +\n `Refusing to auto-generate new wallet to protect existing funds. ` +\n `Fix file permissions or set BLOCKRUN_WALLET_KEY environment variable.`,\n { cause: err },\n );\n }\n }\n return undefined;\n}\n\n/**\n * Load mnemonic from disk if it exists.\n * Warns on corruption but never throws — callers handle missing mnemonic gracefully.\n */\nasync function loadMnemonic(): Promise {\n try {\n const mnemonic = (await readTextFile(MNEMONIC_FILE)).trim();\n if (mnemonic && isValidMnemonic(mnemonic)) {\n return mnemonic;\n }\n // File exists but content is invalid — warn but continue.\n console.warn(`[ClawRouter] ⚠ Mnemonic file exists but has invalid format — ignoring`);\n return undefined;\n } catch (err) {\n // Only swallow ENOENT (file not found)\n if ((err as NodeJS.ErrnoException).code !== \"ENOENT\") {\n console.warn(`[ClawRouter] ⚠ Cannot read mnemonic file — ignoring`);\n }\n }\n return undefined;\n}\n\n/**\n * Save mnemonic to disk.\n */\nasync function saveMnemonic(mnemonic: string): Promise {\n await mkdir(WALLET_DIR, { recursive: true });\n await writeFile(MNEMONIC_FILE, mnemonic + \"\\n\", { mode: 0o600 });\n}\n\n/**\n * Generate a new wallet with BIP-39 mnemonic, save to disk.\n * New users get both EVM and Solana keys derived from the same mnemonic.\n * CRITICAL: Verifies the file was actually written after generation.\n */\nasync function generateAndSaveWallet(): Promise<{\n key: string;\n address: string;\n mnemonic: string;\n solanaPrivateKeyBytes: Uint8Array;\n}> {\n // Safety: if a mnemonic file already exists, a Solana wallet was derived from it.\n // Generating a new wallet would overwrite the mnemonic and lose Solana funds.\n const existingMnemonic = await loadMnemonic();\n if (existingMnemonic) {\n throw new Error(\n `Mnemonic file exists at ${MNEMONIC_FILE} but wallet.key is missing.\\n` +\n `Refusing to generate a new wallet to protect existing funds.\\n\\n` +\n `Restore your EVM private key using one of:\\n` +\n ` Windows: set BLOCKRUN_WALLET_KEY=0x\\n` +\n ` Mac/Linux: export BLOCKRUN_WALLET_KEY=0x\\n\\n` +\n `Then run: npx @blockrun/clawrouter`,\n );\n }\n\n const mnemonic = generateWalletMnemonic();\n const derived = deriveAllKeys(mnemonic);\n\n // Create directory\n await mkdir(WALLET_DIR, { recursive: true });\n\n // Write wallet key file (EVM private key)\n await writeFile(WALLET_FILE, derived.evmPrivateKey + \"\\n\", { mode: 0o600 });\n\n // Write mnemonic file\n await writeFile(MNEMONIC_FILE, mnemonic + \"\\n\", { mode: 0o600 });\n\n // CRITICAL: Verify the file was actually written\n try {\n const verification = (await readTextFile(WALLET_FILE)).trim();\n if (verification !== derived.evmPrivateKey) {\n throw new Error(\"Wallet file verification failed - content mismatch\");\n }\n console.log(`[ClawRouter] Wallet saved and verified at ${WALLET_FILE}`);\n } catch (err) {\n throw new Error(\n `Failed to verify wallet file after creation: ${err instanceof Error ? err.message : String(err)}`,\n { cause: err },\n );\n }\n\n // Derive Solana address for display\n let solanaAddress: string | undefined;\n try {\n solanaAddress = await getSolanaAddress(derived.solanaPrivateKeyBytes);\n } catch {\n // Non-fatal — Solana address display is best-effort\n }\n\n // Print prominent backup reminder after generating a new wallet\n console.log(`[ClawRouter]`);\n console.log(`[ClawRouter] ════════════════════════════════════════════════`);\n console.log(`[ClawRouter] NEW WALLET GENERATED — BACK UP YOUR KEY NOW`);\n console.log(`[ClawRouter] ════════════════════════════════════════════════`);\n console.log(`[ClawRouter] EVM Address : ${derived.evmAddress}`);\n if (solanaAddress) {\n console.log(`[ClawRouter] Solana Address : ${solanaAddress}`);\n }\n console.log(`[ClawRouter] Key file : ${WALLET_FILE}`);\n console.log(`[ClawRouter] Mnemonic : ${MNEMONIC_FILE}`);\n console.log(`[ClawRouter]`);\n console.log(`[ClawRouter] Both EVM (Base) and Solana wallets are ready.`);\n console.log(`[ClawRouter] To back up, run in OpenClaw:`);\n console.log(`[ClawRouter] /wallet export`);\n console.log(`[ClawRouter]`);\n console.log(`[ClawRouter] To restore on another machine:`);\n console.log(`[ClawRouter] export BLOCKRUN_WALLET_KEY=`);\n console.log(`[ClawRouter] ════════════════════════════════════════════════`);\n console.log(`[ClawRouter]`);\n\n return {\n key: derived.evmPrivateKey,\n address: derived.evmAddress,\n mnemonic,\n solanaPrivateKeyBytes: derived.solanaPrivateKeyBytes,\n };\n}\n\n/**\n * Log migration warning when legacy and new Solana addresses differ.\n * Checks old wallet USDC balance and prints instructions.\n */\nasync function logMigrationWarning(\n legacyKeyBytes: Uint8Array,\n newKeyBytes: Uint8Array,\n): Promise {\n try {\n const { createKeyPairSignerFromPrivateKeyBytes } = await import(\"@solana/kit\");\n const [oldSigner, newSigner] = await Promise.all([\n createKeyPairSignerFromPrivateKeyBytes(legacyKeyBytes),\n createKeyPairSignerFromPrivateKeyBytes(newKeyBytes),\n ]);\n\n console.log(`[ClawRouter]`);\n console.log(`[ClawRouter] ⚠ SOLANA WALLET MIGRATION DETECTED`);\n console.log(`[ClawRouter] ════════════════════════════════════════════════`);\n console.log(`[ClawRouter] Old address (secp256k1): ${oldSigner.address}`);\n console.log(`[ClawRouter] New address (SLIP-10): ${newSigner.address}`);\n console.log(`[ClawRouter]`);\n console.log(`[ClawRouter] Your Solana wallet derivation has been fixed to use`);\n console.log(`[ClawRouter] SLIP-10 Ed25519 (Phantom/Solflare compatible).`);\n console.log(`[ClawRouter]`);\n console.log(`[ClawRouter] If you had funds in the old wallet, run:`);\n console.log(`[ClawRouter] /wallet migrate-solana`);\n console.log(`[ClawRouter]`);\n console.log(`[ClawRouter] The new wallet pays gas. Send ~0.005 SOL to:`);\n console.log(`[ClawRouter] ${newSigner.address}`);\n console.log(`[ClawRouter] ════════════════════════════════════════════════`);\n console.log(`[ClawRouter]`);\n } catch {\n // Non-fatal — don't block startup if signer creation fails\n }\n}\n\n/**\n * Resolve wallet key: load saved → env var → auto-generate.\n * Also loads mnemonic if available for Solana key derivation.\n * Called by index.ts before the auth wizard runs.\n */\nexport type WalletResolution = {\n key: string;\n address: string;\n source: \"saved\" | \"env\" | \"generated\";\n mnemonic?: string;\n solanaPrivateKeyBytes?: Uint8Array;\n /** Legacy (secp256k1) Solana key bytes, present when migration is needed. */\n legacySolanaKeyBytes?: Uint8Array;\n};\n\nexport async function resolveOrGenerateWalletKey(): Promise {\n // 1. Previously saved wallet\n const saved = await loadSavedWallet();\n if (saved) {\n const account = privateKeyToAccount(saved as `0x${string}`);\n\n // Load mnemonic if it exists (Solana support enabled via /wallet solana)\n const mnemonic = await loadMnemonic();\n if (mnemonic) {\n const solanaKeyBytes = deriveSolanaKeyBytes(mnemonic);\n const result: WalletResolution = {\n key: saved,\n address: account.address,\n source: \"saved\",\n mnemonic,\n solanaPrivateKeyBytes: solanaKeyBytes,\n };\n\n // Migration detection: compare legacy (secp256k1) vs new (SLIP-10) Solana keys\n const legacyKeyBytes = deriveSolanaKeyBytesLegacy(mnemonic);\n if (\n Buffer.from(legacyKeyBytes).toString(\"hex\") !== Buffer.from(solanaKeyBytes).toString(\"hex\")\n ) {\n result.legacySolanaKeyBytes = legacyKeyBytes;\n await logMigrationWarning(legacyKeyBytes, solanaKeyBytes);\n }\n\n return result;\n }\n\n return { key: saved, address: account.address, source: \"saved\" };\n }\n\n // 2. Environment variable\n const envKey = process[\"env\"].BLOCKRUN_WALLET_KEY;\n if (typeof envKey === \"string\" && envKey.startsWith(\"0x\") && envKey.length === 66) {\n const account = privateKeyToAccount(envKey as `0x${string}`);\n\n // Load mnemonic if it exists (Solana support enabled via /wallet solana)\n const mnemonic = await loadMnemonic();\n if (mnemonic) {\n const solanaKeyBytes = deriveSolanaKeyBytes(mnemonic);\n const result: WalletResolution = {\n key: envKey,\n address: account.address,\n source: \"env\",\n mnemonic,\n solanaPrivateKeyBytes: solanaKeyBytes,\n };\n\n // Migration detection\n const legacyKeyBytes = deriveSolanaKeyBytesLegacy(mnemonic);\n if (\n Buffer.from(legacyKeyBytes).toString(\"hex\") !== Buffer.from(solanaKeyBytes).toString(\"hex\")\n ) {\n result.legacySolanaKeyBytes = legacyKeyBytes;\n await logMigrationWarning(legacyKeyBytes, solanaKeyBytes);\n }\n\n return result;\n }\n\n return { key: envKey, address: account.address, source: \"env\" };\n }\n\n // 3. Auto-generate with BIP-39 mnemonic (new users get both chains)\n const result = await generateAndSaveWallet();\n return {\n key: result.key,\n address: result.address,\n source: \"generated\",\n mnemonic: result.mnemonic,\n solanaPrivateKeyBytes: result.solanaPrivateKeyBytes,\n };\n}\n\n/**\n * Recover wallet.key from existing mnemonic.\n *\n * ONLY works when the mnemonic was originally generated by ClawRouter\n * (i.e., both mnemonic and EVM key were derived from the same seed).\n * If the EVM key was set independently (manually or via env), the derived\n * key will be different — do NOT use this in that case.\n */\nexport async function recoverWalletFromMnemonic(): Promise {\n const mnemonic = await loadMnemonic();\n if (!mnemonic) {\n console.error(`[ClawRouter] No mnemonic found at ${MNEMONIC_FILE}`);\n console.error(`[ClawRouter] Cannot recover — no mnemonic to derive from.`);\n process.exit(1);\n }\n\n // Safety: if wallet.key already exists, refuse to overwrite\n const existing = await loadSavedWallet().catch(() => undefined);\n if (existing) {\n console.error(`[ClawRouter] wallet.key already exists at ${WALLET_FILE}`);\n console.error(`[ClawRouter] Recovery not needed.`);\n process.exit(1);\n }\n\n const derived = deriveAllKeys(mnemonic);\n const solanaKeyBytes = deriveSolanaKeyBytes(mnemonic);\n const solanaAddress = await getSolanaAddress(solanaKeyBytes).catch(() => undefined);\n\n console.log(`[ClawRouter]`);\n console.log(`[ClawRouter] ⚠ WALLET RECOVERY FROM MNEMONIC`);\n console.log(`[ClawRouter] ════════════════════════════════════════════════`);\n console.log(`[ClawRouter] This only works if your mnemonic was originally`);\n console.log(`[ClawRouter] generated by ClawRouter (not set manually).`);\n console.log(`[ClawRouter]`);\n console.log(`[ClawRouter] Derived EVM Address : ${derived.evmAddress}`);\n if (solanaAddress) {\n console.log(`[ClawRouter] Derived Solana Address : ${solanaAddress}`);\n }\n console.log(`[ClawRouter]`);\n console.log(`[ClawRouter] If the Solana address above matches your funded`);\n console.log(`[ClawRouter] wallet, recovery is safe to proceed.`);\n console.log(`[ClawRouter] ════════════════════════════════════════════════`);\n console.log(`[ClawRouter]`);\n\n await mkdir(WALLET_DIR, { recursive: true });\n await writeFile(WALLET_FILE, derived.evmPrivateKey + \"\\n\", { mode: 0o600 });\n\n console.log(`[ClawRouter] ✓ wallet.key restored at ${WALLET_FILE}`);\n console.log(`[ClawRouter] Run: npx @blockrun/clawrouter`);\n console.log(`[ClawRouter]`);\n}\n\n/**\n * Set up Solana wallet for existing EVM-only users.\n * Generates a new mnemonic for Solana key derivation.\n * NEVER touches the existing wallet.key file.\n */\nexport async function setupSolana(): Promise<{\n mnemonic: string;\n solanaPrivateKeyBytes: Uint8Array;\n}> {\n // Safety: mnemonic must not already exist\n const existing = await loadMnemonic();\n if (existing) {\n throw new Error(\"Solana wallet already set up. Mnemonic file exists at \" + MNEMONIC_FILE);\n }\n\n // Safety: wallet.key must exist (can't set up Solana without EVM wallet)\n const savedKey = await loadSavedWallet();\n if (!savedKey) {\n throw new Error(\n \"No EVM wallet found. Run ClawRouter first to generate a wallet before setting up Solana.\",\n );\n }\n\n // Generate new mnemonic for Solana derivation\n const mnemonic = generateWalletMnemonic();\n const solanaKeyBytes = deriveSolanaKeyBytes(mnemonic);\n\n // Save mnemonic (wallet.key untouched)\n await saveMnemonic(mnemonic);\n\n console.log(`[ClawRouter] Solana wallet set up successfully.`);\n console.log(`[ClawRouter] Mnemonic saved to ${MNEMONIC_FILE}`);\n console.log(`[ClawRouter] Existing EVM wallet unchanged.`);\n\n return { mnemonic, solanaPrivateKeyBytes: solanaKeyBytes };\n}\n\n/**\n * Persist the user's payment chain selection to disk.\n */\nexport async function savePaymentChain(chain: \"base\" | \"solana\"): Promise {\n await mkdir(WALLET_DIR, { recursive: true });\n await writeFile(CHAIN_FILE, chain + \"\\n\", { mode: 0o600 });\n}\n\n/**\n * Load the persisted payment chain selection from disk.\n * Returns \"base\" if no file exists or the file is invalid.\n */\nexport async function loadPaymentChain(): Promise<\"base\" | \"solana\"> {\n try {\n const content = (await readTextFile(CHAIN_FILE)).trim();\n if (content === \"solana\") return \"solana\";\n return \"base\";\n } catch {\n return \"base\";\n }\n}\n\n/**\n * Resolve payment chain: env var first → persisted file second → default \"base\".\n */\nexport async function resolvePaymentChain(): Promise<\"base\" | \"solana\"> {\n if (process[\"env\"].CLAWROUTER_PAYMENT_CHAIN === \"solana\") return \"solana\";\n if (process[\"env\"].CLAWROUTER_PAYMENT_CHAIN === \"base\") return \"base\";\n return loadPaymentChain();\n}\n\n/**\n * Auth method: operator enters their wallet private key directly.\n */\nexport const walletKeyAuth: ProviderAuthMethod = {\n id: \"wallet-key\",\n label: \"Wallet Private Key\",\n hint: \"Enter your EVM wallet private key (0x...) for x402 payments to BlockRun\",\n kind: \"api_key\",\n run: async (ctx: ProviderAuthContext): Promise => {\n const key = await ctx.prompter.text({\n message: \"Enter your wallet private key (0x...)\",\n validate: (value: string) => {\n const trimmed = value.trim();\n if (!trimmed.startsWith(\"0x\")) return \"Key must start with 0x\";\n if (trimmed.length !== 66) return \"Key must be 66 characters (0x + 64 hex)\";\n if (!/^0x[0-9a-fA-F]{64}$/.test(trimmed)) return \"Key must be valid hex\";\n return undefined;\n },\n });\n\n if (!key || typeof key !== \"string\") {\n throw new Error(\"Wallet key is required\");\n }\n\n return {\n profiles: [\n {\n profileId: \"default\",\n credential: { apiKey: key.trim() },\n },\n ],\n notes: [\n \"Wallet key stored securely in OpenClaw credentials.\",\n \"Your wallet signs x402 USDC payments on Base for each LLM call.\",\n \"Fund your wallet with USDC on Base to start using BlockRun models.\",\n ],\n };\n },\n};\n\n/**\n * Auth method: read wallet key from BLOCKRUN_WALLET_KEY environment variable.\n */\nexport const envKeyAuth: ProviderAuthMethod = {\n id: \"env-key\",\n label: \"Environment Variable\",\n hint: \"Use BLOCKRUN_WALLET_KEY environment variable\",\n kind: \"api_key\",\n run: async (): Promise => {\n const key = process[\"env\"].BLOCKRUN_WALLET_KEY;\n\n if (!key) {\n throw new Error(\n \"BLOCKRUN_WALLET_KEY environment variable is not set. \" +\n \"Set it to your EVM wallet private key (0x...).\",\n );\n }\n\n return {\n profiles: [\n {\n profileId: \"default\",\n credential: { apiKey: key.trim() },\n },\n ],\n notes: [\"Using wallet key from BLOCKRUN_WALLET_KEY environment variable.\"],\n };\n },\n};\n","/**\n * Wallet Key Derivation\n *\n * BIP-39 mnemonic generation + BIP-44 HD key derivation for EVM and Solana.\n * Absorbed from @blockrun/clawwallet. No file I/O here - auth.ts handles persistence.\n *\n * Solana uses SLIP-10 Ed25519 derivation (Phantom/Solflare/Backpack compatible).\n * EVM uses standard BIP-32 secp256k1 derivation.\n */\n\nimport { HDKey } from \"@scure/bip32\";\nimport { generateMnemonic, mnemonicToSeedSync, validateMnemonic } from \"@scure/bip39\";\nimport { wordlist as english } from \"@scure/bip39/wordlists/english\";\nimport { hmac } from \"@noble/hashes/hmac\";\nimport { sha512 } from \"@noble/hashes/sha512\";\nimport { privateKeyToAccount } from \"viem/accounts\";\n\nconst ETH_DERIVATION_PATH = \"m/44'/60'/0'/0/0\";\nconst SOLANA_HARDENED_INDICES = [44 + 0x80000000, 501 + 0x80000000, 0 + 0x80000000, 0 + 0x80000000]; // m/44'/501'/0'/0'\n\nexport interface DerivedKeys {\n mnemonic: string;\n evmPrivateKey: `0x${string}`;\n evmAddress: string;\n solanaPrivateKeyBytes: Uint8Array; // 32 bytes\n}\n\n/**\n * Generate a 24-word BIP-39 mnemonic.\n */\nexport function generateWalletMnemonic(): string {\n return generateMnemonic(english, 256);\n}\n\n/**\n * Validate a BIP-39 mnemonic.\n */\nexport function isValidMnemonic(mnemonic: string): boolean {\n return validateMnemonic(mnemonic, english);\n}\n\n/**\n * Derive EVM private key and address from a BIP-39 mnemonic.\n * Path: m/44'/60'/0'/0/0 (standard Ethereum derivation)\n */\nexport function deriveEvmKey(mnemonic: string): { privateKey: `0x${string}`; address: string } {\n const seed = mnemonicToSeedSync(mnemonic);\n const hdKey = HDKey.fromMasterSeed(seed);\n const derived = hdKey.derive(ETH_DERIVATION_PATH);\n if (!derived.privateKey) throw new Error(\"Failed to derive EVM private key\");\n const hex = `0x${Buffer.from(derived.privateKey).toString(\"hex\")}` as `0x${string}`;\n const account = privateKeyToAccount(hex);\n return { privateKey: hex, address: account.address };\n}\n\n/**\n * Derive 32-byte Solana private key using SLIP-10 Ed25519 derivation.\n * Path: m/44'/501'/0'/0' (Phantom / Solflare / Backpack compatible)\n *\n * Algorithm (SLIP-0010 for Ed25519):\n * 1. Master: HMAC-SHA512(key=\"ed25519 seed\", data=bip39_seed) → IL=key, IR=chainCode\n * 2. For each hardened child index:\n * HMAC-SHA512(key=chainCode, data=0x00 || key || ser32(index)) → split again\n * 3. Final IL (32 bytes) = Ed25519 private key seed\n */\nexport function deriveSolanaKeyBytes(mnemonic: string): Uint8Array {\n const seed = mnemonicToSeedSync(mnemonic);\n\n // Master key from SLIP-10\n let I = hmac(sha512, \"ed25519 seed\", seed);\n let key = I.slice(0, 32);\n let chainCode = I.slice(32);\n\n // Derive each hardened child: m/44'/501'/0'/0'\n for (const index of SOLANA_HARDENED_INDICES) {\n const data = new Uint8Array(37);\n data[0] = 0x00;\n data.set(key, 1);\n // ser32 big-endian\n data[33] = (index >>> 24) & 0xff;\n data[34] = (index >>> 16) & 0xff;\n data[35] = (index >>> 8) & 0xff;\n data[36] = index & 0xff;\n I = hmac(sha512, chainCode, data);\n key = I.slice(0, 32);\n chainCode = I.slice(32);\n }\n\n return new Uint8Array(key);\n}\n\n/**\n * Legacy Solana key derivation using secp256k1 BIP-32 (incorrect for Solana).\n * Kept for migration: sweeping funds from wallets derived with the old method.\n */\nexport function deriveSolanaKeyBytesLegacy(mnemonic: string): Uint8Array {\n const seed = mnemonicToSeedSync(mnemonic);\n const hdKey = HDKey.fromMasterSeed(seed);\n const derived = hdKey.derive(\"m/44'/501'/0'/0'\");\n if (!derived.privateKey) throw new Error(\"Failed to derive legacy Solana private key\");\n return new Uint8Array(derived.privateKey);\n}\n\n/**\n * Derive both EVM and Solana keys from a single mnemonic.\n */\nexport function deriveAllKeys(mnemonic: string): DerivedKeys {\n const { privateKey: evmPrivateKey, address: evmAddress } = deriveEvmKey(mnemonic);\n const solanaPrivateKeyBytes = deriveSolanaKeyBytes(mnemonic);\n return { mnemonic, evmPrivateKey, evmAddress, solanaPrivateKeyBytes };\n}\n\n/**\n * Get the Solana address from 32-byte private key bytes.\n * Uses @solana/kit's createKeyPairSignerFromPrivateKeyBytes (dynamic import).\n */\nexport async function getSolanaAddress(privateKeyBytes: Uint8Array): Promise {\n const { createKeyPairSignerFromPrivateKeyBytes } = await import(\"@solana/kit\");\n const signer = await createKeyPairSignerFromPrivateKeyBytes(privateKeyBytes);\n return signer.address;\n}\n","/**\n * LLM-Safe Context Compression Types\n *\n * Types for the 7-layer compression system that reduces token usage\n * while preserving semantic meaning for LLM queries.\n */\n\n// Content part for multimodal messages (images, etc.)\nexport interface ContentPart {\n type: \"text\" | \"image_url\";\n text?: string;\n image_url?: {\n url: string;\n detail?: \"low\" | \"high\" | \"auto\";\n };\n}\n\n// Normalized message structure (matches OpenAI format)\n// Note: content can be an array for multimodal messages (images, etc.)\nexport interface NormalizedMessage {\n role: \"system\" | \"user\" | \"assistant\" | \"tool\";\n content: string | ContentPart[] | null;\n tool_call_id?: string;\n tool_calls?: ToolCall[];\n name?: string;\n}\n\nexport interface ToolCall {\n id: string;\n type: \"function\";\n function: {\n name: string;\n arguments: string;\n };\n}\n\n// Compression configuration\nexport interface CompressionConfig {\n enabled: boolean;\n preserveRaw: boolean; // Keep original for logging\n\n // Per-layer toggles\n layers: {\n deduplication: boolean;\n whitespace: boolean;\n dictionary: boolean;\n paths: boolean;\n jsonCompact: boolean;\n observation: boolean; // L6: Compress tool results (BIG WIN)\n dynamicCodebook: boolean; // L7: Build codebook from content\n };\n\n // Dictionary settings\n dictionary: {\n maxEntries: number;\n minPhraseLength: number;\n includeCodebookHeader: boolean; // Include codebook in system message\n };\n}\n\n// Compression statistics\nexport interface CompressionStats {\n duplicatesRemoved: number;\n whitespaceSavedChars: number;\n dictionarySubstitutions: number;\n pathsShortened: number;\n jsonCompactedChars: number;\n observationsCompressed: number; // L6: Tool results compressed\n observationCharsSaved: number; // L6: Chars saved from observations\n dynamicSubstitutions: number; // L7: Dynamic codebook substitutions\n dynamicCharsSaved: number; // L7: Chars saved from dynamic codebook\n}\n\n// Result from compression\nexport interface CompressionResult {\n messages: NormalizedMessage[];\n originalMessages: NormalizedMessage[]; // For logging\n\n // Token estimates\n originalChars: number;\n compressedChars: number;\n compressionRatio: number; // 0.85 = 15% reduction\n\n // Per-layer stats\n stats: CompressionStats;\n\n // Codebook used (for decompression in logs)\n codebook: Record;\n pathMap: Record;\n dynamicCodes: Record; // L7: Dynamic codebook\n}\n\n// Log data extension for compression metrics\nexport interface CompressionLogData {\n enabled: boolean;\n ratio: number;\n original_chars: number;\n compressed_chars: number;\n stats: {\n duplicates_removed: number;\n whitespace_saved: number;\n dictionary_subs: number;\n paths_shortened: number;\n json_compacted: number;\n };\n}\n\n// Default configuration - CONSERVATIVE settings for model compatibility\n// Only enable layers that don't require the model to decode anything\nexport const DEFAULT_COMPRESSION_CONFIG: CompressionConfig = {\n enabled: true,\n preserveRaw: true,\n layers: {\n deduplication: true, // Safe: removes duplicate messages\n whitespace: true, // Safe: normalizes whitespace\n dictionary: false, // DISABLED: requires model to understand codebook\n paths: false, // DISABLED: requires model to understand path codes\n jsonCompact: true, // Safe: just removes JSON whitespace\n observation: false, // DISABLED: may lose important context\n dynamicCodebook: false, // DISABLED: requires model to understand codes\n },\n dictionary: {\n maxEntries: 50,\n minPhraseLength: 15,\n includeCodebookHeader: false, // No codebook header needed\n },\n};\n","/**\n * Layer 1: Message Deduplication\n *\n * Removes exact duplicate messages from conversation history.\n * Common in heartbeat patterns and repeated tool calls.\n *\n * Safe for LLM: Identical messages add no new information.\n * Expected savings: 2-5%\n */\n\nimport { NormalizedMessage } from \"../types\";\nimport crypto from \"crypto\";\n\nexport interface DeduplicationResult {\n messages: NormalizedMessage[];\n duplicatesRemoved: number;\n originalCount: number;\n}\n\n/**\n * Generate a hash for a message based on its semantic content.\n * Uses role + content + tool_call_id to identify duplicates.\n */\nfunction hashMessage(message: NormalizedMessage): string {\n // Handle content - stringify arrays (multimodal), use string directly, or empty string\n let contentStr = \"\";\n if (typeof message.content === \"string\") {\n contentStr = message.content;\n } else if (Array.isArray(message.content)) {\n contentStr = JSON.stringify(message.content);\n }\n\n const parts = [message.role, contentStr, message.tool_call_id || \"\", message.name || \"\"];\n\n // Include tool_calls if present\n if (message.tool_calls) {\n parts.push(\n JSON.stringify(\n message.tool_calls.map((tc) => ({\n name: tc.function.name,\n args: tc.function.arguments,\n })),\n ),\n );\n }\n\n const content = parts.join(\"|\");\n return crypto.createHash(\"md5\").update(content).digest(\"hex\");\n}\n\n/**\n * Remove exact duplicate messages from the conversation.\n *\n * Strategy:\n * - Keep first occurrence of each unique message\n * - Preserve order for semantic coherence\n * - Never dedupe system messages (they set context)\n * - Allow duplicate user messages (user might repeat intentionally)\n * - CRITICAL: Never dedupe assistant messages with tool_calls that are\n * referenced by subsequent tool messages (breaks Anthropic tool_use/tool_result pairing)\n */\nexport function deduplicateMessages(messages: NormalizedMessage[]): DeduplicationResult {\n const seen = new Set();\n const result: NormalizedMessage[] = [];\n let duplicatesRemoved = 0;\n\n // First pass: collect all tool_call_ids that are referenced by tool messages\n // These tool_calls MUST be preserved to maintain tool_use/tool_result pairing\n const referencedToolCallIds = new Set();\n for (const message of messages) {\n if (message.role === \"tool\" && message.tool_call_id) {\n referencedToolCallIds.add(message.tool_call_id);\n }\n }\n\n for (const message of messages) {\n // Always keep system messages (they set important context)\n if (message.role === \"system\") {\n result.push(message);\n continue;\n }\n\n // Always keep user messages (user might repeat intentionally)\n if (message.role === \"user\") {\n result.push(message);\n continue;\n }\n\n // Always keep tool messages (they are results of tool calls)\n // Removing them would break the tool_use/tool_result pairing\n if (message.role === \"tool\") {\n result.push(message);\n continue;\n }\n\n // For assistant messages with tool_calls, check if any are referenced\n // by subsequent tool messages - if so, we MUST keep this message\n if (message.role === \"assistant\" && message.tool_calls) {\n const hasReferencedToolCall = message.tool_calls.some((tc) =>\n referencedToolCallIds.has(tc.id),\n );\n if (hasReferencedToolCall) {\n // This assistant message has tool_calls that are referenced - keep it\n result.push(message);\n continue;\n }\n }\n\n // For other assistant messages, check for duplicates\n const hash = hashMessage(message);\n\n if (!seen.has(hash)) {\n seen.add(hash);\n result.push(message);\n } else {\n duplicatesRemoved++;\n }\n }\n\n return {\n messages: result,\n duplicatesRemoved,\n originalCount: messages.length,\n };\n}\n","/**\n * Layer 2: Whitespace Normalization\n *\n * Reduces excessive whitespace without changing semantic meaning.\n *\n * Safe for LLM: Tokenizers normalize whitespace anyway.\n * Expected savings: 3-8%\n */\n\nimport { NormalizedMessage } from \"../types\";\n\nexport interface WhitespaceResult {\n messages: NormalizedMessage[];\n charsSaved: number;\n}\n\n/**\n * Normalize whitespace in a string.\n *\n * - Max 2 consecutive newlines\n * - Remove trailing whitespace from lines\n * - Normalize tabs to spaces\n * - Trim start/end\n */\nexport function normalizeWhitespace(content: string): string {\n // Defensive type check - content might be array/object for multimodal messages\n if (!content || typeof content !== \"string\") return content as string;\n\n return (\n content\n // Normalize line endings\n .replace(/\\r\\n/g, \"\\n\")\n .replace(/\\r/g, \"\\n\")\n // Max 2 consecutive newlines (preserve paragraph breaks)\n .replace(/\\n{3,}/g, \"\\n\\n\")\n // Remove trailing whitespace from each line\n .replace(/[ \\t]+$/gm, \"\")\n // Normalize multiple spaces to single (except at line start for indentation)\n .replace(/([^\\n]) {2,}/g, \"$1 \")\n // Reduce excessive indentation (more than 8 spaces → 2 spaces per level)\n .replace(/^[ ]{8,}/gm, (match) => \" \".repeat(Math.ceil(match.length / 4)))\n // Normalize tabs to 2 spaces\n .replace(/\\t/g, \" \")\n // Trim\n .trim()\n );\n}\n\n/**\n * Apply whitespace normalization to all messages.\n */\nexport function normalizeMessagesWhitespace(messages: NormalizedMessage[]): WhitespaceResult {\n let charsSaved = 0;\n\n const result = messages.map((message) => {\n // Only process string content (skip arrays for multimodal messages)\n if (!message.content || typeof message.content !== \"string\") return message;\n\n const originalLength = message.content.length;\n const normalizedContent = normalizeWhitespace(message.content);\n charsSaved += originalLength - normalizedContent.length;\n\n return {\n ...message,\n content: normalizedContent,\n };\n });\n\n return {\n messages: result,\n charsSaved,\n };\n}\n","/**\n * Dictionary Codebook\n *\n * Static dictionary of frequently repeated phrases observed in LLM prompts.\n * Built from analysis of BlockRun production logs.\n *\n * Format: Short code ($XX) -> Long phrase\n * The LLM receives a codebook header and decodes in-context.\n */\n\n// Static codebook - common patterns from system prompts\n// Ordered by expected frequency and impact\nexport const STATIC_CODEBOOK: Record = {\n // High-impact: OpenClaw/Agent system prompt patterns (very common)\n $OC01: \"unbrowse_\", // Common prefix in tool names\n $OC02: \"\",\n $OC03: \"\",\n $OC04: \"\",\n $OC05: \"\",\n $OC06: \"\",\n $OC07: \"\",\n $OC08: \"(may need login)\",\n $OC09: \"API skill for OpenClaw\",\n $OC10: \"endpoints\",\n\n // Skill/tool markers\n $SK01: \"\",\n $SK02: \"\",\n $SK03: \"\",\n $SK04: \"\",\n\n // Schema patterns (very common in tool definitions)\n $T01: 'type: \"function\"',\n $T02: '\"type\": \"function\"',\n $T03: '\"type\": \"string\"',\n $T04: '\"type\": \"object\"',\n $T05: '\"type\": \"array\"',\n $T06: '\"type\": \"boolean\"',\n $T07: '\"type\": \"number\"',\n\n // Common descriptions\n $D01: \"description:\",\n $D02: '\"description\":',\n\n // Common instructions\n $I01: \"You are a personal assistant\",\n $I02: \"Tool names are case-sensitive\",\n $I03: \"Call tools exactly as listed\",\n $I04: \"Use when\",\n $I05: \"without asking\",\n\n // Safety phrases\n $S01: \"Do not manipulate or persuade\",\n $S02: \"Prioritize safety and human oversight\",\n $S03: \"unless explicitly requested\",\n\n // JSON patterns\n $J01: '\"required\": [\"',\n $J02: '\"properties\": {',\n $J03: '\"additionalProperties\": false',\n\n // Heartbeat patterns\n $H01: \"HEARTBEAT_OK\",\n $H02: \"Read HEARTBEAT.md if it exists\",\n\n // Role markers\n $R01: '\"role\": \"system\"',\n $R02: '\"role\": \"user\"',\n $R03: '\"role\": \"assistant\"',\n $R04: '\"role\": \"tool\"',\n\n // Common endings/phrases\n $E01: \"would you like to\",\n $E02: \"Let me know if you\",\n $E03: \"internal APIs\",\n $E04: \"session cookies\",\n\n // BlockRun model aliases (common in prompts)\n $M01: \"blockrun/\",\n $M02: \"openai/\",\n $M03: \"anthropic/\",\n $M04: \"google/\",\n $M05: \"xai/\",\n};\n\n/**\n * Get the inverse codebook for decompression.\n */\nexport function getInverseCodebook(): Record {\n const inverse: Record = {};\n for (const [code, phrase] of Object.entries(STATIC_CODEBOOK)) {\n inverse[phrase] = code;\n }\n return inverse;\n}\n\n/**\n * Generate the codebook header for inclusion in system message.\n * LLMs can decode in-context using this header.\n */\nexport function generateCodebookHeader(\n usedCodes: Set,\n pathMap: Record = {},\n): string {\n if (usedCodes.size === 0 && Object.keys(pathMap).length === 0) {\n return \"\";\n }\n\n const parts: string[] = [];\n\n // Add used dictionary codes\n if (usedCodes.size > 0) {\n const codeEntries = Array.from(usedCodes)\n .map((code) => `${code}=${STATIC_CODEBOOK[code]}`)\n .join(\", \");\n parts.push(`[Dict: ${codeEntries}]`);\n }\n\n // Add path map\n if (Object.keys(pathMap).length > 0) {\n const pathEntries = Object.entries(pathMap)\n .map(([code, path]) => `${code}=${path}`)\n .join(\", \");\n parts.push(`[Paths: ${pathEntries}]`);\n }\n\n return parts.join(\"\\n\");\n}\n\n/**\n * Decompress a string using the codebook (for logging).\n */\nexport function decompressContent(\n content: string,\n codebook: Record = STATIC_CODEBOOK,\n): string {\n let result = content;\n for (const [code, phrase] of Object.entries(codebook)) {\n result = result.split(code).join(phrase);\n }\n return result;\n}\n","/**\n * Layer 3: Dictionary Encoding\n *\n * Replaces frequently repeated long phrases with short codes.\n * Uses a static codebook of common patterns from production logs.\n *\n * Safe for LLM: Reversible substitution with codebook header.\n * Expected savings: 4-8%\n */\n\nimport { NormalizedMessage } from \"../types\";\nimport { getInverseCodebook } from \"../codebook\";\n\nexport interface DictionaryResult {\n messages: NormalizedMessage[];\n substitutionCount: number;\n usedCodes: Set;\n charsSaved: number;\n}\n\n/**\n * Apply dictionary encoding to a string.\n * Returns the encoded string and stats.\n */\nfunction encodeContent(\n content: string,\n inverseCodebook: Record,\n): { encoded: string; substitutions: number; codes: Set; charsSaved: number } {\n // Defensive type check - content might be array/object for multimodal messages\n if (!content || typeof content !== \"string\") {\n return { encoded: content, substitutions: 0, codes: new Set(), charsSaved: 0 };\n }\n let encoded = content;\n let substitutions = 0;\n let charsSaved = 0;\n const codes = new Set();\n\n // Sort phrases by length (longest first) to avoid partial matches\n const phrases = Object.keys(inverseCodebook).sort((a, b) => b.length - a.length);\n\n for (const phrase of phrases) {\n const code = inverseCodebook[phrase];\n const regex = new RegExp(escapeRegex(phrase), \"g\");\n const matches = encoded.match(regex);\n\n if (matches && matches.length > 0) {\n encoded = encoded.replace(regex, code);\n substitutions += matches.length;\n charsSaved += matches.length * (phrase.length - code.length);\n codes.add(code);\n }\n }\n\n return { encoded, substitutions, codes, charsSaved };\n}\n\n/**\n * Escape special regex characters in a string.\n */\nfunction escapeRegex(str: string): string {\n return str.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n}\n\n/**\n * Apply dictionary encoding to all messages.\n */\nexport function encodeMessages(messages: NormalizedMessage[]): DictionaryResult {\n const inverseCodebook = getInverseCodebook();\n let totalSubstitutions = 0;\n let totalCharsSaved = 0;\n const allUsedCodes = new Set();\n\n const result = messages.map((message) => {\n // Only process string content (skip arrays for multimodal messages)\n if (!message.content || typeof message.content !== \"string\") return message;\n\n const { encoded, substitutions, codes, charsSaved } = encodeContent(\n message.content,\n inverseCodebook,\n );\n\n totalSubstitutions += substitutions;\n totalCharsSaved += charsSaved;\n codes.forEach((code) => allUsedCodes.add(code));\n\n return {\n ...message,\n content: encoded,\n };\n });\n\n return {\n messages: result,\n substitutionCount: totalSubstitutions,\n usedCodes: allUsedCodes,\n charsSaved: totalCharsSaved,\n };\n}\n","/**\n * Layer 4: Path Shortening\n *\n * Detects common filesystem path prefixes and replaces them with short codes.\n * Common in coding assistant contexts with repeated file paths.\n *\n * Safe for LLM: Lossless abbreviation with path map header.\n * Expected savings: 1-3%\n */\n\nimport { NormalizedMessage } from \"../types\";\n\nexport interface PathShorteningResult {\n messages: NormalizedMessage[];\n pathMap: Record; // $P1 -> /home/user/project/\n charsSaved: number;\n}\n\n// Regex to match filesystem paths\nconst PATH_REGEX = /(?:\\/[\\w.-]+){3,}/g;\n\n/**\n * Extract all paths from messages and find common prefixes.\n */\nfunction extractPaths(messages: NormalizedMessage[]): string[] {\n const paths: string[] = [];\n\n for (const message of messages) {\n // Only process string content (skip arrays for multimodal messages)\n if (!message.content || typeof message.content !== \"string\") continue;\n const matches = message.content.match(PATH_REGEX);\n if (matches) {\n paths.push(...matches);\n }\n }\n\n return paths;\n}\n\n/**\n * Group paths by their common prefixes.\n * Returns prefixes that appear at least 3 times.\n */\nfunction findFrequentPrefixes(paths: string[]): string[] {\n const prefixCounts = new Map();\n\n for (const path of paths) {\n const parts = path.split(\"/\").filter(Boolean);\n\n // Try prefixes of different lengths\n for (let i = 2; i < parts.length; i++) {\n const prefix = \"/\" + parts.slice(0, i).join(\"/\") + \"/\";\n prefixCounts.set(prefix, (prefixCounts.get(prefix) || 0) + 1);\n }\n }\n\n // Return prefixes that appear 3+ times, sorted by length (longest first)\n return Array.from(prefixCounts.entries())\n .filter(([, count]) => count >= 3)\n .sort((a, b) => b[0].length - a[0].length)\n .slice(0, 5) // Max 5 path codes\n .map(([prefix]) => prefix);\n}\n\n/**\n * Apply path shortening to all messages.\n */\nexport function shortenPaths(messages: NormalizedMessage[]): PathShorteningResult {\n const allPaths = extractPaths(messages);\n\n if (allPaths.length < 5) {\n // Not enough paths to benefit from shortening\n return {\n messages,\n pathMap: {},\n charsSaved: 0,\n };\n }\n\n const prefixes = findFrequentPrefixes(allPaths);\n\n if (prefixes.length === 0) {\n return {\n messages,\n pathMap: {},\n charsSaved: 0,\n };\n }\n\n // Create path map\n const pathMap: Record = {};\n prefixes.forEach((prefix, i) => {\n pathMap[`$P${i + 1}`] = prefix;\n });\n\n // Replace paths in messages\n let charsSaved = 0;\n\n const result = messages.map((message) => {\n // Only process string content (skip arrays for multimodal messages)\n if (!message.content || typeof message.content !== \"string\") return message;\n\n let content = message.content;\n const originalLength = content.length;\n\n // Replace prefixes (longest first to avoid partial replacements)\n for (const [code, prefix] of Object.entries(pathMap)) {\n content = content.split(prefix).join(code + \"/\");\n }\n\n charsSaved += originalLength - content.length;\n\n return {\n ...message,\n content,\n };\n });\n\n return {\n messages: result,\n pathMap,\n charsSaved,\n };\n}\n\n/**\n * Generate the path map header for the codebook.\n */\nexport function generatePathMapHeader(pathMap: Record): string {\n if (Object.keys(pathMap).length === 0) return \"\";\n\n const entries = Object.entries(pathMap)\n .map(([code, path]) => `${code}=${path}`)\n .join(\", \");\n\n return `[Paths: ${entries}]`;\n}\n","/**\n * Layer 5: JSON Compaction\n *\n * Minifies JSON in tool_call arguments and tool results.\n * Removes pretty-print whitespace from JSON strings.\n *\n * Safe for LLM: JSON semantics unchanged.\n * Expected savings: 2-4%\n */\n\nimport { NormalizedMessage, ToolCall } from \"../types\";\n\nexport interface JsonCompactResult {\n messages: NormalizedMessage[];\n charsSaved: number;\n}\n\n/**\n * Compact a JSON string by parsing and re-stringifying without formatting.\n */\nfunction compactJson(jsonString: string): string {\n try {\n const parsed = JSON.parse(jsonString);\n return JSON.stringify(parsed);\n } catch {\n // Not valid JSON, return as-is\n return jsonString;\n }\n}\n\n/**\n * Check if a string looks like JSON (starts with { or [).\n */\nfunction looksLikeJson(str: string): boolean {\n const trimmed = str.trim();\n return (\n (trimmed.startsWith(\"{\") && trimmed.endsWith(\"}\")) ||\n (trimmed.startsWith(\"[\") && trimmed.endsWith(\"]\"))\n );\n}\n\n/**\n * Compact tool_call arguments in a message.\n */\nfunction compactToolCalls(toolCalls: ToolCall[]): ToolCall[] {\n return toolCalls.map((tc) => ({\n ...tc,\n function: {\n ...tc.function,\n arguments: compactJson(tc.function.arguments),\n },\n }));\n}\n\n/**\n * Apply JSON compaction to all messages.\n *\n * Targets:\n * - tool_call arguments (in assistant messages)\n * - tool message content (often JSON)\n */\nexport function compactMessagesJson(messages: NormalizedMessage[]): JsonCompactResult {\n let charsSaved = 0;\n\n const result = messages.map((message) => {\n const newMessage = { ...message };\n\n // Compact tool_calls arguments\n if (message.tool_calls && message.tool_calls.length > 0) {\n const originalLength = JSON.stringify(message.tool_calls).length;\n newMessage.tool_calls = compactToolCalls(message.tool_calls);\n const newLength = JSON.stringify(newMessage.tool_calls).length;\n charsSaved += originalLength - newLength;\n }\n\n // Compact tool message content if it looks like JSON\n // Only process string content (skip arrays for multimodal messages)\n if (\n message.role === \"tool\" &&\n message.content &&\n typeof message.content === \"string\" &&\n looksLikeJson(message.content)\n ) {\n const originalLength = message.content.length;\n const compacted = compactJson(message.content);\n charsSaved += originalLength - compacted.length;\n newMessage.content = compacted;\n }\n\n return newMessage;\n });\n\n return {\n messages: result,\n charsSaved,\n };\n}\n","/**\n * L6: Observation Compression (AGGRESSIVE)\n *\n * Inspired by claw-compactor's 97% compression on tool results.\n * Tool call results (especially large ones) are summarized to key info only.\n *\n * This is the biggest compression win - tool outputs can be 10KB+ but\n * only ~200 chars of actual useful information.\n */\n\nimport { NormalizedMessage } from \"../types\";\n\ninterface ObservationResult {\n messages: NormalizedMessage[];\n charsSaved: number;\n observationsCompressed: number;\n}\n\n// Max length for tool results before compression kicks in\nconst TOOL_RESULT_THRESHOLD = 500;\n\n// Max length to compress tool results down to\nconst COMPRESSED_RESULT_MAX = 300;\n\n/**\n * Extract key information from tool result.\n * Keeps: errors, key values, status, first/last important lines.\n */\nfunction compressToolResult(content: string): string {\n if (!content || content.length <= TOOL_RESULT_THRESHOLD) {\n return content;\n }\n\n const lines = content\n .split(\"\\n\")\n .map((l) => l.trim())\n .filter(Boolean);\n\n // Priority 1: Error messages (always keep)\n const errorLines = lines.filter(\n (l) => /error|exception|failed|denied|refused|timeout|invalid/i.test(l) && l.length < 200,\n );\n\n // Priority 2: Status/result lines\n const statusLines = lines.filter(\n (l) =>\n /success|complete|created|updated|found|result|status|total|count/i.test(l) && l.length < 150,\n );\n\n // Priority 3: Key JSON fields (extract important values)\n const jsonMatches: string[] = [];\n const jsonPattern = /\"(id|name|status|error|message|count|total|url|path)\":\\s*\"?([^\",}\\n]+)\"?/gi;\n let match;\n while ((match = jsonPattern.exec(content)) !== null) {\n jsonMatches.push(`${match[1]}: ${match[2].slice(0, 50)}`);\n }\n\n // Priority 4: First and last meaningful lines\n const firstLine = lines[0]?.slice(0, 100);\n const lastLine = lines.length > 1 ? lines[lines.length - 1]?.slice(0, 100) : \"\";\n\n // Build compressed observation\n const parts: string[] = [];\n\n if (errorLines.length > 0) {\n parts.push(\"[ERR] \" + errorLines.slice(0, 3).join(\" | \"));\n }\n\n if (statusLines.length > 0) {\n parts.push(statusLines.slice(0, 3).join(\" | \"));\n }\n\n if (jsonMatches.length > 0) {\n parts.push(jsonMatches.slice(0, 5).join(\", \"));\n }\n\n if (parts.length === 0) {\n // Fallback: keep first/last lines with truncation marker\n parts.push(firstLine || \"\");\n if (lines.length > 2) {\n parts.push(`[...${lines.length - 2} lines...]`);\n }\n if (lastLine && lastLine !== firstLine) {\n parts.push(lastLine);\n }\n }\n\n let result = parts.join(\"\\n\");\n\n // Final length cap\n if (result.length > COMPRESSED_RESULT_MAX) {\n result = result.slice(0, COMPRESSED_RESULT_MAX - 20) + \"\\n[...truncated]\";\n }\n\n return result;\n}\n\n/**\n * Compress large repeated content blocks.\n * Detects when same large block appears multiple times.\n */\nfunction deduplicateLargeBlocks(messages: NormalizedMessage[]): {\n messages: NormalizedMessage[];\n charsSaved: number;\n} {\n const blockHashes = new Map(); // hash -> first occurrence index\n let charsSaved = 0;\n\n const result = messages.map((msg, idx) => {\n // Only process string content (skip arrays for multimodal messages)\n if (!msg.content || typeof msg.content !== \"string\" || msg.content.length < 500) {\n return msg;\n }\n\n // Hash first 200 chars as block identifier\n const blockKey = msg.content.slice(0, 200);\n\n if (blockHashes.has(blockKey)) {\n const firstIdx = blockHashes.get(blockKey)!;\n const original = msg.content;\n const compressed = `[See message #${firstIdx + 1} - same content]`;\n charsSaved += original.length - compressed.length;\n return { ...msg, content: compressed };\n }\n\n blockHashes.set(blockKey, idx);\n return msg;\n });\n\n return { messages: result, charsSaved };\n}\n\n/**\n * Compress tool results in messages.\n */\nexport function compressObservations(messages: NormalizedMessage[]): ObservationResult {\n let charsSaved = 0;\n let observationsCompressed = 0;\n\n // First pass: compress individual tool results\n let result = messages.map((msg) => {\n // Only compress tool role messages (these are tool call results)\n // Only process string content (skip arrays for multimodal messages)\n if (msg.role !== \"tool\" || !msg.content || typeof msg.content !== \"string\") {\n return msg;\n }\n\n const original = msg.content;\n if (original.length <= TOOL_RESULT_THRESHOLD) {\n return msg;\n }\n\n const compressed = compressToolResult(original);\n const saved = original.length - compressed.length;\n\n if (saved > 50) {\n charsSaved += saved;\n observationsCompressed++;\n return { ...msg, content: compressed };\n }\n\n return msg;\n });\n\n // Second pass: deduplicate large repeated blocks\n const dedupResult = deduplicateLargeBlocks(result);\n result = dedupResult.messages;\n charsSaved += dedupResult.charsSaved;\n\n return {\n messages: result,\n charsSaved,\n observationsCompressed,\n };\n}\n","/**\n * L7: Dynamic Codebook Builder\n *\n * Inspired by claw-compactor's frequency-based codebook.\n * Builds codebook from actual content being compressed,\n * rather than relying on static patterns.\n *\n * Finds phrases that appear 3+ times and replaces with short codes.\n */\n\nimport { NormalizedMessage } from \"../types\";\n\ninterface DynamicCodebookResult {\n messages: NormalizedMessage[];\n charsSaved: number;\n dynamicCodes: Record; // code -> phrase\n substitutions: number;\n}\n\n// Config\nconst MIN_PHRASE_LENGTH = 20;\nconst MAX_PHRASE_LENGTH = 200;\nconst MIN_FREQUENCY = 3;\nconst MAX_ENTRIES = 100;\nconst CODE_PREFIX = \"$D\"; // Dynamic codes: $D01, $D02, etc.\n\n/**\n * Find repeated phrases in content.\n */\nfunction findRepeatedPhrases(allContent: string): Map {\n const phrases = new Map();\n\n // Split by sentence-like boundaries\n const segments = allContent.split(/(?<=[.!?\\n])\\s+/);\n\n for (const segment of segments) {\n const trimmed = segment.trim();\n if (trimmed.length >= MIN_PHRASE_LENGTH && trimmed.length <= MAX_PHRASE_LENGTH) {\n phrases.set(trimmed, (phrases.get(trimmed) || 0) + 1);\n }\n }\n\n // Also find repeated lines\n const lines = allContent.split(\"\\n\");\n for (const line of lines) {\n const trimmed = line.trim();\n if (trimmed.length >= MIN_PHRASE_LENGTH && trimmed.length <= MAX_PHRASE_LENGTH) {\n phrases.set(trimmed, (phrases.get(trimmed) || 0) + 1);\n }\n }\n\n return phrases;\n}\n\n/**\n * Build dynamic codebook from message content.\n */\nfunction buildDynamicCodebook(messages: NormalizedMessage[]): Record {\n // Combine all content\n let allContent = \"\";\n for (const msg of messages) {\n // Only process string content (skip arrays for multimodal messages)\n if (msg.content && typeof msg.content === \"string\") {\n allContent += msg.content + \"\\n\";\n }\n }\n\n // Find repeated phrases\n const phrases = findRepeatedPhrases(allContent);\n\n // Filter by frequency and sort by savings potential\n const candidates: Array<{ phrase: string; count: number; savings: number }> = [];\n for (const [phrase, count] of phrases.entries()) {\n if (count >= MIN_FREQUENCY) {\n // Savings = (phrase length - code length) * occurrences\n const codeLength = 4; // e.g., \"$D01\"\n const savings = (phrase.length - codeLength) * count;\n if (savings > 50) {\n candidates.push({ phrase, count, savings });\n }\n }\n }\n\n // Sort by savings (descending) and take top entries\n candidates.sort((a, b) => b.savings - a.savings);\n const topCandidates = candidates.slice(0, MAX_ENTRIES);\n\n // Build codebook\n const codebook: Record = {};\n topCandidates.forEach((c, i) => {\n const code = `${CODE_PREFIX}${String(i + 1).padStart(2, \"0\")}`;\n codebook[code] = c.phrase;\n });\n\n return codebook;\n}\n\n/**\n * Escape special regex characters.\n */\nfunction escapeRegex(str: string): string {\n // Defensive type check\n if (!str || typeof str !== \"string\") return \"\";\n return str.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n}\n\n/**\n * Apply dynamic codebook to messages.\n */\nexport function applyDynamicCodebook(messages: NormalizedMessage[]): DynamicCodebookResult {\n // Build codebook from content\n const codebook = buildDynamicCodebook(messages);\n\n if (Object.keys(codebook).length === 0) {\n return {\n messages,\n charsSaved: 0,\n dynamicCodes: {},\n substitutions: 0,\n };\n }\n\n // Create inverse map for replacement\n const phraseToCode: Record = {};\n for (const [code, phrase] of Object.entries(codebook)) {\n phraseToCode[phrase] = code;\n }\n\n // Sort phrases by length (longest first) to avoid partial replacements\n const sortedPhrases = Object.keys(phraseToCode).sort((a, b) => b.length - a.length);\n\n let charsSaved = 0;\n let substitutions = 0;\n\n // Apply replacements\n const result = messages.map((msg) => {\n // Only process string content (skip arrays for multimodal messages)\n if (!msg.content || typeof msg.content !== \"string\") return msg;\n\n let content = msg.content;\n for (const phrase of sortedPhrases) {\n const code = phraseToCode[phrase];\n const regex = new RegExp(escapeRegex(phrase), \"g\");\n const matches = content.match(regex);\n if (matches) {\n content = content.replace(regex, code);\n charsSaved += (phrase.length - code.length) * matches.length;\n substitutions += matches.length;\n }\n }\n\n return { ...msg, content };\n });\n\n return {\n messages: result,\n charsSaved,\n dynamicCodes: codebook,\n substitutions,\n };\n}\n\n/**\n * Generate header for dynamic codes (to include in system message).\n */\nexport function generateDynamicCodebookHeader(codebook: Record): string {\n if (Object.keys(codebook).length === 0) return \"\";\n\n const entries = Object.entries(codebook)\n .slice(0, 20) // Limit header size\n .map(([code, phrase]) => {\n // Truncate long phrases in header\n const displayPhrase = phrase.length > 40 ? phrase.slice(0, 37) + \"...\" : phrase;\n return `${code}=${displayPhrase}`;\n })\n .join(\", \");\n\n return `[DynDict: ${entries}]`;\n}\n","/**\n * LLM-Safe Context Compression\n *\n * Reduces token usage by 15-40% while preserving semantic meaning.\n * Implements 7 compression layers inspired by claw-compactor.\n *\n * Usage:\n * const result = await compressContext(messages);\n * // result.messages -> compressed version to send to provider\n * // result.originalMessages -> original for logging\n */\n\nimport {\n NormalizedMessage,\n CompressionConfig,\n CompressionResult,\n CompressionStats,\n DEFAULT_COMPRESSION_CONFIG,\n} from \"./types\";\nimport { deduplicateMessages } from \"./layers/deduplication\";\nimport { normalizeMessagesWhitespace } from \"./layers/whitespace\";\nimport { encodeMessages } from \"./layers/dictionary\";\nimport { shortenPaths } from \"./layers/paths\";\nimport { compactMessagesJson } from \"./layers/json-compact\";\nimport { compressObservations } from \"./layers/observation\";\nimport { applyDynamicCodebook, generateDynamicCodebookHeader } from \"./layers/dynamic-codebook\";\nimport { generateCodebookHeader, STATIC_CODEBOOK } from \"./codebook\";\n\nexport * from \"./types\";\nexport { STATIC_CODEBOOK } from \"./codebook\";\n\n/**\n * Calculate total character count for messages.\n */\nfunction calculateTotalChars(messages: NormalizedMessage[]): number {\n return messages.reduce((total, msg) => {\n let chars = 0;\n if (typeof msg.content === \"string\") {\n chars = msg.content.length;\n } else if (Array.isArray(msg.content)) {\n // For multimodal content, stringify to get approximate size\n chars = JSON.stringify(msg.content).length;\n }\n if (msg.tool_calls) {\n chars += JSON.stringify(msg.tool_calls).length;\n }\n return total + chars;\n }, 0);\n}\n\n/**\n * Deep clone messages to preserve originals.\n */\nfunction cloneMessages(messages: NormalizedMessage[]): NormalizedMessage[] {\n return JSON.parse(JSON.stringify(messages));\n}\n\n/**\n * Prepend codebook header to the first USER message (not system).\n *\n * Why not system message?\n * - Google Gemini uses systemInstruction which doesn't support codebook format\n * - The codebook header in user message is still visible to all LLMs\n * - This ensures compatibility across all providers\n */\nfunction prependCodebookHeader(\n messages: NormalizedMessage[],\n usedCodes: Set,\n pathMap: Record,\n): NormalizedMessage[] {\n const header = generateCodebookHeader(usedCodes, pathMap);\n if (!header) return messages;\n\n // Find first user message (not system - Google's systemInstruction doesn't support codebook)\n const userIndex = messages.findIndex((m) => m.role === \"user\");\n\n if (userIndex === -1) {\n // No user message, add codebook as system (fallback)\n return [{ role: \"system\", content: header }, ...messages];\n }\n\n // Prepend to first user message (only if content is a string)\n return messages.map((msg, i) => {\n if (i === userIndex) {\n // Only prepend to string content - skip arrays (multimodal messages)\n if (typeof msg.content === \"string\") {\n return {\n ...msg,\n content: `${header}\\n\\n${msg.content}`,\n };\n }\n // For non-string content, don't modify the message\n // The codebook header would corrupt array content\n }\n return msg;\n });\n}\n\n/**\n * Main compression function.\n *\n * Applies 5 layers in sequence:\n * 1. Deduplication - Remove exact duplicate messages\n * 2. Whitespace - Normalize excessive whitespace\n * 3. Dictionary - Replace common phrases with codes\n * 4. Paths - Shorten repeated file paths\n * 5. JSON - Compact JSON in tool calls\n *\n * Then prepends a codebook header for the LLM to decode in-context.\n */\nexport async function compressContext(\n messages: NormalizedMessage[],\n config: Partial = {},\n): Promise {\n const fullConfig: CompressionConfig = {\n ...DEFAULT_COMPRESSION_CONFIG,\n ...config,\n layers: {\n ...DEFAULT_COMPRESSION_CONFIG.layers,\n ...config.layers,\n },\n dictionary: {\n ...DEFAULT_COMPRESSION_CONFIG.dictionary,\n ...config.dictionary,\n },\n };\n\n // If compression disabled, return as-is\n if (!fullConfig.enabled) {\n const originalChars = calculateTotalChars(messages);\n return {\n messages,\n originalMessages: messages,\n originalChars,\n compressedChars: originalChars,\n compressionRatio: 1,\n stats: {\n duplicatesRemoved: 0,\n whitespaceSavedChars: 0,\n dictionarySubstitutions: 0,\n pathsShortened: 0,\n jsonCompactedChars: 0,\n observationsCompressed: 0,\n observationCharsSaved: 0,\n dynamicSubstitutions: 0,\n dynamicCharsSaved: 0,\n },\n codebook: {},\n pathMap: {},\n dynamicCodes: {},\n };\n }\n\n // Preserve originals for logging\n const originalMessages = fullConfig.preserveRaw ? cloneMessages(messages) : messages;\n const originalChars = calculateTotalChars(messages);\n\n // Initialize stats\n const stats: CompressionStats = {\n duplicatesRemoved: 0,\n whitespaceSavedChars: 0,\n dictionarySubstitutions: 0,\n pathsShortened: 0,\n jsonCompactedChars: 0,\n observationsCompressed: 0,\n observationCharsSaved: 0,\n dynamicSubstitutions: 0,\n dynamicCharsSaved: 0,\n };\n\n let result = cloneMessages(messages);\n let usedCodes = new Set();\n let pathMap: Record = {};\n let dynamicCodes: Record = {};\n\n // Layer 1: Deduplication\n if (fullConfig.layers.deduplication) {\n const dedupResult = deduplicateMessages(result);\n result = dedupResult.messages;\n stats.duplicatesRemoved = dedupResult.duplicatesRemoved;\n }\n\n // Layer 2: Whitespace normalization\n if (fullConfig.layers.whitespace) {\n const wsResult = normalizeMessagesWhitespace(result);\n result = wsResult.messages;\n stats.whitespaceSavedChars = wsResult.charsSaved;\n }\n\n // Layer 3: Dictionary encoding\n if (fullConfig.layers.dictionary) {\n const dictResult = encodeMessages(result);\n result = dictResult.messages;\n stats.dictionarySubstitutions = dictResult.substitutionCount;\n usedCodes = dictResult.usedCodes;\n }\n\n // Layer 4: Path shortening\n if (fullConfig.layers.paths) {\n const pathResult = shortenPaths(result);\n result = pathResult.messages;\n pathMap = pathResult.pathMap;\n stats.pathsShortened = Object.keys(pathMap).length;\n }\n\n // Layer 5: JSON compaction\n if (fullConfig.layers.jsonCompact) {\n const jsonResult = compactMessagesJson(result);\n result = jsonResult.messages;\n stats.jsonCompactedChars = jsonResult.charsSaved;\n }\n\n // Layer 6: Observation compression (BIG WIN - 97% on tool results)\n if (fullConfig.layers.observation) {\n const obsResult = compressObservations(result);\n result = obsResult.messages;\n stats.observationsCompressed = obsResult.observationsCompressed;\n stats.observationCharsSaved = obsResult.charsSaved;\n }\n\n // Layer 7: Dynamic codebook (learns from actual content)\n if (fullConfig.layers.dynamicCodebook) {\n const dynResult = applyDynamicCodebook(result);\n result = dynResult.messages;\n stats.dynamicSubstitutions = dynResult.substitutions;\n stats.dynamicCharsSaved = dynResult.charsSaved;\n dynamicCodes = dynResult.dynamicCodes;\n }\n\n // Add codebook header if enabled and we have codes to include\n if (\n fullConfig.dictionary.includeCodebookHeader &&\n (usedCodes.size > 0 || Object.keys(pathMap).length > 0 || Object.keys(dynamicCodes).length > 0)\n ) {\n result = prependCodebookHeader(result, usedCodes, pathMap);\n // Also add dynamic codebook header if we have dynamic codes\n if (Object.keys(dynamicCodes).length > 0) {\n const dynHeader = generateDynamicCodebookHeader(dynamicCodes);\n if (dynHeader) {\n const systemIndex = result.findIndex((m) => m.role === \"system\");\n // Only prepend to string content - skip arrays (multimodal messages)\n if (systemIndex >= 0 && typeof result[systemIndex].content === \"string\") {\n result[systemIndex] = {\n ...result[systemIndex],\n content: `${dynHeader}\\n${result[systemIndex].content}`,\n };\n }\n }\n }\n }\n\n // Calculate final stats\n const compressedChars = calculateTotalChars(result);\n const compressionRatio = compressedChars / originalChars;\n\n // Build used codebook for logging\n const usedCodebook: Record = {};\n usedCodes.forEach((code) => {\n usedCodebook[code] = STATIC_CODEBOOK[code];\n });\n\n return {\n messages: result,\n originalMessages,\n originalChars,\n compressedChars,\n compressionRatio,\n stats,\n codebook: usedCodebook,\n pathMap,\n dynamicCodes,\n };\n}\n\n/**\n * Quick check if compression would benefit these messages.\n * Returns true if messages are large enough to warrant compression.\n */\nexport function shouldCompress(messages: NormalizedMessage[]): boolean {\n const chars = calculateTotalChars(messages);\n // Only compress if > 5000 chars (roughly 1000 tokens)\n return chars > 5000;\n}\n","/**\n * Session Persistence Store\n *\n * Tracks model selections per session to prevent model switching mid-task.\n * When a session is active, the router will continue using the same model\n * instead of re-routing each request.\n */\n\nimport { createHash } from \"node:crypto\";\n\nexport type SessionEntry = {\n model: string;\n tier: string;\n createdAt: number;\n lastUsedAt: number;\n requestCount: number;\n // --- Three-strike escalation ---\n recentHashes: string[]; // Sliding window of last 3 request content fingerprints\n strikes: number; // Consecutive similar request count\n escalated: boolean; // Whether session was already escalated via three-strike\n};\n\nexport type SessionConfig = {\n /** Enable session persistence (default: false) */\n enabled: boolean;\n /** Session timeout in ms (default: 30 minutes) */\n timeoutMs: number;\n /** Header name for session ID (default: X-Session-ID) */\n headerName: string;\n};\n\nexport const DEFAULT_SESSION_CONFIG: SessionConfig = {\n enabled: true,\n timeoutMs: 30 * 60 * 1000, // 30 minutes\n headerName: \"x-session-id\",\n};\n\n/**\n * Session persistence store for maintaining model selections.\n */\nexport class SessionStore {\n private sessions: Map = new Map();\n private config: SessionConfig;\n private cleanupInterval: ReturnType | null = null;\n\n constructor(config: Partial = {}) {\n this.config = { ...DEFAULT_SESSION_CONFIG, ...config };\n\n // Start cleanup interval (every 5 minutes)\n if (this.config.enabled) {\n this.cleanupInterval = setInterval(() => this.cleanup(), 5 * 60 * 1000);\n }\n }\n\n /**\n * Get the pinned model for a session, if any.\n */\n getSession(sessionId: string): SessionEntry | undefined {\n if (!this.config.enabled || !sessionId) {\n return undefined;\n }\n\n const entry = this.sessions.get(sessionId);\n if (!entry) {\n return undefined;\n }\n\n // Check if session has expired\n const now = Date.now();\n if (now - entry.lastUsedAt > this.config.timeoutMs) {\n this.sessions.delete(sessionId);\n return undefined;\n }\n\n return entry;\n }\n\n /**\n * Pin a model to a session.\n */\n setSession(sessionId: string, model: string, tier: string): void {\n if (!this.config.enabled || !sessionId) {\n return;\n }\n\n const existing = this.sessions.get(sessionId);\n const now = Date.now();\n\n if (existing) {\n existing.lastUsedAt = now;\n existing.requestCount++;\n // Update model if different (e.g., fallback)\n if (existing.model !== model) {\n existing.model = model;\n existing.tier = tier;\n }\n } else {\n this.sessions.set(sessionId, {\n model,\n tier,\n createdAt: now,\n lastUsedAt: now,\n requestCount: 1,\n recentHashes: [],\n strikes: 0,\n escalated: false,\n });\n }\n }\n\n /**\n * Touch a session to extend its timeout.\n */\n touchSession(sessionId: string): void {\n if (!this.config.enabled || !sessionId) {\n return;\n }\n\n const entry = this.sessions.get(sessionId);\n if (entry) {\n entry.lastUsedAt = Date.now();\n entry.requestCount++;\n }\n }\n\n /**\n * Clear a specific session.\n */\n clearSession(sessionId: string): void {\n this.sessions.delete(sessionId);\n }\n\n /**\n * Clear all sessions.\n */\n clearAll(): void {\n this.sessions.clear();\n }\n\n /**\n * Get session stats for debugging.\n */\n getStats(): { count: number; sessions: Array<{ id: string; model: string; age: number }> } {\n const now = Date.now();\n const sessions = Array.from(this.sessions.entries()).map(([id, entry]) => ({\n id: id.slice(0, 8) + \"...\",\n model: entry.model,\n age: Math.round((now - entry.createdAt) / 1000),\n }));\n return { count: this.sessions.size, sessions };\n }\n\n /**\n * Clean up expired sessions.\n */\n private cleanup(): void {\n const now = Date.now();\n for (const [id, entry] of this.sessions) {\n if (now - entry.lastUsedAt > this.config.timeoutMs) {\n this.sessions.delete(id);\n }\n }\n }\n\n /**\n * Record a request content hash and detect repetitive patterns.\n * Returns true if escalation should be triggered (3+ consecutive similar requests).\n */\n recordRequestHash(sessionId: string, hash: string): boolean {\n const entry = this.sessions.get(sessionId);\n if (!entry) return false;\n\n const prev = entry.recentHashes;\n if (prev.length > 0 && prev[prev.length - 1] === hash) {\n entry.strikes++;\n } else {\n entry.strikes = 0;\n }\n\n entry.recentHashes.push(hash);\n if (entry.recentHashes.length > 3) {\n entry.recentHashes.shift();\n }\n\n return entry.strikes >= 2 && !entry.escalated;\n }\n\n /**\n * Escalate session to next tier. Returns the new model/tier or null if already at max.\n */\n escalateSession(\n sessionId: string,\n tierConfigs: Record,\n ): { model: string; tier: string } | null {\n const entry = this.sessions.get(sessionId);\n if (!entry) return null;\n\n const TIER_ORDER = [\"SIMPLE\", \"MEDIUM\", \"COMPLEX\", \"REASONING\"];\n const currentIdx = TIER_ORDER.indexOf(entry.tier);\n if (currentIdx < 0 || currentIdx >= TIER_ORDER.length - 1) return null;\n\n const nextTier = TIER_ORDER[currentIdx + 1];\n const nextConfig = tierConfigs[nextTier];\n if (!nextConfig) return null;\n\n entry.model = nextConfig.primary;\n entry.tier = nextTier;\n entry.strikes = 0;\n entry.escalated = true;\n\n return { model: nextConfig.primary, tier: nextTier };\n }\n\n /**\n * Stop the cleanup interval.\n */\n close(): void {\n if (this.cleanupInterval) {\n clearInterval(this.cleanupInterval);\n this.cleanupInterval = null;\n }\n }\n}\n\n/**\n * Generate a session ID from request headers or create a default.\n */\nexport function getSessionId(\n headers: Record,\n headerName: string = DEFAULT_SESSION_CONFIG.headerName,\n): string | undefined {\n const value = headers[headerName] || headers[headerName.toLowerCase()];\n if (typeof value === \"string\" && value.length > 0) {\n return value;\n }\n if (Array.isArray(value) && value.length > 0) {\n return value[0];\n }\n return undefined;\n}\n\n/**\n * Derive a stable session ID from message content when no explicit session\n * header is provided. Uses the first user message as the conversation anchor —\n * same opening message = same session ID across all subsequent turns.\n *\n * This prevents model-switching mid-conversation even when OpenClaw doesn't\n * send an x-session-id header (which is the default OpenClaw behaviour).\n */\nexport function deriveSessionId(\n messages: Array<{ role: string; content: unknown }>,\n): string | undefined {\n const firstUser = messages.find((m) => m.role === \"user\");\n if (!firstUser) return undefined;\n\n const content =\n typeof firstUser.content === \"string\" ? firstUser.content : JSON.stringify(firstUser.content);\n\n // 8-char hex prefix of SHA-256 — short enough for logs, collision-resistant\n // enough for session tracking within a single gateway instance.\n return createHash(\"sha256\").update(content).digest(\"hex\").slice(0, 8);\n}\n\n/**\n * Generate a short hash fingerprint from request content.\n * Captures: last user message text + tool call names (if any).\n * Normalizes whitespace to avoid false negatives from minor formatting diffs.\n */\nexport function hashRequestContent(lastUserContent: string, toolCallNames?: string[]): string {\n const normalized = lastUserContent.replace(/\\s+/g, \" \").trim().slice(0, 500);\n const toolSuffix = toolCallNames?.length ? `|tools:${toolCallNames.sort().join(\",\")}` : \"\";\n return createHash(\"sha256\")\n .update(normalized + toolSuffix)\n .digest(\"hex\")\n .slice(0, 12);\n}\n","/**\n * Auto-update checker for ClawRouter.\n * Checks npm registry on startup and notifies user if update available.\n */\n\nimport { VERSION } from \"./version.js\";\n\nconst NPM_REGISTRY = \"https://registry.npmjs.org/@blockrun/clawrouter/latest\";\nconst CHECK_TIMEOUT_MS = 5_000; // Don't block startup for more than 5s\n\n/**\n * Compare semver versions. Returns:\n * 1 if a > b\n * 0 if a === b\n * -1 if a < b\n */\nfunction compareSemver(a: string, b: string): number {\n const pa = a.split(\".\").map(Number);\n const pb = b.split(\".\").map(Number);\n for (let i = 0; i < 3; i++) {\n if ((pa[i] || 0) > (pb[i] || 0)) return 1;\n if ((pa[i] || 0) < (pb[i] || 0)) return -1;\n }\n return 0;\n}\n\n/**\n * Check npm registry for latest version.\n * Non-blocking, silent on errors.\n */\nexport async function checkForUpdates(): Promise {\n try {\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), CHECK_TIMEOUT_MS);\n\n const res = await fetch(NPM_REGISTRY, {\n signal: controller.signal,\n headers: { Accept: \"application/json\" },\n });\n clearTimeout(timeout);\n\n if (!res.ok) return;\n\n const data = (await res.json()) as { version?: string };\n const latest = data.version;\n\n if (!latest) return;\n\n if (compareSemver(latest, VERSION) > 0) {\n console.log(\"\");\n console.log(`\\x1b[33m⬆️ ClawRouter ${latest} available (you have ${VERSION})\\x1b[0m`);\n console.log(` Run: \\x1b[36mnpx @blockrun/clawrouter@latest\\x1b[0m`);\n console.log(\"\");\n }\n } catch {\n // Silent fail - don't disrupt startup\n }\n}\n","/**\n * Configuration Module\n *\n * Reads environment variables at module load time.\n * Separated from network code to avoid security scanner false positives.\n */\n\nconst DEFAULT_PORT = 8402;\n\n/**\n * Proxy port configuration - resolved once at module load.\n * Reads BLOCKRUN_PROXY_PORT env var or defaults to 8402.\n */\nexport const PROXY_PORT = (() => {\n const envPort = process[\"env\"].BLOCKRUN_PROXY_PORT;\n if (envPort) {\n const parsed = parseInt(envPort, 10);\n if (!isNaN(parsed) && parsed > 0 && parsed < 65536) {\n return parsed;\n }\n }\n return DEFAULT_PORT;\n})();\n","/**\n * Session Journal - Memory layer for ClawRouter\n *\n * Maintains a compact record of key actions per session, enabling agents\n * to recall earlier work even when OpenClaw's sessions_history is truncated.\n *\n * How it works:\n * 1. As LLM responses flow through, extracts key actions (\"I created X\", \"I fixed Y\")\n * 2. Stores them in a compact journal per session\n * 3. When a request mentions past work (\"what did you do today?\"), injects the journal\n */\n\nexport interface JournalEntry {\n timestamp: number;\n action: string; // Compact description: \"Created login component\"\n model?: string;\n}\n\nexport interface SessionJournalConfig {\n /** Maximum entries per session (default: 100) */\n maxEntries?: number;\n /** Maximum age of entries in ms (default: 24 hours) */\n maxAgeMs?: number;\n /** Maximum events to extract per response (default: 5) */\n maxEventsPerResponse?: number;\n}\n\nconst DEFAULT_CONFIG: Required = {\n maxEntries: 100,\n maxAgeMs: 24 * 60 * 60 * 1000, // 24 hours\n maxEventsPerResponse: 5,\n};\n\nexport class SessionJournal {\n private journals: Map = new Map();\n private config: Required;\n\n constructor(config?: SessionJournalConfig) {\n this.config = { ...DEFAULT_CONFIG, ...config };\n }\n\n /**\n * Extract key events from assistant response content.\n * Looks for patterns like \"I created...\", \"I fixed...\", \"Successfully...\"\n */\n extractEvents(content: string): string[] {\n if (!content || typeof content !== \"string\") {\n return [];\n }\n\n const events: string[] = [];\n const seen = new Set();\n\n // Patterns for identifying key actions\n // Note: Patterns allow optional words like \"also\", \"then\", \"have\" between \"I\" and verb\n const patterns = [\n // Creation patterns\n /I (?:also |then |have |)?(?:created|implemented|added|wrote|built|generated|set up|initialized) ([^.!?\\n]{10,150})/gi,\n // Fix patterns\n /I (?:also |then |have |)?(?:fixed|resolved|solved|patched|corrected|addressed|debugged) ([^.!?\\n]{10,150})/gi,\n // Completion patterns\n /I (?:also |then |have |)?(?:completed|finished|done with|wrapped up) ([^.!?\\n]{10,150})/gi,\n // Update patterns\n /I (?:also |then |have |)?(?:updated|modified|changed|refactored|improved|enhanced|optimized) ([^.!?\\n]{10,150})/gi,\n // Success patterns\n /Successfully ([^.!?\\n]{10,150})/gi,\n // Tool usage patterns (when agent uses tools)\n /I (?:also |then |have |)?(?:ran|executed|called|invoked) ([^.!?\\n]{10,100})/gi,\n ];\n\n for (const pattern of patterns) {\n // Reset pattern lastIndex for each iteration\n pattern.lastIndex = 0;\n\n let match;\n while ((match = pattern.exec(content)) !== null) {\n const action = match[0].trim();\n\n // Skip if already seen (dedup)\n const normalized = action.toLowerCase();\n if (seen.has(normalized)) {\n continue;\n }\n\n // Validate length (not too short or too long)\n if (action.length >= 15 && action.length <= 200) {\n events.push(action);\n seen.add(normalized);\n }\n\n // Stop if we have enough events\n if (events.length >= this.config.maxEventsPerResponse) {\n break;\n }\n }\n\n if (events.length >= this.config.maxEventsPerResponse) {\n break;\n }\n }\n\n return events;\n }\n\n /**\n * Record events to the session journal.\n */\n record(sessionId: string, events: string[], model?: string): void {\n if (!sessionId || !events.length) {\n return;\n }\n\n const journal = this.journals.get(sessionId) || [];\n const now = Date.now();\n\n for (const action of events) {\n journal.push({\n timestamp: now,\n action,\n model,\n });\n }\n\n // Trim old entries and enforce max count\n const cutoff = now - this.config.maxAgeMs;\n const trimmed = journal.filter((e) => e.timestamp > cutoff).slice(-this.config.maxEntries);\n\n this.journals.set(sessionId, trimmed);\n }\n\n /**\n * Check if the user message indicates a need for historical context.\n */\n needsContext(lastUserMessage: string): boolean {\n if (!lastUserMessage || typeof lastUserMessage !== \"string\") {\n return false;\n }\n\n const lower = lastUserMessage.toLowerCase();\n\n // Trigger phrases that indicate user wants to recall past work\n const triggers = [\n // Direct questions about past work\n \"what did you do\",\n \"what have you done\",\n \"what did we do\",\n \"what have we done\",\n // Temporal references\n \"earlier\",\n \"before\",\n \"previously\",\n \"this session\",\n \"today\",\n \"so far\",\n // Summary requests\n \"remind me\",\n \"summarize\",\n \"summary of\",\n \"recap\",\n // Progress inquiries\n \"your work\",\n \"your progress\",\n \"accomplished\",\n \"achievements\",\n \"completed tasks\",\n ];\n\n return triggers.some((t) => lower.includes(t));\n }\n\n /**\n * Format the journal for injection into system message.\n * Returns null if journal is empty.\n */\n format(sessionId: string): string | null {\n const journal = this.journals.get(sessionId);\n if (!journal?.length) {\n return null;\n }\n\n const lines = journal.map((e) => {\n const time = new Date(e.timestamp).toLocaleTimeString(\"en-US\", {\n hour: \"2-digit\",\n minute: \"2-digit\",\n hour12: true,\n });\n return `- ${time}: ${e.action}`;\n });\n\n return `[Session Memory - Key Actions]\\n${lines.join(\"\\n\")}`;\n }\n\n /**\n * Get the raw journal entries for a session (for debugging/testing).\n */\n getEntries(sessionId: string): JournalEntry[] {\n return this.journals.get(sessionId) || [];\n }\n\n /**\n * Clear journal for a specific session.\n */\n clear(sessionId: string): void {\n this.journals.delete(sessionId);\n }\n\n /**\n * Clear all journals.\n */\n clearAll(): void {\n this.journals.clear();\n }\n\n /**\n * Get stats about the journal.\n */\n getStats(): { sessions: number; totalEntries: number } {\n let totalEntries = 0;\n for (const entries of this.journals.values()) {\n totalEntries += entries.length;\n }\n return {\n sessions: this.journals.size,\n totalEntries,\n };\n }\n}\n","/**\n * End-to-end test for smart routing + proxy.\n *\n * Part 1: Router classification (no network, no wallet needed)\n * Part 2: Proxy startup + live request (requires BLOCKRUN_WALLET_KEY with funded USDC)\n *\n * Usage:\n * npx tsup test/e2e.ts --format esm --outDir test/dist --no-dts && node test/dist/e2e.js\n */\n\nimport { route, DEFAULT_ROUTING_CONFIG, type RoutingDecision } from \"../src/router/index.js\";\nimport { classifyByRules } from \"../src/router/rules.js\";\nimport { BLOCKRUN_MODELS } from \"../src/models.js\";\nimport { startProxy } from \"../src/proxy.js\";\nimport type { ModelPricing } from \"../src/router/selector.js\";\n\n// ─── Helpers ───\n\nfunction buildModelPricing(): Map {\n const map = new Map();\n for (const m of BLOCKRUN_MODELS) {\n if (m.id === \"blockrun/auto\") continue;\n map.set(m.id, { inputPrice: m.inputPrice, outputPrice: m.outputPrice });\n }\n return map;\n}\n\nlet passed = 0;\nlet failed = 0;\n\nfunction assert(condition: boolean, msg: string) {\n if (condition) {\n console.log(` ✓ ${msg}`);\n passed++;\n } else {\n console.error(` ✗ FAIL: ${msg}`);\n failed++;\n }\n}\n\n// ─── Part 1: Rule-Based Classifier ───\n\nconsole.log(\"\\n═══ Part 1: Rule-Based Classifier ═══\\n\");\n\nconst config = DEFAULT_ROUTING_CONFIG;\n\n// Simple queries\n{\n console.log(\"Simple queries:\");\n const r1 = classifyByRules(\"What is the capital of France?\", undefined, 8, config.scoring);\n assert(\n r1.tier === \"SIMPLE\",\n `\"What is the capital of France?\" → ${r1.tier} (score=${r1.score.toFixed(3)})`,\n );\n\n const r2 = classifyByRules(\"Hello\", undefined, 2, config.scoring);\n assert(r2.tier === \"SIMPLE\", `\"Hello\" → ${r2.tier} (score=${r2.score.toFixed(3)})`);\n\n const r3 = classifyByRules(\"Define photosynthesis\", undefined, 4, config.scoring);\n // With adjusted weights, this may route to SIMPLE or MEDIUM\n assert(\n r3.tier === \"SIMPLE\" || r3.tier === \"MEDIUM\" || r3.tier === null,\n `\"Define photosynthesis\" → ${r3.tier} (score=${r3.score.toFixed(3)})`,\n );\n\n const r4 = classifyByRules(\"Translate hello to Spanish\", undefined, 6, config.scoring);\n assert(\n r4.tier === \"SIMPLE\",\n `\"Translate hello to Spanish\" → ${r4.tier} (score=${r4.score.toFixed(3)})`,\n );\n\n const r5 = classifyByRules(\"Yes or no: is the sky blue?\", undefined, 8, config.scoring);\n assert(\n r5.tier === \"SIMPLE\",\n `\"Yes or no: is the sky blue?\" → ${r5.tier} (score=${r5.score.toFixed(3)})`,\n );\n}\n\n// System prompt with reasoning/agentic keywords should NOT affect simple queries\n// Bug: if client's system prompt had \"step by step\" / \"edit files\" / \"fix bugs\", ALL queries became REASONING/agentic\n{\n console.log(\"\\nSystem prompt with reasoning keywords (should NOT affect simple queries):\");\n const systemPrompt = \"Think step by step and reason logically about the user's question.\";\n\n const r1 = classifyByRules(\"What is 2+2?\", systemPrompt, 10, config.scoring);\n assert(\n r1.tier === \"SIMPLE\",\n `\"2+2\" with reasoning system prompt → ${r1.tier} (should be SIMPLE)`,\n );\n\n const r2 = classifyByRules(\"Hello\", systemPrompt, 5, config.scoring);\n assert(\n r2.tier === \"SIMPLE\",\n `\"Hello\" with reasoning system prompt → ${r2.tier} (should be SIMPLE)`,\n );\n\n const r3 = classifyByRules(\"What is the capital of France?\", systemPrompt, 12, config.scoring);\n assert(\n r3.tier === \"SIMPLE\",\n `\"Capital of France\" with reasoning system prompt → ${r3.tier} (should be SIMPLE)`,\n );\n\n // But if USER explicitly asks for step-by-step, it SHOULD trigger REASONING\n const r4 = classifyByRules(\n \"Prove step by step that sqrt(2) is irrational\",\n systemPrompt,\n 50,\n config.scoring,\n );\n assert(\n r4.tier === \"REASONING\",\n `User asks for step-by-step proof → ${r4.tier} (should be REASONING)`,\n );\n}\n\n// Coding assistant system prompt should NOT force agentic mode on simple queries\n// Bug: OpenClaw's system prompt (\"edit files\", \"fix bugs\", \"check\", \"verify\") was\n// triggering agenticScore >= 0.6 on EVERY request, routing all to Sonnet via agentic tiers\n{\n console.log(\"\\nCoding assistant system prompt (should NOT force agentic mode):\");\n const codingSystemPrompt =\n \"You are a coding assistant. You can edit files, fix bugs, check code quality, \" +\n \"verify tests, deploy applications, and install dependencies. Make sure to follow \" +\n \"best practices and confirm changes before applying them.\";\n\n const r1 = classifyByRules(\"What does this function do?\", codingSystemPrompt, 20, config.scoring);\n assert(\n r1.agenticScore < 0.5,\n `Simple question with coding system prompt → agenticScore=${r1.agenticScore} (should be <0.5, not forced agentic)`,\n );\n\n const r2 = classifyByRules(\"What is React?\", codingSystemPrompt, 15, config.scoring);\n assert(\n r2.agenticScore < 0.5,\n `\"What is React?\" with coding system prompt → agenticScore=${r2.agenticScore} (should be <0.5)`,\n );\n\n // But if USER explicitly requests agentic work, it SHOULD trigger agentic mode\n // Need 3+ agentic keyword matches for score 0.6: \"fix\", \"deploy\", \"make sure\"\n const r3 = classifyByRules(\n \"Fix the bug in auth.ts, deploy to staging, and make sure it works\",\n codingSystemPrompt,\n 30,\n config.scoring,\n );\n assert(\n r3.agenticScore >= 0.5,\n `User asks for multi-step agentic task → agenticScore=${r3.agenticScore} (should be >=0.5)`,\n );\n}\n\n// Medium queries (may be ambiguous — that's ok, LLM classifier handles them)\n{\n console.log(\"\\nMedium/Ambiguous queries:\");\n const r1 = classifyByRules(\n \"Summarize the key differences between REST and GraphQL APIs\",\n undefined,\n 30,\n config.scoring,\n );\n console.log(\n ` → \"Summarize REST vs GraphQL\" → tier=${r1.tier ?? \"AMBIGUOUS\"} (score=${r1.score.toFixed(3)}, conf=${r1.confidence.toFixed(3)}) [${r1.signals.join(\", \")}]`,\n );\n\n const r2 = classifyByRules(\n \"Write a Python function to sort a list using merge sort\",\n undefined,\n 40,\n config.scoring,\n );\n console.log(\n ` → \"Write merge sort\" → tier=${r2.tier ?? \"AMBIGUOUS\"} (score=${r2.score.toFixed(3)}, conf=${r2.confidence.toFixed(3)}) [${r2.signals.join(\", \")}]`,\n );\n}\n\n// Complex/high-signal queries should not be down-routed to SIMPLE.\n// Depending on scoring weights, these may be MEDIUM/COMPLEX or ambiguous.\n{\n console.log(\"\\nComplex queries (expected: non-SIMPLE):\");\n const r1 = classifyByRules(\n \"Build a React component with TypeScript that implements a drag-and-drop kanban board with async data loading, error handling, and unit tests\",\n undefined,\n 200,\n config.scoring,\n );\n assert(\n r1.tier === null || r1.tier === \"MEDIUM\" || r1.tier === \"COMPLEX\" || r1.tier === \"REASONING\",\n `Kanban board → ${r1.tier ?? \"AMBIGUOUS\"} (score=${r1.score.toFixed(3)}, conf=${r1.confidence.toFixed(3)})`,\n );\n\n const r2 = classifyByRules(\n \"Design a distributed microservice architecture for a real-time trading platform. Include the database schema, API endpoints, message queue topology, and kubernetes deployment manifests.\",\n undefined,\n 250,\n config.scoring,\n );\n assert(\n r2.tier === null || r2.tier === \"MEDIUM\" || r2.tier === \"COMPLEX\" || r2.tier === \"REASONING\",\n `Distributed trading platform → ${r2.tier ?? \"AMBIGUOUS\"} (score=${r2.score.toFixed(3)}, conf=${r2.confidence.toFixed(3)})`,\n );\n}\n\n// Reasoning queries\n{\n console.log(\"\\nReasoning queries:\");\n const r1 = classifyByRules(\n \"Prove that the square root of 2 is irrational using proof by contradiction. Show each step formally.\",\n undefined,\n 60,\n config.scoring,\n );\n assert(\n r1.tier === \"REASONING\",\n `\"Prove sqrt(2) irrational\" → ${r1.tier} (score=${r1.score.toFixed(3)}, conf=${r1.confidence.toFixed(3)})`,\n );\n\n const r2 = classifyByRules(\n \"Derive the time complexity of the following algorithm step by step, then prove it is optimal using a lower bound argument.\",\n undefined,\n 80,\n config.scoring,\n );\n assert(\n r2.tier === \"REASONING\",\n `\"Derive time complexity + prove optimal\" → ${r2.tier} (score=${r2.score.toFixed(3)}, conf=${r2.confidence.toFixed(3)})`,\n );\n\n const r3 = classifyByRules(\n \"Using chain of thought, solve this mathematical proof: for all n >= 1, prove that 1 + 2 + ... + n = n(n+1)/2\",\n undefined,\n 70,\n config.scoring,\n );\n assert(\n r3.tier === \"REASONING\",\n `\"Chain of thought proof\" → ${r3.tier} (score=${r3.score.toFixed(3)}, conf=${r3.confidence.toFixed(3)})`,\n );\n}\n\n// Multilingual keyword tests\n{\n console.log(\"\\nMultilingual keyword tests:\");\n\n // Chinese reasoning - 证明 (prove) + 逐步 (step by step)\n const zhReasoning = classifyByRules(\n \"请证明根号2是无理数,逐步推导\",\n undefined,\n 20,\n config.scoring,\n );\n assert(\n zhReasoning.tier === \"REASONING\",\n `Chinese \"证明...逐步\" → ${zhReasoning.tier} (should be REASONING)`,\n );\n\n // Chinese simple - 你好 (hello) + 什么是 (what is)\n const zhSimple = classifyByRules(\"你好,什么是人工智能?\", undefined, 15, config.scoring);\n assert(\n zhSimple.tier === \"SIMPLE\",\n `Chinese \"你好...什么是\" → ${zhSimple.tier} (should be SIMPLE)`,\n );\n\n // Japanese simple - こんにちは (hello)\n const jaSimple = classifyByRules(\"こんにちは、東京とは何ですか\", undefined, 15, config.scoring);\n assert(\n jaSimple.tier === \"SIMPLE\",\n `Japanese \"こんにちは...とは\" → ${jaSimple.tier} (should be SIMPLE)`,\n );\n\n // Russian technical - алгоритм (algorithm) + оптимизировать (optimize)\n const ruTech = classifyByRules(\n \"Оптимизировать алгоритм сортировки для распределённой системы\",\n undefined,\n 20,\n config.scoring,\n );\n assert(\n ruTech.tier !== \"SIMPLE\",\n `Russian \"алгоритм...распределённой\" → ${ruTech.tier} (should NOT be SIMPLE)`,\n );\n\n // Russian simple - привет (hello) + что такое (what is)\n const ruSimple = classifyByRules(\n \"Привет, что такое машинное обучение?\",\n undefined,\n 15,\n config.scoring,\n );\n assert(\n ruSimple.tier === \"SIMPLE\",\n `Russian \"привет...что такое\" → ${ruSimple.tier} (should be SIMPLE)`,\n );\n\n // German reasoning - beweisen (prove) + schritt für schritt (step by step)\n const deReasoning = classifyByRules(\n \"Beweisen Sie, dass die Quadratwurzel von 2 irrational ist, Schritt für Schritt\",\n undefined,\n 25,\n config.scoring,\n );\n assert(\n deReasoning.tier === \"REASONING\",\n `German \"beweisen...schritt für schritt\" → ${deReasoning.tier} (should be REASONING)`,\n );\n\n // German simple - hallo (hello) + was ist (what is)\n const deSimple = classifyByRules(\n \"Hallo, was ist maschinelles Lernen?\",\n undefined,\n 10,\n config.scoring,\n );\n assert(\n deSimple.tier === \"SIMPLE\",\n `German \"hallo...was ist\" → ${deSimple.tier} (should be SIMPLE)`,\n );\n\n // German technical - algorithmus (algorithm) + optimieren (optimize)\n const deTech = classifyByRules(\n \"Optimieren Sie den Sortieralgorithmus für eine verteilte Architektur\",\n undefined,\n 20,\n config.scoring,\n );\n assert(\n deTech.tier !== \"SIMPLE\",\n `German \"algorithmus...verteilt\" → ${deTech.tier} (should NOT be SIMPLE)`,\n );\n}\n\n// Issue #50: Large OpenClaw-style system prompt (~6000 tokens) should NOT dominate scoring\n// Previously all requests scored ~0.47 regardless of user intent because the system prompt\n// contained keywords matching nearly every scoring dimension.\n{\n console.log(\"\\nIssue #50: OpenClaw-scale system prompt (should NOT dominate scoring):\");\n\n // Simulate a realistic OpenClaw system prompt with tool definitions, workspace files,\n // skill descriptions, and behavioral rules — all containing scorer keywords\n const openClawSystemPrompt = `You are an AI assistant with access to the following tools:\n\nTool: run - Execute shell commands in the user's terminal. Use this to run tests, build projects, or execute scripts.\n Parameters: command (string, required), timeout (number, optional)\n\nTool: edit - Edit files in the workspace. Supports creating, modifying, and deleting files.\n Parameters: file_path (string), content (string), mode (enum: create, replace, delete)\n\nTool: test - Run the project's test suite. Automatically detects the testing framework (jest, vitest, pytest, etc).\n Parameters: pattern (string, optional), watch (boolean, optional)\n\nTool: deploy - Deploy the application to staging or production environments.\n Parameters: environment (enum: staging, production), config (object, optional)\n\nTool: search - Search for files, functions, or text patterns across the codebase.\n Parameters: query (string), type (enum: file, function, text), regex (boolean)\n\nTool: fix - Apply automated fixes for linting errors, formatting issues, or simple bugs.\n Parameters: file_path (string), rule (string, optional)\n\nTool: build - Build the project using the detected build system (webpack, vite, esbuild, etc).\n Parameters: mode (enum: development, production), clean (boolean)\n\nTool: install - Install project dependencies using the detected package manager.\n Parameters: packages (string[], optional), dev (boolean, optional)\n\nTool: verify - Verify the integrity of the build output and check for common issues.\n Parameters: strict (boolean, optional)\n\nTool: check - Run static analysis and type checking on the codebase.\n Parameters: fix (boolean, optional)\n\nTool: create - Create new files, components, or project structures from templates.\n Parameters: template (string), name (string), path (string, optional)\n\nTool: analyze - Analyze code complexity, dependencies, and architecture patterns.\n Parameters: scope (string, optional), format (enum: json, table, markdown)\n\nTool: generate - Generate code, documentation, or configuration files.\n Parameters: type (string), spec (string)\n\nTool: refactor - Refactor code by extracting functions, renaming symbols, or restructuring modules.\n Parameters: action (string), target (string)\n\nTool: optimize - Optimize code performance, bundle size, or resource usage.\n Parameters: target (string), strategy (string, optional)\n\nTool: debug - Start a debugging session with breakpoints and step-through execution.\n Parameters: file_path (string), line (number, optional)\n\nTool: document - Generate or update documentation for functions, classes, or modules.\n Parameters: scope (string), format (enum: jsdoc, markdown, yaml)\n\nTool: schema - Generate or validate JSON Schema, OpenAPI specs, or database schemas.\n Parameters: input (string), output_format (string)\n\nTool: migrate - Run database migrations or upgrade configuration formats.\n Parameters: direction (enum: up, down), steps (number, optional)\n\nTool: monitor - Monitor application logs, metrics, and health status.\n Parameters: service (string), duration (number, optional)\n\nWorkspace Files:\n- AGENTS.md: Defines agent behavior, tool usage patterns, and decision-making guidelines.\n Contains step-by-step instructions for handling complex multi-step tasks.\n Includes algorithm descriptions for distributed task scheduling.\n\n- SOUL.md: Core behavioral rules. Don't make assumptions. Avoid unnecessary changes.\n At most 3 file edits per turn. Within the scope of the current task only.\n Limit output to what was explicitly requested.\n\n- TOOLS.md: Detailed tool documentation with examples.\n \"Use the edit tool to create new files...\"\n \"Use the build tool to compile the project...\"\n \"import { something } from './module'\"\n \"async function handleRequest() { ... }\"\n \"export default class Controller { ... }\"\n\nSkills:\n1. Code Review - Analyze code quality, suggest improvements, and check for common patterns.\n Step 1: Read the file. Step 2: Identify issues. Step 3: Generate suggestions.\n2. Architecture Design - Design system architecture with diagrams and documentation.\n Covers: microservices, event sourcing, CQRS, distributed systems, kubernetes deployment.\n3. Performance Optimization - Profile and optimize application performance.\n Analyze algorithm complexity, identify bottlenecks, implement caching strategies.\n\nRules:\n- Always confirm before applying destructive changes\n- Don't modify files outside the project directory\n- Avoid making changes that aren't directly requested\n- Do not add unnecessary dependencies\n- Follow the existing code style and conventions`;\n\n // Test 1: Simple greeting — score should be much lower than the broken ~0.47\n // Note: tokenCount dimension legitimately considers total context size (6200 tokens),\n // so MEDIUM is acceptable. The key is scores now DIFFERENTIATE instead of all being ~0.47.\n const ocr1 = classifyByRules(\"What time is it?\", openClawSystemPrompt, 6200, config.scoring);\n assert(\n ocr1.score < 0.2,\n `\"What time is it?\" + OpenClaw prompt → score=${ocr1.score.toFixed(3)} (should be <0.2, was ~0.47 before fix)`,\n );\n\n // Test 2: Simple question — same: score should be well below the broken ~0.47\n const ocr2 = classifyByRules(\"What's the weather?\", openClawSystemPrompt, 6200, config.scoring);\n assert(\n ocr2.score < 0.2,\n `\"What's the weather?\" + OpenClaw prompt → score=${ocr2.score.toFixed(3)} (should be <0.2, was ~0.47 before fix)`,\n );\n\n // Test 3: Complex coding task — should be COMPLEX or higher, not same score as simple\n const ocr3 = classifyByRules(\n \"Build a React component with TypeScript that implements a sortable data table with pagination, filtering, and async data loading from a REST API\",\n openClawSystemPrompt,\n 6250,\n config.scoring,\n );\n assert(\n ocr3.score > ocr1.score,\n `Complex task score (${ocr3.score.toFixed(3)}) > simple task score (${ocr1.score.toFixed(3)}) — scores should differentiate`,\n );\n\n // Test 4: Reasoning task — should be REASONING\n const ocr4 = classifyByRules(\n \"Prove that sqrt(2) is irrational using proof by contradiction, step by step\",\n openClawSystemPrompt,\n 6220,\n config.scoring,\n );\n assert(\n ocr4.tier === \"REASONING\",\n `Reasoning task + OpenClaw prompt → ${ocr4.tier} score=${ocr4.score.toFixed(3)} (should be REASONING)`,\n );\n\n // Test 5: Scores should NOT all be the same (~0.47)\n const scores = [ocr1.score, ocr2.score, ocr3.score, ocr4.score];\n const uniqueScores = new Set(scores.map((s) => s.toFixed(2)));\n assert(\n uniqueScores.size >= 3,\n `${uniqueScores.size} unique scores out of 4 queries (scores: ${scores.map((s) => s.toFixed(3)).join(\", \")}) — should differentiate, not all ~0.47`,\n );\n\n // Test 6: Agentic score should be 0 for non-agentic queries\n assert(\n ocr1.agenticScore === 0,\n `\"What time is it?\" agenticScore=${ocr1.agenticScore} (should be 0, not triggered by system prompt tools)`,\n );\n}\n\n// Override: large context\n{\n console.log(\"\\nOverride: large context:\");\n const r1 = classifyByRules(\"What is 2+2?\", undefined, 150000, config.scoring);\n // The rules classifier doesn't handle the override — that's in router/index.ts\n // But token count should push score up\n console.log(\n ` → 150K tokens \"What is 2+2?\" → tier=${r1.tier ?? \"AMBIGUOUS\"} (score=${r1.score.toFixed(3)}, conf=${r1.confidence.toFixed(3)})`,\n );\n}\n\n// ─── Part 2: Full Router (route function, no LLM classifier — uses mock) ───\n\nconsole.log(\"\\n═══ Part 2: Full Router (rules-only path) ═══\\n\");\n\nconst modelPricing = buildModelPricing();\n\n// Mock payFetch that won't be called (rules handle these clearly)\nconst mockPayFetch = async () => new Response(\"\", { status: 500 });\n\nconst routerOpts = {\n config: DEFAULT_ROUTING_CONFIG,\n modelPricing,\n payFetch: mockPayFetch,\n apiBase: \"http://localhost:0\",\n};\n\nasync function testRoute(prompt: string, label: string, expectedTier?: string) {\n const decision = await route(prompt, undefined, 4096, routerOpts);\n const savingsPct = (decision.savings * 100).toFixed(1);\n if (expectedTier) {\n assert(\n decision.tier === expectedTier,\n `${label} → ${decision.model} (${decision.tier}, ${decision.method}) saved=${savingsPct}%`,\n );\n } else {\n console.log(\n ` → ${label} → ${decision.model} (${decision.tier}, ${decision.method}) saved=${savingsPct}%`,\n );\n }\n return decision;\n}\n\nawait testRoute(\"What is the capital of France?\", \"Simple factual\", \"SIMPLE\");\nawait testRoute(\"Hello, how are you?\", \"Greeting\", \"SIMPLE\");\nawait testRoute(\n \"Prove that sqrt(2) is irrational step by step using proof by contradiction\",\n \"Math proof\",\n \"REASONING\",\n);\n\n// Large context override\n{\n const longPrompt = \"x\".repeat(500000); // ~125K tokens\n const decision = await route(longPrompt, undefined, 4096, routerOpts);\n assert(\n decision.tier === \"COMPLEX\",\n `125K token input → ${decision.tier} (forced COMPLEX override)`,\n );\n}\n\n// Structured output override\n{\n const decision = await route(\n \"What is 2+2?\",\n \"Respond in JSON format with the answer\",\n 4096,\n routerOpts,\n );\n assert(\n decision.tier === \"MEDIUM\" || decision.tier === \"SIMPLE\",\n `Structured output \"What is 2+2?\" → ${decision.tier} (min MEDIUM applied: ${decision.tier !== \"SIMPLE\"})`,\n );\n}\n\n// Cost estimates sanity check\n{\n console.log(\"\\nCost estimate sanity:\");\n const d = await route(\"What is 2+2?\", undefined, 4096, routerOpts);\n assert(d.costEstimate > 0, `Cost estimate > 0: $${d.costEstimate.toFixed(6)}`);\n assert(d.baselineCost > 0, `Baseline cost > 0: $${d.baselineCost.toFixed(6)}`);\n assert(d.savings >= 0 && d.savings <= 1, `Savings in range [0,1]: ${d.savings.toFixed(4)}`);\n assert(\n d.costEstimate <= d.baselineCost,\n `Cost ($${d.costEstimate.toFixed(6)}) <= Baseline ($${d.baselineCost.toFixed(6)})`,\n );\n}\n\n// ─── Part 3: Proxy Startup (requires wallet key) ───\n\nconsole.log(\"\\n═══ Part 3: Proxy Startup ═══\\n\");\n\nconst walletKey = process.env.BLOCKRUN_WALLET_KEY;\nif (!walletKey) {\n console.log(\" Skipped — set BLOCKRUN_WALLET_KEY to test proxy startup\\n\");\n} else {\n try {\n const proxy = await startProxy({\n wallet: walletKey,\n port: 0,\n onReady: (port) => console.log(` Proxy started on port ${port}`),\n onError: (err) => console.error(` Proxy error: ${err.message}`),\n onRouted: (d) => {\n const pct = (d.savings * 100).toFixed(1);\n console.log(` [routed] ${d.model} (${d.tier}) saved=${pct}%`);\n },\n });\n\n // Test health endpoint\n const health = await fetch(`${proxy.baseUrl}/health`);\n const healthData = (await health.json()) as { status: string; wallet: string };\n assert(\n healthData.status === \"ok\",\n `Health check: ${healthData.status}, wallet: ${healthData.wallet}`,\n );\n\n // Send a test chat completion with blockrun/auto\n console.log(\"\\n Sending test request (blockrun/auto)...\");\n try {\n const chatRes = await fetch(`${proxy.baseUrl}/v1/chat/completions`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n model: \"blockrun/auto\",\n messages: [{ role: \"user\", content: \"What is 2+2?\" }],\n max_tokens: 50,\n }),\n });\n\n if (chatRes.ok) {\n const chatData = (await chatRes.json()) as {\n choices?: Array<{ message?: { content?: string } }>;\n };\n const content = chatData.choices?.[0]?.message?.content ?? \"(no content)\";\n console.log(` ✓ Response: ${content.slice(0, 100)}`);\n passed++;\n } else {\n const errText = await chatRes.text();\n console.log(` Response status: ${chatRes.status} — ${errText.slice(0, 200)}`);\n // 402 or payment errors are expected if wallet isn't funded\n if (chatRes.status === 402) {\n console.log(\" (402 = wallet needs USDC funding — routing still worked)\");\n }\n }\n } catch (err) {\n console.log(` Request error: ${err instanceof Error ? err.message : String(err)}`);\n }\n\n await proxy.close();\n console.log(\" Proxy closed.\\n\");\n } catch (err) {\n console.error(` Proxy startup failed: ${err instanceof Error ? err.message : String(err)}`);\n failed++;\n }\n}\n\n// ─── Summary ───\n\nconsole.log(\"═══════════════════════════════════\");\nconsole.log(` ${passed} passed, ${failed} failed`);\nconsole.log(\"═══════════════════════════════════\\n\");\n\nprocess.exit(failed > 0 ? 1 : 0);\n"],"mappings":"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;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,IACA,mBAAmB;AAAA;AAAA,MAEjB;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,IACA,gBAAgB;AAAA;AAAA,MAEd;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,IACA,mBAAmB;AAAA;AAAA,MAEjB;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,IACA,kBAAkB;AAAA;AAAA,MAEhB;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA;AAAA,IAGA,iBAAiB;AAAA;AAAA,MAEf;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,IACA,sBAAsB;AAAA;AAAA,MAEpB;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,IACA,sBAAsB;AAAA;AAAA,MAEpB;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,IACA,mBAAmB;AAAA;AAAA,MAEjB;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,IACA,kBAAkB;AAAA;AAAA,MAEhB;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA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","names":["config","confidence","modelPricing","config","supportsToolCalling","supportsVision","config","modelPricing","homedir","join","mkdir","writeFile","createPublicClient","http","base","privateKeyToAccount","payload","response","join","homedir","join","require","LOG_DIR","join","homedir","LOG_DIR","join","DEFAULT_TTL_MS","createHash","canonicalize","TIMESTAMP_PATTERN","config","CACHE_TTL_MS","mkdir","join","homedir","privateKeyToAccount","join","homedir","escapeRegex","config","originalChars","createHash","config","DEFAULT_CONFIG","config","join","homedir","sanitized","walletKey","account","privateKeyToAccount","baseUrl","balanceMonitor","createPublicClient","base","http","modelPricing","routerOpts","mkdir","port","writeFile","normalizedModel","buildModelPricing"]} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 16546df..b71568d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@blockrun/clawrouter", - "version": "0.12.24", + "version": "0.12.27", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@blockrun/clawrouter", - "version": "0.12.24", + "version": "0.12.27", "license": "MIT", "dependencies": { "@scure/bip32": "^1.6.0", diff --git a/package.json b/package.json index 4e4c7ea..b154788 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@blockrun/clawrouter", - "version": "0.12.26", + "version": "0.12.27", "description": "Smart LLM router — save 92% on inference costs. 41+ models, one wallet, x402 micropayments.", "type": "module", "main": "dist/index.js", @@ -22,6 +22,7 @@ "files": [ "dist", "scripts", + "skills", "openclaw.plugin.json" ], "scripts": { diff --git a/src/proxy.ts b/src/proxy.ts index 7999fd0..37c5656 100644 --- a/src/proxy.ts +++ b/src/proxy.ts @@ -151,6 +151,10 @@ function transformPaymentError(errorBody: string): string { const parsed = JSON.parse(errorBody) as { error?: string; details?: string; + // blockrun-sol (Solana) format uses code+debug instead of details + code?: string; + debug?: string; + payer?: string; }; // Check if this is a payment verification error @@ -219,6 +223,66 @@ function transformPaymentError(errorBody: string): string { } } + // Handle blockrun-sol (Solana) format: code=PAYMENT_INVALID + debug=invalidReason string + if (parsed.error === "Payment verification failed" && parsed.code === "PAYMENT_INVALID" && parsed.debug) { + const debugLower = parsed.debug.toLowerCase(); + const wallet = parsed.payer || "unknown"; + const shortWallet = wallet.length > 12 ? `${wallet.slice(0, 6)}...${wallet.slice(-4)}` : wallet; + + if (debugLower.includes("insufficient")) { + return JSON.stringify({ + error: { + message: "Insufficient Solana USDC balance.", + type: "insufficient_funds", + wallet, + help: `Fund wallet ${shortWallet} with USDC on Solana, or switch to Base: /wallet base`, + }, + }); + } + + if (debugLower.includes("transaction_simulation_failed") || debugLower.includes("simulation")) { + console.error(`[ClawRouter] Solana transaction simulation failed: ${parsed.debug}`); + return JSON.stringify({ + error: { + message: "Solana payment simulation failed. Retrying with a different model.", + type: "transaction_simulation_failed", + help: "This is usually temporary. If it persists, try: /model free", + }, + }); + } + + if (debugLower.includes("invalid signature") || debugLower.includes("invalid_signature")) { + return JSON.stringify({ + error: { + message: "Solana payment signature invalid.", + type: "invalid_payload", + help: "Try again. If this persists, reinstall ClawRouter: curl -fsSL https://blockrun.ai/ClawRouter-update | bash", + }, + }); + } + + if (debugLower.includes("expired")) { + return JSON.stringify({ + error: { + message: "Solana payment expired. Retrying.", + type: "expired", + help: "This is usually temporary.", + }, + }); + } + + // Unknown Solana verification error — surface the debug reason + console.error(`[ClawRouter] Solana payment verification failed: ${parsed.debug} payer=${wallet}`); + return JSON.stringify({ + error: { + message: `Solana payment verification failed: ${parsed.debug}`, + type: "payment_invalid", + wallet, + help: "Try again or switch to Base: /wallet base", + }, + }); + } + // Handle settlement failures (gas estimation, on-chain errors) if ( parsed.error === "Settlement failed" ||