titan-agent 5.6.2 → 5.6.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/providers/router.ts"],"sourcesContent":["/**\n * TITAN — Universal Model Router\n * Routes model requests to the correct provider with failover, alias resolution,\n * and live model discovery across all configured providers (including local Ollama).\n *\n * Error Recovery Features:\n * - Exponential backoff retry for transient failures (429, 503, timeouts)\n * - Circuit breaker pattern to avoid hammering failing providers\n * - Automatic fallback to next provider in chain on persistent errors\n * - Detailed error messages including provider name and model\n */\nimport { LLMProvider, type ChatOptions, type ChatResponse, type ChatStreamChunk } from './base.js';\nimport { AnthropicProvider } from './anthropic.js';\nimport { OpenAIProvider } from './openai.js';\nimport { GoogleProvider } from './google.js';\nimport { OllamaProvider } from './ollama.js';\nimport { ClaudeCodeProvider } from './claudeCode.js';\nimport { OpenAICompatProvider, PROVIDER_PRESETS } from './openai_compat.js';\nimport { loadConfig } from '../config/config.js';\nimport logger from '../utils/logger.js';\nimport { findModelOnMesh } from '../mesh/registry.js';\nimport type { MeshPeer } from '../mesh/discovery.js';\nimport { routeTaskToNode } from '../mesh/transport.js';\nimport { randomBytes } from 'crypto';\nimport { sleep } from '../utils/helpers.js';\nimport { classifyProviderError, shouldAffectCircuitBreaker, FailoverReason } from './errorTaxonomy.js';\nimport { getExistingPool } from './credentialPool.js';\nimport { buildSmartContext } from '../agent/contextManager.js';\nimport { shouldBackOff } from './rateLimitTracker.js';\n\nconst COMPONENT = 'Router';\n\n/** Build failover order from all registered providers, sorted by capability priority */\nfunction getFailoverOrder(excludeProvider: string): string[] {\n const priority: Record<string, number> = {\n anthropic: 100,\n openai: 90,\n google: 80,\n openrouter: 75,\n groq: 70,\n together: 65,\n deepseek: 60,\n xai: 55,\n mistral: 50,\n cerebras: 45,\n cohere: 40,\n 'cohere-v2': 40,\n fireworks: 35,\n perplexity: 30,\n 'claude-code': 15,\n ollama: 10,\n };\n initProviders();\n return Array.from(providers.keys())\n .filter(name => name !== excludeProvider)\n .sort((a, b) => (priority[b] ?? 25) - (priority[a] ?? 25));\n}\n\n// ── Chain-of-thought stripping ──────────────────────────────────\n// Some local models (qwen, glm, deepseek, etc.) leak their internal\n// reasoning into the response. This runs on EVERY chat() response so\n// no consumer (FB posts, Messenger, comments, web chat) ever sees it.\n\nfunction stripThinkingFromResponse(text: string): string {\n let cleaned = text;\n\n // 1. Remove <think>...</think> blocks (deepseek, qwen thinking mode)\n cleaned = cleaned.replace(/<think>[\\s\\S]*?<\\/think>/gi, '');\n\n // 2. Remove ```thinking ... ``` blocks\n cleaned = cleaned.replace(/```thinking[\\s\\S]*?```/gi, '');\n\n // 3. Cut at \"multiple draft\" boundaries — models often generate several\n // versions inline: \"Let me try another version:\", \"Here's another:\", etc.\n const draftBoundary = /\\n+[\"']?\\n*(Let me (try|make|write|do|craft)|Here'?s? (another|a better|a more)|Another (version|option|take|attempt)|Or (maybe|how about|alternatively)|Version \\d|Option \\d|Draft \\d|---)/i;\n const draftMatch = cleaned.match(draftBoundary);\n if (draftMatch?.index !== undefined && draftMatch.index > 20) {\n cleaned = cleaned.slice(0, draftMatch.index);\n }\n\n // 4. If the response starts with meta-reasoning, extract just the reply\n const reasoningStart = /^(The user wants|The comment|I need to|I should|Let me (think|craft|write|consider|analyze)|OK so|Alright,|Hmm,|This is a)/i;\n if (reasoningStart.test(cleaned.trim())) {\n const parts = cleaned.split(/\\n{2,}|^---$/m);\n const replyParts = parts.filter(p => {\n const trimmed = p.trim();\n if (!trimmed) return false;\n if (reasoningStart.test(trimmed)) return false;\n if (/^(Wait|Actually|But |So |Since |That works|That's about|Let me (count|check|think|try))/i.test(trimmed)) return false;\n if (/\\b(characters|under \\d+ char|personality|mentioned|the rules)\\b/i.test(trimmed)) return false;\n return true;\n });\n if (replyParts.length > 0) {\n cleaned = replyParts.join('\\n\\n');\n }\n }\n\n // 5. Remove wrapping quotes that some models add\n cleaned = cleaned.trim().replace(/^[\"']|[\"']$/g, '').trim();\n\n return cleaned;\n}\n\n// ── Provider name normalization ─────────────────────────────────\nconst PROVIDER_ALIASES: Record<string, string> = {\n 'z.ai': 'xai',\n 'zai': 'xai',\n 'grok': 'xai',\n 'local': 'ollama',\n 'vertex': 'google',\n 'vertex-ai': 'google',\n 'azure-openai': 'azure',\n 'aws': 'bedrock',\n 'amazon': 'bedrock',\n 'litellm-proxy': 'litellm',\n 'hf': 'huggingface',\n 'hugging-face': 'huggingface',\n '01ai': 'yi',\n '01.ai': 'yi',\n 'glm': 'zhipu',\n 'bigmodel': 'zhipu',\n 'pi': 'inflection',\n 'octoai': 'octo',\n 'nim': 'nvidia',\n 'nvidia-nim': 'nvidia',\n};\n\n/** Normalize provider names for consistency (e.g. \"grok\" → \"xai\", \"local\" → \"ollama\") */\nexport function normalizeProvider(name: string): string {\n const lower = name.toLowerCase();\n return PROVIDER_ALIASES[lower] || lower;\n}\n\n/** Provider registry */\nconst providers: Map<string, LLMProvider> = new Map();\nlet initialized = false;\n\nfunction initProviders(): void {\n if (initialized) return;\n // Core providers (custom implementations)\n providers.set('anthropic', new AnthropicProvider());\n providers.set('openai', new OpenAIProvider());\n providers.set('google', new GoogleProvider());\n providers.set('ollama', new OllamaProvider());\n providers.set('claude-code', new ClaudeCodeProvider());\n // OpenAI-compatible providers (Groq, Mistral, OpenRouter, xAI, etc.)\n for (const preset of PROVIDER_PRESETS) {\n providers.set(preset.name, new OpenAICompatProvider(preset));\n }\n initialized = true;\n}\n\n/** Get a provider by name */\nexport function getProvider(name: string): LLMProvider | undefined {\n initProviders();\n return providers.get(name);\n}\n\n/** Get all registered providers */\nexport function getAllProviders(): Map<string, LLMProvider> {\n initProviders();\n return providers;\n}\n\n/** Resolve a model alias (e.g. \"fast\" → \"openai/gpt-4o-mini\") */\nfunction resolveAlias(modelId: string): string {\n const config = loadConfig();\n const aliases = config.agent.modelAliases;\n if (aliases && aliases[modelId]) {\n const resolved = aliases[modelId];\n logger.debug(COMPONENT, `Alias \"${modelId}\" → \"${resolved}\"`);\n return resolved;\n }\n return modelId;\n}\n\n\n/** Resolve the provider and model from a model ID like \"anthropic/claude-3\" or alias like \"fast\" */\nexport function resolveModel(modelId: string): { provider: LLMProvider; model: string } {\n initProviders();\n // First resolve aliases\n const resolved = resolveAlias(modelId);\n const { provider: rawProviderName, model } = LLMProvider.parseModelId(resolved);\n\n\n // Normalize provider name (e.g. \"grok\" → \"xai\", \"local\" → \"ollama\")\n const providerName = normalizeProvider(rawProviderName);\n const provider = providers.get(providerName);\n if (!provider) {\n throw new Error(`Unknown provider: ${providerName}. Available: ${Array.from(providers.keys()).join(', ')}`);\n }\n return { provider, model };\n}\n\n/**\n * Non-throwing variant of resolveModel — returns null on an unknown\n * provider instead of throwing. Used by gateway endpoints to fail-fast\n * with a helpful 400 BEFORE the agent loop builds the prompt and burns\n * tokens. v5.5.30+. Bug from 2026-05-08 audit: requests with bad model\n * IDs (e.g. typoed providers) used to crash deep inside the agent loop\n * after prompt assembly, returning 500 with a stack trace.\n */\nexport function tryResolveModel(modelId: string): { provider: LLMProvider; model: string } | null {\n try { return resolveModel(modelId); } catch { return null; }\n}\n\n/** List of provider names known to this gateway (for \"did you mean\" suggestions). */\nexport function getKnownProviderNames(): string[] {\n initProviders();\n return Array.from(providers.keys()).sort();\n}\n\n/** Check if a model is allowed by the allowlist. Empty list = all allowed. */\nexport function isModelAllowed(modelId: string): boolean {\n const config = loadConfig();\n const allowedModels = config.agent.allowedModels;\n if (!allowedModels || allowedModels.length === 0) return true;\n\n // Resolve alias first\n const resolved = resolveAlias(modelId);\n\n for (const pattern of allowedModels) {\n if (pattern === resolved) return true;\n // Wildcard support: \"openai/*\" matches \"openai/gpt-4o\"\n if (pattern.endsWith('/*')) {\n const prefix = pattern.slice(0, -1); // \"openai/\"\n if (resolved.startsWith(prefix)) return true;\n }\n }\n return false;\n}\n\n/** Discovered model info */\nexport interface DiscoveredModel {\n id: string; // Full ID e.g. \"ollama/llama3.1\"\n provider: string; // Provider name e.g. \"ollama\"\n model: string; // Model name e.g. \"llama3.1\"\n displayName: string; // Provider display name e.g. \"Ollama (Local)\"\n source: 'static' | 'live'; // Whether discovered via live API or hardcoded list\n}\n\n/** Cache for discovered models (refreshed on demand, 60s TTL) */\nlet modelCache: { models: DiscoveredModel[]; timestamp: number } | null = null;\nconst MODEL_CACHE_TTL = 60_000; // 60 seconds\n\n/**\n * Discover all available models across all providers.\n * Queries each provider's listModels() — for Ollama this hits the local API\n * to find actually-installed models. Results are cached for 60s.\n */\nexport async function discoverAllModels(forceRefresh = false): Promise<DiscoveredModel[]> {\n initProviders();\n\n if (!forceRefresh && modelCache && (Date.now() - modelCache.timestamp) < MODEL_CACHE_TTL) {\n return modelCache.models;\n }\n\n const discovered: DiscoveredModel[] = [];\n const health = await healthCheckAll();\n\n const tasks = Array.from(providers.entries()).map(async ([name, provider]) => {\n try {\n const models = await provider.listModels();\n const isLive = health[name] === true;\n for (const model of models) {\n discovered.push({\n id: `${name}/${model}`,\n provider: name,\n model,\n displayName: provider.displayName,\n source: (name === 'ollama' && isLive) ? 'live' : 'static',\n });\n }\n } catch (err) {\n logger.debug(COMPONENT, `Failed to list models for ${name}: ${(err as Error).message}`);\n }\n });\n\n await Promise.all(tasks);\n\n modelCache = { models: discovered, timestamp: Date.now() };\n logger.info(COMPONENT, `Discovered ${discovered.length} models across ${providers.size} providers`);\n return discovered;\n}\n\n/** Get current model aliases from config */\nexport function getModelAliases(): Record<string, string> {\n const config = loadConfig();\n return config.agent.modelAliases || {};\n}\n\n// ── Circuit Breaker ─────────────────────────────────────────────\n/** Circuit breaker states for each provider */\ntype CircuitState = 'closed' | 'open' | 'half-open';\n\ninterface CircuitBreakerState {\n state: CircuitState;\n failureCount: number;\n lastFailureTime: number | null;\n lastSuccessTime: number | null;\n openSince: number | null;\n}\n\n/** Circuit breaker configuration — tuned for cloud model tolerance */\nconst CIRCUIT_BREAKER_CONFIG = {\n failureThreshold: 8, // Number of failures before opening circuit (was 5 — too aggressive for cloud)\n resetTimeout: 60000, // 60s before trying again (was 30s — cloud models need recovery time)\n monitoringWindow: 120000, // 120s window for counting failures (was 60s — cloud latency spikes are normal)\n successThreshold: 2, // Successes needed in half-open to close circuit (was 3)\n};\n\n/** Track circuit breaker state per provider */\nconst circuitBreakers = new Map<string, CircuitBreakerState>();\n\n// Prune stale closed circuit breakers every 5 minutes to prevent unbounded growth\nsetInterval(() => {\n const now = Date.now();\n for (const [name, state] of circuitBreakers) {\n if (state.state === 'closed' && state.lastFailureTime && now - state.lastFailureTime > 600_000) {\n circuitBreakers.delete(name);\n }\n }\n}, 300_000);\n\n\n/**\n * G2: Cooldown-aware probe throttling (OpenClaw pattern).\n * When a provider is rate-limited, don't probe it again for MIN_PROBE_INTERVAL_MS.\n * Prevents cascade failures during provider outages.\n */\nconst MIN_PROBE_INTERVAL_MS = 30000; // 30s between probes\nconst providerRateLimitCooldowns = new Map<string, number>(); // provider → timestamp of last rate-limit\n\n/** Record that a provider returned a rate-limit error */\nfunction recordRateLimitCooldown(providerName: string): void {\n providerRateLimitCooldowns.set(providerName, Date.now());\n}\n\n/** Check if a provider is still in its rate-limit cooldown window */\nfunction isInRateLimitCooldown(providerName: string): boolean {\n const lastRateLimit = providerRateLimitCooldowns.get(providerName);\n if (!lastRateLimit) return false;\n const elapsed = Date.now() - lastRateLimit;\n if (elapsed >= MIN_PROBE_INTERVAL_MS) {\n providerRateLimitCooldowns.delete(providerName); // Cooldown expired\n return false;\n }\n return true;\n}\n\n/**\n * Get or create circuit breaker state for a provider.\n */\nfunction getCircuitBreaker(providerName: string): CircuitBreakerState {\n if (!circuitBreakers.has(providerName)) {\n circuitBreakers.set(providerName, {\n state: 'closed',\n failureCount: 0,\n lastFailureTime: null,\n lastSuccessTime: null,\n openSince: null,\n });\n }\n return circuitBreakers.get(providerName)!;\n}\n\n/**\n * Record a successful request for a provider.\n * Resets failure count and updates state appropriately.\n */\nfunction recordSuccess(providerName: string): void {\n const cb = getCircuitBreaker(providerName);\n cb.lastSuccessTime = Date.now();\n\n if (cb.state === 'half-open') {\n // In half-open state, success reduces the counter\n cb.failureCount = Math.max(0, cb.failureCount - 1);\n // If we've had enough successes, close the circuit\n if (cb.failureCount <= 0) {\n cb.state = 'closed';\n cb.openSince = null;\n cb.failureCount = 0;\n logger.info(COMPONENT, `[CircuitBreaker] ${providerName} circuit CLOSED after successful recovery`);\n }\n } else if (cb.state === 'closed') {\n // In closed state, reset the failure count on success\n cb.failureCount = 0;\n }\n}\n\n/**\n * Record a failed request for a provider.\n * Opens circuit if failure threshold is exceeded.\n */\nfunction recordFailure(providerName: string): void {\n const cb = getCircuitBreaker(providerName);\n const now = Date.now();\n cb.lastFailureTime = now;\n\n // Only count failures within the monitoring window\n const windowStart = now - CIRCUIT_BREAKER_CONFIG.monitoringWindow;\n if (cb.lastFailureTime && cb.lastFailureTime < windowStart) {\n // Reset if outside monitoring window\n cb.failureCount = 1;\n } else {\n cb.failureCount++;\n }\n\n // Check if we should open the circuit\n if (cb.failureCount >= CIRCUIT_BREAKER_CONFIG.failureThreshold && cb.state === 'closed') {\n cb.state = 'open';\n cb.openSince = now;\n logger.warn(COMPONENT, `[CircuitBreaker] ${providerName} circuit OPENED after ${cb.failureCount} failures`);\n }\n}\n\n/**\n * Check if a provider's circuit breaker allows requests.\n * Returns true if closed or if half-open (time to test).\n * Returns false if open and still in timeout period.\n */\nfunction canRequest(providerName: string, isFallbackProbe = false): boolean {\n // G2: Rate-limit cooldown only blocks FALLBACK probes, not primary model retries.\n // Primary model has its own backoff logic — don't double-gate it.\n if (isFallbackProbe && isInRateLimitCooldown(providerName)) {\n logger.debug(COMPONENT, `[RateLimitCooldown] ${providerName} still cooling down — skipping fallback probe`);\n return false;\n }\n\n const cb = getCircuitBreaker(providerName);\n\n if (cb.state === 'closed') {\n return true;\n }\n\n if (cb.state === 'open') {\n const now = Date.now();\n if (cb.openSince && (now - cb.openSince) >= CIRCUIT_BREAKER_CONFIG.resetTimeout) {\n // Timeout expired, transition to half-open\n cb.state = 'half-open';\n cb.failureCount = CIRCUIT_BREAKER_CONFIG.successThreshold; // Need this many successes to close\n logger.info(COMPONENT, `[CircuitBreaker] ${providerName} circuit transitioned to HALF-OPEN (testing)`);\n return true;\n }\n return false; // Still open, don't try\n }\n\n // half-open: allow testing\n return true;\n}\n\n/**\n * Get circuit breaker status for all providers (for health dashboards).\n */\nexport function getCircuitBreakerStatus(): Record<string, { state: CircuitState; failureCount: number; openSince?: number }> {\n const status: Record<string, { state: CircuitState; failureCount: number; openSince?: number }> = {};\n for (const [providerName, cb] of circuitBreakers) {\n status[providerName] = {\n state: cb.state,\n failureCount: cb.failureCount,\n ...(cb.openSince !== null ? { openSince: cb.openSince } : {}),\n };\n }\n return status;\n}\n\n/**\n * Reset all circuit breaker state (for testing).\n * NOT exported to production API - test use only.\n */\nexport function __resetCircuitBreakers__(): void {\n circuitBreakers.clear();\n lastFallbackEvent = null;\n}\n\nexport function resetCircuitBreaker(providerName: string): void {\n const cb = circuitBreakers.get(providerName);\n if (cb) {\n cb.state = 'closed';\n cb.failureCount = 0;\n cb.openSince = null;\n }\n}\n\n// ── Fallback chain state ─────────────────────────────────────────\n/** Tracks the most recent fallback event for dashboard display */\nlet lastFallbackEvent: { primary: string; active: string; reason: string; timestamp: number } | null = null;\n\n/** Get the current fallback state (for dashboard display) */\nexport function getFallbackState(): { primary: string; active: string; reason: string; timestamp: number } | null {\n // Expire after 5 minutes\n if (lastFallbackEvent && (Date.now() - lastFallbackEvent.timestamp) > 300_000) {\n lastFallbackEvent = null;\n }\n return lastFallbackEvent;\n}\n\n/** Retry configuration with exponential backoff */\nconst RETRY_CONFIG = {\n maxRetries: 4, // 4 retries (was 3) — cloud APIs need more chances\n initialDelayMs: 1500, // 1.5s initial (was 1s) — give cloud APIs breathing room\n maxDelayMs: 45000, // 45s cap (was 30s) — cloud models can take longer to recover\n backoffMultiplier: 2,\n jitter: true,\n};\n\n/**\n * Monotonic counter seed for decorrelated jitter. Without this, two retries\n * triggered in the same millisecond can receive identical Math.random() values\n * if V8 happens to share a seed under load — that's exactly the thundering\n * herd we're trying to avoid.\n */\nlet _jitterCounter = 0;\n\n/**\n * Calculate delay with exponential backoff + asymmetric additive jitter.\n *\n * Ported from Hermes `agent/retry_utils.py:jittered_backoff` — proven to\n * decorrelate concurrent retries across multiple sessions hitting the same\n * rate-limited provider simultaneously.\n *\n * Formula:\n * base_delay = min(initial * multiplier^attempt, max)\n * jitter = random_uniform(0, jitter_ratio * base_delay)\n * final = base_delay + jitter\n *\n * Key difference from the previous TITAN implementation:\n * - Old: jitter was ±20% centered on base (could reduce delay below base)\n * - New: jitter is 0..+50% of base (only extends delay, never shortens)\n * This matters for rate-limit recovery — we never want to retry EARLIER than\n * the exponential schedule intended.\n *\n * The counter-seeded PRNG guarantees two concurrent retries get different\n * jitter values even in the same millisecond.\n */\nfunction calculateBackoffDelay(attempt: number): number {\n const exponentialDelay = RETRY_CONFIG.initialDelayMs * Math.pow(RETRY_CONFIG.backoffMultiplier, attempt);\n const cappedDelay = Math.min(exponentialDelay, RETRY_CONFIG.maxDelayMs);\n\n if (!RETRY_CONFIG.jitter) return cappedDelay;\n\n // Counter-seeded jitter — decorrelates concurrent callers.\n _jitterCounter = (_jitterCounter + 1) >>> 0;\n const seed = (Date.now() ^ (_jitterCounter * 0x9e3779b9)) >>> 0;\n // Simple xorshift from the seed — fast, good enough for jitter.\n let s = seed || 1;\n s ^= s << 13; s >>>= 0;\n s ^= s >>> 17;\n s ^= s << 5; s >>>= 0;\n const rand01 = (s >>> 0) / 0xffffffff; // [0, 1)\n\n const jitterRatio = 0.5; // up to +50% of base\n const jitter = rand01 * jitterRatio * cappedDelay;\n return cappedDelay + jitter;\n}\n\n/** Parse retry-after header value (seconds or HTTP date) */\nfunction parseRetryAfter(header: string | null): number | null {\n if (!header) return null;\n\n // Try parsing as seconds\n const seconds = parseInt(header, 10);\n if (!isNaN(seconds)) {\n return Math.min(seconds * 1000, RETRY_CONFIG.maxDelayMs); // Cap at max delay\n }\n\n // Try parsing as HTTP date\n const date = new Date(header);\n if (!isNaN(date.getTime())) {\n const delay = date.getTime() - Date.now();\n return Math.max(1000, Math.min(delay, RETRY_CONFIG.maxDelayMs)); // Min 1s, max configured cap\n }\n\n return null;\n}\n\n/**\n * Check if an error is retryable using the centralized error taxonomy.\n */\nfunction isRetryableError(error: unknown): boolean {\n return classifyProviderError(error).retryable;\n}\n\n/**\n * Extract HTTP status code from an error object if present.\n */\nfunction getErrorStatus(error: unknown): number | undefined {\n return classifyProviderError(error).httpStatus;\n}\n\n/** Try the fallback chain for a chat request. Returns null if chain is empty or exhausted. */\nasync function tryFallbackChain(\n options: ChatOptions,\n primaryModelId: string,\n originalError: Error,\n): Promise<ChatResponse | null> {\n const config = loadConfig();\n const chain = config.agent.fallbackChain;\n if (!chain || chain.length === 0) return null;\n\n const maxRetries = config.agent.fallbackMaxRetries ?? 3;\n let attempts = 0;\n\n for (const fallbackModelId of chain) {\n if (attempts >= maxRetries) break;\n if (fallbackModelId === primaryModelId) continue;\n\n attempts++;\n try {\n const { provider: fbProvider, model: fbModel } = resolveModel(fallbackModelId);\n const fbProviderName = fbProvider.name;\n\n // Check circuit breaker + rate-limit cooldown for fallback provider\n if (!canRequest(fbProviderName, true)) {\n const cb = getCircuitBreaker(fbProviderName);\n logger.warn(COMPONENT, `Skipping fallback ${fallbackModelId} — circuit breaker OPEN (${cb.failureCount} failures)`);\n continue;\n }\n\n logger.warn(COMPONENT, `Model ${primaryModelId} failed (${originalError.message}), falling back to ${fallbackModelId}`);\n const result = await fbProvider.chat({ ...options, model: fbModel });\n\n // Record success for circuit breaker\n recordSuccess(fbProviderName);\n\n lastFallbackEvent = {\n primary: primaryModelId,\n active: fallbackModelId,\n reason: originalError.message,\n timestamp: Date.now(),\n };\n return result;\n } catch (chainErr) {\n // Record failure for circuit breaker\n try {\n const { provider: fbProvider } = resolveModel(fallbackModelId);\n recordFailure(fbProvider.name);\n } catch {\n // Ignore if we can't resolve the provider for recording\n }\n logger.warn(COMPONENT, `Fallback model ${fallbackModelId} also failed: ${(chainErr as Error).message}`);\n continue;\n }\n }\n return null;\n}\n\n/**\n * Try the fallback chain for a streaming request. Returns an async generator\n * or null if no fallback could be attempted.\n *\n * Circuit-breaker accounting (fix for Phase X / streaming optimism bug):\n * The pre-fix version called `recordSuccess(fbProviderName)` immediately\n * after acquiring the generator — *before* a single chunk was emitted.\n * That meant a fallback provider that opened a stream and then errored\n * mid-flight was recorded as a success, lying to the breaker.\n *\n * This version returns a wrapped generator that:\n * - records success only after a `done` chunk OR the underlying\n * generator completes without throwing (real outcome)\n * - records failure if the underlying stream throws or yields an\n * `error` chunk after the first chunk\n */\nasync function tryFallbackChainStream(\n options: ChatOptions,\n primaryModelId: string,\n originalError: Error,\n): Promise<AsyncGenerator<ChatStreamChunk> | null> {\n const config = loadConfig();\n const chain = config.agent.fallbackChain;\n if (!chain || chain.length === 0) return null;\n\n const maxRetries = config.agent.fallbackMaxRetries ?? 3;\n let attempts = 0;\n\n for (const fallbackModelId of chain) {\n if (attempts >= maxRetries) break;\n if (fallbackModelId === primaryModelId) continue;\n\n attempts++;\n let fbProviderName: string;\n let gen: AsyncGenerator<ChatStreamChunk>;\n\n try {\n const { provider: fbProvider, model: fbModel } = resolveModel(fallbackModelId);\n fbProviderName = fbProvider.name;\n\n // Check circuit breaker + rate-limit cooldown for fallback provider\n if (!canRequest(fbProviderName, true)) {\n const cb = getCircuitBreaker(fbProviderName);\n logger.warn(COMPONENT, `Skipping stream fallback ${fallbackModelId} — circuit breaker OPEN (${cb.failureCount} failures)`);\n continue;\n }\n\n logger.warn(COMPONENT, `Stream model ${primaryModelId} failed (${originalError.message}), falling back to ${fallbackModelId}`);\n gen = fbProvider.chatStream({ ...options, model: fbModel });\n } catch (chainErr) {\n // Setup failure (resolveModel threw, etc.) — record breaker failure\n try {\n const { provider: fbProvider } = resolveModel(fallbackModelId);\n recordFailure(fbProvider.name);\n } catch {\n // Ignore if we can't resolve the provider for recording\n }\n logger.warn(COMPONENT, `Fallback stream model ${fallbackModelId} setup failed: ${(chainErr as Error).message}`);\n continue;\n }\n\n lastFallbackEvent = {\n primary: primaryModelId,\n active: fallbackModelId,\n reason: originalError.message,\n timestamp: Date.now(),\n };\n\n return monitorStreamForBreaker(gen, fbProviderName);\n }\n return null;\n}\n\n/**\n * Wrap a chat stream so circuit-breaker bookkeeping reflects real outcomes —\n * success only after a clean stream end, failure on error chunks or thrown\n * errors mid-stream. Hoisted to module scope so ESLint's `no-inner-declarations`\n * is happy and so the same wrapper can be reused by chatStream's priority\n * failover path below.\n */\nasync function* monitorStreamForBreaker(\n inner: AsyncGenerator<ChatStreamChunk>,\n providerName: string,\n): AsyncGenerator<ChatStreamChunk> {\n let recorded = false;\n try {\n for await (const chunk of inner) {\n if (chunk.type === 'error') {\n if (!recorded) { recordFailure(providerName); recorded = true; }\n }\n yield chunk;\n }\n if (!recorded) recordSuccess(providerName);\n } catch (innerErr) {\n if (!recorded) { recordFailure(providerName); recorded = true; }\n throw innerErr;\n }\n}\n\n/** Route a chat request to a mesh peer */\nasync function meshChat(peer: MeshPeer, modelId: string, message: string): Promise<ChatResponse> {\n const requestId = randomBytes(8).toString('hex');\n const config = loadConfig();\n const timeoutMs = config.mesh?.taskTimeoutMs || 120_000;\n logger.info(COMPONENT, `Routing \"${modelId}\" to mesh peer ${peer.hostname} (${peer.nodeId.slice(0, 8)}...)`);\n const result = await routeTaskToNode(peer.nodeId, requestId, message, modelId, timeoutMs) as Record<string, unknown>;\n if (result.error) {\n throw new Error(`Mesh peer error: ${result.error}`);\n }\n // Fire-and-forget analytics\n (async () => {\n const { trackModelUsage } = await import('../analytics/featureTracker.js');\n trackModelUsage(modelId, 'mesh', true);\n })().catch(() => {});\n return result as unknown as ChatResponse;\n}\n\n/**\n * Enhanced error message with provider and model context.\n */\nfunction createEnhancedErrorMessage(error: Error, providerName: string, model: string, attempt: number): string {\n const status = getErrorStatus(error);\n const statusInfo = status ? `[HTTP ${status}] ` : '';\n\n return [\n `Provider ${providerName}/${model} failed`,\n statusInfo + error.message,\n attempt > 0 ? `(attempt ${attempt + 1})` : null,\n ].filter(Boolean).join(': ');\n}\n\n/**\n * Send a chat request with exponential backoff retry and circuit breaker protection.\n * Automatically routes to the correct provider with error recovery and fallback chain.\n */\nexport async function chat(options: ChatOptions): Promise<ChatResponse> {\n const modelId = options.model || 'anthropic/claude-sonnet-4-20250514';\n const { provider, model } = resolveModel(modelId);\n const providerName = provider.name;\n\n logger.info(COMPONENT, `Routing to ${provider.displayName} (model: ${model})`);\n\n // G4: Track fallback attempts for structured error reporting (OpenClaw pattern)\n const fallbackAttempts: Array<{ provider: string; model: string; error: string; reason: string }> = [];\n\n // Check circuit breaker before attempting request\n if (!canRequest(providerName)) {\n const cb = getCircuitBreaker(providerName);\n const errorMsg = `Circuit breaker OPEN for ${providerName}/${model} (${cb.failureCount} failures, reset in ${\n cb.openSince ? Math.round((CIRCUIT_BREAKER_CONFIG.resetTimeout - (Date.now() - cb.openSince)) / 1000) : 'unknown'\n }s)`;\n logger.warn(COMPONENT, errorMsg);\n const enhancedError = new Error(errorMsg);\n Object.assign(enhancedError, { status: 503, provider: providerName, model });\n throw enhancedError;\n }\n\n let lastError: Error | null = null;\n const maxRetries = RETRY_CONFIG.maxRetries;\n\n // Gap 1 (plan-this-logical-ocean): one-shot compression on CONTEXT_OVERFLOW.\n // The error taxonomy classifies overflows and sets `shouldCompress: true`,\n // but nothing used to act on it — the hint was dead code. Now we compact\n // options.messages via buildSmartContext and retry the SAME provider once\n // before falling through to model fallback / cross-provider failover.\n let compressionRetried = false;\n let thinkingStripped = false;\n\n // v4.13 ancestor-extraction (Hermes rate_limit_tracker): proactive backoff\n // before even sending the request. If the last response from this provider\n // indicated the quota window is nearly depleted, hold off briefly instead\n // of firing the request and getting a 429.\n try {\n const backoff = shouldBackOff(providerName);\n if (backoff) {\n logger.info(COMPONENT, `[RateLimit] Proactive backoff on ${providerName}: ${backoff.reason} — waiting ${Math.round(backoff.backoffMs)}ms`);\n await sleep(backoff.backoffMs);\n }\n } catch { /* never block on tracker errors */ }\n\n // Attempt request with retry logic\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n try {\n const result = await provider.chat({ ...options, model });\n\n // Strip chain-of-thought leakage from model responses\n if (result.content) {\n result.content = stripThinkingFromResponse(result.content);\n }\n\n // Record success for circuit breaker\n recordSuccess(providerName);\n lastFallbackEvent = null; // Clear fallback state on primary success\n\n // Log if this was a retry that succeeded\n if (attempt > 0) {\n logger.info(COMPONENT, `${provider.displayName}/${model} recovered after ${attempt} retry attempt(s)`);\n }\n\n // Fire-and-forget analytics\n (async () => {\n const { trackModelUsage } = await import('../analytics/featureTracker.js');\n trackModelUsage(model, providerName, true);\n })().catch(() => {});\n\n return result;\n } catch (error) {\n lastError = error as Error;\n\n // Classify error using centralized taxonomy\n const classified = classifyProviderError(error);\n\n // Only affect circuit breaker for genuine provider instability\n if (shouldAffectCircuitBreaker(classified)) {\n recordFailure(providerName);\n }\n\n // noFallback: caller (e.g. ModelProbe) requires the target model to\n // answer or the request to fail cleanly. Skip retries, fallback\n // chain, mesh routing, and provider failover entirely — otherwise\n // we would silently probe a different model and poison the caller's\n // data with unrelated capabilities.\n if (options.noFallback) {\n const errorMsg = createEnhancedErrorMessage(error as Error, providerName, model, attempt);\n const noFallbackError = new Error(\n `Probe target ${providerName}/${model} unreachable (noFallback=true): ${errorMsg}`\n );\n Object.assign(noFallbackError, {\n status: classified.httpStatus,\n provider: providerName,\n model,\n cause: error,\n failoverReason: classified.reason,\n noFallback: true,\n });\n throw noFallbackError;\n }\n\n // G2: Record rate-limit cooldown to prevent probe hammering\n if (classified.reason === FailoverReason.RATE_LIMIT) {\n recordRateLimitCooldown(providerName);\n }\n\n // Exhaust credential in pool if rotation is recommended\n if (classified.shouldRotateCredential) {\n const pool = getExistingPool(providerName);\n if (pool) {\n // Find which credential was used and exhaust it\n const status = pool.status();\n const lastUsed = status.find(s => s.available);\n if (lastUsed) {\n pool.exhaust(lastUsed.name, classified.cooldownMs || 60000);\n }\n }\n }\n\n // Gap 1: act on shouldCompress hint BEFORE generic retry/fallback.\n // On CONTEXT_OVERFLOW (or any future reason that sets shouldCompress),\n // compact options.messages via buildSmartContext and retry the same\n // provider+model once. Only fires on the FIRST such error per call —\n // if the compacted request still overflows, we drop through to the\n // normal retry/fallback ladder instead of shrinking forever.\n if (classified.shouldCompress && !compressionRetried && Array.isArray(options.messages)) {\n compressionRetried = true;\n const beforeCount = options.messages.length;\n // Conservative target — most of the whitelisted Ollama cloud\n // models have >=32K context; 24K leaves headroom for the\n // completion itself and any tool schemas the provider adds.\n const compactTokens = 24000;\n try {\n const compacted = buildSmartContext(options.messages, compactTokens);\n if (compacted.length > 0 && compacted.length <= beforeCount) {\n options = { ...options, messages: compacted };\n logger.info(\n COMPONENT,\n `[Router] ${classified.reason} — compacted context ${beforeCount}→${compacted.length} msgs, retrying ${providerName}/${model}`,\n );\n // Retry immediately — no backoff needed, we changed the input\n continue;\n }\n logger.warn(COMPONENT, `[Router] Compression produced empty/larger output — skipping compress retry`);\n } catch (compErr) {\n logger.warn(COMPONENT, `[Router] Compression failed: ${(compErr as Error).message} — falling through`);\n }\n }\n\n // Gap 2: act on THINKING_NOT_SUPPORTED — strip thinking options and retry\n // once on the same provider. This handles models like titan-qwen3.5:4b\n // that return HTTP 400 \"does not support thinking\". We mutate options only\n // once so a second THINKING_NOT_SUPPORTED falls through to normal retry ladder.\n if (classified.reason === FailoverReason.THINKING_NOT_SUPPORTED && !thinkingStripped) {\n thinkingStripped = true;\n const providerOpts = options.providerOptions ? { ...options.providerOptions } : {};\n // Remove Ollama/OpenAI-compat thinking keys\n delete (providerOpts as Record<string, unknown>).think;\n delete (providerOpts as Record<string, unknown>).thinking;\n delete (providerOpts as Record<string, unknown>).thinking_mode;\n delete (providerOpts as Record<string, unknown>).budget_tokens;\n delete (providerOpts as Record<string, unknown>).enable_thinking;\n options = { ...options, providerOptions: providerOpts };\n logger.info(COMPONENT, `[Router] THINKING_NOT_SUPPORTED — stripped thinking flags, retrying ${providerName}/${model}`);\n continue;\n }\n\n const errorMsg = createEnhancedErrorMessage(error as Error, providerName, model, attempt);\n\n // Check if we should retry\n if (classified.retryable && attempt < maxRetries) {\n // Use taxonomy cooldown or calculate backoff, whichever is larger\n let retryDelayMs = Math.max(classified.cooldownMs, calculateBackoffDelay(attempt));\n\n // Hunt Finding #37 (2026-04-14): previous code tried\n // `(error as Response)?.headers?.get?.('Retry-After')` which\n // always returned undefined at runtime because the error is\n // an Error object, not a Response. Retry-After headers were\n // never actually respected. Providers now attach retryAfterMs\n // to the thrown error via createProviderError().\n const errWithHints = error as { retryAfterMs?: number | null; headers?: { get?(k: string): string | null } };\n if (typeof errWithHints.retryAfterMs === 'number' && errWithHints.retryAfterMs > 0) {\n retryDelayMs = errWithHints.retryAfterMs;\n logger.info(COMPONENT, `[RateLimit] Respecting Retry-After: ${Math.round(retryDelayMs / 1000)}s`);\n } else {\n // Back-compat: old-style error that happens to wrap a Response\n const retryAfter = errWithHints.headers?.get?.('Retry-After');\n if (retryAfter) {\n const parsed = parseRetryAfter(retryAfter);\n if (parsed !== null) {\n retryDelayMs = parsed;\n logger.info(COMPONENT, `[RateLimit] Respecting Retry-After (legacy): ${Math.round(retryDelayMs / 1000)}s`);\n }\n }\n }\n\n logger.warn(COMPONENT, `${errorMsg} [${classified.reason}] — retrying in ${Math.round(retryDelayMs)}ms`);\n await sleep(retryDelayMs);\n continue;\n }\n\n // Not retryable or max retries exceeded\n if (!classified.retryable) {\n logger.error(COMPONENT, `${errorMsg} — not retryable [${classified.reason}] (${classified.httpStatus ? `HTTP ${classified.httpStatus}` : 'unknown error'})`);\n } else {\n logger.error(COMPONENT, `${errorMsg} — max retries (${maxRetries}) exceeded [${classified.reason}]`);\n }\n\n // Try configured fallback chain first (model-level fallback)\n if (classified.retryable || classified.shouldFallback) {\n const chainResult = await tryFallbackChain(options, modelId, error as Error);\n if (chainResult) {\n logger.info(COMPONENT, `Fallback chain recovered from ${providerName}/${model} failure [${classified.reason}]`);\n return chainResult;\n }\n }\n\n // Try mesh peers before local failover\n const config = loadConfig();\n if (config.mesh?.enabled) {\n const peer = findModelOnMesh(modelId);\n if (peer) {\n try {\n const message = Array.isArray(options.messages)\n ? options.messages.map(m => m.content).join('\\n')\n : (options as unknown as Record<string, unknown>).message as string || '';\n return await meshChat(peer, modelId, message);\n } catch (meshErr) {\n logger.warn(COMPONENT, `Mesh routing failed: ${(meshErr as Error).message}`);\n }\n }\n }\n\n // Attempt failover to other providers (only on first failure, not after retries)\n if (attempt === 0) {\n const failoverOrder = getFailoverOrder(providerName);\n for (const fallbackName of failoverOrder) {\n if (fallbackName === providerName) continue;\n\n // Check circuit breaker + rate-limit cooldown for fallback provider\n if (!canRequest(fallbackName, true)) {\n logger.debug(COMPONENT, `Skipping fallback ${fallbackName} — circuit breaker OPEN`);\n continue;\n }\n\n const fallback = providers.get(fallbackName);\n if (!fallback) continue;\n\n let fbModelName = 'unknown';\n try {\n const healthy = await fallback.healthCheck();\n if (!healthy) continue;\n\n const models = await fallback.listModels();\n if (models.length === 0) continue;\n\n // Prefer a model with a similar name prefix (e.g. claude-* → claude-*)\n const originalPrefix = model.split('-')[0];\n fbModelName = models.find(m => m.startsWith(originalPrefix)) || models[0];\n\n logger.warn(COMPONENT, `Failing over from ${providerName}/${model} → ${fallbackName}/${fbModelName}`);\n const result = await fallback.chat({ ...options, model: fbModelName });\n recordSuccess(fallbackName); // Record success for the fallback provider\n // Fire-and-forget analytics\n (async () => {\n const { trackModelUsage } = await import('../analytics/featureTracker.js');\n trackModelUsage(fbModelName, fallbackName, true);\n })().catch(() => {});\n return result;\n } catch (fallbackErr) {\n recordFailure(fallbackName); // Record failure for the fallback provider too\n // G4: Record fallback attempt for structured error chain\n fallbackAttempts.push({\n provider: fallbackName,\n model: fbModelName,\n error: (fallbackErr as Error).message,\n reason: classifyProviderError(fallbackErr).reason,\n });\n logger.warn(COMPONENT, `Fallback ${fallbackName} also failed: ${(fallbackErr as Error).message}`);\n continue;\n }\n }\n }\n\n // G4: Record the primary attempt too\n fallbackAttempts.unshift({\n provider: providerName,\n model,\n error: (error as Error).message,\n reason: classified.reason,\n });\n\n // Fire-and-forget analytics\n (async () => {\n const { trackModelUsage } = await import('../analytics/featureTracker.js');\n trackModelUsage(model, providerName, false);\n })().catch(() => {});\n\n // All recovery options exhausted, throw enhanced error\n const attemptSummary = fallbackAttempts.length > 1\n ? ` | Tried ${fallbackAttempts.length} providers: ${fallbackAttempts.map(a => `${a.provider}/${a.model} [${a.reason}]`).join(', ')}`\n : '';\n const finalError = new Error(`All providers failed: ${errorMsg}${attemptSummary}`);\n Object.assign(finalError, {\n status: classified.httpStatus,\n provider: providerName,\n model,\n cause: error,\n failoverReason: classified.reason,\n // G4: Structured fallback attempt chain (OpenClaw FallbackSummaryError pattern)\n fallbackAttempts,\n });\n throw finalError;\n }\n }\n\n // Should never reach here, but TypeScript requires it\n throw lastError || new Error(`Provider ${providerName}/${model} failed after all retries`);\n}\n\n/**\n * Send a streaming chat request with exponential backoff retry and circuit breaker protection.\n */\nexport async function* chatStream(options: ChatOptions): AsyncGenerator<ChatStreamChunk> {\n const modelId = options.model || 'anthropic/claude-sonnet-4-20250514';\n const { provider, model } = resolveModel(modelId);\n const providerName = provider.name;\n\n logger.info(COMPONENT, `Streaming via ${provider.displayName} (model: ${model})`);\n\n // Check circuit breaker before attempting request\n if (!canRequest(providerName)) {\n const cb = getCircuitBreaker(providerName);\n yield {\n type: 'error',\n error: `[CircuitBreaker] Circuit OPEN: ${providerName}/${model} (${cb.failureCount} failures, testing in ${\n Math.round((CIRCUIT_BREAKER_CONFIG.resetTimeout - (Date.now() - cb.openSince!)) / 1000)\n }s)`,\n };\n return;\n }\n\n let lastError: Error | null = null;\n const maxRetries = RETRY_CONFIG.maxRetries;\n\n // Once-per-call latches so we don't repeat failover work after a retry\n // burst — both fallback paths can be reached on any exhausted-retry\n // attempt, but each is attempted at most once per chatStream invocation\n // (Task 4: prevent infinite-loop recovery, formerly attempt===0 gate).\n let fallbackChainAttempted = false;\n let priorityFailoverAttempted = false;\n\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n try {\n // Stream from provider — record success only on first non-error\n // chunk so we don't claim success for a stream that never produced.\n let recordedSuccess = false;\n for await (const chunk of provider.chatStream({ ...options, model })) {\n if (!recordedSuccess && chunk.type !== 'error' && attempt === 0) {\n recordSuccess(providerName);\n recordedSuccess = true;\n }\n lastFallbackEvent = null;\n yield chunk;\n }\n\n // Log if this was a retry that succeeded\n if (attempt > 0) {\n logger.info(COMPONENT, `${provider.displayName}/${model} stream recovered after ${attempt} retry attempt(s)`);\n }\n return;\n } catch (error) {\n lastError = error as Error;\n\n // Classify error using centralized taxonomy\n const classified = classifyProviderError(error);\n if (shouldAffectCircuitBreaker(classified)) {\n recordFailure(providerName);\n }\n\n const errorMsg = createEnhancedErrorMessage(error as Error, providerName, model, attempt);\n\n // Check if we should retry\n if (classified.retryable && attempt < maxRetries) {\n const retryDelayMs = Math.max(classified.cooldownMs, calculateBackoffDelay(attempt));\n logger.warn(COMPONENT, `${errorMsg} [${classified.reason}] — streaming retry in ${Math.round(retryDelayMs)}ms`);\n\n // Task 2: emit a dedicated `retry` event instead of leaking a\n // text chunk (e.g. \"[Retrying request (1/4) due to ...]\") into\n // the user-visible stream. UI consumers should display this\n // as a status indicator, never forward to the assistant message.\n yield {\n type: 'retry' as const,\n attempt: attempt + 1,\n maxRetries,\n reason: classified.reason,\n provider: providerName,\n model,\n delayMs: Math.round(retryDelayMs),\n };\n\n await sleep(retryDelayMs);\n continue;\n }\n\n // Not retryable or max retries exceeded\n if (!classified.retryable) {\n logger.error(COMPONENT, `${errorMsg} — streaming not retryable [${classified.reason}]`);\n } else {\n logger.error(COMPONENT, `${errorMsg} — streaming max retries exceeded [${classified.reason}]`);\n }\n\n // Try configured fallback chain first (once per chatStream call)\n if (!fallbackChainAttempted && (classified.retryable || classified.shouldFallback)) {\n fallbackChainAttempted = true;\n const chainStream = await tryFallbackChainStream(options, modelId, error as Error);\n if (chainStream) {\n yield {\n type: 'failover' as const,\n originalProvider: providerName,\n originalModel: model,\n error: (error as Error).message,\n };\n yield* chainStream;\n return;\n }\n }\n\n // Try mesh peers (non-streaming fallback for now)\n const config = loadConfig();\n if (config.mesh?.enabled) {\n const peer = findModelOnMesh(modelId);\n if (peer) {\n try {\n const message = Array.isArray(options.messages)\n ? options.messages.map(m => m.content).join('\\n')\n : (options as unknown as Record<string, unknown>).message as string || '';\n const result = await meshChat(peer, modelId, message);\n yield { type: 'text' as const, content: result.content };\n yield { type: 'done' as const };\n return;\n } catch (meshErr) {\n logger.warn(COMPONENT, `Mesh stream routing failed: ${(meshErr as Error).message}`);\n }\n }\n }\n\n // Task 4: priority-failover loop now runs on ANY exhausted-retry\n // path, not just attempt === 0. The `priorityFailoverAttempted`\n // latch ensures it executes at most once per chatStream call so\n // we don't loop through the failover order on every retry burst.\n if (!priorityFailoverAttempted) {\n priorityFailoverAttempted = true;\n const failoverOrder = getFailoverOrder(providerName);\n let failedOver = false;\n\n for (const fallbackName of failoverOrder) {\n if (fallbackName === providerName) continue;\n\n if (!canRequest(fallbackName, true)) {\n logger.debug(COMPONENT, `Skipping stream fallback ${fallbackName} — circuit breaker OPEN`);\n continue;\n }\n\n const fallback = providers.get(fallbackName);\n if (!fallback) continue;\n\n try {\n const healthy = await fallback.healthCheck();\n if (!healthy) continue;\n\n const models = await fallback.listModels();\n if (models.length === 0) continue;\n\n const originalPrefix = model.split('-')[0];\n const preferred = models.find(m => m.startsWith(originalPrefix)) || models[0];\n\n logger.warn(COMPONENT, `Stream failing over from ${providerName}/${model} → ${fallbackName}/${preferred}`);\n\n // Notify consumer about failover\n yield {\n type: 'failover' as const,\n originalProvider: providerName,\n originalModel: model,\n error: errorMsg,\n };\n\n // Wrap the failover stream so we record actual outcome,\n // not just optimistic success-on-generator-acquire (Task 3\n // applied here too — same pattern as tryFallbackChainStream).\n let recorded = false;\n try {\n for await (const chunk of fallback.chatStream({ ...options, model: preferred })) {\n if (chunk.type === 'error' && !recorded) {\n recordFailure(fallbackName);\n recorded = true;\n }\n yield chunk;\n }\n if (!recorded) recordSuccess(fallbackName);\n } catch (innerErr) {\n if (!recorded) recordFailure(fallbackName);\n throw innerErr;\n }\n failedOver = true;\n break;\n } catch (fallbackErr) {\n recordFailure(fallbackName);\n logger.warn(COMPONENT, `Stream fallback ${fallbackName} also failed: ${(fallbackErr as Error).message}`);\n continue;\n }\n }\n\n if (failedOver) return;\n }\n\n // All recovery options exhausted\n yield { type: 'error', error: `All streaming providers failed: ${errorMsg}` };\n return;\n }\n }\n\n // Should never reach here\n yield { type: 'error', error: lastError?.message || 'Streaming failed after all retries' };\n}\n\n/** Health check all providers */\nexport async function healthCheckAll(): Promise<Record<string, boolean>> {\n initProviders();\n const entries = Array.from(providers.entries());\n const settled = await Promise.allSettled(\n entries.map(([, provider]) => provider.healthCheck())\n );\n const results: Record<string, boolean> = {};\n for (let i = 0; i < entries.length; i++) {\n const [name] = entries[i];\n const outcome = settled[i];\n results[name] = outcome.status === 'fulfilled' ? outcome.value : false;\n }\n return results;\n}\n"],"mappings":"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","names":["errorMsg"]}
1
+ {"version":3,"sources":["../../src/providers/router.ts"],"sourcesContent":["/**\n * TITAN — Universal Model Router\n * Routes model requests to the correct provider with failover, alias resolution,\n * and live model discovery across all configured providers (including local Ollama).\n *\n * Error Recovery Features:\n * - Exponential backoff retry for transient failures (429, 503, timeouts)\n * - Circuit breaker pattern to avoid hammering failing providers\n * - Automatic fallback to next provider in chain on persistent errors\n * - Detailed error messages including provider name and model\n */\nimport { LLMProvider, type ChatOptions, type ChatResponse, type ChatStreamChunk } from './base.js';\nimport { AnthropicProvider } from './anthropic.js';\nimport { OpenAIProvider } from './openai.js';\nimport { GoogleProvider } from './google.js';\nimport { OllamaProvider } from './ollama.js';\nimport { ClaudeCodeProvider } from './claudeCode.js';\nimport { OpenAICompatProvider, PROVIDER_PRESETS } from './openai_compat.js';\nimport { loadConfig } from '../config/config.js';\nimport logger from '../utils/logger.js';\nimport { findModelOnMesh } from '../mesh/registry.js';\nimport type { MeshPeer } from '../mesh/discovery.js';\nimport { routeTaskToNode } from '../mesh/transport.js';\nimport { randomBytes } from 'crypto';\nimport { sleep } from '../utils/helpers.js';\nimport { classifyProviderError, shouldAffectCircuitBreaker, FailoverReason } from './errorTaxonomy.js';\nimport { getExistingPool } from './credentialPool.js';\nimport { buildSmartContext } from '../agent/contextManager.js';\nimport { shouldBackOff } from './rateLimitTracker.js';\n\nconst COMPONENT = 'Router';\n\n/** Build failover order from all registered providers, sorted by capability priority */\nfunction getFailoverOrder(excludeProvider: string): string[] {\n const priority: Record<string, number> = {\n anthropic: 100,\n openai: 90,\n google: 80,\n openrouter: 75,\n groq: 70,\n together: 65,\n deepseek: 60,\n xai: 55,\n mistral: 50,\n cerebras: 45,\n cohere: 40,\n 'cohere-v2': 40,\n fireworks: 35,\n perplexity: 30,\n 'claude-code': 15,\n ollama: 10,\n };\n initProviders();\n return Array.from(providers.keys())\n .filter(name => name !== excludeProvider)\n .sort((a, b) => (priority[b] ?? 25) - (priority[a] ?? 25));\n}\n\n// ── Chain-of-thought stripping ──────────────────────────────────\n// Some local models (qwen, glm, deepseek, etc.) leak their internal\n// reasoning into the response. This runs on EVERY chat() response so\n// no consumer (FB posts, Messenger, comments, web chat) ever sees it.\n\nfunction stripThinkingFromResponse(text: string): string {\n let cleaned = text;\n\n // 1. Remove <think>...</think> blocks (deepseek, qwen thinking mode)\n cleaned = cleaned.replace(/<think>[\\s\\S]*?<\\/think>/gi, '');\n\n // 2. Remove ```thinking ... ``` blocks\n cleaned = cleaned.replace(/```thinking[\\s\\S]*?```/gi, '');\n\n // 3. Cut at \"multiple draft\" boundaries — models often generate several\n // versions inline: \"Let me try another version:\", \"Here's another:\", etc.\n const draftBoundary = /\\n+[\"']?\\n*(Let me (try|make|write|do|craft)|Here'?s? (another|a better|a more)|Another (version|option|take|attempt)|Or (maybe|how about|alternatively)|Version \\d|Option \\d|Draft \\d|---)/i;\n const draftMatch = cleaned.match(draftBoundary);\n if (draftMatch?.index !== undefined && draftMatch.index > 20) {\n cleaned = cleaned.slice(0, draftMatch.index);\n }\n\n // 4. If the response starts with meta-reasoning, extract just the reply\n const reasoningStart = /^(The user wants|The comment|I need to|I should|Let me (think|craft|write|consider|analyze)|OK so|Alright,|Hmm,|This is a)/i;\n if (reasoningStart.test(cleaned.trim())) {\n const parts = cleaned.split(/\\n{2,}|^---$/m);\n const replyParts = parts.filter(p => {\n const trimmed = p.trim();\n if (!trimmed) return false;\n if (reasoningStart.test(trimmed)) return false;\n if (/^(Wait|Actually|But |So |Since |That works|That's about|Let me (count|check|think|try))/i.test(trimmed)) return false;\n if (/\\b(characters|under \\d+ char|personality|mentioned|the rules)\\b/i.test(trimmed)) return false;\n return true;\n });\n if (replyParts.length > 0) {\n cleaned = replyParts.join('\\n\\n');\n }\n }\n\n // 5. Remove wrapping quotes that some models add\n cleaned = cleaned.trim().replace(/^[\"']|[\"']$/g, '').trim();\n\n return cleaned;\n}\n\n// ── Provider name normalization ─────────────────────────────────\nconst PROVIDER_ALIASES: Record<string, string> = {\n 'z.ai': 'xai',\n 'zai': 'xai',\n 'grok': 'xai',\n 'local': 'ollama',\n 'vertex': 'google',\n 'vertex-ai': 'google',\n 'azure-openai': 'azure',\n 'aws': 'bedrock',\n 'amazon': 'bedrock',\n 'litellm-proxy': 'litellm',\n 'hf': 'huggingface',\n 'hugging-face': 'huggingface',\n '01ai': 'yi',\n '01.ai': 'yi',\n 'glm': 'zhipu',\n 'bigmodel': 'zhipu',\n 'pi': 'inflection',\n 'octoai': 'octo',\n 'nim': 'nvidia',\n 'nvidia-nim': 'nvidia',\n};\n\n/** Normalize provider names for consistency (e.g. \"grok\" → \"xai\", \"local\" → \"ollama\") */\nexport function normalizeProvider(name: string): string {\n const lower = name.toLowerCase();\n return PROVIDER_ALIASES[lower] || lower;\n}\n\n/** Provider registry */\nconst providers: Map<string, LLMProvider> = new Map();\nlet initialized = false;\n\nfunction initProviders(): void {\n if (initialized) return;\n // Core providers (custom implementations)\n providers.set('anthropic', new AnthropicProvider());\n providers.set('openai', new OpenAIProvider());\n providers.set('google', new GoogleProvider());\n providers.set('ollama', new OllamaProvider());\n providers.set('claude-code', new ClaudeCodeProvider());\n // OpenAI-compatible providers (Groq, Mistral, OpenRouter, xAI, etc.)\n for (const preset of PROVIDER_PRESETS) {\n providers.set(preset.name, new OpenAICompatProvider(preset));\n }\n initialized = true;\n}\n\n/** Get a provider by name */\nexport function getProvider(name: string): LLMProvider | undefined {\n initProviders();\n return providers.get(name);\n}\n\n/** Get all registered providers */\nexport function getAllProviders(): Map<string, LLMProvider> {\n initProviders();\n return providers;\n}\n\n/** Resolve a model alias (e.g. \"fast\" → \"openai/gpt-4o-mini\") */\nfunction resolveAlias(modelId: string): string {\n const config = loadConfig();\n const aliases = config.agent.modelAliases;\n if (aliases && aliases[modelId]) {\n const resolved = aliases[modelId];\n logger.debug(COMPONENT, `Alias \"${modelId}\" → \"${resolved}\"`);\n return resolved;\n }\n return modelId;\n}\n\n\n/** Resolve the provider and model from a model ID like \"anthropic/claude-3\" or alias like \"fast\" */\nexport function resolveModel(modelId: string): { provider: LLMProvider; model: string } {\n initProviders();\n // First resolve aliases\n const resolved = resolveAlias(modelId);\n const { provider: rawProviderName, model } = LLMProvider.parseModelId(resolved);\n\n\n // Normalize provider name (e.g. \"grok\" → \"xai\", \"local\" → \"ollama\")\n const providerName = normalizeProvider(rawProviderName);\n const provider = providers.get(providerName);\n if (!provider) {\n throw new Error(`Unknown provider: ${providerName}. Available: ${Array.from(providers.keys()).join(', ')}`);\n }\n return { provider, model };\n}\n\n/**\n * Non-throwing variant of resolveModel — returns null on an unknown\n * provider instead of throwing. Used by gateway endpoints to fail-fast\n * with a helpful 400 BEFORE the agent loop builds the prompt and burns\n * tokens. v5.5.30+. Bug from 2026-05-08 audit: requests with bad model\n * IDs (e.g. typoed providers) used to crash deep inside the agent loop\n * after prompt assembly, returning 500 with a stack trace.\n */\nexport function tryResolveModel(modelId: string): { provider: LLMProvider; model: string } | null {\n try { return resolveModel(modelId); } catch { return null; }\n}\n\n/** List of provider names known to this gateway (for \"did you mean\" suggestions). */\nexport function getKnownProviderNames(): string[] {\n initProviders();\n return Array.from(providers.keys()).sort();\n}\n\n/** Check if a model is allowed by the allowlist. Empty list = all allowed. */\nexport function isModelAllowed(modelId: string): boolean {\n const config = loadConfig();\n const allowedModels = config.agent.allowedModels;\n if (!allowedModels || allowedModels.length === 0) return true;\n\n // Resolve alias first\n const resolved = resolveAlias(modelId);\n\n for (const pattern of allowedModels) {\n if (pattern === resolved) return true;\n // Wildcard support: \"openai/*\" matches \"openai/gpt-4o\"\n if (pattern.endsWith('/*')) {\n const prefix = pattern.slice(0, -1); // \"openai/\"\n if (resolved.startsWith(prefix)) return true;\n }\n }\n return false;\n}\n\n/** Discovered model info */\nexport interface DiscoveredModel {\n id: string; // Full ID e.g. \"ollama/llama3.1\"\n provider: string; // Provider name e.g. \"ollama\"\n model: string; // Model name e.g. \"llama3.1\"\n displayName: string; // Provider display name e.g. \"Ollama (Local)\"\n source: 'static' | 'live'; // Whether discovered via live API or hardcoded list\n /** True if the provider has the credentials it needs to actually serve a request for this model. */\n keyConfigured: boolean;\n}\n\n/** Cache for discovered models (refreshed on demand, 60s TTL) */\nlet modelCache: { models: DiscoveredModel[]; timestamp: number } | null = null;\nconst MODEL_CACHE_TTL = 60_000; // 60 seconds\n\n/**\n * Discover all available models across all providers.\n * Queries each provider's listModels() — for Ollama this hits the local API\n * to find actually-installed models. Results are cached for 60s.\n */\nexport async function discoverAllModels(forceRefresh = false): Promise<DiscoveredModel[]> {\n initProviders();\n\n if (!forceRefresh && modelCache && (Date.now() - modelCache.timestamp) < MODEL_CACHE_TTL) {\n return modelCache.models;\n }\n\n const discovered: DiscoveredModel[] = [];\n const health = await healthCheckAll();\n\n const tasks = Array.from(providers.entries()).map(async ([name, provider]) => {\n try {\n const models = await provider.listModels();\n const isLive = health[name] === true;\n const keyConfigured = provider.isConfigured();\n for (const model of models) {\n discovered.push({\n id: `${name}/${model}`,\n provider: name,\n model,\n displayName: provider.displayName,\n source: (name === 'ollama' && isLive) ? 'live' : 'static',\n keyConfigured,\n });\n }\n } catch (err) {\n logger.debug(COMPONENT, `Failed to list models for ${name}: ${(err as Error).message}`);\n }\n });\n\n await Promise.all(tasks);\n\n modelCache = { models: discovered, timestamp: Date.now() };\n logger.info(COMPONENT, `Discovered ${discovered.length} models across ${providers.size} providers`);\n return discovered;\n}\n\n/** Get current model aliases from config */\nexport function getModelAliases(): Record<string, string> {\n const config = loadConfig();\n return config.agent.modelAliases || {};\n}\n\n// ── Circuit Breaker ─────────────────────────────────────────────\n/** Circuit breaker states for each provider */\ntype CircuitState = 'closed' | 'open' | 'half-open';\n\ninterface CircuitBreakerState {\n state: CircuitState;\n failureCount: number;\n lastFailureTime: number | null;\n lastSuccessTime: number | null;\n openSince: number | null;\n}\n\n/** Circuit breaker configuration — tuned for cloud model tolerance */\nconst CIRCUIT_BREAKER_CONFIG = {\n failureThreshold: 8, // Number of failures before opening circuit (was 5 — too aggressive for cloud)\n resetTimeout: 60000, // 60s before trying again (was 30s — cloud models need recovery time)\n monitoringWindow: 120000, // 120s window for counting failures (was 60s — cloud latency spikes are normal)\n successThreshold: 2, // Successes needed in half-open to close circuit (was 3)\n};\n\n/** Track circuit breaker state per provider */\nconst circuitBreakers = new Map<string, CircuitBreakerState>();\n\n// Prune stale closed circuit breakers every 5 minutes to prevent unbounded growth\nsetInterval(() => {\n const now = Date.now();\n for (const [name, state] of circuitBreakers) {\n if (state.state === 'closed' && state.lastFailureTime && now - state.lastFailureTime > 600_000) {\n circuitBreakers.delete(name);\n }\n }\n}, 300_000);\n\n\n/**\n * G2: Cooldown-aware probe throttling (OpenClaw pattern).\n * When a provider is rate-limited, don't probe it again for MIN_PROBE_INTERVAL_MS.\n * Prevents cascade failures during provider outages.\n */\nconst MIN_PROBE_INTERVAL_MS = 30000; // 30s between probes\nconst providerRateLimitCooldowns = new Map<string, number>(); // provider → timestamp of last rate-limit\n\n/** Record that a provider returned a rate-limit error */\nfunction recordRateLimitCooldown(providerName: string): void {\n providerRateLimitCooldowns.set(providerName, Date.now());\n}\n\n/** Check if a provider is still in its rate-limit cooldown window */\nfunction isInRateLimitCooldown(providerName: string): boolean {\n const lastRateLimit = providerRateLimitCooldowns.get(providerName);\n if (!lastRateLimit) return false;\n const elapsed = Date.now() - lastRateLimit;\n if (elapsed >= MIN_PROBE_INTERVAL_MS) {\n providerRateLimitCooldowns.delete(providerName); // Cooldown expired\n return false;\n }\n return true;\n}\n\n/**\n * Get or create circuit breaker state for a provider.\n */\nfunction getCircuitBreaker(providerName: string): CircuitBreakerState {\n if (!circuitBreakers.has(providerName)) {\n circuitBreakers.set(providerName, {\n state: 'closed',\n failureCount: 0,\n lastFailureTime: null,\n lastSuccessTime: null,\n openSince: null,\n });\n }\n return circuitBreakers.get(providerName)!;\n}\n\n/**\n * Record a successful request for a provider.\n * Resets failure count and updates state appropriately.\n */\nfunction recordSuccess(providerName: string): void {\n const cb = getCircuitBreaker(providerName);\n cb.lastSuccessTime = Date.now();\n\n if (cb.state === 'half-open') {\n // In half-open state, success reduces the counter\n cb.failureCount = Math.max(0, cb.failureCount - 1);\n // If we've had enough successes, close the circuit\n if (cb.failureCount <= 0) {\n cb.state = 'closed';\n cb.openSince = null;\n cb.failureCount = 0;\n logger.info(COMPONENT, `[CircuitBreaker] ${providerName} circuit CLOSED after successful recovery`);\n }\n } else if (cb.state === 'closed') {\n // In closed state, reset the failure count on success\n cb.failureCount = 0;\n }\n}\n\n/**\n * Record a failed request for a provider.\n * Opens circuit if failure threshold is exceeded.\n */\nfunction recordFailure(providerName: string): void {\n const cb = getCircuitBreaker(providerName);\n const now = Date.now();\n cb.lastFailureTime = now;\n\n // Only count failures within the monitoring window\n const windowStart = now - CIRCUIT_BREAKER_CONFIG.monitoringWindow;\n if (cb.lastFailureTime && cb.lastFailureTime < windowStart) {\n // Reset if outside monitoring window\n cb.failureCount = 1;\n } else {\n cb.failureCount++;\n }\n\n // Check if we should open the circuit\n if (cb.failureCount >= CIRCUIT_BREAKER_CONFIG.failureThreshold && cb.state === 'closed') {\n cb.state = 'open';\n cb.openSince = now;\n logger.warn(COMPONENT, `[CircuitBreaker] ${providerName} circuit OPENED after ${cb.failureCount} failures`);\n }\n}\n\n/**\n * Check if a provider's circuit breaker allows requests.\n * Returns true if closed or if half-open (time to test).\n * Returns false if open and still in timeout period.\n */\nfunction canRequest(providerName: string, isFallbackProbe = false): boolean {\n // G2: Rate-limit cooldown only blocks FALLBACK probes, not primary model retries.\n // Primary model has its own backoff logic — don't double-gate it.\n if (isFallbackProbe && isInRateLimitCooldown(providerName)) {\n logger.debug(COMPONENT, `[RateLimitCooldown] ${providerName} still cooling down — skipping fallback probe`);\n return false;\n }\n\n const cb = getCircuitBreaker(providerName);\n\n if (cb.state === 'closed') {\n return true;\n }\n\n if (cb.state === 'open') {\n const now = Date.now();\n if (cb.openSince && (now - cb.openSince) >= CIRCUIT_BREAKER_CONFIG.resetTimeout) {\n // Timeout expired, transition to half-open\n cb.state = 'half-open';\n cb.failureCount = CIRCUIT_BREAKER_CONFIG.successThreshold; // Need this many successes to close\n logger.info(COMPONENT, `[CircuitBreaker] ${providerName} circuit transitioned to HALF-OPEN (testing)`);\n return true;\n }\n return false; // Still open, don't try\n }\n\n // half-open: allow testing\n return true;\n}\n\n/**\n * Get circuit breaker status for all providers (for health dashboards).\n */\nexport function getCircuitBreakerStatus(): Record<string, { state: CircuitState; failureCount: number; openSince?: number }> {\n const status: Record<string, { state: CircuitState; failureCount: number; openSince?: number }> = {};\n for (const [providerName, cb] of circuitBreakers) {\n status[providerName] = {\n state: cb.state,\n failureCount: cb.failureCount,\n ...(cb.openSince !== null ? { openSince: cb.openSince } : {}),\n };\n }\n return status;\n}\n\n/**\n * Reset all circuit breaker state (for testing).\n * NOT exported to production API - test use only.\n */\nexport function __resetCircuitBreakers__(): void {\n circuitBreakers.clear();\n lastFallbackEvent = null;\n}\n\nexport function resetCircuitBreaker(providerName: string): void {\n const cb = circuitBreakers.get(providerName);\n if (cb) {\n cb.state = 'closed';\n cb.failureCount = 0;\n cb.openSince = null;\n }\n}\n\n// ── Fallback chain state ─────────────────────────────────────────\n/** Tracks the most recent fallback event for dashboard display */\nlet lastFallbackEvent: { primary: string; active: string; reason: string; timestamp: number } | null = null;\n\n/** Get the current fallback state (for dashboard display) */\nexport function getFallbackState(): { primary: string; active: string; reason: string; timestamp: number } | null {\n // Expire after 5 minutes\n if (lastFallbackEvent && (Date.now() - lastFallbackEvent.timestamp) > 300_000) {\n lastFallbackEvent = null;\n }\n return lastFallbackEvent;\n}\n\n/** Retry configuration with exponential backoff */\nconst RETRY_CONFIG = {\n maxRetries: 4, // 4 retries (was 3) — cloud APIs need more chances\n initialDelayMs: 1500, // 1.5s initial (was 1s) — give cloud APIs breathing room\n maxDelayMs: 45000, // 45s cap (was 30s) — cloud models can take longer to recover\n backoffMultiplier: 2,\n jitter: true,\n};\n\n/**\n * Monotonic counter seed for decorrelated jitter. Without this, two retries\n * triggered in the same millisecond can receive identical Math.random() values\n * if V8 happens to share a seed under load — that's exactly the thundering\n * herd we're trying to avoid.\n */\nlet _jitterCounter = 0;\n\n/**\n * Calculate delay with exponential backoff + asymmetric additive jitter.\n *\n * Ported from Hermes `agent/retry_utils.py:jittered_backoff` — proven to\n * decorrelate concurrent retries across multiple sessions hitting the same\n * rate-limited provider simultaneously.\n *\n * Formula:\n * base_delay = min(initial * multiplier^attempt, max)\n * jitter = random_uniform(0, jitter_ratio * base_delay)\n * final = base_delay + jitter\n *\n * Key difference from the previous TITAN implementation:\n * - Old: jitter was ±20% centered on base (could reduce delay below base)\n * - New: jitter is 0..+50% of base (only extends delay, never shortens)\n * This matters for rate-limit recovery — we never want to retry EARLIER than\n * the exponential schedule intended.\n *\n * The counter-seeded PRNG guarantees two concurrent retries get different\n * jitter values even in the same millisecond.\n */\nfunction calculateBackoffDelay(attempt: number): number {\n const exponentialDelay = RETRY_CONFIG.initialDelayMs * Math.pow(RETRY_CONFIG.backoffMultiplier, attempt);\n const cappedDelay = Math.min(exponentialDelay, RETRY_CONFIG.maxDelayMs);\n\n if (!RETRY_CONFIG.jitter) return cappedDelay;\n\n // Counter-seeded jitter — decorrelates concurrent callers.\n _jitterCounter = (_jitterCounter + 1) >>> 0;\n const seed = (Date.now() ^ (_jitterCounter * 0x9e3779b9)) >>> 0;\n // Simple xorshift from the seed — fast, good enough for jitter.\n let s = seed || 1;\n s ^= s << 13; s >>>= 0;\n s ^= s >>> 17;\n s ^= s << 5; s >>>= 0;\n const rand01 = (s >>> 0) / 0xffffffff; // [0, 1)\n\n const jitterRatio = 0.5; // up to +50% of base\n const jitter = rand01 * jitterRatio * cappedDelay;\n return cappedDelay + jitter;\n}\n\n/** Parse retry-after header value (seconds or HTTP date) */\nfunction parseRetryAfter(header: string | null): number | null {\n if (!header) return null;\n\n // Try parsing as seconds\n const seconds = parseInt(header, 10);\n if (!isNaN(seconds)) {\n return Math.min(seconds * 1000, RETRY_CONFIG.maxDelayMs); // Cap at max delay\n }\n\n // Try parsing as HTTP date\n const date = new Date(header);\n if (!isNaN(date.getTime())) {\n const delay = date.getTime() - Date.now();\n return Math.max(1000, Math.min(delay, RETRY_CONFIG.maxDelayMs)); // Min 1s, max configured cap\n }\n\n return null;\n}\n\n/**\n * Check if an error is retryable using the centralized error taxonomy.\n */\nfunction isRetryableError(error: unknown): boolean {\n return classifyProviderError(error).retryable;\n}\n\n/**\n * Extract HTTP status code from an error object if present.\n */\nfunction getErrorStatus(error: unknown): number | undefined {\n return classifyProviderError(error).httpStatus;\n}\n\n/** Try the fallback chain for a chat request. Returns null if chain is empty or exhausted. */\nasync function tryFallbackChain(\n options: ChatOptions,\n primaryModelId: string,\n originalError: Error,\n): Promise<ChatResponse | null> {\n const config = loadConfig();\n const chain = config.agent.fallbackChain;\n if (!chain || chain.length === 0) return null;\n\n const maxRetries = config.agent.fallbackMaxRetries ?? 3;\n let attempts = 0;\n\n for (const fallbackModelId of chain) {\n if (attempts >= maxRetries) break;\n if (fallbackModelId === primaryModelId) continue;\n\n attempts++;\n try {\n const { provider: fbProvider, model: fbModel } = resolveModel(fallbackModelId);\n const fbProviderName = fbProvider.name;\n\n // Check circuit breaker + rate-limit cooldown for fallback provider\n if (!canRequest(fbProviderName, true)) {\n const cb = getCircuitBreaker(fbProviderName);\n logger.warn(COMPONENT, `Skipping fallback ${fallbackModelId} — circuit breaker OPEN (${cb.failureCount} failures)`);\n continue;\n }\n\n logger.warn(COMPONENT, `Model ${primaryModelId} failed (${originalError.message}), falling back to ${fallbackModelId}`);\n const result = await fbProvider.chat({ ...options, model: fbModel });\n\n // Record success for circuit breaker\n recordSuccess(fbProviderName);\n\n lastFallbackEvent = {\n primary: primaryModelId,\n active: fallbackModelId,\n reason: originalError.message,\n timestamp: Date.now(),\n };\n return result;\n } catch (chainErr) {\n // Record failure for circuit breaker\n try {\n const { provider: fbProvider } = resolveModel(fallbackModelId);\n recordFailure(fbProvider.name);\n } catch {\n // Ignore if we can't resolve the provider for recording\n }\n logger.warn(COMPONENT, `Fallback model ${fallbackModelId} also failed: ${(chainErr as Error).message}`);\n continue;\n }\n }\n return null;\n}\n\n/**\n * Try the fallback chain for a streaming request. Returns an async generator\n * or null if no fallback could be attempted.\n *\n * Circuit-breaker accounting (fix for Phase X / streaming optimism bug):\n * The pre-fix version called `recordSuccess(fbProviderName)` immediately\n * after acquiring the generator — *before* a single chunk was emitted.\n * That meant a fallback provider that opened a stream and then errored\n * mid-flight was recorded as a success, lying to the breaker.\n *\n * This version returns a wrapped generator that:\n * - records success only after a `done` chunk OR the underlying\n * generator completes without throwing (real outcome)\n * - records failure if the underlying stream throws or yields an\n * `error` chunk after the first chunk\n */\nasync function tryFallbackChainStream(\n options: ChatOptions,\n primaryModelId: string,\n originalError: Error,\n): Promise<AsyncGenerator<ChatStreamChunk> | null> {\n const config = loadConfig();\n const chain = config.agent.fallbackChain;\n if (!chain || chain.length === 0) return null;\n\n const maxRetries = config.agent.fallbackMaxRetries ?? 3;\n let attempts = 0;\n\n for (const fallbackModelId of chain) {\n if (attempts >= maxRetries) break;\n if (fallbackModelId === primaryModelId) continue;\n\n attempts++;\n let fbProviderName: string;\n let gen: AsyncGenerator<ChatStreamChunk>;\n\n try {\n const { provider: fbProvider, model: fbModel } = resolveModel(fallbackModelId);\n fbProviderName = fbProvider.name;\n\n // Check circuit breaker + rate-limit cooldown for fallback provider\n if (!canRequest(fbProviderName, true)) {\n const cb = getCircuitBreaker(fbProviderName);\n logger.warn(COMPONENT, `Skipping stream fallback ${fallbackModelId} — circuit breaker OPEN (${cb.failureCount} failures)`);\n continue;\n }\n\n logger.warn(COMPONENT, `Stream model ${primaryModelId} failed (${originalError.message}), falling back to ${fallbackModelId}`);\n gen = fbProvider.chatStream({ ...options, model: fbModel });\n } catch (chainErr) {\n // Setup failure (resolveModel threw, etc.) — record breaker failure\n try {\n const { provider: fbProvider } = resolveModel(fallbackModelId);\n recordFailure(fbProvider.name);\n } catch {\n // Ignore if we can't resolve the provider for recording\n }\n logger.warn(COMPONENT, `Fallback stream model ${fallbackModelId} setup failed: ${(chainErr as Error).message}`);\n continue;\n }\n\n lastFallbackEvent = {\n primary: primaryModelId,\n active: fallbackModelId,\n reason: originalError.message,\n timestamp: Date.now(),\n };\n\n return monitorStreamForBreaker(gen, fbProviderName);\n }\n return null;\n}\n\n/**\n * Wrap a chat stream so circuit-breaker bookkeeping reflects real outcomes —\n * success only after a clean stream end, failure on error chunks or thrown\n * errors mid-stream. Hoisted to module scope so ESLint's `no-inner-declarations`\n * is happy and so the same wrapper can be reused by chatStream's priority\n * failover path below.\n */\nasync function* monitorStreamForBreaker(\n inner: AsyncGenerator<ChatStreamChunk>,\n providerName: string,\n): AsyncGenerator<ChatStreamChunk> {\n let recorded = false;\n try {\n for await (const chunk of inner) {\n if (chunk.type === 'error') {\n if (!recorded) { recordFailure(providerName); recorded = true; }\n }\n yield chunk;\n }\n if (!recorded) recordSuccess(providerName);\n } catch (innerErr) {\n if (!recorded) { recordFailure(providerName); recorded = true; }\n throw innerErr;\n }\n}\n\n/** Route a chat request to a mesh peer */\nasync function meshChat(peer: MeshPeer, modelId: string, message: string): Promise<ChatResponse> {\n const requestId = randomBytes(8).toString('hex');\n const config = loadConfig();\n const timeoutMs = config.mesh?.taskTimeoutMs || 120_000;\n logger.info(COMPONENT, `Routing \"${modelId}\" to mesh peer ${peer.hostname} (${peer.nodeId.slice(0, 8)}...)`);\n const result = await routeTaskToNode(peer.nodeId, requestId, message, modelId, timeoutMs) as Record<string, unknown>;\n if (result.error) {\n throw new Error(`Mesh peer error: ${result.error}`);\n }\n // Fire-and-forget analytics\n (async () => {\n const { trackModelUsage } = await import('../analytics/featureTracker.js');\n trackModelUsage(modelId, 'mesh', true);\n })().catch(() => {});\n return result as unknown as ChatResponse;\n}\n\n/**\n * Enhanced error message with provider and model context.\n */\nfunction createEnhancedErrorMessage(error: Error, providerName: string, model: string, attempt: number): string {\n const status = getErrorStatus(error);\n const statusInfo = status ? `[HTTP ${status}] ` : '';\n\n return [\n `Provider ${providerName}/${model} failed`,\n statusInfo + error.message,\n attempt > 0 ? `(attempt ${attempt + 1})` : null,\n ].filter(Boolean).join(': ');\n}\n\n/**\n * Send a chat request with exponential backoff retry and circuit breaker protection.\n * Automatically routes to the correct provider with error recovery and fallback chain.\n */\nexport async function chat(options: ChatOptions): Promise<ChatResponse> {\n const modelId = options.model || 'anthropic/claude-sonnet-4-20250514';\n const { provider, model } = resolveModel(modelId);\n const providerName = provider.name;\n\n logger.info(COMPONENT, `Routing to ${provider.displayName} (model: ${model})`);\n\n // Fail-fast: reject before the circuit breaker if the provider has no\n // configured credentials. Without this guard, picking a model from a\n // provider you haven't configured a key for sends N requests that can\n // never succeed, trips the circuit breaker, and locks the provider out\n // for the reset window. (Real incident, 2026-05-10: openrouter circuit\n // tripped after 8 failures because OPENROUTER_API_KEY wasn't set.)\n if (!provider.isConfigured()) {\n const errorMsg = `Provider ${providerName} has no API key configured. Set ${\n providerName.toUpperCase().replace(/-/g, '_')\n }_API_KEY in env or via Settings → Integrations to use ${providerName} models.`;\n logger.warn(COMPONENT, errorMsg);\n const enhancedError = new Error(errorMsg);\n Object.assign(enhancedError, { status: 401, provider: providerName, model, missingKey: true });\n throw enhancedError;\n }\n\n // G4: Track fallback attempts for structured error reporting (OpenClaw pattern)\n const fallbackAttempts: Array<{ provider: string; model: string; error: string; reason: string }> = [];\n\n // Check circuit breaker before attempting request\n if (!canRequest(providerName)) {\n const cb = getCircuitBreaker(providerName);\n const errorMsg = `Circuit breaker OPEN for ${providerName}/${model} (${cb.failureCount} failures, reset in ${\n cb.openSince ? Math.round((CIRCUIT_BREAKER_CONFIG.resetTimeout - (Date.now() - cb.openSince)) / 1000) : 'unknown'\n }s)`;\n logger.warn(COMPONENT, errorMsg);\n const enhancedError = new Error(errorMsg);\n Object.assign(enhancedError, { status: 503, provider: providerName, model });\n throw enhancedError;\n }\n\n let lastError: Error | null = null;\n const maxRetries = RETRY_CONFIG.maxRetries;\n\n // Gap 1 (plan-this-logical-ocean): one-shot compression on CONTEXT_OVERFLOW.\n // The error taxonomy classifies overflows and sets `shouldCompress: true`,\n // but nothing used to act on it — the hint was dead code. Now we compact\n // options.messages via buildSmartContext and retry the SAME provider once\n // before falling through to model fallback / cross-provider failover.\n let compressionRetried = false;\n let thinkingStripped = false;\n\n // v4.13 ancestor-extraction (Hermes rate_limit_tracker): proactive backoff\n // before even sending the request. If the last response from this provider\n // indicated the quota window is nearly depleted, hold off briefly instead\n // of firing the request and getting a 429.\n try {\n const backoff = shouldBackOff(providerName);\n if (backoff) {\n logger.info(COMPONENT, `[RateLimit] Proactive backoff on ${providerName}: ${backoff.reason} — waiting ${Math.round(backoff.backoffMs)}ms`);\n await sleep(backoff.backoffMs);\n }\n } catch { /* never block on tracker errors */ }\n\n // Attempt request with retry logic\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n try {\n const result = await provider.chat({ ...options, model });\n\n // Strip chain-of-thought leakage from model responses\n if (result.content) {\n result.content = stripThinkingFromResponse(result.content);\n }\n\n // Record success for circuit breaker\n recordSuccess(providerName);\n lastFallbackEvent = null; // Clear fallback state on primary success\n\n // Log if this was a retry that succeeded\n if (attempt > 0) {\n logger.info(COMPONENT, `${provider.displayName}/${model} recovered after ${attempt} retry attempt(s)`);\n }\n\n // Fire-and-forget analytics\n (async () => {\n const { trackModelUsage } = await import('../analytics/featureTracker.js');\n trackModelUsage(model, providerName, true);\n })().catch(() => {});\n\n return result;\n } catch (error) {\n lastError = error as Error;\n\n // Classify error using centralized taxonomy\n const classified = classifyProviderError(error);\n\n // Only affect circuit breaker for genuine provider instability\n if (shouldAffectCircuitBreaker(classified)) {\n recordFailure(providerName);\n }\n\n // noFallback: caller (e.g. ModelProbe) requires the target model to\n // answer or the request to fail cleanly. Skip retries, fallback\n // chain, mesh routing, and provider failover entirely — otherwise\n // we would silently probe a different model and poison the caller's\n // data with unrelated capabilities.\n if (options.noFallback) {\n const errorMsg = createEnhancedErrorMessage(error as Error, providerName, model, attempt);\n const noFallbackError = new Error(\n `Probe target ${providerName}/${model} unreachable (noFallback=true): ${errorMsg}`\n );\n Object.assign(noFallbackError, {\n status: classified.httpStatus,\n provider: providerName,\n model,\n cause: error,\n failoverReason: classified.reason,\n noFallback: true,\n });\n throw noFallbackError;\n }\n\n // G2: Record rate-limit cooldown to prevent probe hammering\n if (classified.reason === FailoverReason.RATE_LIMIT) {\n recordRateLimitCooldown(providerName);\n }\n\n // Exhaust credential in pool if rotation is recommended\n if (classified.shouldRotateCredential) {\n const pool = getExistingPool(providerName);\n if (pool) {\n // Find which credential was used and exhaust it\n const status = pool.status();\n const lastUsed = status.find(s => s.available);\n if (lastUsed) {\n pool.exhaust(lastUsed.name, classified.cooldownMs || 60000);\n }\n }\n }\n\n // Gap 1: act on shouldCompress hint BEFORE generic retry/fallback.\n // On CONTEXT_OVERFLOW (or any future reason that sets shouldCompress),\n // compact options.messages via buildSmartContext and retry the same\n // provider+model once. Only fires on the FIRST such error per call —\n // if the compacted request still overflows, we drop through to the\n // normal retry/fallback ladder instead of shrinking forever.\n if (classified.shouldCompress && !compressionRetried && Array.isArray(options.messages)) {\n compressionRetried = true;\n const beforeCount = options.messages.length;\n // Conservative target — most of the whitelisted Ollama cloud\n // models have >=32K context; 24K leaves headroom for the\n // completion itself and any tool schemas the provider adds.\n const compactTokens = 24000;\n try {\n const compacted = buildSmartContext(options.messages, compactTokens);\n if (compacted.length > 0 && compacted.length <= beforeCount) {\n options = { ...options, messages: compacted };\n logger.info(\n COMPONENT,\n `[Router] ${classified.reason} — compacted context ${beforeCount}→${compacted.length} msgs, retrying ${providerName}/${model}`,\n );\n // Retry immediately — no backoff needed, we changed the input\n continue;\n }\n logger.warn(COMPONENT, `[Router] Compression produced empty/larger output — skipping compress retry`);\n } catch (compErr) {\n logger.warn(COMPONENT, `[Router] Compression failed: ${(compErr as Error).message} — falling through`);\n }\n }\n\n // Gap 2: act on THINKING_NOT_SUPPORTED — strip thinking options and retry\n // once on the same provider. This handles models like titan-qwen3.5:4b\n // that return HTTP 400 \"does not support thinking\". We mutate options only\n // once so a second THINKING_NOT_SUPPORTED falls through to normal retry ladder.\n if (classified.reason === FailoverReason.THINKING_NOT_SUPPORTED && !thinkingStripped) {\n thinkingStripped = true;\n const providerOpts = options.providerOptions ? { ...options.providerOptions } : {};\n // Remove Ollama/OpenAI-compat thinking keys\n delete (providerOpts as Record<string, unknown>).think;\n delete (providerOpts as Record<string, unknown>).thinking;\n delete (providerOpts as Record<string, unknown>).thinking_mode;\n delete (providerOpts as Record<string, unknown>).budget_tokens;\n delete (providerOpts as Record<string, unknown>).enable_thinking;\n options = { ...options, providerOptions: providerOpts };\n logger.info(COMPONENT, `[Router] THINKING_NOT_SUPPORTED — stripped thinking flags, retrying ${providerName}/${model}`);\n continue;\n }\n\n const errorMsg = createEnhancedErrorMessage(error as Error, providerName, model, attempt);\n\n // Check if we should retry\n if (classified.retryable && attempt < maxRetries) {\n // Use taxonomy cooldown or calculate backoff, whichever is larger\n let retryDelayMs = Math.max(classified.cooldownMs, calculateBackoffDelay(attempt));\n\n // Hunt Finding #37 (2026-04-14): previous code tried\n // `(error as Response)?.headers?.get?.('Retry-After')` which\n // always returned undefined at runtime because the error is\n // an Error object, not a Response. Retry-After headers were\n // never actually respected. Providers now attach retryAfterMs\n // to the thrown error via createProviderError().\n const errWithHints = error as { retryAfterMs?: number | null; headers?: { get?(k: string): string | null } };\n if (typeof errWithHints.retryAfterMs === 'number' && errWithHints.retryAfterMs > 0) {\n retryDelayMs = errWithHints.retryAfterMs;\n logger.info(COMPONENT, `[RateLimit] Respecting Retry-After: ${Math.round(retryDelayMs / 1000)}s`);\n } else {\n // Back-compat: old-style error that happens to wrap a Response\n const retryAfter = errWithHints.headers?.get?.('Retry-After');\n if (retryAfter) {\n const parsed = parseRetryAfter(retryAfter);\n if (parsed !== null) {\n retryDelayMs = parsed;\n logger.info(COMPONENT, `[RateLimit] Respecting Retry-After (legacy): ${Math.round(retryDelayMs / 1000)}s`);\n }\n }\n }\n\n logger.warn(COMPONENT, `${errorMsg} [${classified.reason}] — retrying in ${Math.round(retryDelayMs)}ms`);\n await sleep(retryDelayMs);\n continue;\n }\n\n // Not retryable or max retries exceeded\n if (!classified.retryable) {\n logger.error(COMPONENT, `${errorMsg} — not retryable [${classified.reason}] (${classified.httpStatus ? `HTTP ${classified.httpStatus}` : 'unknown error'})`);\n } else {\n logger.error(COMPONENT, `${errorMsg} — max retries (${maxRetries}) exceeded [${classified.reason}]`);\n }\n\n // Try configured fallback chain first (model-level fallback)\n if (classified.retryable || classified.shouldFallback) {\n const chainResult = await tryFallbackChain(options, modelId, error as Error);\n if (chainResult) {\n logger.info(COMPONENT, `Fallback chain recovered from ${providerName}/${model} failure [${classified.reason}]`);\n return chainResult;\n }\n }\n\n // Try mesh peers before local failover\n const config = loadConfig();\n if (config.mesh?.enabled) {\n const peer = findModelOnMesh(modelId);\n if (peer) {\n try {\n const message = Array.isArray(options.messages)\n ? options.messages.map(m => m.content).join('\\n')\n : (options as unknown as Record<string, unknown>).message as string || '';\n return await meshChat(peer, modelId, message);\n } catch (meshErr) {\n logger.warn(COMPONENT, `Mesh routing failed: ${(meshErr as Error).message}`);\n }\n }\n }\n\n // Attempt failover to other providers (only on first failure, not after retries)\n if (attempt === 0) {\n const failoverOrder = getFailoverOrder(providerName);\n for (const fallbackName of failoverOrder) {\n if (fallbackName === providerName) continue;\n\n // Check circuit breaker + rate-limit cooldown for fallback provider\n if (!canRequest(fallbackName, true)) {\n logger.debug(COMPONENT, `Skipping fallback ${fallbackName} — circuit breaker OPEN`);\n continue;\n }\n\n const fallback = providers.get(fallbackName);\n if (!fallback) continue;\n\n let fbModelName = 'unknown';\n try {\n const healthy = await fallback.healthCheck();\n if (!healthy) continue;\n\n const models = await fallback.listModels();\n if (models.length === 0) continue;\n\n // Prefer a model with a similar name prefix (e.g. claude-* → claude-*)\n const originalPrefix = model.split('-')[0];\n fbModelName = models.find(m => m.startsWith(originalPrefix)) || models[0];\n\n logger.warn(COMPONENT, `Failing over from ${providerName}/${model} → ${fallbackName}/${fbModelName}`);\n const result = await fallback.chat({ ...options, model: fbModelName });\n recordSuccess(fallbackName); // Record success for the fallback provider\n // Fire-and-forget analytics\n (async () => {\n const { trackModelUsage } = await import('../analytics/featureTracker.js');\n trackModelUsage(fbModelName, fallbackName, true);\n })().catch(() => {});\n return result;\n } catch (fallbackErr) {\n recordFailure(fallbackName); // Record failure for the fallback provider too\n // G4: Record fallback attempt for structured error chain\n fallbackAttempts.push({\n provider: fallbackName,\n model: fbModelName,\n error: (fallbackErr as Error).message,\n reason: classifyProviderError(fallbackErr).reason,\n });\n logger.warn(COMPONENT, `Fallback ${fallbackName} also failed: ${(fallbackErr as Error).message}`);\n continue;\n }\n }\n }\n\n // G4: Record the primary attempt too\n fallbackAttempts.unshift({\n provider: providerName,\n model,\n error: (error as Error).message,\n reason: classified.reason,\n });\n\n // Fire-and-forget analytics\n (async () => {\n const { trackModelUsage } = await import('../analytics/featureTracker.js');\n trackModelUsage(model, providerName, false);\n })().catch(() => {});\n\n // All recovery options exhausted, throw enhanced error\n const attemptSummary = fallbackAttempts.length > 1\n ? ` | Tried ${fallbackAttempts.length} providers: ${fallbackAttempts.map(a => `${a.provider}/${a.model} [${a.reason}]`).join(', ')}`\n : '';\n const finalError = new Error(`All providers failed: ${errorMsg}${attemptSummary}`);\n Object.assign(finalError, {\n status: classified.httpStatus,\n provider: providerName,\n model,\n cause: error,\n failoverReason: classified.reason,\n // G4: Structured fallback attempt chain (OpenClaw FallbackSummaryError pattern)\n fallbackAttempts,\n });\n throw finalError;\n }\n }\n\n // Should never reach here, but TypeScript requires it\n throw lastError || new Error(`Provider ${providerName}/${model} failed after all retries`);\n}\n\n/**\n * Send a streaming chat request with exponential backoff retry and circuit breaker protection.\n */\nexport async function* chatStream(options: ChatOptions): AsyncGenerator<ChatStreamChunk> {\n const modelId = options.model || 'anthropic/claude-sonnet-4-20250514';\n const { provider, model } = resolveModel(modelId);\n const providerName = provider.name;\n\n logger.info(COMPONENT, `Streaming via ${provider.displayName} (model: ${model})`);\n\n // Fail-fast: see chat() for full reasoning. Reject before the circuit\n // breaker if the provider has no configured credentials, so picking\n // an unconfigured model can't trip the breaker.\n if (!provider.isConfigured()) {\n const errorMsg = `Provider ${providerName} has no API key configured. Set ${\n providerName.toUpperCase().replace(/-/g, '_')\n }_API_KEY in env or via Settings → Integrations to use ${providerName} models.`;\n logger.warn(COMPONENT, errorMsg);\n yield { type: 'error', error: errorMsg };\n return;\n }\n\n // Check circuit breaker before attempting request\n if (!canRequest(providerName)) {\n const cb = getCircuitBreaker(providerName);\n yield {\n type: 'error',\n error: `[CircuitBreaker] Circuit OPEN: ${providerName}/${model} (${cb.failureCount} failures, testing in ${\n Math.round((CIRCUIT_BREAKER_CONFIG.resetTimeout - (Date.now() - cb.openSince!)) / 1000)\n }s)`,\n };\n return;\n }\n\n let lastError: Error | null = null;\n const maxRetries = RETRY_CONFIG.maxRetries;\n\n // Once-per-call latches so we don't repeat failover work after a retry\n // burst — both fallback paths can be reached on any exhausted-retry\n // attempt, but each is attempted at most once per chatStream invocation\n // (Task 4: prevent infinite-loop recovery, formerly attempt===0 gate).\n let fallbackChainAttempted = false;\n let priorityFailoverAttempted = false;\n\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n try {\n // Stream from provider — record success only on first non-error\n // chunk so we don't claim success for a stream that never produced.\n let recordedSuccess = false;\n for await (const chunk of provider.chatStream({ ...options, model })) {\n if (!recordedSuccess && chunk.type !== 'error' && attempt === 0) {\n recordSuccess(providerName);\n recordedSuccess = true;\n }\n lastFallbackEvent = null;\n yield chunk;\n }\n\n // Log if this was a retry that succeeded\n if (attempt > 0) {\n logger.info(COMPONENT, `${provider.displayName}/${model} stream recovered after ${attempt} retry attempt(s)`);\n }\n return;\n } catch (error) {\n lastError = error as Error;\n\n // Classify error using centralized taxonomy\n const classified = classifyProviderError(error);\n if (shouldAffectCircuitBreaker(classified)) {\n recordFailure(providerName);\n }\n\n const errorMsg = createEnhancedErrorMessage(error as Error, providerName, model, attempt);\n\n // Check if we should retry\n if (classified.retryable && attempt < maxRetries) {\n const retryDelayMs = Math.max(classified.cooldownMs, calculateBackoffDelay(attempt));\n logger.warn(COMPONENT, `${errorMsg} [${classified.reason}] — streaming retry in ${Math.round(retryDelayMs)}ms`);\n\n // Task 2: emit a dedicated `retry` event instead of leaking a\n // text chunk (e.g. \"[Retrying request (1/4) due to ...]\") into\n // the user-visible stream. UI consumers should display this\n // as a status indicator, never forward to the assistant message.\n yield {\n type: 'retry' as const,\n attempt: attempt + 1,\n maxRetries,\n reason: classified.reason,\n provider: providerName,\n model,\n delayMs: Math.round(retryDelayMs),\n };\n\n await sleep(retryDelayMs);\n continue;\n }\n\n // Not retryable or max retries exceeded\n if (!classified.retryable) {\n logger.error(COMPONENT, `${errorMsg} — streaming not retryable [${classified.reason}]`);\n } else {\n logger.error(COMPONENT, `${errorMsg} — streaming max retries exceeded [${classified.reason}]`);\n }\n\n // Try configured fallback chain first (once per chatStream call)\n if (!fallbackChainAttempted && (classified.retryable || classified.shouldFallback)) {\n fallbackChainAttempted = true;\n const chainStream = await tryFallbackChainStream(options, modelId, error as Error);\n if (chainStream) {\n yield {\n type: 'failover' as const,\n originalProvider: providerName,\n originalModel: model,\n error: (error as Error).message,\n };\n yield* chainStream;\n return;\n }\n }\n\n // Try mesh peers (non-streaming fallback for now)\n const config = loadConfig();\n if (config.mesh?.enabled) {\n const peer = findModelOnMesh(modelId);\n if (peer) {\n try {\n const message = Array.isArray(options.messages)\n ? options.messages.map(m => m.content).join('\\n')\n : (options as unknown as Record<string, unknown>).message as string || '';\n const result = await meshChat(peer, modelId, message);\n yield { type: 'text' as const, content: result.content };\n yield { type: 'done' as const };\n return;\n } catch (meshErr) {\n logger.warn(COMPONENT, `Mesh stream routing failed: ${(meshErr as Error).message}`);\n }\n }\n }\n\n // Task 4: priority-failover loop now runs on ANY exhausted-retry\n // path, not just attempt === 0. The `priorityFailoverAttempted`\n // latch ensures it executes at most once per chatStream call so\n // we don't loop through the failover order on every retry burst.\n if (!priorityFailoverAttempted) {\n priorityFailoverAttempted = true;\n const failoverOrder = getFailoverOrder(providerName);\n let failedOver = false;\n\n for (const fallbackName of failoverOrder) {\n if (fallbackName === providerName) continue;\n\n if (!canRequest(fallbackName, true)) {\n logger.debug(COMPONENT, `Skipping stream fallback ${fallbackName} — circuit breaker OPEN`);\n continue;\n }\n\n const fallback = providers.get(fallbackName);\n if (!fallback) continue;\n\n try {\n const healthy = await fallback.healthCheck();\n if (!healthy) continue;\n\n const models = await fallback.listModels();\n if (models.length === 0) continue;\n\n const originalPrefix = model.split('-')[0];\n const preferred = models.find(m => m.startsWith(originalPrefix)) || models[0];\n\n logger.warn(COMPONENT, `Stream failing over from ${providerName}/${model} → ${fallbackName}/${preferred}`);\n\n // Notify consumer about failover\n yield {\n type: 'failover' as const,\n originalProvider: providerName,\n originalModel: model,\n error: errorMsg,\n };\n\n // Wrap the failover stream so we record actual outcome,\n // not just optimistic success-on-generator-acquire (Task 3\n // applied here too — same pattern as tryFallbackChainStream).\n let recorded = false;\n try {\n for await (const chunk of fallback.chatStream({ ...options, model: preferred })) {\n if (chunk.type === 'error' && !recorded) {\n recordFailure(fallbackName);\n recorded = true;\n }\n yield chunk;\n }\n if (!recorded) recordSuccess(fallbackName);\n } catch (innerErr) {\n if (!recorded) recordFailure(fallbackName);\n throw innerErr;\n }\n failedOver = true;\n break;\n } catch (fallbackErr) {\n recordFailure(fallbackName);\n logger.warn(COMPONENT, `Stream fallback ${fallbackName} also failed: ${(fallbackErr as Error).message}`);\n continue;\n }\n }\n\n if (failedOver) return;\n }\n\n // All recovery options exhausted\n yield { type: 'error', error: `All streaming providers failed: ${errorMsg}` };\n return;\n }\n }\n\n // Should never reach here\n yield { type: 'error', error: lastError?.message || 'Streaming failed after all retries' };\n}\n\n/** Health check all providers */\nexport async function healthCheckAll(): Promise<Record<string, boolean>> {\n initProviders();\n const entries = Array.from(providers.entries());\n const settled = await Promise.allSettled(\n entries.map(([, provider]) => provider.healthCheck())\n );\n const results: Record<string, boolean> = {};\n for (let i = 0; i < entries.length; i++) {\n const [name] = entries[i];\n const outcome = settled[i];\n results[name] = outcome.status === 'fulfilled' ? outcome.value : false;\n }\n return results;\n}\n"],"mappings":"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","names":["errorMsg"]}
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { homedir } from "os";
3
3
  import { join } from "path";
