titan-agent 5.3.2 → 5.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent/agent.js +11 -1
- package/dist/agent/agent.js.map +1 -1
- package/dist/agent/agentLoop.js +36 -1
- package/dist/agent/agentLoop.js.map +1 -1
- package/dist/agent/session.js +106 -5
- package/dist/agent/session.js.map +1 -1
- package/dist/agent/subAgent.js +62 -1
- package/dist/agent/subAgent.js.map +1 -1
- package/dist/config/config.js +30 -8
- package/dist/config/config.js.map +1 -1
- package/dist/config/schema.js +25 -2
- package/dist/config/schema.js.map +1 -1
- package/dist/gateway/server.js +32 -1
- package/dist/gateway/server.js.map +1 -1
- package/dist/memory/graph.js +49 -15
- package/dist/memory/graph.js.map +1 -1
- package/dist/memory/index.js +192 -0
- package/dist/memory/index.js.map +1 -0
- package/dist/memory/memory.js +1 -0
- package/dist/memory/memory.js.map +1 -1
- package/dist/mesh/transport.js +60 -8
- package/dist/mesh/transport.js.map +1 -1
- package/dist/providers/anthropic.js +3 -2
- package/dist/providers/anthropic.js.map +1 -1
- package/dist/providers/base.js.map +1 -1
- package/dist/providers/google.js +94 -20
- package/dist/providers/google.js.map +1 -1
- package/dist/providers/modelCapabilities.js +59 -0
- package/dist/providers/modelCapabilities.js.map +1 -0
- package/dist/providers/ollama.js +3 -2
- package/dist/providers/ollama.js.map +1 -1
- package/dist/providers/openai.js +4 -3
- package/dist/providers/openai.js.map +1 -1
- package/dist/providers/openai_compat.js +3 -2
- package/dist/providers/openai_compat.js.map +1 -1
- package/dist/providers/router.js +63 -21
- package/dist/providers/router.js.map +1 -1
- package/dist/safety/fabricationGuard.js +140 -0
- package/dist/safety/fabricationGuard.js.map +1 -0
- package/dist/skills/builtin/gepa.js +23 -1
- package/dist/skills/builtin/gepa.js.map +1 -1
- package/dist/skills/builtin/model_trainer.js +31 -4
- package/dist/skills/builtin/model_trainer.js.map +1 -1
- package/dist/skills/builtin/self_improve.js +50 -2
- package/dist/skills/builtin/self_improve.js.map +1 -1
- package/dist/utils/constants.js +2 -2
- package/dist/utils/constants.js.map +1 -1
- package/package.json +1 -1
|
@@ -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/** 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/** Try the fallback chain for a streaming request. Returns an async generator or null if exhausted. */\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 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 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 // Verify the provider responds by getting the generator (will throw on immediate errors)\n const gen = fbProvider.chatStream({ ...options, model: fbModel });\n\n // Record success for circuit breaker (optimistically, actual success tracked in chatStream)\n recordSuccess(fbProviderName);\n\n lastFallbackEvent = {\n primary: primaryModelId,\n active: fallbackModelId,\n reason: originalError.message,\n timestamp: Date.now(),\n };\n return gen;\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 stream model ${fallbackModelId} also failed: ${(chainErr as Error).message}`);\n continue;\n }\n }\n return null;\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\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 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 for (let attempt = 0; attempt <= maxRetries; attempt++) {\n try {\n // Stream from provider\n for await (const chunk of provider.chatStream({ ...options, model })) {\n // Record success on first successful chunk\n if (attempt === 0 && chunk.type !== 'error') {\n recordSuccess(providerName);\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 // Notify consumer about the retry\n yield {\n type: 'text',\n content: `\\n[Retrying request (${attempt + 1}/${maxRetries}) due to ${classified.reason}...]\\n\\n`,\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\n if (classified.retryable || classified.shouldFallback) {\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 // Attempt provider failover (only on first attempt)\n if (attempt === 0) {\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 yield* fallback.chatStream({ ...options, model: preferred });\n recordSuccess(fallbackName);\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/** 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 // Wrap the generator so circuit-breaker bookkeeping reflects real\n // outcomes — success only after a clean stream end, failure on\n // error chunks or thrown errors mid-stream.\n async function* monitored(\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 // Stream ended without throwing — record success unless we\n // already booked a failure from an error chunk.\n if (!recorded) recordSuccess(providerName);\n } catch (innerErr) {\n if (!recorded) { recordFailure(providerName); recorded = true; }\n throw innerErr;\n }\n }\n\n return monitored(gen, fbProviderName);\n }\n return null;\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\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 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"]}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { existsSync, statSync, readFileSync } from "fs";
|
|
3
|
+
import { createHash } from "crypto";
|
|
4
|
+
const VERB_PATTERNS = [
|
|
5
|
+
// Past-tense file writes — Hunt #47 lineage. Catches "I have written X
|
|
6
|
+
// to /tmp/foo.md", "I saved the report at /home/dj/report.txt".
|
|
7
|
+
{
|
|
8
|
+
regex: /\b(?:I(?:'ve| have| just)?)\s+(written|saved|wrote|created|generated|produced)\s+(?:[^.!?\n]*?)(?:to|at|in)\s+["'`]?(\/[\w/.-]+\.[a-z0-9]+|\.\/[\w/.-]+|~\/[\w/.-]+|[\w./_-]+\.[a-z0-9]{1,5})["'`]?/i,
|
|
9
|
+
category: "file_write",
|
|
10
|
+
expectedTool: "write_file",
|
|
11
|
+
verbGroup: 1,
|
|
12
|
+
targetGroup: 2
|
|
13
|
+
},
|
|
14
|
+
// File edits — "I edited X", "I fixed the bug in X", "I modified config.ts".
|
|
15
|
+
{
|
|
16
|
+
regex: /\b(?:I(?:'ve| have| just)?)\s+(edited|modified|fixed|patched|updated|refactored)\s+(?:the\s+\w+\s+(?:in|at)\s+)?["'`]?([\w/.-]+\.[a-z0-9]{1,5}|[\w/.-]+\/[\w._-]+)["'`]?/i,
|
|
17
|
+
category: "file_edit",
|
|
18
|
+
expectedTool: "edit_file",
|
|
19
|
+
verbGroup: 1,
|
|
20
|
+
targetGroup: 2
|
|
21
|
+
},
|
|
22
|
+
// File deletes — "I deleted /tmp/foo", "I removed the old config".
|
|
23
|
+
{
|
|
24
|
+
regex: /\b(?:I(?:'ve| have| just)?)\s+(deleted|removed|cleaned\s+up)\s+(?:the\s+)?["'`]?(\/[\w/.-]+|[\w/.-]+\.[a-z0-9]{1,5})["'`]?/i,
|
|
25
|
+
category: "file_delete",
|
|
26
|
+
expectedTool: "shell",
|
|
27
|
+
verbGroup: 1,
|
|
28
|
+
targetGroup: 2
|
|
29
|
+
},
|
|
30
|
+
// Shell command claims — "I ran `npm test`", "I executed git status".
|
|
31
|
+
// Backtick form is the strong signal; bare-text "I ran npm install"
|
|
32
|
+
// also triggers but only when followed by a recognizable command.
|
|
33
|
+
{
|
|
34
|
+
regex: /\b(?:I(?:'ve| have| just)?)\s+(ran|executed|installed|launched)\s+["`]?([a-z][a-z0-9_-]+(?:\s+[\w.-]+)*)/i,
|
|
35
|
+
category: "shell_run",
|
|
36
|
+
expectedTool: "shell",
|
|
37
|
+
verbGroup: 1,
|
|
38
|
+
targetGroup: 2
|
|
39
|
+
},
|
|
40
|
+
// Web actions — "I searched for X", "I browsed to Y", "I fetched Z".
|
|
41
|
+
{
|
|
42
|
+
regex: /\b(?:I(?:'ve| have| just)?)\s+(searched|browsed|fetched|googled|looked\s+up)\s+(?:for\s+|to\s+)?["'`]?([^"'`.!?\n]{2,80})["'`]?/i,
|
|
43
|
+
category: "web_action",
|
|
44
|
+
expectedTool: "web_search",
|
|
45
|
+
verbGroup: 1,
|
|
46
|
+
targetGroup: 2
|
|
47
|
+
},
|
|
48
|
+
// Generic tool-name claim — "I used the shell tool", "I used write_file".
|
|
49
|
+
// This is the weakest signal and the most likely to misfire — only
|
|
50
|
+
// included so the system can flag for human review, not auto-correct.
|
|
51
|
+
{
|
|
52
|
+
regex: /\b(?:I(?:'ve| have| just)?)\s+(used|called|invoked)\s+(?:the\s+)?["'`]?([a-z_]{3,30})["'`]?\s+tool\b/i,
|
|
53
|
+
category: "tool_used",
|
|
54
|
+
expectedTool: "*",
|
|
55
|
+
// wildcard — match against any tool that has the same name
|
|
56
|
+
verbGroup: 1,
|
|
57
|
+
targetGroup: 2
|
|
58
|
+
}
|
|
59
|
+
];
|
|
60
|
+
function detectFabrication(content, toolHistory) {
|
|
61
|
+
if (!content || content.length < 5) return [];
|
|
62
|
+
const findings = [];
|
|
63
|
+
const usedTools = new Set(toolHistory.map((t) => t.name.toLowerCase()));
|
|
64
|
+
for (const pat of VERB_PATTERNS) {
|
|
65
|
+
const m = content.match(pat.regex);
|
|
66
|
+
if (!m) continue;
|
|
67
|
+
const verb = (pat.verbGroup ? m[pat.verbGroup] : m[1]) || "did";
|
|
68
|
+
const target = (pat.targetGroup ? m[pat.targetGroup] : m[2]) || "";
|
|
69
|
+
if (!target) continue;
|
|
70
|
+
const claimSatisfied = pat.expectedTool === "*" ? usedTools.has(target.toLowerCase()) : usedTools.has(pat.expectedTool);
|
|
71
|
+
if (!claimSatisfied) {
|
|
72
|
+
findings.push({
|
|
73
|
+
category: pat.category,
|
|
74
|
+
verb: verb.toLowerCase(),
|
|
75
|
+
target: target.trim(),
|
|
76
|
+
expectedTool: pat.expectedTool,
|
|
77
|
+
excerpt: m[0]
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return findings;
|
|
82
|
+
}
|
|
83
|
+
function verifyFileWriteClaim(filePath, expectedContent) {
|
|
84
|
+
let exists;
|
|
85
|
+
try {
|
|
86
|
+
exists = existsSync(filePath);
|
|
87
|
+
} catch {
|
|
88
|
+
return { fileExists: false, reason: "fs.existsSync threw \u2014 invalid path" };
|
|
89
|
+
}
|
|
90
|
+
if (!exists) {
|
|
91
|
+
return { fileExists: false, reason: `file not present at ${filePath}` };
|
|
92
|
+
}
|
|
93
|
+
let size = 0;
|
|
94
|
+
try {
|
|
95
|
+
size = statSync(filePath).size;
|
|
96
|
+
} catch {
|
|
97
|
+
return { fileExists: true, reason: "fs.statSync threw on existing path" };
|
|
98
|
+
}
|
|
99
|
+
if (size === 0) {
|
|
100
|
+
return { fileExists: true, reason: "file exists but is empty" };
|
|
101
|
+
}
|
|
102
|
+
let actualContent;
|
|
103
|
+
let fileHash;
|
|
104
|
+
try {
|
|
105
|
+
actualContent = readFileSync(filePath, "utf-8");
|
|
106
|
+
fileHash = createHash("sha256").update(actualContent).digest("hex");
|
|
107
|
+
} catch (e) {
|
|
108
|
+
return { fileExists: true, reason: `read failed: ${e.message}` };
|
|
109
|
+
}
|
|
110
|
+
if (expectedContent === void 0) {
|
|
111
|
+
return { fileExists: true, fileHash };
|
|
112
|
+
}
|
|
113
|
+
const a = actualContent.trim();
|
|
114
|
+
const b = expectedContent.trim();
|
|
115
|
+
const contentMatches = a === b;
|
|
116
|
+
return {
|
|
117
|
+
fileExists: true,
|
|
118
|
+
fileHash,
|
|
119
|
+
contentMatches,
|
|
120
|
+
reason: contentMatches ? void 0 : "file exists but content differs from claim"
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
function buildNudgeMessage(findings) {
|
|
124
|
+
if (findings.length === 0) return "";
|
|
125
|
+
const lines = ["You claimed to perform actions you did NOT actually do via tools:"];
|
|
126
|
+
for (const f of findings) {
|
|
127
|
+
lines.push(` - You said you ${f.verb} "${f.target}", but you did not call ${f.expectedTool === "*" ? "any matching tool" : `the ${f.expectedTool} tool`}.`);
|
|
128
|
+
}
|
|
129
|
+
lines.push("");
|
|
130
|
+
lines.push("Either:");
|
|
131
|
+
lines.push(" 1. Actually call the right tool now.");
|
|
132
|
+
lines.push(" 2. Correct your claim \u2014 say what you DID do, or admit you did not do it.");
|
|
133
|
+
return lines.join("\n");
|
|
134
|
+
}
|
|
135
|
+
export {
|
|
136
|
+
buildNudgeMessage,
|
|
137
|
+
detectFabrication,
|
|
138
|
+
verifyFileWriteClaim
|
|
139
|
+
};
|
|
140
|
+
//# sourceMappingURL=fabricationGuard.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/safety/fabricationGuard.ts"],"sourcesContent":["/**\n * FabricationGuard (Phase 9 / Track D, v5.4.0)\n *\n * Catches model responses that CLAIM to have done something but didn't\n * actually call the tool that would do it. The original guard lived\n * inline in `agentLoop.ts` and only matched past-tense write claims via\n * a single narrow regex. This module:\n * 1. Expands pattern coverage to all common action verbs\n * (edit/fix/run/search/browse/create/delete + write/save).\n * 2. Cross-checks claims against the actual tool history — \"I ran\n * `npm test`\" only counts as truthful if a `shell` tool call\n * actually happened in this turn.\n * 3. For file-write claims, exposes a verifier that checks the file\n * exists and (optionally) hashes the content.\n *\n * It's a pure module: no I/O at import time, easy to unit-test. The\n * agent loop can call `detectFabrication(content, toolHistory)` after\n * each response and choose to nudge the model, force a tool call, or\n * return a redacted answer.\n */\n\nimport { existsSync, statSync, readFileSync } from 'fs';\nimport { createHash } from 'crypto';\n\n/** A single tool invocation captured by the agent loop, in execution order. */\nexport interface ToolHistoryEntry {\n /** Tool name (`shell`, `write_file`, `web_search`, ...). */\n name: string;\n /** Arguments passed to the tool, parsed from the model's tool_calls. */\n args?: Record<string, unknown>;\n /** Raw output, when available — used by the file-write verifier. */\n output?: string;\n}\n\n/** Discriminated category of fabrication signal. */\nexport type FabricationCategory =\n | 'file_write' // I wrote/saved/created file X\n | 'file_edit' // I edited/fixed/modified X\n | 'file_delete' // I deleted/removed X\n | 'shell_run' // I ran/executed/installed X\n | 'web_action' // I searched/browsed/fetched X\n | 'tool_used'; // I used [tool_name] (generic catch-all)\n\n/** A single fabrication finding from `detectFabrication`. */\nexport interface FabricationFinding {\n category: FabricationCategory;\n /** The verb the model used (write, edit, ran, etc.). */\n verb: string;\n /** The object/target the verb acted on (file path, URL, command). */\n target: string;\n /** The tool name that *would* satisfy this claim. */\n expectedTool: string;\n /** The exact substring of `content` that triggered the match. */\n excerpt: string;\n}\n\n// ── Pattern table ────────────────────────────────────────────────────\n//\n// Each entry is { regex, category, expectedTool }. We keep the regexes\n// strict to avoid false positives — TITAN's chat output is usually\n// short, so a wide pattern surface produces too many bogus rejections.\n//\n// All patterns require the verb to start near a sentence boundary\n// (^|[.!?\\n]\\s*) and end with a recognizable target. Matches are\n// case-insensitive but anchor on first-person voice (\"I have\", \"I've\",\n// \"I just\"); third-person summaries (e.g. quoting the user) don't fire.\n\nconst VERB_PATTERNS: Array<{\n regex: RegExp;\n category: FabricationCategory;\n expectedTool: string;\n verbGroup?: number;\n targetGroup?: number;\n}> = [\n // Past-tense file writes — Hunt #47 lineage. Catches \"I have written X\n // to /tmp/foo.md\", \"I saved the report at /home/dj/report.txt\".\n {\n regex: /\\b(?:I(?:'ve| have| just)?)\\s+(written|saved|wrote|created|generated|produced)\\s+(?:[^.!?\\n]*?)(?:to|at|in)\\s+[\"'`]?(\\/[\\w/.-]+\\.[a-z0-9]+|\\.\\/[\\w/.-]+|~\\/[\\w/.-]+|[\\w./_-]+\\.[a-z0-9]{1,5})[\"'`]?/i,\n category: 'file_write',\n expectedTool: 'write_file',\n verbGroup: 1,\n targetGroup: 2,\n },\n // File edits — \"I edited X\", \"I fixed the bug in X\", \"I modified config.ts\".\n {\n regex: /\\b(?:I(?:'ve| have| just)?)\\s+(edited|modified|fixed|patched|updated|refactored)\\s+(?:the\\s+\\w+\\s+(?:in|at)\\s+)?[\"'`]?([\\w/.-]+\\.[a-z0-9]{1,5}|[\\w/.-]+\\/[\\w._-]+)[\"'`]?/i,\n category: 'file_edit',\n expectedTool: 'edit_file',\n verbGroup: 1,\n targetGroup: 2,\n },\n // File deletes — \"I deleted /tmp/foo\", \"I removed the old config\".\n {\n regex: /\\b(?:I(?:'ve| have| just)?)\\s+(deleted|removed|cleaned\\s+up)\\s+(?:the\\s+)?[\"'`]?(\\/[\\w/.-]+|[\\w/.-]+\\.[a-z0-9]{1,5})[\"'`]?/i,\n category: 'file_delete',\n expectedTool: 'shell',\n verbGroup: 1,\n targetGroup: 2,\n },\n // Shell command claims — \"I ran `npm test`\", \"I executed git status\".\n // Backtick form is the strong signal; bare-text \"I ran npm install\"\n // also triggers but only when followed by a recognizable command.\n {\n regex: /\\b(?:I(?:'ve| have| just)?)\\s+(ran|executed|installed|launched)\\s+[\"`]?([a-z][a-z0-9_-]+(?:\\s+[\\w.-]+)*)/i,\n category: 'shell_run',\n expectedTool: 'shell',\n verbGroup: 1,\n targetGroup: 2,\n },\n // Web actions — \"I searched for X\", \"I browsed to Y\", \"I fetched Z\".\n {\n regex: /\\b(?:I(?:'ve| have| just)?)\\s+(searched|browsed|fetched|googled|looked\\s+up)\\s+(?:for\\s+|to\\s+)?[\"'`]?([^\"'`.!?\\n]{2,80})[\"'`]?/i,\n category: 'web_action',\n expectedTool: 'web_search',\n verbGroup: 1,\n targetGroup: 2,\n },\n // Generic tool-name claim — \"I used the shell tool\", \"I used write_file\".\n // This is the weakest signal and the most likely to misfire — only\n // included so the system can flag for human review, not auto-correct.\n {\n regex: /\\b(?:I(?:'ve| have| just)?)\\s+(used|called|invoked)\\s+(?:the\\s+)?[\"'`]?([a-z_]{3,30})[\"'`]?\\s+tool\\b/i,\n category: 'tool_used',\n expectedTool: '*', // wildcard — match against any tool that has the same name\n verbGroup: 1,\n targetGroup: 2,\n },\n];\n\n/**\n * Scan the model's response for action claims and return any that aren't\n * backed by a real tool call. `toolHistory` should contain every tool\n * invocation the agent made in this turn (and ideally the prior turn,\n * since \"I already wrote X\" can refer to a previous round).\n *\n * Returns an empty array when no fabrication is detected.\n */\nexport function detectFabrication(\n content: string,\n toolHistory: ToolHistoryEntry[],\n): FabricationFinding[] {\n if (!content || content.length < 5) return [];\n\n const findings: FabricationFinding[] = [];\n const usedTools = new Set(toolHistory.map(t => t.name.toLowerCase()));\n\n for (const pat of VERB_PATTERNS) {\n const m = content.match(pat.regex);\n if (!m) continue;\n const verb = (pat.verbGroup ? m[pat.verbGroup] : m[1]) || 'did';\n const target = (pat.targetGroup ? m[pat.targetGroup] : m[2]) || '';\n if (!target) continue;\n\n // Did the agent actually call a tool that satisfies this claim?\n const claimSatisfied = pat.expectedTool === '*'\n ? usedTools.has(target.toLowerCase())\n : usedTools.has(pat.expectedTool);\n\n if (!claimSatisfied) {\n findings.push({\n category: pat.category,\n verb: verb.toLowerCase(),\n target: target.trim(),\n expectedTool: pat.expectedTool,\n excerpt: m[0],\n });\n }\n }\n\n return findings;\n}\n\n// ── Verify-before-trust on file operations ──────────────────────────\n\n/** Result of `verifyFileWriteClaim`. */\nexport interface FileWriteVerification {\n /** True when the file exists at the claimed path with non-zero size. */\n fileExists: boolean;\n /** SHA-256 of the file contents, when present. */\n fileHash?: string;\n /** True when the file's content matches the model's claimed body\n * (only computed if `expectedContent` was passed). */\n contentMatches?: boolean;\n /** Why the claim fails verification, if it does. */\n reason?: string;\n}\n\n/**\n * Verify a file-write claim against the real filesystem.\n *\n * Use this AFTER the agent claims to have written/edited a file but\n * BEFORE accepting the response as final. If the file doesn't exist at\n * the claimed path, the agent fabricated and the loop should retry.\n *\n * Optionally pass `expectedContent` to also verify the body matches —\n * useful when the model includes the literal content in its response.\n */\nexport function verifyFileWriteClaim(\n filePath: string,\n expectedContent?: string,\n): FileWriteVerification {\n let exists: boolean;\n try {\n exists = existsSync(filePath);\n } catch {\n return { fileExists: false, reason: 'fs.existsSync threw — invalid path' };\n }\n if (!exists) {\n return { fileExists: false, reason: `file not present at ${filePath}` };\n }\n\n let size = 0;\n try {\n size = statSync(filePath).size;\n } catch {\n return { fileExists: true, reason: 'fs.statSync threw on existing path' };\n }\n if (size === 0) {\n return { fileExists: true, reason: 'file exists but is empty' };\n }\n\n let actualContent: string;\n let fileHash: string;\n try {\n actualContent = readFileSync(filePath, 'utf-8');\n fileHash = createHash('sha256').update(actualContent).digest('hex');\n } catch (e) {\n return { fileExists: true, reason: `read failed: ${(e as Error).message}` };\n }\n\n if (expectedContent === undefined) {\n return { fileExists: true, fileHash };\n }\n\n // Content match: lenient — strip trailing whitespace, compare.\n const a = actualContent.trim();\n const b = expectedContent.trim();\n const contentMatches = a === b;\n return {\n fileExists: true,\n fileHash,\n contentMatches,\n reason: contentMatches ? undefined : 'file exists but content differs from claim',\n };\n}\n\n/**\n * Build a structured nudge message the agent loop can append to the\n * model's next-turn user message when fabrication is detected. The\n * message is deliberately blunt — most weak models need to be told\n * directly that they didn't do what they claimed.\n */\nexport function buildNudgeMessage(findings: FabricationFinding[]): string {\n if (findings.length === 0) return '';\n const lines = ['You claimed to perform actions you did NOT actually do via tools:'];\n for (const f of findings) {\n lines.push(` - You said you ${f.verb} \"${f.target}\", but you did not call ${f.expectedTool === '*' ? 'any matching tool' : `the ${f.expectedTool} tool`}.`);\n }\n lines.push('');\n lines.push('Either:');\n lines.push(' 1. Actually call the right tool now.');\n lines.push(' 2. Correct your claim — say what you DID do, or admit you did not do it.');\n return lines.join('\\n');\n}\n"],"mappings":"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","names":[]}
|
|
@@ -637,7 +637,29 @@ function registerGepaSkill() {
|
|
|
637
637
|
},
|
|
638
638
|
execute: gepaHistory
|
|
639
639
|
});
|
|
640
|
-
|
|
640
|
+
void (async () => {
|
|
641
|
+
try {
|
|
642
|
+
const { registerWatcher } = await import("../../agent/daemon.js");
|
|
643
|
+
registerWatcher("gepa-daily-evolve", async () => {
|
|
644
|
+
const cfg = loadConfig();
|
|
645
|
+
if (!cfg.selfImprove?.enabled) {
|
|
646
|
+
logger.debug(COMPONENT, "GEPA daily watcher skipped \u2014 selfImprove.disabled");
|
|
647
|
+
return;
|
|
648
|
+
}
|
|
649
|
+
const area = IMPROVEMENT_AREAS[Math.floor(Math.random() * IMPROVEMENT_AREAS.length)];
|
|
650
|
+
logger.info(COMPONENT, `GEPA daily watcher starting evolution for area: ${area.id}`);
|
|
651
|
+
try {
|
|
652
|
+
await gepaEvolve({ area: area.id, budgetMinutes: 30, maxGenerations: 5 });
|
|
653
|
+
} catch (e) {
|
|
654
|
+
logger.error(COMPONENT, `GEPA daily watcher failed: ${e.message}`);
|
|
655
|
+
}
|
|
656
|
+
}, 24 * 60 * 60 * 1e3);
|
|
657
|
+
logger.info(COMPONENT, "GEPA daily watcher registered (24h interval)");
|
|
658
|
+
} catch {
|
|
659
|
+
logger.warn(COMPONENT, "Daemon system not available \u2014 GEPA daily watcher not registered");
|
|
660
|
+
}
|
|
661
|
+
})();
|
|
662
|
+
logger.info(COMPONENT, "GEPA evolution skill registered (3 tools + daily watcher)");
|
|
641
663
|
}
|
|
642
664
|
export {
|
|
643
665
|
crossover,
|