4
- const TITAN_VERSION = "5.6.2";
4
+ const TITAN_VERSION = "5.6.4";
5
5
  const TITAN_CODENAME = "Spacewalk";
6
6
  const TITAN_NAME = "TITAN";
7
7
  const TITAN_FULL_NAME = "The Intelligent Task Automation Network";
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/utils/constants.ts"],"sourcesContent":["/**\n * TITAN Constants\n */\nimport { homedir } from 'os';\nimport { join } from 'path';\n\nexport const TITAN_VERSION = '5.6.2';\nexport const TITAN_CODENAME = 'Spacewalk';\nexport const TITAN_NAME = 'TITAN';\nexport const TITAN_FULL_NAME = 'The Intelligent Task Automation Network';\nexport const TITAN_ASCII_LOGO = `\n╔══════════════════════════════════════════════════════╗\n║ ║\n║ ████████╗██╗████████╗ █████╗ ███╗ ██╗ ║\n║ ██║ ██║ ██║ ██╔══██╗████╗ ██║ ║\n║ ██║ ██║ ██║ ███████║██╔██╗ ██║ ║\n║ ██║ ██║ ██║ ██╔══██║██║╚██╗██║ ║\n║ ██║ ██║ ██║ ██║ ██║██║ ╚████║ ║\n║ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═══╝ ║\n║ ║\n║ The Intelligent Task Automation Network ║\n║ v${TITAN_VERSION} • by Tony Elliott ║\n╚══════════════════════════════════════════════════════╝`;\n\n// Paths\n// Hunt Finding #03 (2026-04-14): honor TITAN_HOME env var if set.\n// Previously this was hardcoded to `~/.titan`, which meant:\n// - Docker containers couldn't override the config path\n// - Shared machines couldn't isolate per-user state\n// - Test fixtures couldn't run against an isolated home\n// - The systemd unit's `Environment=TITAN_HOME=...` was silently ignored\n// The env var is read once at module load (constants are resolved at import time).\n// If TITAN_HOME starts with `~/`, expand it to the user's home dir.\nfunction resolveTitanHome(): string {\n const envHome = process.env.TITAN_HOME;\n if (envHome && envHome.trim().length > 0) {\n const trimmed = envHome.trim();\n if (trimmed.startsWith('~/')) {\n return join(homedir(), trimmed.slice(2));\n }\n if (trimmed === '~') {\n return homedir();\n }\n return trimmed;\n }\n return join(homedir(), '.titan');\n}\nexport const TITAN_HOME = resolveTitanHome();\nexport const TITAN_CONFIG_PATH = join(TITAN_HOME, 'titan.json');\nexport const TITAN_DB_PATH = join(TITAN_HOME, 'titan.db');\nexport const TITAN_WORKSPACE = join(TITAN_HOME, 'workspace');\nexport const TITAN_SKILLS_DIR = join(TITAN_WORKSPACE, 'skills');\nexport const TITAN_LOGS_DIR = join(TITAN_HOME, 'logs');\nexport const TITAN_MEMORY_DIR = join(TITAN_HOME, 'memory');\n\n// Workspace prompt files (injected into agent context)\nexport const AGENTS_MD = join(TITAN_WORKSPACE, 'AGENTS.md');\nexport const SOUL_MD = join(TITAN_WORKSPACE, 'SOUL.md');\nexport const TOOLS_MD = join(TITAN_WORKSPACE, 'TOOLS.md');\nexport const TITAN_MD_FILENAME = 'TITAN.md';\nexport const AUTOPILOT_MD = join(TITAN_HOME, 'AUTOPILOT.md');\nexport const AUTOPILOT_RUNS_PATH = join(TITAN_HOME, 'autopilot-runs.jsonl');\nexport const TITAN_CREDENTIALS_DIR = join(TITAN_HOME, 'credentials');\n\n// Income & lead tracking\nexport const INCOME_LEDGER_PATH = join(TITAN_HOME, 'income-ledger.jsonl');\nexport const FREELANCE_LEADS_PATH = join(TITAN_HOME, 'freelance-leads.jsonl');\nexport const FREELANCE_PROFILE_PATH = join(TITAN_HOME, 'freelance-profile.json');\nexport const LEADS_PATH = join(TITAN_HOME, 'leads.jsonl');\nexport const TELEMETRY_EVENTS_PATH = join(TITAN_HOME, 'telemetry-events.jsonl');\nexport const SOMADRIVE_STATE_PATH = join(TITAN_HOME, 'soma-drive-state.json');\nexport const ACTIVITY_LOG_PATH = join(TITAN_HOME, 'activity-log.jsonl');\n\n// Gateway defaults\nexport const DEFAULT_GATEWAY_HOST = '0.0.0.0';\nexport const DEFAULT_GATEWAY_PORT = 48420;\nexport const DEFAULT_WEB_PORT = 48421;\n\n// Agent defaults\nexport const DEFAULT_MODEL = 'anthropic/claude-sonnet-4-20250514';\n/** v5.4.1: User-preference ceiling. Providers clamp per-model via\n * clampMaxTokens() so this can be high without causing 400s on\n * capped endpoints (e.g. Claude Sonnet 4 8K, Cohere 4K). */\nexport const DEFAULT_MAX_TOKENS = 200000;\nexport const DEFAULT_TEMPERATURE = 0.7;\nexport const MAX_CONTEXT_MESSAGES = 50;\nexport const SESSION_TIMEOUT_MS = 30 * 60 * 1000; // 30 minutes\n\n// Security\nexport const DEFAULT_SANDBOX_MODE = 'host';\n/** Default allowed tools. Empty = allow ALL registered tools.\n * Use security.deniedTools to block specific tools instead. */\nexport const ALLOWED_TOOLS_DEFAULT: string[] = [];\nexport const DENIED_TOOLS_DEFAULT: string[] = [];\n"],"mappings":";AAGA,SAAS,eAAe;AACxB,SAAS,YAAY;AAEd,MAAM,gBAAgB;AACtB,MAAM,iBAAiB;AACvB,MAAM,aAAa;AACnB,MAAM,kBAAkB;AACxB,MAAM,mBAAmB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,WAW1B,aAAa;AAAA;AAYnB,SAAS,mBAA2B;AAChC,QAAM,UAAU,QAAQ,IAAI;AAC5B,MAAI,WAAW,QAAQ,KAAK,EAAE,SAAS,GAAG;AACtC,UAAM,UAAU,QAAQ,KAAK;AAC7B,QAAI,QAAQ,WAAW,IAAI,GAAG;AAC1B,aAAO,KAAK,QAAQ,GAAG,QAAQ,MAAM,CAAC,CAAC;AAAA,IAC3C;AACA,QAAI,YAAY,KAAK;AACjB,aAAO,QAAQ;AAAA,IACnB;AACA,WAAO;AAAA,EACX;AACA,SAAO,KAAK,QAAQ,GAAG,QAAQ;AACnC;AACO,MAAM,aAAa,iBAAiB;AACpC,MAAM,oBAAoB,KAAK,YAAY,YAAY;AACvD,MAAM,gBAAgB,KAAK,YAAY,UAAU;AACjD,MAAM,kBAAkB,KAAK,YAAY,WAAW;AACpD,MAAM,mBAAmB,KAAK,iBAAiB,QAAQ;AACvD,MAAM,iBAAiB,KAAK,YAAY,MAAM;AAC9C,MAAM,mBAAmB,KAAK,YAAY,QAAQ;AAGlD,MAAM,YAAY,KAAK,iBAAiB,WAAW;AACnD,MAAM,UAAU,KAAK,iBAAiB,SAAS;AAC/C,MAAM,WAAW,KAAK,iBAAiB,UAAU;AACjD,MAAM,oBAAoB;AAC1B,MAAM,eAAe,KAAK,YAAY,cAAc;AACpD,MAAM,sBAAsB,KAAK,YAAY,sBAAsB;AACnE,MAAM,wBAAwB,KAAK,YAAY,aAAa;AAG5D,MAAM,qBAAqB,KAAK,YAAY,qBAAqB;AACjE,MAAM,uBAAuB,KAAK,YAAY,uBAAuB;AACrE,MAAM,yBAAyB,KAAK,YAAY,wBAAwB;AACxE,MAAM,aAAa,KAAK,YAAY,aAAa;AACjD,MAAM,wBAAwB,KAAK,YAAY,wBAAwB;AACvE,MAAM,uBAAuB,KAAK,YAAY,uBAAuB;AACrE,MAAM,oBAAoB,KAAK,YAAY,oBAAoB;AAG/D,MAAM,uBAAuB;AAC7B,MAAM,uBAAuB;AAC7B,MAAM,mBAAmB;AAGzB,MAAM,gBAAgB;AAItB,MAAM,qBAAqB;AAC3B,MAAM,sBAAsB;AAC5B,MAAM,uBAAuB;AAC7B,MAAM,qBAAqB,KAAK,KAAK;AAGrC,MAAM,uBAAuB;AAG7B,MAAM,wBAAkC,CAAC;AACzC,MAAM,uBAAiC,CAAC;","names":[]}
1
+ {"version":3,"sources":["../../src/utils/constants.ts"],"sourcesContent":["/**\n * TITAN Constants\n */\nimport { homedir } from 'os';\nimport { join } from 'path';\n\nexport const TITAN_VERSION = '5.6.4';\nexport const TITAN_CODENAME = 'Spacewalk';\nexport const TITAN_NAME = 'TITAN';\nexport const TITAN_FULL_NAME = 'The Intelligent Task Automation Network';\nexport const TITAN_ASCII_LOGO = `\n╔══════════════════════════════════════════════════════╗\n║ ║\n║ ████████╗██╗████████╗ █████╗ ███╗ ██╗ ║\n║ ██║ ██║ ██║ ██╔══██╗████╗ ██║ ║\n║ ██║ ██║ ██║ ███████║██╔██╗ ██║ ║\n║ ██║ ██║ ██║ ██╔══██║██║╚██╗██║ ║\n║ ██║ ██║ ██║ ██║ ██║██║ ╚████║ ║\n║ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═══╝ ║\n║ ║\n║ The Intelligent Task Automation Network ║\n║ v${TITAN_VERSION} • by Tony Elliott ║\n╚══════════════════════════════════════════════════════╝`;\n\n// Paths\n// Hunt Finding #03 (2026-04-14): honor TITAN_HOME env var if set.\n// Previously this was hardcoded to `~/.titan`, which meant:\n// - Docker containers couldn't override the config path\n// - Shared machines couldn't isolate per-user state\n// - Test fixtures couldn't run against an isolated home\n// - The systemd unit's `Environment=TITAN_HOME=...` was silently ignored\n// The env var is read once at module load (constants are resolved at import time).\n// If TITAN_HOME starts with `~/`, expand it to the user's home dir.\nfunction resolveTitanHome(): string {\n const envHome = process.env.TITAN_HOME;\n if (envHome && envHome.trim().length > 0) {\n const trimmed = envHome.trim();\n if (trimmed.startsWith('~/')) {\n return join(homedir(), trimmed.slice(2));\n }\n if (trimmed === '~') {\n return homedir();\n }\n return trimmed;\n }\n return join(homedir(), '.titan');\n}\nexport const TITAN_HOME = resolveTitanHome();\nexport const TITAN_CONFIG_PATH = join(TITAN_HOME, 'titan.json');\nexport const TITAN_DB_PATH = join(TITAN_HOME, 'titan.db');\nexport const TITAN_WORKSPACE = join(TITAN_HOME, 'workspace');\nexport const TITAN_SKILLS_DIR = join(TITAN_WORKSPACE, 'skills');\nexport const TITAN_LOGS_DIR = join(TITAN_HOME, 'logs');\nexport const TITAN_MEMORY_DIR = join(TITAN_HOME, 'memory');\n\n// Workspace prompt files (injected into agent context)\nexport const AGENTS_MD = join(TITAN_WORKSPACE, 'AGENTS.md');\nexport const SOUL_MD = join(TITAN_WORKSPACE, 'SOUL.md');\nexport const TOOLS_MD = join(TITAN_WORKSPACE, 'TOOLS.md');\nexport const TITAN_MD_FILENAME = 'TITAN.md';\nexport const AUTOPILOT_MD = join(TITAN_HOME, 'AUTOPILOT.md');\nexport const AUTOPILOT_RUNS_PATH = join(TITAN_HOME, 'autopilot-runs.jsonl');\nexport const TITAN_CREDENTIALS_DIR = join(TITAN_HOME, 'credentials');\n\n// Income & lead tracking\nexport const INCOME_LEDGER_PATH = join(TITAN_HOME, 'income-ledger.jsonl');\nexport const FREELANCE_LEADS_PATH = join(TITAN_HOME, 'freelance-leads.jsonl');\nexport const FREELANCE_PROFILE_PATH = join(TITAN_HOME, 'freelance-profile.json');\nexport const LEADS_PATH = join(TITAN_HOME, 'leads.jsonl');\nexport const TELEMETRY_EVENTS_PATH = join(TITAN_HOME, 'telemetry-events.jsonl');\nexport const SOMADRIVE_STATE_PATH = join(TITAN_HOME, 'soma-drive-state.json');\nexport const ACTIVITY_LOG_PATH = join(TITAN_HOME, 'activity-log.jsonl');\n\n// Gateway defaults\nexport const DEFAULT_GATEWAY_HOST = '0.0.0.0';\nexport const DEFAULT_GATEWAY_PORT = 48420;\nexport const DEFAULT_WEB_PORT = 48421;\n\n// Agent defaults\nexport const DEFAULT_MODEL = 'anthropic/claude-sonnet-4-20250514';\n/** v5.4.1: User-preference ceiling. Providers clamp per-model via\n * clampMaxTokens() so this can be high without causing 400s on\n * capped endpoints (e.g. Claude Sonnet 4 8K, Cohere 4K). */\nexport const DEFAULT_MAX_TOKENS = 200000;\nexport const DEFAULT_TEMPERATURE = 0.7;\nexport const MAX_CONTEXT_MESSAGES = 50;\nexport const SESSION_TIMEOUT_MS = 30 * 60 * 1000; // 30 minutes\n\n// Security\nexport const DEFAULT_SANDBOX_MODE = 'host';\n/** Default allowed tools. Empty = allow ALL registered tools.\n * Use security.deniedTools to block specific tools instead. */\nexport const ALLOWED_TOOLS_DEFAULT: string[] = [];\nexport const DENIED_TOOLS_DEFAULT: string[] = [];\n"],"mappings":";AAGA,SAAS,eAAe;AACxB,SAAS,YAAY;AAEd,MAAM,gBAAgB;AACtB,MAAM,iBAAiB;AACvB,MAAM,aAAa;AACnB,MAAM,kBAAkB;AACxB,MAAM,mBAAmB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,WAW1B,aAAa;AAAA;AAYnB,SAAS,mBAA2B;AAChC,QAAM,UAAU,QAAQ,IAAI;AAC5B,MAAI,WAAW,QAAQ,KAAK,EAAE,SAAS,GAAG;AACtC,UAAM,UAAU,QAAQ,KAAK;AAC7B,QAAI,QAAQ,WAAW,IAAI,GAAG;AAC1B,aAAO,KAAK,QAAQ,GAAG,QAAQ,MAAM,CAAC,CAAC;AAAA,IAC3C;AACA,QAAI,YAAY,KAAK;AACjB,aAAO,QAAQ;AAAA,IACnB;AACA,WAAO;AAAA,EACX;AACA,SAAO,KAAK,QAAQ,GAAG,QAAQ;AACnC;AACO,MAAM,aAAa,iBAAiB;AACpC,MAAM,oBAAoB,KAAK,YAAY,YAAY;AACvD,MAAM,gBAAgB,KAAK,YAAY,UAAU;AACjD,MAAM,kBAAkB,KAAK,YAAY,WAAW;AACpD,MAAM,mBAAmB,KAAK,iBAAiB,QAAQ;AACvD,MAAM,iBAAiB,KAAK,YAAY,MAAM;AAC9C,MAAM,mBAAmB,KAAK,YAAY,QAAQ;AAGlD,MAAM,YAAY,KAAK,iBAAiB,WAAW;AACnD,MAAM,UAAU,KAAK,iBAAiB,SAAS;AAC/C,MAAM,WAAW,KAAK,iBAAiB,UAAU;AACjD,MAAM,oBAAoB;AAC1B,MAAM,eAAe,KAAK,YAAY,cAAc;AACpD,MAAM,sBAAsB,KAAK,YAAY,sBAAsB;AACnE,MAAM,wBAAwB,KAAK,YAAY,aAAa;AAG5D,MAAM,qBAAqB,KAAK,YAAY,qBAAqB;AACjE,MAAM,uBAAuB,KAAK,YAAY,uBAAuB;AACrE,MAAM,yBAAyB,KAAK,YAAY,wBAAwB;AACxE,MAAM,aAAa,KAAK,YAAY,aAAa;AACjD,MAAM,wBAAwB,KAAK,YAAY,wBAAwB;AACvE,MAAM,uBAAuB,KAAK,YAAY,uBAAuB;AACrE,MAAM,oBAAoB,KAAK,YAAY,oBAAoB;AAG/D,MAAM,uBAAuB;AAC7B,MAAM,uBAAuB;AAC7B,MAAM,mBAAmB;AAGzB,MAAM,gBAAgB;AAItB,MAAM,qBAAqB;AAC3B,MAAM,sBAAsB;AAC5B,MAAM,uBAAuB;AAC7B,MAAM,qBAAqB,KAAK,KAAK;AAGrC,MAAM,uBAAuB;AAG7B,MAAM,wBAAkC,CAAC;AACzC,MAAM,uBAAiC,CAAC;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "titan-agent",
3
- "version": "5.6.2",
3
+ "version": "5.6.4",
4
4
  "description": "TITAN — Autonomous AI agent framework with self-improvement, multi-agent orchestration, 36 LLM providers, 16 channel adapters, GPU VRAM management, mesh networking, LiveKit voice, TITAN-Soma homeostatic drives, and a React Mission Control dashboard. Open-source, TypeScript, MIT licensed.",
5
5
  "author": "Tony Elliott (https://github.com/Djtony707)",
6
6
  "repository": {
package/ui/dist/sw.js CHANGED
@@ -20,7 +20,7 @@
20
20
  * but a default falls back to the source-controlled value here.
21
21
  */
22
22
 
23
- const CACHE_NAME = 'titan-' + ('1778460366675');
23
+ const CACHE_NAME = 'titan-' + ('1778461544987');
24
24
  const ASSETS_PREFIX = '/assets/';
25
25
 
26
26
  self.addEventListener('install', (event) => {