titan-agent 6.0.0 → 6.0.2
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/somaInitiative.js +69 -10
- package/dist/agent/somaInitiative.js.map +1 -1
- package/dist/config/schema.js +11 -17
- package/dist/config/schema.js.map +1 -1
- package/dist/gateway/routes/agents.js +7 -2
- package/dist/gateway/routes/agents.js.map +1 -1
- package/dist/gateway/server.js +8 -0
- package/dist/gateway/server.js.map +1 -1
- package/dist/providers/defaultModel.js +140 -0
- package/dist/providers/defaultModel.js.map +1 -0
- package/dist/providers/router.js +3 -2
- package/dist/providers/router.js.map +1 -1
- package/dist/utils/constants.js +1 -1
- package/dist/utils/constants.js.map +1 -1
- package/package.json +1 -1
- package/ui/dist/sw.js +1 -1
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { existsSync, readFileSync } from "fs";
|
|
3
|
+
import { join } from "path";
|
|
4
|
+
import { homedir } from "os";
|
|
5
|
+
const TITAN_HOME_DEFAULT = join(homedir(), ".titan");
|
|
6
|
+
const PROVIDERS = [
|
|
7
|
+
{
|
|
8
|
+
name: "anthropic",
|
|
9
|
+
envKey: "ANTHROPIC_API_KEY",
|
|
10
|
+
model: "anthropic/claude-sonnet-4-20250514",
|
|
11
|
+
aliases: {
|
|
12
|
+
fast: "anthropic/claude-haiku-4-20250514",
|
|
13
|
+
smart: "anthropic/claude-sonnet-4-20250514",
|
|
14
|
+
cheap: "anthropic/claude-haiku-4-20250514",
|
|
15
|
+
reasoning: "anthropic/claude-sonnet-4-20250514"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
name: "openai",
|
|
20
|
+
envKey: "OPENAI_API_KEY",
|
|
21
|
+
model: "openai/gpt-4o",
|
|
22
|
+
aliases: {
|
|
23
|
+
fast: "openai/gpt-4o-mini",
|
|
24
|
+
smart: "openai/gpt-4o",
|
|
25
|
+
cheap: "openai/gpt-4o-mini",
|
|
26
|
+
reasoning: "openai/gpt-4o"
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
name: "google",
|
|
31
|
+
envKey: "GOOGLE_API_KEY",
|
|
32
|
+
model: "google/gemini-2.5-pro",
|
|
33
|
+
aliases: {
|
|
34
|
+
fast: "google/gemini-2.5-flash",
|
|
35
|
+
smart: "google/gemini-2.5-pro",
|
|
36
|
+
cheap: "google/gemini-2.5-flash",
|
|
37
|
+
reasoning: "google/gemini-2.5-pro"
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
name: "openrouter",
|
|
42
|
+
envKey: "OPENROUTER_API_KEY",
|
|
43
|
+
model: "openrouter/anthropic/claude-sonnet-4",
|
|
44
|
+
aliases: {
|
|
45
|
+
fast: "openrouter/anthropic/claude-haiku-4",
|
|
46
|
+
smart: "openrouter/anthropic/claude-sonnet-4",
|
|
47
|
+
cheap: "openrouter/anthropic/claude-haiku-4",
|
|
48
|
+
reasoning: "openrouter/anthropic/claude-sonnet-4"
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
];
|
|
52
|
+
const OLLAMA_FALLBACK = {
|
|
53
|
+
model: "ollama/qwen3.5:cloud",
|
|
54
|
+
aliases: {
|
|
55
|
+
fast: "ollama/qwen3.5:cloud",
|
|
56
|
+
smart: "ollama/glm-5:cloud",
|
|
57
|
+
cheap: "ollama/qwen3.5:cloud",
|
|
58
|
+
reasoning: "ollama/kimi-k2.6:cloud",
|
|
59
|
+
local: "ollama/qwen3.5:4b",
|
|
60
|
+
cloud: "ollama/kimi-k2.6:cloud"
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
function hasNonEmptyEnv(key) {
|
|
64
|
+
const v = process.env[key];
|
|
65
|
+
return typeof v === "string" && v.trim().length > 0;
|
|
66
|
+
}
|
|
67
|
+
function readUserConfigBlob() {
|
|
68
|
+
const titanHome = process.env.TITAN_HOME && process.env.TITAN_HOME.length > 0 ? process.env.TITAN_HOME.startsWith("~/") ? join(homedir(), process.env.TITAN_HOME.slice(2)) : process.env.TITAN_HOME : TITAN_HOME_DEFAULT;
|
|
69
|
+
const path = join(titanHome, "titan.json");
|
|
70
|
+
if (!existsSync(path)) return null;
|
|
71
|
+
try {
|
|
72
|
+
return JSON.parse(readFileSync(path, "utf-8"));
|
|
73
|
+
} catch {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
function pickDefaultModel() {
|
|
78
|
+
const cfg = readUserConfigBlob();
|
|
79
|
+
const userAgentModel = cfg?.agent?.model;
|
|
80
|
+
if (typeof userAgentModel === "string" && userAgentModel.trim().length > 0) {
|
|
81
|
+
const provider = userAgentModel.split("/")[0] || "unknown";
|
|
82
|
+
return {
|
|
83
|
+
model: userAgentModel,
|
|
84
|
+
aliases: defaultAliasesForProvider(provider),
|
|
85
|
+
reason: `user config (agent.model=${userAgentModel})`,
|
|
86
|
+
provider
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
for (const candidate of PROVIDERS) {
|
|
90
|
+
if (hasNonEmptyEnv(candidate.envKey)) {
|
|
91
|
+
return {
|
|
92
|
+
model: candidate.model,
|
|
93
|
+
aliases: candidate.aliases,
|
|
94
|
+
reason: `${candidate.envKey} is set \u2014 picked ${candidate.name}`,
|
|
95
|
+
provider: candidate.name
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
const compatHits = Object.keys(process.env).filter((k) => k.endsWith("_API_KEY") && (process.env[k] ?? "").trim().length > 0).filter((k) => !["ANTHROPIC_API_KEY", "OPENAI_API_KEY", "GOOGLE_API_KEY", "OPENROUTER_API_KEY"].includes(k));
|
|
100
|
+
if (compatHits.length > 0) {
|
|
101
|
+
const providerName = compatHits[0].replace("_API_KEY", "").toLowerCase();
|
|
102
|
+
return {
|
|
103
|
+
model: OLLAMA_FALLBACK.model,
|
|
104
|
+
aliases: OLLAMA_FALLBACK.aliases,
|
|
105
|
+
reason: `detected ${compatHits[0]} but no first-class adapter mapping; falling back to Ollama defaults \u2014 override agent.model in config to use ${providerName}`,
|
|
106
|
+
provider: "ollama"
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
return {
|
|
110
|
+
model: OLLAMA_FALLBACK.model,
|
|
111
|
+
aliases: OLLAMA_FALLBACK.aliases,
|
|
112
|
+
reason: "no cloud API keys detected \u2014 defaulting to local Ollama (set ANTHROPIC_API_KEY / OPENAI_API_KEY / GOOGLE_API_KEY to switch providers)",
|
|
113
|
+
provider: "ollama"
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
function defaultAliasesForProvider(provider) {
|
|
117
|
+
const match = PROVIDERS.find((p) => p.name === provider);
|
|
118
|
+
if (match) return match.aliases;
|
|
119
|
+
return OLLAMA_FALLBACK.aliases;
|
|
120
|
+
}
|
|
121
|
+
function getDefaultModelId() {
|
|
122
|
+
return pickDefaultModel().model;
|
|
123
|
+
}
|
|
124
|
+
function getDefaultModelAliases() {
|
|
125
|
+
const pick = pickDefaultModel();
|
|
126
|
+
return {
|
|
127
|
+
// Ollama floor — any tier the chosen provider doesn't define
|
|
128
|
+
// still resolves to a working model.
|
|
129
|
+
...OLLAMA_FALLBACK.aliases,
|
|
130
|
+
// Chosen-provider aliases win for fast / smart / cheap /
|
|
131
|
+
// reasoning. `local` + `cloud` keys stay on the Ollama floor.
|
|
132
|
+
...pick.aliases
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
export {
|
|
136
|
+
getDefaultModelAliases,
|
|
137
|
+
getDefaultModelId,
|
|
138
|
+
pickDefaultModel
|
|
139
|
+
};
|
|
140
|
+
//# sourceMappingURL=defaultModel.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/providers/defaultModel.ts"],"sourcesContent":["/**\n * TITAN — Default Model Picker (v6.0.1)\n *\n * \"Model-agnostic\" promise: TITAN runs with whatever LLM provider the\n * user has credentials for. If they only have an OpenAI key, TITAN picks\n * OpenAI. If only Anthropic, TITAN picks Anthropic. If nothing's keyed,\n * TITAN falls back to a local Ollama default (still works — the package\n * runs end-to-end on a laptop with no cloud creds).\n *\n * Order of preference (highest tier first):\n *\n * 1. `agent.model` in user config — explicit override, always wins.\n * 2. `ANTHROPIC_API_KEY` → claude-sonnet-4 (strongest agent reasoning)\n * 3. `OPENAI_API_KEY` → gpt-4o\n * 4. `GOOGLE_API_KEY` → gemini-2.5-pro\n * 5. `OPENROUTER_API_KEY` → claude-sonnet via openrouter\n * 6. (anything else with `*_API_KEY` set among the 32 OpenAI-compat\n * providers TITAN ships) → use that\n * 7. Local Ollama → `qwen3.5:cloud` (the standing fast default —\n * routes through `ollama serve` on localhost:11434).\n *\n * The picker is pure (no network, no async). It only inspects\n * `process.env` + the user config blob. It's safe to call from the Zod\n * schema's `.default()` thunks AND from runtime resolution paths.\n *\n * On gateway boot we log the pick + the reason so the user knows which\n * provider TITAN selected.\n */\n\nimport { existsSync, readFileSync } from 'fs';\nimport { join } from 'path';\nimport { homedir } from 'os';\n\nconst TITAN_HOME_DEFAULT = join(homedir(), '.titan');\n\n// ── Provider preference table ───────────────────────────────────\n//\n// Order is intentional: stronger reasoning models first, then size,\n// then breadth. We pin to widely-available checkpoints (no preview-only\n// ids) so the default works out of the box for everyone.\n\ninterface ProviderCandidate {\n name: string;\n envKey: string;\n /** The model id we pick when this env key is present. */\n model: string;\n /** Reasonable fast / smart / cheap fallbacks within this provider. */\n aliases: { fast: string; smart: string; cheap: string; reasoning: string };\n}\n\nconst PROVIDERS: ProviderCandidate[] = [\n {\n name: 'anthropic',\n envKey: 'ANTHROPIC_API_KEY',\n model: 'anthropic/claude-sonnet-4-20250514',\n aliases: {\n fast: 'anthropic/claude-haiku-4-20250514',\n smart: 'anthropic/claude-sonnet-4-20250514',\n cheap: 'anthropic/claude-haiku-4-20250514',\n reasoning: 'anthropic/claude-sonnet-4-20250514',\n },\n },\n {\n name: 'openai',\n envKey: 'OPENAI_API_KEY',\n model: 'openai/gpt-4o',\n aliases: {\n fast: 'openai/gpt-4o-mini',\n smart: 'openai/gpt-4o',\n cheap: 'openai/gpt-4o-mini',\n reasoning: 'openai/gpt-4o',\n },\n },\n {\n name: 'google',\n envKey: 'GOOGLE_API_KEY',\n model: 'google/gemini-2.5-pro',\n aliases: {\n fast: 'google/gemini-2.5-flash',\n smart: 'google/gemini-2.5-pro',\n cheap: 'google/gemini-2.5-flash',\n reasoning: 'google/gemini-2.5-pro',\n },\n },\n {\n name: 'openrouter',\n envKey: 'OPENROUTER_API_KEY',\n model: 'openrouter/anthropic/claude-sonnet-4',\n aliases: {\n fast: 'openrouter/anthropic/claude-haiku-4',\n smart: 'openrouter/anthropic/claude-sonnet-4',\n cheap: 'openrouter/anthropic/claude-haiku-4',\n reasoning: 'openrouter/anthropic/claude-sonnet-4',\n },\n },\n];\n\n// ── Ollama-cloud fallback (works without any cloud key) ──────────\n//\n// These are the same defaults we shipped through v5.x. Kept here as the\n// last-resort floor so a user with NO cloud credentials still gets a\n// working agent out of the box (assuming `ollama serve` is up — which\n// the `titan onboard` wizard helps with).\n\nconst OLLAMA_FALLBACK = {\n model: 'ollama/qwen3.5:cloud',\n aliases: {\n fast: 'ollama/qwen3.5:cloud',\n smart: 'ollama/glm-5:cloud',\n cheap: 'ollama/qwen3.5:cloud',\n reasoning: 'ollama/kimi-k2.6:cloud',\n local: 'ollama/qwen3.5:4b',\n cloud: 'ollama/kimi-k2.6:cloud',\n },\n};\n\n// ── Credential probing ──────────────────────────────────────────\n\nfunction hasNonEmptyEnv(key: string): boolean {\n const v = process.env[key];\n return typeof v === 'string' && v.trim().length > 0;\n}\n\n/** Read user config without depending on the full Zod schema (we may be\n * called from inside the schema itself). Returns the raw JSON blob or\n * null. Best-effort — corrupted config returns null. */\nfunction readUserConfigBlob(): Record<string, unknown> | null {\n const titanHome = process.env.TITAN_HOME && process.env.TITAN_HOME.length > 0\n ? (process.env.TITAN_HOME.startsWith('~/') ? join(homedir(), process.env.TITAN_HOME.slice(2)) : process.env.TITAN_HOME)\n : TITAN_HOME_DEFAULT;\n const path = join(titanHome, 'titan.json');\n if (!existsSync(path)) return null;\n try { return JSON.parse(readFileSync(path, 'utf-8')) as Record<string, unknown>; } catch { return null; }\n}\n\n// ── Public picker ───────────────────────────────────────────────\n\nexport interface DefaultModelPick {\n /** The chosen model id (provider/model-name). */\n model: string;\n /** Model aliases for the fast / smart / cheap / reasoning tiers. */\n aliases: Record<string, string>;\n /** Human-readable reason for the pick — printed at gateway boot. */\n reason: string;\n /** Provider name (anthropic / openai / google / openrouter / ollama). */\n provider: string;\n}\n\n/**\n * Decide the default agent model + tier aliases from currently-available\n * credentials. Pure function — only reads `process.env` and (optionally)\n * the on-disk config blob.\n *\n * Returns the same shape regardless of which provider wins so the\n * config schema can plug the pick straight in.\n */\nexport function pickDefaultModel(): DefaultModelPick {\n // 1. Explicit user config wins\n const cfg = readUserConfigBlob();\n const userAgentModel = ((cfg?.agent as Record<string, unknown> | undefined)?.model);\n if (typeof userAgentModel === 'string' && userAgentModel.trim().length > 0) {\n const provider = userAgentModel.split('/')[0] || 'unknown';\n return {\n model: userAgentModel,\n aliases: defaultAliasesForProvider(provider),\n reason: `user config (agent.model=${userAgentModel})`,\n provider,\n };\n }\n\n // 2. Walk providers in preference order; first env key wins.\n for (const candidate of PROVIDERS) {\n if (hasNonEmptyEnv(candidate.envKey)) {\n return {\n model: candidate.model,\n aliases: candidate.aliases,\n reason: `${candidate.envKey} is set — picked ${candidate.name}`,\n provider: candidate.name,\n };\n }\n }\n\n // 3. Catch-all: any of the 32 OpenAI-compat providers with a key set.\n // Conservative — we only auto-pick when the key pattern is\n // unmistakable (`*_API_KEY` or known names like GROQ_API_KEY).\n const compatHits = Object.keys(process.env)\n .filter(k => k.endsWith('_API_KEY') && (process.env[k] ?? '').trim().length > 0)\n .filter(k => !['ANTHROPIC_API_KEY', 'OPENAI_API_KEY', 'GOOGLE_API_KEY', 'OPENROUTER_API_KEY'].includes(k));\n if (compatHits.length > 0) {\n // Pick the first one alphabetically for determinism. The user can\n // always override via config — this is just the bootstrap default.\n const providerName = compatHits[0].replace('_API_KEY', '').toLowerCase();\n // For these we don't know the exact model id; reuse Ollama\n // fallback aliases but flag the provider so the user sees the\n // log line and can correct it in config.\n return {\n model: OLLAMA_FALLBACK.model,\n aliases: OLLAMA_FALLBACK.aliases,\n reason: `detected ${compatHits[0]} but no first-class adapter mapping; falling back to Ollama defaults — override agent.model in config to use ${providerName}`,\n provider: 'ollama',\n };\n }\n\n // 4. Final fallback — local Ollama. Works on a clean laptop with\n // `ollama serve` running and the default models pulled.\n return {\n model: OLLAMA_FALLBACK.model,\n aliases: OLLAMA_FALLBACK.aliases,\n reason: 'no cloud API keys detected — defaulting to local Ollama (set ANTHROPIC_API_KEY / OPENAI_API_KEY / GOOGLE_API_KEY to switch providers)',\n provider: 'ollama',\n };\n}\n\nfunction defaultAliasesForProvider(provider: string): Record<string, string> {\n const match = PROVIDERS.find(p => p.name === provider);\n if (match) return match.aliases;\n return OLLAMA_FALLBACK.aliases;\n}\n\n/**\n * Resolve the default agent model. Thin wrapper used by the schema's\n * `.default()` thunk. Stable across calls within a process (env doesn't\n * change at runtime), so the schema's default is consistent.\n */\nexport function getDefaultModelId(): string {\n return pickDefaultModel().model;\n}\n\n/**\n * Resolve the default tier-alias map. Used by the schema's modelAliases\n * default. Merges with the Ollama floor so any tier name not provided\n * by the chosen provider still resolves to something working.\n */\nexport function getDefaultModelAliases(): Record<string, string> {\n const pick = pickDefaultModel();\n return {\n // Ollama floor — any tier the chosen provider doesn't define\n // still resolves to a working model.\n ...OLLAMA_FALLBACK.aliases,\n // Chosen-provider aliases win for fast / smart / cheap /\n // reasoning. `local` + `cloud` keys stay on the Ollama floor.\n ...pick.aliases,\n };\n}\n"],"mappings":"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","names":[]}
|
package/dist/providers/router.js
CHANGED
|
@@ -16,6 +16,7 @@ import { classifyProviderError, shouldAffectCircuitBreaker, FailoverReason } fro
|
|
|
16
16
|
import { getExistingPool } from "./credentialPool.js";
|
|
17
17
|
import { buildSmartContext } from "../agent/contextManager.js";
|
|
18
18
|
import { shouldBackOff } from "./rateLimitTracker.js";
|
|
19
|
+
import { getDefaultModelId } from "./defaultModel.js";
|
|
19
20
|
const COMPONENT = "Router";
|
|
20
21
|
function getFailoverOrder(excludeProvider) {
|
|
21
22
|
const priority = {
|
|
@@ -500,7 +501,7 @@ function createEnhancedErrorMessage(error, providerName, model, attempt) {
|
|
|
500
501
|
].filter(Boolean).join(": ");
|
|
501
502
|
}
|
|
502
503
|
async function chat(options) {
|
|
503
|
-
const modelId = options.model ||
|
|
504
|
+
const modelId = options.model || getDefaultModelId();
|
|
504
505
|
const { provider, model } = resolveModel(modelId);
|
|
505
506
|
const providerName = provider.name;
|
|
506
507
|
logger.info(COMPONENT, `Routing to ${provider.displayName} (model: ${model})`);
|
|
@@ -727,7 +728,7 @@ async function chat(options) {
|
|
|
727
728
|
throw lastError || new Error(`Provider ${providerName}/${model} failed after all retries`);
|
|
728
729
|
}
|
|
729
730
|
async function* chatStream(options) {
|
|
730
|
-
const modelId = options.model ||
|
|
731
|
+
const modelId = options.model || getDefaultModelId();
|
|
731
732
|
const { provider, model } = resolveModel(modelId);
|
|
732
733
|
const providerName = provider.name;
|
|
733
734
|
logger.info(COMPONENT, `Streaming via ${provider.displayName} (model: ${model})`);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/providers/router.ts"],"sourcesContent":["/**\n * TITAN — Universal Model Router\n * Routes model requests to the correct provider with failover, alias resolution,\n * and live model discovery across all configured providers (including local Ollama).\n *\n * Error Recovery Features:\n * - Exponential backoff retry for transient failures (429, 503, timeouts)\n * - Circuit breaker pattern to avoid hammering failing providers\n * - Automatic fallback to next provider in chain on persistent errors\n * - Detailed error messages including provider name and model\n */\nimport { LLMProvider, type ChatOptions, type ChatResponse, type ChatStreamChunk } from './base.js';\nimport { AnthropicProvider } from './anthropic.js';\nimport { OpenAIProvider } from './openai.js';\nimport { GoogleProvider } from './google.js';\nimport { OllamaProvider } from './ollama.js';\nimport { ClaudeCodeProvider } from './claudeCode.js';\nimport { OpenAICompatProvider, PROVIDER_PRESETS } from './openai_compat.js';\nimport { loadConfig } from '../config/config.js';\nimport logger from '../utils/logger.js';\nimport { findModelOnMesh } from '../mesh/registry.js';\nimport type { MeshPeer } from '../mesh/discovery.js';\nimport { routeTaskToNode } from '../mesh/transport.js';\nimport { randomBytes } from 'crypto';\nimport { sleep } from '../utils/helpers.js';\nimport { classifyProviderError, shouldAffectCircuitBreaker, FailoverReason } from './errorTaxonomy.js';\nimport { getExistingPool } from './credentialPool.js';\nimport { buildSmartContext } from '../agent/contextManager.js';\nimport { shouldBackOff } from './rateLimitTracker.js';\n\nconst COMPONENT = 'Router';\n\n/** Build failover order from all registered providers, sorted by capability priority */\nfunction getFailoverOrder(excludeProvider: string): string[] {\n const priority: Record<string, number> = {\n anthropic: 100,\n openai: 90,\n google: 80,\n openrouter: 75,\n groq: 70,\n together: 65,\n deepseek: 60,\n xai: 55,\n mistral: 50,\n cerebras: 45,\n cohere: 40,\n 'cohere-v2': 40,\n fireworks: 35,\n perplexity: 30,\n 'claude-code': 15,\n ollama: 10,\n };\n initProviders();\n return Array.from(providers.keys())\n .filter(name => name !== excludeProvider)\n .sort((a, b) => (priority[b] ?? 25) - (priority[a] ?? 25));\n}\n\n// ── Chain-of-thought stripping ──────────────────────────────────\n// Some local models (qwen, glm, deepseek, etc.) leak their internal\n// reasoning into the response. This runs on EVERY chat() response so\n// no consumer (FB posts, Messenger, comments, web chat) ever sees it.\n\nfunction stripThinkingFromResponse(text: string): string {\n let cleaned = text;\n\n // 1. Remove <think>...</think> blocks (deepseek, qwen thinking mode)\n cleaned = cleaned.replace(/<think>[\\s\\S]*?<\\/think>/gi, '');\n\n // 2. Remove ```thinking ... ``` blocks\n cleaned = cleaned.replace(/```thinking[\\s\\S]*?```/gi, '');\n\n // 3. Cut at \"multiple draft\" boundaries — models often generate several\n // versions inline: \"Let me try another version:\", \"Here's another:\", etc.\n const draftBoundary = /\\n+[\"']?\\n*(Let me (try|make|write|do|craft)|Here'?s? (another|a better|a more)|Another (version|option|take|attempt)|Or (maybe|how about|alternatively)|Version \\d|Option \\d|Draft \\d|---)/i;\n const draftMatch = cleaned.match(draftBoundary);\n if (draftMatch?.index !== undefined && draftMatch.index > 20) {\n cleaned = cleaned.slice(0, draftMatch.index);\n }\n\n // 4. If the response starts with meta-reasoning, extract just the reply\n const reasoningStart = /^(The user wants|The comment|I need to|I should|Let me (think|craft|write|consider|analyze)|OK so|Alright,|Hmm,|This is a)/i;\n if (reasoningStart.test(cleaned.trim())) {\n const parts = cleaned.split(/\\n{2,}|^---$/m);\n const replyParts = parts.filter(p => {\n const trimmed = p.trim();\n if (!trimmed) return false;\n if (reasoningStart.test(trimmed)) return false;\n if (/^(Wait|Actually|But |So |Since |That works|That's about|Let me (count|check|think|try))/i.test(trimmed)) return false;\n if (/\\b(characters|under \\d+ char|personality|mentioned|the rules)\\b/i.test(trimmed)) return false;\n return true;\n });\n if (replyParts.length > 0) {\n cleaned = replyParts.join('\\n\\n');\n }\n }\n\n // 5. Remove wrapping quotes that some models add\n cleaned = cleaned.trim().replace(/^[\"']|[\"']$/g, '').trim();\n\n return cleaned;\n}\n\n// ── Provider name normalization ─────────────────────────────────\nconst PROVIDER_ALIASES: Record<string, string> = {\n 'z.ai': 'xai',\n 'zai': 'xai',\n 'grok': 'xai',\n 'local': 'ollama',\n 'vertex': 'google',\n 'vertex-ai': 'google',\n 'azure-openai': 'azure',\n 'aws': 'bedrock',\n 'amazon': 'bedrock',\n 'litellm-proxy': 'litellm',\n 'hf': 'huggingface',\n 'hugging-face': 'huggingface',\n '01ai': 'yi',\n '01.ai': 'yi',\n 'glm': 'zhipu',\n 'bigmodel': 'zhipu',\n 'pi': 'inflection',\n 'octoai': 'octo',\n 'nim': 'nvidia',\n 'nvidia-nim': 'nvidia',\n};\n\n/** Normalize provider names for consistency (e.g. \"grok\" → \"xai\", \"local\" → \"ollama\") */\nexport function normalizeProvider(name: string): string {\n const lower = name.toLowerCase();\n return PROVIDER_ALIASES[lower] || lower;\n}\n\n/** Provider registry */\nconst providers: Map<string, LLMProvider> = new Map();\nlet initialized = false;\n\nfunction initProviders(): void {\n if (initialized) return;\n // Core providers (custom implementations)\n providers.set('anthropic', new AnthropicProvider());\n providers.set('openai', new OpenAIProvider());\n providers.set('google', new GoogleProvider());\n providers.set('ollama', new OllamaProvider());\n providers.set('claude-code', new ClaudeCodeProvider());\n // OpenAI-compatible providers (Groq, Mistral, OpenRouter, xAI, etc.)\n for (const preset of PROVIDER_PRESETS) {\n providers.set(preset.name, new OpenAICompatProvider(preset));\n }\n initialized = true;\n}\n\n/** Get a provider by name */\nexport function getProvider(name: string): LLMProvider | undefined {\n initProviders();\n return providers.get(name);\n}\n\n/** Get all registered providers */\nexport function getAllProviders(): Map<string, LLMProvider> {\n initProviders();\n return providers;\n}\n\n/** Resolve a model alias (e.g. \"fast\" → \"openai/gpt-4o-mini\") */\nfunction resolveAlias(modelId: string): string {\n const config = loadConfig();\n const aliases = config.agent.modelAliases;\n if (aliases && aliases[modelId]) {\n const resolved = aliases[modelId];\n logger.debug(COMPONENT, `Alias \"${modelId}\" → \"${resolved}\"`);\n return resolved;\n }\n return modelId;\n}\n\n\n/** Resolve the provider and model from a model ID like \"anthropic/claude-3\" or alias like \"fast\" */\nexport function resolveModel(modelId: string): { provider: LLMProvider; model: string } {\n initProviders();\n // First resolve aliases\n const resolved = resolveAlias(modelId);\n const { provider: rawProviderName, model } = LLMProvider.parseModelId(resolved);\n\n\n // Normalize provider name (e.g. \"grok\" → \"xai\", \"local\" → \"ollama\")\n const providerName = normalizeProvider(rawProviderName);\n const provider = providers.get(providerName);\n if (!provider) {\n throw new Error(`Unknown provider: ${providerName}. Available: ${Array.from(providers.keys()).join(', ')}`);\n }\n return { provider, model };\n}\n\n/**\n * Non-throwing variant of resolveModel — returns null on an unknown\n * provider instead of throwing. Used by gateway endpoints to fail-fast\n * with a helpful 400 BEFORE the agent loop builds the prompt and burns\n * tokens. v5.5.30+. Bug from 2026-05-08 audit: requests with bad model\n * IDs (e.g. typoed providers) used to crash deep inside the agent loop\n * after prompt assembly, returning 500 with a stack trace.\n */\nexport function tryResolveModel(modelId: string): { provider: LLMProvider; model: string } | null {\n try { return resolveModel(modelId); } catch { return null; }\n}\n\n/** List of provider names known to this gateway (for \"did you mean\" suggestions). */\nexport function getKnownProviderNames(): string[] {\n initProviders();\n return Array.from(providers.keys()).sort();\n}\n\n/** Check if a model is allowed by the allowlist. Empty list = all allowed. */\nexport function isModelAllowed(modelId: string): boolean {\n const config = loadConfig();\n const allowedModels = config.agent.allowedModels;\n if (!allowedModels || allowedModels.length === 0) return true;\n\n // Resolve alias first\n const resolved = resolveAlias(modelId);\n\n for (const pattern of allowedModels) {\n if (pattern === resolved) return true;\n // Wildcard support: \"openai/*\" matches \"openai/gpt-4o\"\n if (pattern.endsWith('/*')) {\n const prefix = pattern.slice(0, -1); // \"openai/\"\n if (resolved.startsWith(prefix)) return true;\n }\n }\n return false;\n}\n\n/** Discovered model info */\nexport interface DiscoveredModel {\n id: string; // Full ID e.g. \"ollama/llama3.1\"\n provider: string; // Provider name e.g. \"ollama\"\n model: string; // Model name e.g. \"llama3.1\"\n displayName: string; // Provider display name e.g. \"Ollama (Local)\"\n source: 'static' | 'live'; // Whether discovered via live API or hardcoded list\n /** True if the provider has the credentials it needs to actually serve a request for this model. */\n keyConfigured: boolean;\n}\n\n/** Cache for discovered models (refreshed on demand, 60s TTL) */\nlet modelCache: { models: DiscoveredModel[]; timestamp: number } | null = null;\nconst MODEL_CACHE_TTL = 60_000; // 60 seconds\n\n/**\n * Discover all available models across all providers.\n * Queries each provider's listModels() — for Ollama this hits the local API\n * to find actually-installed models. Results are cached for 60s.\n */\nexport async function discoverAllModels(forceRefresh = false): Promise<DiscoveredModel[]> {\n initProviders();\n\n if (!forceRefresh && modelCache && (Date.now() - modelCache.timestamp) < MODEL_CACHE_TTL) {\n return modelCache.models;\n }\n\n const discovered: DiscoveredModel[] = [];\n const health = await healthCheckAll();\n\n const tasks = Array.from(providers.entries()).map(async ([name, provider]) => {\n try {\n const models = await provider.listModels();\n const isLive = health[name] === true;\n const keyConfigured = provider.isConfigured();\n for (const model of models) {\n discovered.push({\n id: `${name}/${model}`,\n provider: name,\n model,\n displayName: provider.displayName,\n source: (name === 'ollama' && isLive) ? 'live' : 'static',\n keyConfigured,\n });\n }\n } catch (err) {\n logger.debug(COMPONENT, `Failed to list models for ${name}: ${(err as Error).message}`);\n }\n });\n\n await Promise.all(tasks);\n\n modelCache = { models: discovered, timestamp: Date.now() };\n logger.info(COMPONENT, `Discovered ${discovered.length} models across ${providers.size} providers`);\n return discovered;\n}\n\n/** Get current model aliases from config */\nexport function getModelAliases(): Record<string, string> {\n const config = loadConfig();\n return config.agent.modelAliases || {};\n}\n\n// ── Circuit Breaker ─────────────────────────────────────────────\n/** Circuit breaker states for each provider */\ntype CircuitState = 'closed' | 'open' | 'half-open';\n\ninterface CircuitBreakerState {\n state: CircuitState;\n failureCount: number;\n lastFailureTime: number | null;\n lastSuccessTime: number | null;\n openSince: number | null;\n}\n\n/** Circuit breaker configuration — tuned for cloud model tolerance */\nconst CIRCUIT_BREAKER_CONFIG = {\n failureThreshold: 8, // Number of failures before opening circuit (was 5 — too aggressive for cloud)\n resetTimeout: 60000, // 60s before trying again (was 30s — cloud models need recovery time)\n monitoringWindow: 120000, // 120s window for counting failures (was 60s — cloud latency spikes are normal)\n successThreshold: 2, // Successes needed in half-open to close circuit (was 3)\n};\n\n/** Track circuit breaker state per provider */\nconst circuitBreakers = new Map<string, CircuitBreakerState>();\n\n// Prune stale closed circuit breakers every 5 minutes to prevent unbounded growth\nsetInterval(() => {\n const now = Date.now();\n for (const [name, state] of circuitBreakers) {\n if (state.state === 'closed' && state.lastFailureTime && now - state.lastFailureTime > 600_000) {\n circuitBreakers.delete(name);\n }\n }\n}, 300_000);\n\n\n/**\n * G2: Cooldown-aware probe throttling (OpenClaw pattern).\n * When a provider is rate-limited, don't probe it again for MIN_PROBE_INTERVAL_MS.\n * Prevents cascade failures during provider outages.\n */\nconst MIN_PROBE_INTERVAL_MS = 30000; // 30s between probes\nconst providerRateLimitCooldowns = new Map<string, number>(); // provider → timestamp of last rate-limit\n\n/** Record that a provider returned a rate-limit error */\nfunction recordRateLimitCooldown(providerName: string): void {\n providerRateLimitCooldowns.set(providerName, Date.now());\n}\n\n/** Check if a provider is still in its rate-limit cooldown window */\nfunction isInRateLimitCooldown(providerName: string): boolean {\n const lastRateLimit = providerRateLimitCooldowns.get(providerName);\n if (!lastRateLimit) return false;\n const elapsed = Date.now() - lastRateLimit;\n if (elapsed >= MIN_PROBE_INTERVAL_MS) {\n providerRateLimitCooldowns.delete(providerName); // Cooldown expired\n return false;\n }\n return true;\n}\n\n/**\n * Get or create circuit breaker state for a provider.\n */\nfunction getCircuitBreaker(providerName: string): CircuitBreakerState {\n if (!circuitBreakers.has(providerName)) {\n circuitBreakers.set(providerName, {\n state: 'closed',\n failureCount: 0,\n lastFailureTime: null,\n lastSuccessTime: null,\n openSince: null,\n });\n }\n return circuitBreakers.get(providerName)!;\n}\n\n/**\n * Record a successful request for a provider.\n * Resets failure count and updates state appropriately.\n */\nfunction recordSuccess(providerName: string): void {\n const cb = getCircuitBreaker(providerName);\n cb.lastSuccessTime = Date.now();\n\n if (cb.state === 'half-open') {\n // In half-open state, success reduces the counter\n cb.failureCount = Math.max(0, cb.failureCount - 1);\n // If we've had enough successes, close the circuit\n if (cb.failureCount <= 0) {\n cb.state = 'closed';\n cb.openSince = null;\n cb.failureCount = 0;\n logger.info(COMPONENT, `[CircuitBreaker] ${providerName} circuit CLOSED after successful recovery`);\n }\n } else if (cb.state === 'closed') {\n // In closed state, reset the failure count on success\n cb.failureCount = 0;\n }\n}\n\n/**\n * Record a failed request for a provider.\n * Opens circuit if failure threshold is exceeded.\n */\nfunction recordFailure(providerName: string): void {\n const cb = getCircuitBreaker(providerName);\n const now = Date.now();\n cb.lastFailureTime = now;\n\n // Only count failures within the monitoring window\n const windowStart = now - CIRCUIT_BREAKER_CONFIG.monitoringWindow;\n if (cb.lastFailureTime && cb.lastFailureTime < windowStart) {\n // Reset if outside monitoring window\n cb.failureCount = 1;\n } else {\n cb.failureCount++;\n }\n\n // Check if we should open the circuit\n if (cb.failureCount >= CIRCUIT_BREAKER_CONFIG.failureThreshold && cb.state === 'closed') {\n cb.state = 'open';\n cb.openSince = now;\n logger.warn(COMPONENT, `[CircuitBreaker] ${providerName} circuit OPENED after ${cb.failureCount} failures`);\n }\n}\n\n/**\n * Check if a provider's circuit breaker allows requests.\n * Returns true if closed or if half-open (time to test).\n * Returns false if open and still in timeout period.\n */\nfunction canRequest(providerName: string, isFallbackProbe = false): boolean {\n // G2: Rate-limit cooldown only blocks FALLBACK probes, not primary model retries.\n // Primary model has its own backoff logic — don't double-gate it.\n if (isFallbackProbe && isInRateLimitCooldown(providerName)) {\n logger.debug(COMPONENT, `[RateLimitCooldown] ${providerName} still cooling down — skipping fallback probe`);\n return false;\n }\n\n const cb = getCircuitBreaker(providerName);\n\n if (cb.state === 'closed') {\n return true;\n }\n\n if (cb.state === 'open') {\n const now = Date.now();\n if (cb.openSince && (now - cb.openSince) >= CIRCUIT_BREAKER_CONFIG.resetTimeout) {\n // Timeout expired, transition to half-open\n cb.state = 'half-open';\n cb.failureCount = CIRCUIT_BREAKER_CONFIG.successThreshold; // Need this many successes to close\n logger.info(COMPONENT, `[CircuitBreaker] ${providerName} circuit transitioned to HALF-OPEN (testing)`);\n return true;\n }\n return false; // Still open, don't try\n }\n\n // half-open: allow testing\n return true;\n}\n\n/**\n * Get circuit breaker status for all providers (for health dashboards).\n */\nexport function getCircuitBreakerStatus(): Record<string, { state: CircuitState; failureCount: number; openSince?: number }> {\n const status: Record<string, { state: CircuitState; failureCount: number; openSince?: number }> = {};\n for (const [providerName, cb] of circuitBreakers) {\n status[providerName] = {\n state: cb.state,\n failureCount: cb.failureCount,\n ...(cb.openSince !== null ? { openSince: cb.openSince } : {}),\n };\n }\n return status;\n}\n\n/**\n * Reset all circuit breaker state (for testing).\n * NOT exported to production API - test use only.\n */\nexport function __resetCircuitBreakers__(): void {\n circuitBreakers.clear();\n lastFallbackEvent = null;\n}\n\nexport function resetCircuitBreaker(providerName: string): void {\n const cb = circuitBreakers.get(providerName);\n if (cb) {\n cb.state = 'closed';\n cb.failureCount = 0;\n cb.openSince = null;\n }\n}\n\n// ── Fallback chain state ─────────────────────────────────────────\n/** Tracks the most recent fallback event for dashboard display */\nlet lastFallbackEvent: { primary: string; active: string; reason: string; timestamp: number } | null = null;\n\n/** Get the current fallback state (for dashboard display) */\nexport function getFallbackState(): { primary: string; active: string; reason: string; timestamp: number } | null {\n // Expire after 5 minutes\n if (lastFallbackEvent && (Date.now() - lastFallbackEvent.timestamp) > 300_000) {\n lastFallbackEvent = null;\n }\n return lastFallbackEvent;\n}\n\n/** Retry configuration with exponential backoff */\nconst RETRY_CONFIG = {\n maxRetries: 4, // 4 retries (was 3) — cloud APIs need more chances\n initialDelayMs: 1500, // 1.5s initial (was 1s) — give cloud APIs breathing room\n maxDelayMs: 45000, // 45s cap (was 30s) — cloud models can take longer to recover\n backoffMultiplier: 2,\n jitter: true,\n};\n\n/**\n * Monotonic counter seed for decorrelated jitter. Without this, two retries\n * triggered in the same millisecond can receive identical Math.random() values\n * if V8 happens to share a seed under load — that's exactly the thundering\n * herd we're trying to avoid.\n */\nlet _jitterCounter = 0;\n\n/**\n * Calculate delay with exponential backoff + asymmetric additive jitter.\n *\n * Ported from Hermes `agent/retry_utils.py:jittered_backoff` — proven to\n * decorrelate concurrent retries across multiple sessions hitting the same\n * rate-limited provider simultaneously.\n *\n * Formula:\n * base_delay = min(initial * multiplier^attempt, max)\n * jitter = random_uniform(0, jitter_ratio * base_delay)\n * final = base_delay + jitter\n *\n * Key difference from the previous TITAN implementation:\n * - Old: jitter was ±20% centered on base (could reduce delay below base)\n * - New: jitter is 0..+50% of base (only extends delay, never shortens)\n * This matters for rate-limit recovery — we never want to retry EARLIER than\n * the exponential schedule intended.\n *\n * The counter-seeded PRNG guarantees two concurrent retries get different\n * jitter values even in the same millisecond.\n */\nfunction calculateBackoffDelay(attempt: number): number {\n const exponentialDelay = RETRY_CONFIG.initialDelayMs * Math.pow(RETRY_CONFIG.backoffMultiplier, attempt);\n const cappedDelay = Math.min(exponentialDelay, RETRY_CONFIG.maxDelayMs);\n\n if (!RETRY_CONFIG.jitter) return cappedDelay;\n\n // Counter-seeded jitter — decorrelates concurrent callers.\n _jitterCounter = (_jitterCounter + 1) >>> 0;\n const seed = (Date.now() ^ (_jitterCounter * 0x9e3779b9)) >>> 0;\n // Simple xorshift from the seed — fast, good enough for jitter.\n let s = seed || 1;\n s ^= s << 13; s >>>= 0;\n s ^= s >>> 17;\n s ^= s << 5; s >>>= 0;\n const rand01 = (s >>> 0) / 0xffffffff; // [0, 1)\n\n const jitterRatio = 0.5; // up to +50% of base\n const jitter = rand01 * jitterRatio * cappedDelay;\n return cappedDelay + jitter;\n}\n\n/** Parse retry-after header value (seconds or HTTP date) */\nfunction parseRetryAfter(header: string | null): number | null {\n if (!header) return null;\n\n // Try parsing as seconds\n const seconds = parseInt(header, 10);\n if (!isNaN(seconds)) {\n return Math.min(seconds * 1000, RETRY_CONFIG.maxDelayMs); // Cap at max delay\n }\n\n // Try parsing as HTTP date\n const date = new Date(header);\n if (!isNaN(date.getTime())) {\n const delay = date.getTime() - Date.now();\n return Math.max(1000, Math.min(delay, RETRY_CONFIG.maxDelayMs)); // Min 1s, max configured cap\n }\n\n return null;\n}\n\n/**\n * Check if an error is retryable using the centralized error taxonomy.\n */\nfunction isRetryableError(error: unknown): boolean {\n return classifyProviderError(error).retryable;\n}\n\n/**\n * Extract HTTP status code from an error object if present.\n */\nfunction getErrorStatus(error: unknown): number | undefined {\n return classifyProviderError(error).httpStatus;\n}\n\n/** Try the fallback chain for a chat request. Returns null if chain is empty or exhausted. */\nasync function tryFallbackChain(\n options: ChatOptions,\n primaryModelId: string,\n originalError: Error,\n): Promise<ChatResponse | null> {\n const config = loadConfig();\n const chain = config.agent.fallbackChain;\n if (!chain || chain.length === 0) return null;\n\n const maxRetries = config.agent.fallbackMaxRetries ?? 3;\n let attempts = 0;\n\n for (const fallbackModelId of chain) {\n if (attempts >= maxRetries) break;\n if (fallbackModelId === primaryModelId) continue;\n\n attempts++;\n try {\n const { provider: fbProvider, model: fbModel } = resolveModel(fallbackModelId);\n const fbProviderName = fbProvider.name;\n\n // Check circuit breaker + rate-limit cooldown for fallback provider\n if (!canRequest(fbProviderName, true)) {\n const cb = getCircuitBreaker(fbProviderName);\n logger.warn(COMPONENT, `Skipping fallback ${fallbackModelId} — circuit breaker OPEN (${cb.failureCount} failures)`);\n continue;\n }\n\n logger.warn(COMPONENT, `Model ${primaryModelId} failed (${originalError.message}), falling back to ${fallbackModelId}`);\n const result = await fbProvider.chat({ ...options, model: fbModel });\n\n // Record success for circuit breaker\n recordSuccess(fbProviderName);\n\n lastFallbackEvent = {\n primary: primaryModelId,\n active: fallbackModelId,\n reason: originalError.message,\n timestamp: Date.now(),\n };\n return result;\n } catch (chainErr) {\n // Record failure for circuit breaker\n try {\n const { provider: fbProvider } = resolveModel(fallbackModelId);\n recordFailure(fbProvider.name);\n } catch {\n // Ignore if we can't resolve the provider for recording\n }\n logger.warn(COMPONENT, `Fallback model ${fallbackModelId} also failed: ${(chainErr as Error).message}`);\n continue;\n }\n }\n return null;\n}\n\n/**\n * Try the fallback chain for a streaming request. Returns an async generator\n * or null if no fallback could be attempted.\n *\n * Circuit-breaker accounting (fix for Phase X / streaming optimism bug):\n * The pre-fix version called `recordSuccess(fbProviderName)` immediately\n * after acquiring the generator — *before* a single chunk was emitted.\n * That meant a fallback provider that opened a stream and then errored\n * mid-flight was recorded as a success, lying to the breaker.\n *\n * This version returns a wrapped generator that:\n * - records success only after a `done` chunk OR the underlying\n * generator completes without throwing (real outcome)\n * - records failure if the underlying stream throws or yields an\n * `error` chunk after the first chunk\n */\nasync function tryFallbackChainStream(\n options: ChatOptions,\n primaryModelId: string,\n originalError: Error,\n): Promise<AsyncGenerator<ChatStreamChunk> | null> {\n const config = loadConfig();\n const chain = config.agent.fallbackChain;\n if (!chain || chain.length === 0) return null;\n\n const maxRetries = config.agent.fallbackMaxRetries ?? 3;\n let attempts = 0;\n\n for (const fallbackModelId of chain) {\n if (attempts >= maxRetries) break;\n if (fallbackModelId === primaryModelId) continue;\n\n attempts++;\n let fbProviderName: string;\n let gen: AsyncGenerator<ChatStreamChunk>;\n\n try {\n const { provider: fbProvider, model: fbModel } = resolveModel(fallbackModelId);\n fbProviderName = fbProvider.name;\n\n // Check circuit breaker + rate-limit cooldown for fallback provider\n if (!canRequest(fbProviderName, true)) {\n const cb = getCircuitBreaker(fbProviderName);\n logger.warn(COMPONENT, `Skipping stream fallback ${fallbackModelId} — circuit breaker OPEN (${cb.failureCount} failures)`);\n continue;\n }\n\n logger.warn(COMPONENT, `Stream model ${primaryModelId} failed (${originalError.message}), falling back to ${fallbackModelId}`);\n gen = fbProvider.chatStream({ ...options, model: fbModel });\n } catch (chainErr) {\n // Setup failure (resolveModel threw, etc.) — record breaker failure\n try {\n const { provider: fbProvider } = resolveModel(fallbackModelId);\n recordFailure(fbProvider.name);\n } catch {\n // Ignore if we can't resolve the provider for recording\n }\n logger.warn(COMPONENT, `Fallback stream model ${fallbackModelId} setup failed: ${(chainErr as Error).message}`);\n continue;\n }\n\n lastFallbackEvent = {\n primary: primaryModelId,\n active: fallbackModelId,\n reason: originalError.message,\n timestamp: Date.now(),\n };\n\n return monitorStreamForBreaker(gen, fbProviderName);\n }\n return null;\n}\n\n/**\n * Wrap a chat stream so circuit-breaker bookkeeping reflects real outcomes —\n * success only after a clean stream end, failure on error chunks or thrown\n * errors mid-stream. Hoisted to module scope so ESLint's `no-inner-declarations`\n * is happy and so the same wrapper can be reused by chatStream's priority\n * failover path below.\n */\nasync function* monitorStreamForBreaker(\n inner: AsyncGenerator<ChatStreamChunk>,\n providerName: string,\n): AsyncGenerator<ChatStreamChunk> {\n let recorded = false;\n try {\n for await (const chunk of inner) {\n if (chunk.type === 'error') {\n if (!recorded) { recordFailure(providerName); recorded = true; }\n }\n yield chunk;\n }\n if (!recorded) recordSuccess(providerName);\n } catch (innerErr) {\n if (!recorded) { recordFailure(providerName); recorded = true; }\n throw innerErr;\n }\n}\n\n/** Route a chat request to a mesh peer */\nasync function meshChat(peer: MeshPeer, modelId: string, message: string): Promise<ChatResponse> {\n const requestId = randomBytes(8).toString('hex');\n const config = loadConfig();\n const timeoutMs = config.mesh?.taskTimeoutMs || 120_000;\n logger.info(COMPONENT, `Routing \"${modelId}\" to mesh peer ${peer.hostname} (${peer.nodeId.slice(0, 8)}...)`);\n const result = await routeTaskToNode(peer.nodeId, requestId, message, modelId, timeoutMs) as Record<string, unknown>;\n if (result.error) {\n throw new Error(`Mesh peer error: ${result.error}`);\n }\n // Fire-and-forget analytics\n (async () => {\n const { trackModelUsage } = await import('../analytics/featureTracker.js');\n trackModelUsage(modelId, 'mesh', true);\n })().catch(() => {});\n return result as unknown as ChatResponse;\n}\n\n/**\n * Enhanced error message with provider and model context.\n */\nfunction createEnhancedErrorMessage(error: Error, providerName: string, model: string, attempt: number): string {\n const status = getErrorStatus(error);\n const statusInfo = status ? `[HTTP ${status}] ` : '';\n\n return [\n `Provider ${providerName}/${model} failed`,\n statusInfo + error.message,\n attempt > 0 ? `(attempt ${attempt + 1})` : null,\n ].filter(Boolean).join(': ');\n}\n\n/**\n * Send a chat request with exponential backoff retry and circuit breaker protection.\n * Automatically routes to the correct provider with error recovery and fallback chain.\n */\nexport async function chat(options: ChatOptions): Promise<ChatResponse> {\n const modelId = options.model || 'anthropic/claude-sonnet-4-20250514';\n const { provider, model } = resolveModel(modelId);\n const providerName = provider.name;\n\n logger.info(COMPONENT, `Routing to ${provider.displayName} (model: ${model})`);\n\n // Fail-fast: reject before the circuit breaker if the provider has no\n // configured credentials. Without this guard, picking a model from a\n // provider you haven't configured a key for sends N requests that can\n // never succeed, trips the circuit breaker, and locks the provider out\n // for the reset window. (Real incident, 2026-05-10: openrouter circuit\n // tripped after 8 failures because OPENROUTER_API_KEY wasn't set.)\n if (!provider.isConfigured()) {\n const errorMsg = `Provider ${providerName} has no API key configured. Set ${\n providerName.toUpperCase().replace(/-/g, '_')\n }_API_KEY in env or via Settings → Integrations to use ${providerName} models.`;\n logger.warn(COMPONENT, errorMsg);\n const enhancedError = new Error(errorMsg);\n Object.assign(enhancedError, { status: 401, provider: providerName, model, missingKey: true });\n throw enhancedError;\n }\n\n // G4: Track fallback attempts for structured error reporting (OpenClaw pattern)\n const fallbackAttempts: Array<{ provider: string; model: string; error: string; reason: string }> = [];\n\n // Check circuit breaker before attempting request\n if (!canRequest(providerName)) {\n const cb = getCircuitBreaker(providerName);\n const errorMsg = `Circuit breaker OPEN for ${providerName}/${model} (${cb.failureCount} failures, reset in ${\n cb.openSince ? Math.round((CIRCUIT_BREAKER_CONFIG.resetTimeout - (Date.now() - cb.openSince)) / 1000) : 'unknown'\n }s)`;\n logger.warn(COMPONENT, errorMsg);\n const enhancedError = new Error(errorMsg);\n Object.assign(enhancedError, { status: 503, provider: providerName, model });\n throw enhancedError;\n }\n\n let lastError: Error | null = null;\n const maxRetries = RETRY_CONFIG.maxRetries;\n\n // Gap 1 (plan-this-logical-ocean): one-shot compression on CONTEXT_OVERFLOW.\n // The error taxonomy classifies overflows and sets `shouldCompress: true`,\n // but nothing used to act on it — the hint was dead code. Now we compact\n // options.messages via buildSmartContext and retry the SAME provider once\n // before falling through to model fallback / cross-provider failover.\n let compressionRetried = false;\n let thinkingStripped = false;\n\n // v4.13 ancestor-extraction (Hermes rate_limit_tracker): proactive backoff\n // before even sending the request. If the last response from this provider\n // indicated the quota window is nearly depleted, hold off briefly instead\n // of firing the request and getting a 429.\n try {\n const backoff = shouldBackOff(providerName);\n if (backoff) {\n logger.info(COMPONENT, `[RateLimit] Proactive backoff on ${providerName}: ${backoff.reason} — waiting ${Math.round(backoff.backoffMs)}ms`);\n await sleep(backoff.backoffMs);\n }\n } catch { /* never block on tracker errors */ }\n\n // Attempt request with retry logic\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n try {\n const result = await provider.chat({ ...options, model });\n\n // Strip chain-of-thought leakage from model responses\n if (result.content) {\n result.content = stripThinkingFromResponse(result.content);\n }\n\n // Record success for circuit breaker\n recordSuccess(providerName);\n lastFallbackEvent = null; // Clear fallback state on primary success\n\n // Log if this was a retry that succeeded\n if (attempt > 0) {\n logger.info(COMPONENT, `${provider.displayName}/${model} recovered after ${attempt} retry attempt(s)`);\n }\n\n // Fire-and-forget analytics\n (async () => {\n const { trackModelUsage } = await import('../analytics/featureTracker.js');\n trackModelUsage(model, providerName, true);\n })().catch(() => {});\n\n return result;\n } catch (error) {\n lastError = error as Error;\n\n // Classify error using centralized taxonomy\n const classified = classifyProviderError(error);\n\n // Only affect circuit breaker for genuine provider instability\n if (shouldAffectCircuitBreaker(classified)) {\n recordFailure(providerName);\n }\n\n // noFallback: caller (e.g. ModelProbe) requires the target model to\n // answer or the request to fail cleanly. Skip retries, fallback\n // chain, mesh routing, and provider failover entirely — otherwise\n // we would silently probe a different model and poison the caller's\n // data with unrelated capabilities.\n if (options.noFallback) {\n const errorMsg = createEnhancedErrorMessage(error as Error, providerName, model, attempt);\n const noFallbackError = new Error(\n `Probe target ${providerName}/${model} unreachable (noFallback=true): ${errorMsg}`\n );\n Object.assign(noFallbackError, {\n status: classified.httpStatus,\n provider: providerName,\n model,\n cause: error,\n failoverReason: classified.reason,\n noFallback: true,\n });\n throw noFallbackError;\n }\n\n // G2: Record rate-limit cooldown to prevent probe hammering\n if (classified.reason === FailoverReason.RATE_LIMIT) {\n recordRateLimitCooldown(providerName);\n }\n\n // Exhaust credential in pool if rotation is recommended\n if (classified.shouldRotateCredential) {\n const pool = getExistingPool(providerName);\n if (pool) {\n // Find which credential was used and exhaust it\n const status = pool.status();\n const lastUsed = status.find(s => s.available);\n if (lastUsed) {\n pool.exhaust(lastUsed.name, classified.cooldownMs || 60000);\n }\n }\n }\n\n // Gap 1: act on shouldCompress hint BEFORE generic retry/fallback.\n // On CONTEXT_OVERFLOW (or any future reason that sets shouldCompress),\n // compact options.messages via buildSmartContext and retry the same\n // provider+model once. Only fires on the FIRST such error per call —\n // if the compacted request still overflows, we drop through to the\n // normal retry/fallback ladder instead of shrinking forever.\n if (classified.shouldCompress && !compressionRetried && Array.isArray(options.messages)) {\n compressionRetried = true;\n const beforeCount = options.messages.length;\n // Conservative target — most of the whitelisted Ollama cloud\n // models have >=32K context; 24K leaves headroom for the\n // completion itself and any tool schemas the provider adds.\n const compactTokens = 24000;\n try {\n const compacted = buildSmartContext(options.messages, compactTokens);\n if (compacted.length > 0 && compacted.length <= beforeCount) {\n options = { ...options, messages: compacted };\n logger.info(\n COMPONENT,\n `[Router] ${classified.reason} — compacted context ${beforeCount}→${compacted.length} msgs, retrying ${providerName}/${model}`,\n );\n // Retry immediately — no backoff needed, we changed the input\n continue;\n }\n logger.warn(COMPONENT, `[Router] Compression produced empty/larger output — skipping compress retry`);\n } catch (compErr) {\n logger.warn(COMPONENT, `[Router] Compression failed: ${(compErr as Error).message} — falling through`);\n }\n }\n\n // Gap 2: act on THINKING_NOT_SUPPORTED — strip thinking options and retry\n // once on the same provider. This handles models like titan-qwen3.5:4b\n // that return HTTP 400 \"does not support thinking\". We mutate options only\n // once so a second THINKING_NOT_SUPPORTED falls through to normal retry ladder.\n if (classified.reason === FailoverReason.THINKING_NOT_SUPPORTED && !thinkingStripped) {\n thinkingStripped = true;\n const providerOpts = options.providerOptions ? { ...options.providerOptions } : {};\n // Remove Ollama/OpenAI-compat thinking keys\n delete (providerOpts as Record<string, unknown>).think;\n delete (providerOpts as Record<string, unknown>).thinking;\n delete (providerOpts as Record<string, unknown>).thinking_mode;\n delete (providerOpts as Record<string, unknown>).budget_tokens;\n delete (providerOpts as Record<string, unknown>).enable_thinking;\n options = { ...options, providerOptions: providerOpts };\n logger.info(COMPONENT, `[Router] THINKING_NOT_SUPPORTED — stripped thinking flags, retrying ${providerName}/${model}`);\n continue;\n }\n\n const errorMsg = createEnhancedErrorMessage(error as Error, providerName, model, attempt);\n\n // Check if we should retry\n if (classified.retryable && attempt < maxRetries) {\n // Use taxonomy cooldown or calculate backoff, whichever is larger\n let retryDelayMs = Math.max(classified.cooldownMs, calculateBackoffDelay(attempt));\n\n // Hunt Finding #37 (2026-04-14): previous code tried\n // `(error as Response)?.headers?.get?.('Retry-After')` which\n // always returned undefined at runtime because the error is\n // an Error object, not a Response. Retry-After headers were\n // never actually respected. Providers now attach retryAfterMs\n // to the thrown error via createProviderError().\n const errWithHints = error as { retryAfterMs?: number | null; headers?: { get?(k: string): string | null } };\n if (typeof errWithHints.retryAfterMs === 'number' && errWithHints.retryAfterMs > 0) {\n retryDelayMs = errWithHints.retryAfterMs;\n logger.info(COMPONENT, `[RateLimit] Respecting Retry-After: ${Math.round(retryDelayMs / 1000)}s`);\n } else {\n // Back-compat: old-style error that happens to wrap a Response\n const retryAfter = errWithHints.headers?.get?.('Retry-After');\n if (retryAfter) {\n const parsed = parseRetryAfter(retryAfter);\n if (parsed !== null) {\n retryDelayMs = parsed;\n logger.info(COMPONENT, `[RateLimit] Respecting Retry-After (legacy): ${Math.round(retryDelayMs / 1000)}s`);\n }\n }\n }\n\n logger.warn(COMPONENT, `${errorMsg} [${classified.reason}] — retrying in ${Math.round(retryDelayMs)}ms`);\n await sleep(retryDelayMs);\n continue;\n }\n\n // Not retryable or max retries exceeded\n if (!classified.retryable) {\n logger.error(COMPONENT, `${errorMsg} — not retryable [${classified.reason}] (${classified.httpStatus ? `HTTP ${classified.httpStatus}` : 'unknown error'})`);\n } else {\n logger.error(COMPONENT, `${errorMsg} — max retries (${maxRetries}) exceeded [${classified.reason}]`);\n }\n\n // Try configured fallback chain first (model-level fallback)\n if (classified.retryable || classified.shouldFallback) {\n const chainResult = await tryFallbackChain(options, modelId, error as Error);\n if (chainResult) {\n logger.info(COMPONENT, `Fallback chain recovered from ${providerName}/${model} failure [${classified.reason}]`);\n return chainResult;\n }\n }\n\n // Try mesh peers before local failover\n const config = loadConfig();\n if (config.mesh?.enabled) {\n const peer = findModelOnMesh(modelId);\n if (peer) {\n try {\n const message = Array.isArray(options.messages)\n ? options.messages.map(m => m.content).join('\\n')\n : (options as unknown as Record<string, unknown>).message as string || '';\n return await meshChat(peer, modelId, message);\n } catch (meshErr) {\n logger.warn(COMPONENT, `Mesh routing failed: ${(meshErr as Error).message}`);\n }\n }\n }\n\n // Attempt failover to other providers (only on first failure, not after retries)\n if (attempt === 0) {\n const failoverOrder = getFailoverOrder(providerName);\n for (const fallbackName of failoverOrder) {\n if (fallbackName === providerName) continue;\n\n // Check circuit breaker + rate-limit cooldown for fallback provider\n if (!canRequest(fallbackName, true)) {\n logger.debug(COMPONENT, `Skipping fallback ${fallbackName} — circuit breaker OPEN`);\n continue;\n }\n\n const fallback = providers.get(fallbackName);\n if (!fallback) continue;\n\n let fbModelName = 'unknown';\n try {\n const healthy = await fallback.healthCheck();\n if (!healthy) continue;\n\n const models = await fallback.listModels();\n if (models.length === 0) continue;\n\n // Prefer a model with a similar name prefix (e.g. claude-* → claude-*)\n const originalPrefix = model.split('-')[0];\n fbModelName = models.find(m => m.startsWith(originalPrefix)) || models[0];\n\n logger.warn(COMPONENT, `Failing over from ${providerName}/${model} → ${fallbackName}/${fbModelName}`);\n const result = await fallback.chat({ ...options, model: fbModelName });\n recordSuccess(fallbackName); // Record success for the fallback provider\n // Fire-and-forget analytics\n (async () => {\n const { trackModelUsage } = await import('../analytics/featureTracker.js');\n trackModelUsage(fbModelName, fallbackName, true);\n })().catch(() => {});\n return result;\n } catch (fallbackErr) {\n recordFailure(fallbackName); // Record failure for the fallback provider too\n // G4: Record fallback attempt for structured error chain\n fallbackAttempts.push({\n provider: fallbackName,\n model: fbModelName,\n error: (fallbackErr as Error).message,\n reason: classifyProviderError(fallbackErr).reason,\n });\n logger.warn(COMPONENT, `Fallback ${fallbackName} also failed: ${(fallbackErr as Error).message}`);\n continue;\n }\n }\n }\n\n // G4: Record the primary attempt too\n fallbackAttempts.unshift({\n provider: providerName,\n model,\n error: (error as Error).message,\n reason: classified.reason,\n });\n\n // Fire-and-forget analytics\n (async () => {\n const { trackModelUsage } = await import('../analytics/featureTracker.js');\n trackModelUsage(model, providerName, false);\n })().catch(() => {});\n\n // All recovery options exhausted, throw enhanced error\n const attemptSummary = fallbackAttempts.length > 1\n ? ` | Tried ${fallbackAttempts.length} providers: ${fallbackAttempts.map(a => `${a.provider}/${a.model} [${a.reason}]`).join(', ')}`\n : '';\n const finalError = new Error(`All providers failed: ${errorMsg}${attemptSummary}`);\n Object.assign(finalError, {\n status: classified.httpStatus,\n provider: providerName,\n model,\n cause: error,\n failoverReason: classified.reason,\n // G4: Structured fallback attempt chain (OpenClaw FallbackSummaryError pattern)\n fallbackAttempts,\n });\n throw finalError;\n }\n }\n\n // Should never reach here, but TypeScript requires it\n throw lastError || new Error(`Provider ${providerName}/${model} failed after all retries`);\n}\n\n/**\n * Send a streaming chat request with exponential backoff retry and circuit breaker protection.\n */\nexport async function* chatStream(options: ChatOptions): AsyncGenerator<ChatStreamChunk> {\n const modelId = options.model || 'anthropic/claude-sonnet-4-20250514';\n const { provider, model } = resolveModel(modelId);\n const providerName = provider.name;\n\n logger.info(COMPONENT, `Streaming via ${provider.displayName} (model: ${model})`);\n\n // Fail-fast: see chat() for full reasoning. Reject before the circuit\n // breaker if the provider has no configured credentials, so picking\n // an unconfigured model can't trip the breaker.\n if (!provider.isConfigured()) {\n const errorMsg = `Provider ${providerName} has no API key configured. Set ${\n providerName.toUpperCase().replace(/-/g, '_')\n }_API_KEY in env or via Settings → Integrations to use ${providerName} models.`;\n logger.warn(COMPONENT, errorMsg);\n yield { type: 'error', error: errorMsg };\n return;\n }\n\n // Check circuit breaker before attempting request\n if (!canRequest(providerName)) {\n const cb = getCircuitBreaker(providerName);\n yield {\n type: 'error',\n error: `[CircuitBreaker] Circuit OPEN: ${providerName}/${model} (${cb.failureCount} failures, testing in ${\n Math.round((CIRCUIT_BREAKER_CONFIG.resetTimeout - (Date.now() - cb.openSince!)) / 1000)\n }s)`,\n };\n return;\n }\n\n let lastError: Error | null = null;\n const maxRetries = RETRY_CONFIG.maxRetries;\n\n // Once-per-call latches so we don't repeat failover work after a retry\n // burst — both fallback paths can be reached on any exhausted-retry\n // attempt, but each is attempted at most once per chatStream invocation\n // (Task 4: prevent infinite-loop recovery, formerly attempt===0 gate).\n let fallbackChainAttempted = false;\n let priorityFailoverAttempted = false;\n\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n try {\n // Stream from provider — record success only on first non-error\n // chunk so we don't claim success for a stream that never produced.\n let recordedSuccess = false;\n for await (const chunk of provider.chatStream({ ...options, model })) {\n if (!recordedSuccess && chunk.type !== 'error' && attempt === 0) {\n recordSuccess(providerName);\n recordedSuccess = true;\n }\n lastFallbackEvent = null;\n yield chunk;\n }\n\n // Log if this was a retry that succeeded\n if (attempt > 0) {\n logger.info(COMPONENT, `${provider.displayName}/${model} stream recovered after ${attempt} retry attempt(s)`);\n }\n return;\n } catch (error) {\n lastError = error as Error;\n\n // Classify error using centralized taxonomy\n const classified = classifyProviderError(error);\n if (shouldAffectCircuitBreaker(classified)) {\n recordFailure(providerName);\n }\n\n const errorMsg = createEnhancedErrorMessage(error as Error, providerName, model, attempt);\n\n // Check if we should retry\n if (classified.retryable && attempt < maxRetries) {\n const retryDelayMs = Math.max(classified.cooldownMs, calculateBackoffDelay(attempt));\n logger.warn(COMPONENT, `${errorMsg} [${classified.reason}] — streaming retry in ${Math.round(retryDelayMs)}ms`);\n\n // Task 2: emit a dedicated `retry` event instead of leaking a\n // text chunk (e.g. \"[Retrying request (1/4) due to ...]\") into\n // the user-visible stream. UI consumers should display this\n // as a status indicator, never forward to the assistant message.\n yield {\n type: 'retry' as const,\n attempt: attempt + 1,\n maxRetries,\n reason: classified.reason,\n provider: providerName,\n model,\n delayMs: Math.round(retryDelayMs),\n };\n\n await sleep(retryDelayMs);\n continue;\n }\n\n // Not retryable or max retries exceeded\n if (!classified.retryable) {\n logger.error(COMPONENT, `${errorMsg} — streaming not retryable [${classified.reason}]`);\n } else {\n logger.error(COMPONENT, `${errorMsg} — streaming max retries exceeded [${classified.reason}]`);\n }\n\n // Try configured fallback chain first (once per chatStream call)\n if (!fallbackChainAttempted && (classified.retryable || classified.shouldFallback)) {\n fallbackChainAttempted = true;\n const chainStream = await tryFallbackChainStream(options, modelId, error as Error);\n if (chainStream) {\n yield {\n type: 'failover' as const,\n originalProvider: providerName,\n originalModel: model,\n error: (error as Error).message,\n };\n yield* chainStream;\n return;\n }\n }\n\n // Try mesh peers (non-streaming fallback for now)\n const config = loadConfig();\n if (config.mesh?.enabled) {\n const peer = findModelOnMesh(modelId);\n if (peer) {\n try {\n const message = Array.isArray(options.messages)\n ? options.messages.map(m => m.content).join('\\n')\n : (options as unknown as Record<string, unknown>).message as string || '';\n const result = await meshChat(peer, modelId, message);\n yield { type: 'text' as const, content: result.content };\n yield { type: 'done' as const };\n return;\n } catch (meshErr) {\n logger.warn(COMPONENT, `Mesh stream routing failed: ${(meshErr as Error).message}`);\n }\n }\n }\n\n // Task 4: priority-failover loop now runs on ANY exhausted-retry\n // path, not just attempt === 0. The `priorityFailoverAttempted`\n // latch ensures it executes at most once per chatStream call so\n // we don't loop through the failover order on every retry burst.\n if (!priorityFailoverAttempted) {\n priorityFailoverAttempted = true;\n const failoverOrder = getFailoverOrder(providerName);\n let failedOver = false;\n\n for (const fallbackName of failoverOrder) {\n if (fallbackName === providerName) continue;\n\n if (!canRequest(fallbackName, true)) {\n logger.debug(COMPONENT, `Skipping stream fallback ${fallbackName} — circuit breaker OPEN`);\n continue;\n }\n\n const fallback = providers.get(fallbackName);\n if (!fallback) continue;\n\n try {\n const healthy = await fallback.healthCheck();\n if (!healthy) continue;\n\n const models = await fallback.listModels();\n if (models.length === 0) continue;\n\n const originalPrefix = model.split('-')[0];\n const preferred = models.find(m => m.startsWith(originalPrefix)) || models[0];\n\n logger.warn(COMPONENT, `Stream failing over from ${providerName}/${model} → ${fallbackName}/${preferred}`);\n\n // Notify consumer about failover\n yield {\n type: 'failover' as const,\n originalProvider: providerName,\n originalModel: model,\n error: errorMsg,\n };\n\n // Wrap the failover stream so we record actual outcome,\n // not just optimistic success-on-generator-acquire (Task 3\n // applied here too — same pattern as tryFallbackChainStream).\n let recorded = false;\n try {\n for await (const chunk of fallback.chatStream({ ...options, model: preferred })) {\n if (chunk.type === 'error' && !recorded) {\n recordFailure(fallbackName);\n recorded = true;\n }\n yield chunk;\n }\n if (!recorded) recordSuccess(fallbackName);\n } catch (innerErr) {\n if (!recorded) recordFailure(fallbackName);\n throw innerErr;\n }\n failedOver = true;\n break;\n } catch (fallbackErr) {\n recordFailure(fallbackName);\n logger.warn(COMPONENT, `Stream fallback ${fallbackName} also failed: ${(fallbackErr as Error).message}`);\n continue;\n }\n }\n\n if (failedOver) return;\n }\n\n // All recovery options exhausted\n yield { type: 'error', error: `All streaming providers failed: ${errorMsg}` };\n return;\n }\n }\n\n // Should never reach here\n yield { type: 'error', error: lastError?.message || 'Streaming failed after all retries' };\n}\n\n/** Health check all providers */\nexport async function healthCheckAll(): Promise<Record<string, boolean>> {\n initProviders();\n const entries = Array.from(providers.entries());\n const settled = await Promise.allSettled(\n entries.map(([, provider]) => provider.healthCheck())\n );\n const results: Record<string, boolean> = {};\n for (let i = 0; i < entries.length; i++) {\n const [name] = entries[i];\n const outcome = settled[i];\n results[name] = outcome.status === 'fulfilled' ? outcome.value : false;\n }\n return results;\n}\n"],"mappings":";AAWA,SAAS,mBAA8E;AACvF,SAAS,yBAAyB;AAClC,SAAS,sBAAsB;AAC/B,SAAS,sBAAsB;AAC/B,SAAS,sBAAsB;AAC/B,SAAS,0BAA0B;AACnC,SAAS,sBAAsB,wBAAwB;AACvD,SAAS,kBAAkB;AAC3B,OAAO,YAAY;AACnB,SAAS,uBAAuB;AAEhC,SAAS,uBAAuB;AAChC,SAAS,mBAAmB;AAC5B,SAAS,aAAa;AACtB,SAAS,uBAAuB,4BAA4B,sBAAsB;AAClF,SAAS,uBAAuB;AAChC,SAAS,yBAAyB;AAClC,SAAS,qBAAqB;AAE9B,MAAM,YAAY;AAGlB,SAAS,iBAAiB,iBAAmC;AACzD,QAAM,WAAmC;AAAA,IACrC,WAAW;AAAA,IACX,QAAQ;AAAA,IACR,QAAQ;AAAA,IACR,YAAY;AAAA,IACZ,MAAM;AAAA,IACN,UAAU;AAAA,IACV,UAAU;AAAA,IACV,KAAK;AAAA,IACL,SAAS;AAAA,IACT,UAAU;AAAA,IACV,QAAQ;AAAA,IACR,aAAa;AAAA,IACb,WAAW;AAAA,IACX,YAAY;AAAA,IACZ,eAAe;AAAA,IACf,QAAQ;AAAA,EACZ;AACA,gBAAc;AACd,SAAO,MAAM,KAAK,UAAU,KAAK,CAAC,EAC7B,OAAO,UAAQ,SAAS,eAAe,EACvC,KAAK,CAAC,GAAG,OAAO,SAAS,CAAC,KAAK,OAAO,SAAS,CAAC,KAAK,GAAG;AACjE;AAOA,SAAS,0BAA0B,MAAsB;AACrD,MAAI,UAAU;AAGd,YAAU,QAAQ,QAAQ,8BAA8B,EAAE;AAG1D,YAAU,QAAQ,QAAQ,4BAA4B,EAAE;AAIxD,QAAM,gBAAgB;AACtB,QAAM,aAAa,QAAQ,MAAM,aAAa;AAC9C,MAAI,YAAY,UAAU,UAAa,WAAW,QAAQ,IAAI;AAC1D,cAAU,QAAQ,MAAM,GAAG,WAAW,KAAK;AAAA,EAC/C;AAGA,QAAM,iBAAiB;AACvB,MAAI,eAAe,KAAK,QAAQ,KAAK,CAAC,GAAG;AACrC,UAAM,QAAQ,QAAQ,MAAM,eAAe;AAC3C,UAAM,aAAa,MAAM,OAAO,OAAK;AACjC,YAAM,UAAU,EAAE,KAAK;AACvB,UAAI,CAAC,QAAS,QAAO;AACrB,UAAI,eAAe,KAAK,OAAO,EAAG,QAAO;AACzC,UAAI,2FAA2F,KAAK,OAAO,EAAG,QAAO;AACrH,UAAI,mEAAmE,KAAK,OAAO,EAAG,QAAO;AAC7F,aAAO;AAAA,IACX,CAAC;AACD,QAAI,WAAW,SAAS,GAAG;AACvB,gBAAU,WAAW,KAAK,MAAM;AAAA,IACpC;AAAA,EACJ;AAGA,YAAU,QAAQ,KAAK,EAAE,QAAQ,gBAAgB,EAAE,EAAE,KAAK;AAE1D,SAAO;AACX;AAGA,MAAM,mBAA2C;AAAA,EAC7C,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,UAAU;AAAA,EACV,aAAa;AAAA,EACb,gBAAgB;AAAA,EAChB,OAAO;AAAA,EACP,UAAU;AAAA,EACV,iBAAiB;AAAA,EACjB,MAAM;AAAA,EACN,gBAAgB;AAAA,EAChB,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,OAAO;AAAA,EACP,YAAY;AAAA,EACZ,MAAM;AAAA,EACN,UAAU;AAAA,EACV,OAAO;AAAA,EACP,cAAc;AAClB;AAGO,SAAS,kBAAkB,MAAsB;AACpD,QAAM,QAAQ,KAAK,YAAY;AAC/B,SAAO,iBAAiB,KAAK,KAAK;AACtC;AAGA,MAAM,YAAsC,oBAAI,IAAI;AACpD,IAAI,cAAc;AAElB,SAAS,gBAAsB;AAC3B,MAAI,YAAa;AAEjB,YAAU,IAAI,aAAa,IAAI,kBAAkB,CAAC;AAClD,YAAU,IAAI,UAAU,IAAI,eAAe,CAAC;AAC5C,YAAU,IAAI,UAAU,IAAI,eAAe,CAAC;AAC5C,YAAU,IAAI,UAAU,IAAI,eAAe,CAAC;AAC5C,YAAU,IAAI,eAAe,IAAI,mBAAmB,CAAC;AAErD,aAAW,UAAU,kBAAkB;AACnC,cAAU,IAAI,OAAO,MAAM,IAAI,qBAAqB,MAAM,CAAC;AAAA,EAC/D;AACA,gBAAc;AAClB;AAGO,SAAS,YAAY,MAAuC;AAC/D,gBAAc;AACd,SAAO,UAAU,IAAI,IAAI;AAC7B;AAGO,SAAS,kBAA4C;AACxD,gBAAc;AACd,SAAO;AACX;AAGA,SAAS,aAAa,SAAyB;AAC3C,QAAM,SAAS,WAAW;AAC1B,QAAM,UAAU,OAAO,MAAM;AAC7B,MAAI,WAAW,QAAQ,OAAO,GAAG;AAC7B,UAAM,WAAW,QAAQ,OAAO;AAChC,WAAO,MAAM,WAAW,UAAU,OAAO,aAAQ,QAAQ,GAAG;AAC5D,WAAO;AAAA,EACX;AACA,SAAO;AACX;AAIO,SAAS,aAAa,SAA2D;AACpF,gBAAc;AAEd,QAAM,WAAW,aAAa,OAAO;AACrC,QAAM,EAAE,UAAU,iBAAiB,MAAM,IAAI,YAAY,aAAa,QAAQ;AAI9E,QAAM,eAAe,kBAAkB,eAAe;AACtD,QAAM,WAAW,UAAU,IAAI,YAAY;AAC3C,MAAI,CAAC,UAAU;AACX,UAAM,IAAI,MAAM,qBAAqB,YAAY,gBAAgB,MAAM,KAAK,UAAU,KAAK,CAAC,EAAE,KAAK,IAAI,CAAC,EAAE;AAAA,EAC9G;AACA,SAAO,EAAE,UAAU,MAAM;AAC7B;AAUO,SAAS,gBAAgB,SAAkE;AAC9F,MAAI;AAAE,WAAO,aAAa,OAAO;AAAA,EAAG,QAAQ;AAAE,WAAO;AAAA,EAAM;AAC/D;AAGO,SAAS,wBAAkC;AAC9C,gBAAc;AACd,SAAO,MAAM,KAAK,UAAU,KAAK,CAAC,EAAE,KAAK;AAC7C;AAGO,SAAS,eAAe,SAA0B;AACrD,QAAM,SAAS,WAAW;AAC1B,QAAM,gBAAgB,OAAO,MAAM;AACnC,MAAI,CAAC,iBAAiB,cAAc,WAAW,EAAG,QAAO;AAGzD,QAAM,WAAW,aAAa,OAAO;AAErC,aAAW,WAAW,eAAe;AACjC,QAAI,YAAY,SAAU,QAAO;AAEjC,QAAI,QAAQ,SAAS,IAAI,GAAG;AACxB,YAAM,SAAS,QAAQ,MAAM,GAAG,EAAE;AAClC,UAAI,SAAS,WAAW,MAAM,EAAG,QAAO;AAAA,IAC5C;AAAA,EACJ;AACA,SAAO;AACX;AAcA,IAAI,aAAsE;AAC1E,MAAM,kBAAkB;AAOxB,eAAsB,kBAAkB,eAAe,OAAmC;AACtF,gBAAc;AAEd,MAAI,CAAC,gBAAgB,cAAe,KAAK,IAAI,IAAI,WAAW,YAAa,iBAAiB;AACtF,WAAO,WAAW;AAAA,EACtB;AAEA,QAAM,aAAgC,CAAC;AACvC,QAAM,SAAS,MAAM,eAAe;AAEpC,QAAM,QAAQ,MAAM,KAAK,UAAU,QAAQ,CAAC,EAAE,IAAI,OAAO,CAAC,MAAM,QAAQ,MAAM;AAC1E,QAAI;AACA,YAAM,SAAS,MAAM,SAAS,WAAW;AACzC,YAAM,SAAS,OAAO,IAAI,MAAM;AAChC,YAAM,gBAAgB,SAAS,aAAa;AAC5C,iBAAW,SAAS,QAAQ;AACxB,mBAAW,KAAK;AAAA,UACZ,IAAI,GAAG,IAAI,IAAI,KAAK;AAAA,UACpB,UAAU;AAAA,UACV;AAAA,UACA,aAAa,SAAS;AAAA,UACtB,QAAS,SAAS,YAAY,SAAU,SAAS;AAAA,UACjD;AAAA,QACJ,CAAC;AAAA,MACL;AAAA,IACJ,SAAS,KAAK;AACV,aAAO,MAAM,WAAW,6BAA6B,IAAI,KAAM,IAAc,OAAO,EAAE;AAAA,IAC1F;AAAA,EACJ,CAAC;AAED,QAAM,QAAQ,IAAI,KAAK;AAEvB,eAAa,EAAE,QAAQ,YAAY,WAAW,KAAK,IAAI,EAAE;AACzD,SAAO,KAAK,WAAW,cAAc,WAAW,MAAM,kBAAkB,UAAU,IAAI,YAAY;AAClG,SAAO;AACX;AAGO,SAAS,kBAA0C;AACtD,QAAM,SAAS,WAAW;AAC1B,SAAO,OAAO,MAAM,gBAAgB,CAAC;AACzC;AAeA,MAAM,yBAAyB;AAAA,EAC3B,kBAAkB;AAAA;AAAA,EAClB,cAAc;AAAA;AAAA,EACd,kBAAkB;AAAA;AAAA,EAClB,kBAAkB;AAAA;AACtB;AAGA,MAAM,kBAAkB,oBAAI,IAAiC;AAG7D,YAAY,MAAM;AACd,QAAM,MAAM,KAAK,IAAI;AACrB,aAAW,CAAC,MAAM,KAAK,KAAK,iBAAiB;AACzC,QAAI,MAAM,UAAU,YAAY,MAAM,mBAAmB,MAAM,MAAM,kBAAkB,KAAS;AAC5F,sBAAgB,OAAO,IAAI;AAAA,IAC/B;AAAA,EACJ;AACJ,GAAG,GAAO;AAQV,MAAM,wBAAwB;AAC9B,MAAM,6BAA6B,oBAAI,IAAoB;AAG3D,SAAS,wBAAwB,cAA4B;AACzD,6BAA2B,IAAI,cAAc,KAAK,IAAI,CAAC;AAC3D;AAGA,SAAS,sBAAsB,cAA+B;AAC1D,QAAM,gBAAgB,2BAA2B,IAAI,YAAY;AACjE,MAAI,CAAC,cAAe,QAAO;AAC3B,QAAM,UAAU,KAAK,IAAI,IAAI;AAC7B,MAAI,WAAW,uBAAuB;AAClC,+BAA2B,OAAO,YAAY;AAC9C,WAAO;AAAA,EACX;AACA,SAAO;AACX;AAKA,SAAS,kBAAkB,cAA2C;AAClE,MAAI,CAAC,gBAAgB,IAAI,YAAY,GAAG;AACpC,oBAAgB,IAAI,cAAc;AAAA,MAC9B,OAAO;AAAA,MACP,cAAc;AAAA,MACd,iBAAiB;AAAA,MACjB,iBAAiB;AAAA,MACjB,WAAW;AAAA,IACf,CAAC;AAAA,EACL;AACA,SAAO,gBAAgB,IAAI,YAAY;AAC3C;AAMA,SAAS,cAAc,cAA4B;AAC/C,QAAM,KAAK,kBAAkB,YAAY;AACzC,KAAG,kBAAkB,KAAK,IAAI;AAE9B,MAAI,GAAG,UAAU,aAAa;AAE1B,OAAG,eAAe,KAAK,IAAI,GAAG,GAAG,eAAe,CAAC;AAEjD,QAAI,GAAG,gBAAgB,GAAG;AACtB,SAAG,QAAQ;AACX,SAAG,YAAY;AACf,SAAG,eAAe;AAClB,aAAO,KAAK,WAAW,oBAAoB,YAAY,2CAA2C;AAAA,IACtG;AAAA,EACJ,WAAW,GAAG,UAAU,UAAU;AAE9B,OAAG,eAAe;AAAA,EACtB;AACJ;AAMA,SAAS,cAAc,cAA4B;AAC/C,QAAM,KAAK,kBAAkB,YAAY;AACzC,QAAM,MAAM,KAAK,IAAI;AACrB,KAAG,kBAAkB;AAGrB,QAAM,cAAc,MAAM,uBAAuB;AACjD,MAAI,GAAG,mBAAmB,GAAG,kBAAkB,aAAa;AAExD,OAAG,eAAe;AAAA,EACtB,OAAO;AACH,OAAG;AAAA,EACP;AAGA,MAAI,GAAG,gBAAgB,uBAAuB,oBAAoB,GAAG,UAAU,UAAU;AACrF,OAAG,QAAQ;AACX,OAAG,YAAY;AACf,WAAO,KAAK,WAAW,oBAAoB,YAAY,yBAAyB,GAAG,YAAY,WAAW;AAAA,EAC9G;AACJ;AAOA,SAAS,WAAW,cAAsB,kBAAkB,OAAgB;AAGxE,MAAI,mBAAmB,sBAAsB,YAAY,GAAG;AACxD,WAAO,MAAM,WAAW,uBAAuB,YAAY,oDAA+C;AAC1G,WAAO;AAAA,EACX;AAEA,QAAM,KAAK,kBAAkB,YAAY;AAEzC,MAAI,GAAG,UAAU,UAAU;AACvB,WAAO;AAAA,EACX;AAEA,MAAI,GAAG,UAAU,QAAQ;AACrB,UAAM,MAAM,KAAK,IAAI;AACrB,QAAI,GAAG,aAAc,MAAM,GAAG,aAAc,uBAAuB,cAAc;AAE7E,SAAG,QAAQ;AACX,SAAG,eAAe,uBAAuB;AACzC,aAAO,KAAK,WAAW,oBAAoB,YAAY,8CAA8C;AACrG,aAAO;AAAA,IACX;AACA,WAAO;AAAA,EACX;AAGA,SAAO;AACX;AAKO,SAAS,0BAA6G;AACzH,QAAM,SAA4F,CAAC;AACnG,aAAW,CAAC,cAAc,EAAE,KAAK,iBAAiB;AAC9C,WAAO,YAAY,IAAI;AAAA,MACnB,OAAO,GAAG;AAAA,MACV,cAAc,GAAG;AAAA,MACjB,GAAI,GAAG,cAAc,OAAO,EAAE,WAAW,GAAG,UAAU,IAAI,CAAC;AAAA,IAC/D;AAAA,EACJ;AACA,SAAO;AACX;AAMO,SAAS,2BAAiC;AAC7C,kBAAgB,MAAM;AACtB,sBAAoB;AACxB;AAEO,SAAS,oBAAoB,cAA4B;AAC5D,QAAM,KAAK,gBAAgB,IAAI,YAAY;AAC3C,MAAI,IAAI;AACJ,OAAG,QAAQ;AACX,OAAG,eAAe;AAClB,OAAG,YAAY;AAAA,EACnB;AACJ;AAIA,IAAI,oBAAmG;AAGhG,SAAS,mBAAkG;AAE9G,MAAI,qBAAsB,KAAK,IAAI,IAAI,kBAAkB,YAAa,KAAS;AAC3E,wBAAoB;AAAA,EACxB;AACA,SAAO;AACX;AAGA,MAAM,eAAe;AAAA,EACjB,YAAY;AAAA;AAAA,EACZ,gBAAgB;AAAA;AAAA,EAChB,YAAY;AAAA;AAAA,EACZ,mBAAmB;AAAA,EACnB,QAAQ;AACZ;AAQA,IAAI,iBAAiB;AAuBrB,SAAS,sBAAsB,SAAyB;AACpD,QAAM,mBAAmB,aAAa,iBAAiB,KAAK,IAAI,aAAa,mBAAmB,OAAO;AACvG,QAAM,cAAc,KAAK,IAAI,kBAAkB,aAAa,UAAU;AAEtE,MAAI,CAAC,aAAa,OAAQ,QAAO;AAGjC,mBAAkB,iBAAiB,MAAO;AAC1C,QAAM,QAAQ,KAAK,IAAI,IAAK,iBAAiB,gBAAiB;AAE9D,MAAI,IAAI,QAAQ;AAChB,OAAK,KAAK;AAAI,SAAO;AACrB,OAAK,MAAM;AACX,OAAK,KAAK;AAAG,SAAO;AACpB,QAAM,UAAU,MAAM,KAAK;AAE3B,QAAM,cAAc;AACpB,QAAM,SAAS,SAAS,cAAc;AACtC,SAAO,cAAc;AACzB;AAGA,SAAS,gBAAgB,QAAsC;AAC3D,MAAI,CAAC,OAAQ,QAAO;AAGpB,QAAM,UAAU,SAAS,QAAQ,EAAE;AACnC,MAAI,CAAC,MAAM,OAAO,GAAG;AACjB,WAAO,KAAK,IAAI,UAAU,KAAM,aAAa,UAAU;AAAA,EAC3D;AAGA,QAAM,OAAO,IAAI,KAAK,MAAM;AAC5B,MAAI,CAAC,MAAM,KAAK,QAAQ,CAAC,GAAG;AACxB,UAAM,QAAQ,KAAK,QAAQ,IAAI,KAAK,IAAI;AACxC,WAAO,KAAK,IAAI,KAAM,KAAK,IAAI,OAAO,aAAa,UAAU,CAAC;AAAA,EAClE;AAEA,SAAO;AACX;AAKA,SAAS,iBAAiB,OAAyB;AAC/C,SAAO,sBAAsB,KAAK,EAAE;AACxC;AAKA,SAAS,eAAe,OAAoC;AACxD,SAAO,sBAAsB,KAAK,EAAE;AACxC;AAGA,eAAe,iBACX,SACA,gBACA,eAC4B;AAC5B,QAAM,SAAS,WAAW;AAC1B,QAAM,QAAQ,OAAO,MAAM;AAC3B,MAAI,CAAC,SAAS,MAAM,WAAW,EAAG,QAAO;AAEzC,QAAM,aAAa,OAAO,MAAM,sBAAsB;AACtD,MAAI,WAAW;AAEf,aAAW,mBAAmB,OAAO;AACjC,QAAI,YAAY,WAAY;AAC5B,QAAI,oBAAoB,eAAgB;AAExC;AACA,QAAI;AACA,YAAM,EAAE,UAAU,YAAY,OAAO,QAAQ,IAAI,aAAa,eAAe;AAC7E,YAAM,iBAAiB,WAAW;AAGlC,UAAI,CAAC,WAAW,gBAAgB,IAAI,GAAG;AACnC,cAAM,KAAK,kBAAkB,cAAc;AAC3C,eAAO,KAAK,WAAW,qBAAqB,eAAe,iCAA4B,GAAG,YAAY,YAAY;AAClH;AAAA,MACJ;AAEA,aAAO,KAAK,WAAW,SAAS,cAAc,YAAY,cAAc,OAAO,sBAAsB,eAAe,EAAE;AACtH,YAAM,SAAS,MAAM,WAAW,KAAK,EAAE,GAAG,SAAS,OAAO,QAAQ,CAAC;AAGnE,oBAAc,cAAc;AAE5B,0BAAoB;AAAA,QAChB,SAAS;AAAA,QACT,QAAQ;AAAA,QACR,QAAQ,cAAc;AAAA,QACtB,WAAW,KAAK,IAAI;AAAA,MACxB;AACA,aAAO;AAAA,IACX,SAAS,UAAU;AAEf,UAAI;AACA,cAAM,EAAE,UAAU,WAAW,IAAI,aAAa,eAAe;AAC7D,sBAAc,WAAW,IAAI;AAAA,MACjC,QAAQ;AAAA,MAER;AACA,aAAO,KAAK,WAAW,kBAAkB,eAAe,iBAAkB,SAAmB,OAAO,EAAE;AACtG;AAAA,IACJ;AAAA,EACJ;AACA,SAAO;AACX;AAkBA,eAAe,uBACX,SACA,gBACA,eAC+C;AAC/C,QAAM,SAAS,WAAW;AAC1B,QAAM,QAAQ,OAAO,MAAM;AAC3B,MAAI,CAAC,SAAS,MAAM,WAAW,EAAG,QAAO;AAEzC,QAAM,aAAa,OAAO,MAAM,sBAAsB;AACtD,MAAI,WAAW;AAEf,aAAW,mBAAmB,OAAO;AACjC,QAAI,YAAY,WAAY;AAC5B,QAAI,oBAAoB,eAAgB;AAExC;AACA,QAAI;AACJ,QAAI;AAEJ,QAAI;AACA,YAAM,EAAE,UAAU,YAAY,OAAO,QAAQ,IAAI,aAAa,eAAe;AAC7E,uBAAiB,WAAW;AAG5B,UAAI,CAAC,WAAW,gBAAgB,IAAI,GAAG;AACnC,cAAM,KAAK,kBAAkB,cAAc;AAC3C,eAAO,KAAK,WAAW,4BAA4B,eAAe,iCAA4B,GAAG,YAAY,YAAY;AACzH;AAAA,MACJ;AAEA,aAAO,KAAK,WAAW,gBAAgB,cAAc,YAAY,cAAc,OAAO,sBAAsB,eAAe,EAAE;AAC7H,YAAM,WAAW,WAAW,EAAE,GAAG,SAAS,OAAO,QAAQ,CAAC;AAAA,IAC9D,SAAS,UAAU;AAEf,UAAI;AACA,cAAM,EAAE,UAAU,WAAW,IAAI,aAAa,eAAe;AAC7D,sBAAc,WAAW,IAAI;AAAA,MACjC,QAAQ;AAAA,MAER;AACA,aAAO,KAAK,WAAW,yBAAyB,eAAe,kBAAmB,SAAmB,OAAO,EAAE;AAC9G;AAAA,IACJ;AAEA,wBAAoB;AAAA,MAChB,SAAS;AAAA,MACT,QAAQ;AAAA,MACR,QAAQ,cAAc;AAAA,MACtB,WAAW,KAAK,IAAI;AAAA,IACxB;AAEA,WAAO,wBAAwB,KAAK,cAAc;AAAA,EACtD;AACA,SAAO;AACX;AASA,gBAAgB,wBACZ,OACA,cAC+B;AAC/B,MAAI,WAAW;AACf,MAAI;AACA,qBAAiB,SAAS,OAAO;AAC7B,UAAI,MAAM,SAAS,SAAS;AACxB,YAAI,CAAC,UAAU;AAAE,wBAAc,YAAY;AAAG,qBAAW;AAAA,QAAM;AAAA,MACnE;AACA,YAAM;AAAA,IACV;AACA,QAAI,CAAC,SAAU,eAAc,YAAY;AAAA,EAC7C,SAAS,UAAU;AACf,QAAI,CAAC,UAAU;AAAE,oBAAc,YAAY;AAAG,iBAAW;AAAA,IAAM;AAC/D,UAAM;AAAA,EACV;AACJ;AAGA,eAAe,SAAS,MAAgB,SAAiB,SAAwC;AAC7F,QAAM,YAAY,YAAY,CAAC,EAAE,SAAS,KAAK;AAC/C,QAAM,SAAS,WAAW;AAC1B,QAAM,YAAY,OAAO,MAAM,iBAAiB;AAChD,SAAO,KAAK,WAAW,YAAY,OAAO,kBAAkB,KAAK,QAAQ,KAAK,KAAK,OAAO,MAAM,GAAG,CAAC,CAAC,MAAM;AAC3G,QAAM,SAAS,MAAM,gBAAgB,KAAK,QAAQ,WAAW,SAAS,SAAS,SAAS;AACxF,MAAI,OAAO,OAAO;AACd,UAAM,IAAI,MAAM,oBAAoB,OAAO,KAAK,EAAE;AAAA,EACtD;AAEA,GAAC,YAAY;AACT,UAAM,EAAE,gBAAgB,IAAI,MAAM,OAAO,gCAAgC;AACzE,oBAAgB,SAAS,QAAQ,IAAI;AAAA,EACzC,GAAG,EAAE,MAAM,MAAM;AAAA,EAAC,CAAC;AACnB,SAAO;AACX;AAKA,SAAS,2BAA2B,OAAc,cAAsB,OAAe,SAAyB;AAC5G,QAAM,SAAS,eAAe,KAAK;AACnC,QAAM,aAAa,SAAS,SAAS,MAAM,OAAO;AAElD,SAAO;AAAA,IACH,YAAY,YAAY,IAAI,KAAK;AAAA,IACjC,aAAa,MAAM;AAAA,IACnB,UAAU,IAAI,YAAY,UAAU,CAAC,MAAM;AAAA,EAC/C,EAAE,OAAO,OAAO,EAAE,KAAK,IAAI;AAC/B;AAMA,eAAsB,KAAK,SAA6C;AACpE,QAAM,UAAU,QAAQ,SAAS;AACjC,QAAM,EAAE,UAAU,MAAM,IAAI,aAAa,OAAO;AAChD,QAAM,eAAe,SAAS;AAE9B,SAAO,KAAK,WAAW,cAAc,SAAS,WAAW,YAAY,KAAK,GAAG;AAQ7E,MAAI,CAAC,SAAS,aAAa,GAAG;AAC1B,UAAM,WAAW,YAAY,YAAY,mCACrC,aAAa,YAAY,EAAE,QAAQ,MAAM,GAAG,CAChD,8DAAyD,YAAY;AACrE,WAAO,KAAK,WAAW,QAAQ;AAC/B,UAAM,gBAAgB,IAAI,MAAM,QAAQ;AACxC,WAAO,OAAO,eAAe,EAAE,QAAQ,KAAK,UAAU,cAAc,OAAO,YAAY,KAAK,CAAC;AAC7F,UAAM;AAAA,EACV;AAGA,QAAM,mBAA8F,CAAC;AAGrG,MAAI,CAAC,WAAW,YAAY,GAAG;AAC3B,UAAM,KAAK,kBAAkB,YAAY;AACzC,UAAM,WAAW,4BAA4B,YAAY,IAAI,KAAK,KAAK,GAAG,YAAY,uBAClF,GAAG,YAAY,KAAK,OAAO,uBAAuB,gBAAgB,KAAK,IAAI,IAAI,GAAG,cAAc,GAAI,IAAI,SAC5G;AACA,WAAO,KAAK,WAAW,QAAQ;AAC/B,UAAM,gBAAgB,IAAI,MAAM,QAAQ;AACxC,WAAO,OAAO,eAAe,EAAE,QAAQ,KAAK,UAAU,cAAc,MAAM,CAAC;AAC3E,UAAM;AAAA,EACV;AAEA,MAAI,YAA0B;AAC9B,QAAM,aAAa,aAAa;AAOhC,MAAI,qBAAqB;AACzB,MAAI,mBAAmB;AAMvB,MAAI;AACA,UAAM,UAAU,cAAc,YAAY;AAC1C,QAAI,SAAS;AACT,aAAO,KAAK,WAAW,oCAAoC,YAAY,KAAK,QAAQ,MAAM,mBAAc,KAAK,MAAM,QAAQ,SAAS,CAAC,IAAI;AACzI,YAAM,MAAM,QAAQ,SAAS;AAAA,IACjC;AAAA,EACJ,QAAQ;AAAA,EAAsC;AAG9C,WAAS,UAAU,GAAG,WAAW,YAAY,WAAW;AACpD,QAAI;AACA,YAAM,SAAS,MAAM,SAAS,KAAK,EAAE,GAAG,SAAS,MAAM,CAAC;AAGxD,UAAI,OAAO,SAAS;AAChB,eAAO,UAAU,0BAA0B,OAAO,OAAO;AAAA,MAC7D;AAGA,oBAAc,YAAY;AAC1B,0BAAoB;AAGpB,UAAI,UAAU,GAAG;AACb,eAAO,KAAK,WAAW,GAAG,SAAS,WAAW,IAAI,KAAK,oBAAoB,OAAO,mBAAmB;AAAA,MACzG;AAGA,OAAC,YAAY;AACT,cAAM,EAAE,gBAAgB,IAAI,MAAM,OAAO,gCAAgC;AACzE,wBAAgB,OAAO,cAAc,IAAI;AAAA,MAC7C,GAAG,EAAE,MAAM,MAAM;AAAA,MAAC,CAAC;AAEnB,aAAO;AAAA,IACX,SAAS,OAAO;AACZ,kBAAY;AAGZ,YAAM,aAAa,sBAAsB,KAAK;AAG9C,UAAI,2BAA2B,UAAU,GAAG;AACxC,sBAAc,YAAY;AAAA,MAC9B;AAOA,UAAI,QAAQ,YAAY;AACpB,cAAMA,YAAW,2BAA2B,OAAgB,cAAc,OAAO,OAAO;AACxF,cAAM,kBAAkB,IAAI;AAAA,UACxB,gBAAgB,YAAY,IAAI,KAAK,mCAAmCA,SAAQ;AAAA,QACpF;AACA,eAAO,OAAO,iBAAiB;AAAA,UAC3B,QAAQ,WAAW;AAAA,UACnB,UAAU;AAAA,UACV;AAAA,UACA,OAAO;AAAA,UACP,gBAAgB,WAAW;AAAA,UAC3B,YAAY;AAAA,QAChB,CAAC;AACD,cAAM;AAAA,MACV;AAGA,UAAI,WAAW,WAAW,eAAe,YAAY;AACjD,gCAAwB,YAAY;AAAA,MACxC;AAGA,UAAI,WAAW,wBAAwB;AACnC,cAAM,OAAO,gBAAgB,YAAY;AACzC,YAAI,MAAM;AAEN,gBAAM,SAAS,KAAK,OAAO;AAC3B,gBAAM,WAAW,OAAO,KAAK,OAAK,EAAE,SAAS;AAC7C,cAAI,UAAU;AACV,iBAAK,QAAQ,SAAS,MAAM,WAAW,cAAc,GAAK;AAAA,UAC9D;AAAA,QACJ;AAAA,MACJ;AAQA,UAAI,WAAW,kBAAkB,CAAC,sBAAsB,MAAM,QAAQ,QAAQ,QAAQ,GAAG;AACrF,6BAAqB;AACrB,cAAM,cAAc,QAAQ,SAAS;AAIrC,cAAM,gBAAgB;AACtB,YAAI;AACA,gBAAM,YAAY,kBAAkB,QAAQ,UAAU,aAAa;AACnE,cAAI,UAAU,SAAS,KAAK,UAAU,UAAU,aAAa;AACzD,sBAAU,EAAE,GAAG,SAAS,UAAU,UAAU;AAC5C,mBAAO;AAAA,cACH;AAAA,cACA,YAAY,WAAW,MAAM,6BAAwB,WAAW,SAAI,UAAU,MAAM,mBAAmB,YAAY,IAAI,KAAK;AAAA,YAChI;AAEA;AAAA,UACJ;AACA,iBAAO,KAAK,WAAW,kFAA6E;AAAA,QACxG,SAAS,SAAS;AACd,iBAAO,KAAK,WAAW,gCAAiC,QAAkB,OAAO,yBAAoB;AAAA,QACzG;AAAA,MACJ;AAMA,UAAI,WAAW,WAAW,eAAe,0BAA0B,CAAC,kBAAkB;AAClF,2BAAmB;AACnB,cAAM,eAAe,QAAQ,kBAAkB,EAAE,GAAG,QAAQ,gBAAgB,IAAI,CAAC;AAEjF,eAAQ,aAAyC;AACjD,eAAQ,aAAyC;AACjD,eAAQ,aAAyC;AACjD,eAAQ,aAAyC;AACjD,eAAQ,aAAyC;AACjD,kBAAU,EAAE,GAAG,SAAS,iBAAiB,aAAa;AACtD,eAAO,KAAK,WAAW,4EAAuE,YAAY,IAAI,KAAK,EAAE;AACrH;AAAA,MACJ;AAEA,YAAM,WAAW,2BAA2B,OAAgB,cAAc,OAAO,OAAO;AAGxF,UAAI,WAAW,aAAa,UAAU,YAAY;AAE9C,YAAI,eAAe,KAAK,IAAI,WAAW,YAAY,sBAAsB,OAAO,CAAC;AAQjF,cAAM,eAAe;AACrB,YAAI,OAAO,aAAa,iBAAiB,YAAY,aAAa,eAAe,GAAG;AAChF,yBAAe,aAAa;AAC5B,iBAAO,KAAK,WAAW,uCAAuC,KAAK,MAAM,eAAe,GAAI,CAAC,GAAG;AAAA,QACpG,OAAO;AAEH,gBAAM,aAAa,aAAa,SAAS,MAAM,aAAa;AAC5D,cAAI,YAAY;AACZ,kBAAM,SAAS,gBAAgB,UAAU;AACzC,gBAAI,WAAW,MAAM;AACjB,6BAAe;AACf,qBAAO,KAAK,WAAW,gDAAgD,KAAK,MAAM,eAAe,GAAI,CAAC,GAAG;AAAA,YAC7G;AAAA,UACJ;AAAA,QACJ;AAEA,eAAO,KAAK,WAAW,GAAG,QAAQ,KAAK,WAAW,MAAM,wBAAmB,KAAK,MAAM,YAAY,CAAC,IAAI;AACvG,cAAM,MAAM,YAAY;AACxB;AAAA,MACJ;AAGA,UAAI,CAAC,WAAW,WAAW;AACvB,eAAO,MAAM,WAAW,GAAG,QAAQ,0BAAqB,WAAW,MAAM,MAAM,WAAW,aAAa,QAAQ,WAAW,UAAU,KAAK,eAAe,GAAG;AAAA,MAC/J,OAAO;AACH,eAAO,MAAM,WAAW,GAAG,QAAQ,wBAAmB,UAAU,eAAe,WAAW,MAAM,GAAG;AAAA,MACvG;AAGA,UAAI,WAAW,aAAa,WAAW,gBAAgB;AACnD,cAAM,cAAc,MAAM,iBAAiB,SAAS,SAAS,KAAc;AAC3E,YAAI,aAAa;AACb,iBAAO,KAAK,WAAW,iCAAiC,YAAY,IAAI,KAAK,aAAa,WAAW,MAAM,GAAG;AAC9G,iBAAO;AAAA,QACX;AAAA,MACJ;AAGA,YAAM,SAAS,WAAW;AAC1B,UAAI,OAAO,MAAM,SAAS;AACtB,cAAM,OAAO,gBAAgB,OAAO;AACpC,YAAI,MAAM;AACN,cAAI;AACA,kBAAM,UAAU,MAAM,QAAQ,QAAQ,QAAQ,IACxC,QAAQ,SAAS,IAAI,OAAK,EAAE,OAAO,EAAE,KAAK,IAAI,IAC7C,QAA+C,WAAqB;AAC3E,mBAAO,MAAM,SAAS,MAAM,SAAS,OAAO;AAAA,UAChD,SAAS,SAAS;AACd,mBAAO,KAAK,WAAW,wBAAyB,QAAkB,OAAO,EAAE;AAAA,UAC/E;AAAA,QACJ;AAAA,MACJ;AAGA,UAAI,YAAY,GAAG;AACf,cAAM,gBAAgB,iBAAiB,YAAY;AACnD,mBAAW,gBAAgB,eAAe;AACtC,cAAI,iBAAiB,aAAc;AAGnC,cAAI,CAAC,WAAW,cAAc,IAAI,GAAG;AACjC,mBAAO,MAAM,WAAW,qBAAqB,YAAY,8BAAyB;AAClF;AAAA,UACJ;AAEA,gBAAM,WAAW,UAAU,IAAI,YAAY;AAC3C,cAAI,CAAC,SAAU;AAEf,cAAI,cAAc;AAClB,cAAI;AACA,kBAAM,UAAU,MAAM,SAAS,YAAY;AAC3C,gBAAI,CAAC,QAAS;AAEd,kBAAM,SAAS,MAAM,SAAS,WAAW;AACzC,gBAAI,OAAO,WAAW,EAAG;AAGzB,kBAAM,iBAAiB,MAAM,MAAM,GAAG,EAAE,CAAC;AACzC,0BAAc,OAAO,KAAK,OAAK,EAAE,WAAW,cAAc,CAAC,KAAK,OAAO,CAAC;AAExE,mBAAO,KAAK,WAAW,qBAAqB,YAAY,IAAI,KAAK,WAAM,YAAY,IAAI,WAAW,EAAE;AACpG,kBAAM,SAAS,MAAM,SAAS,KAAK,EAAE,GAAG,SAAS,OAAO,YAAY,CAAC;AACrE,0BAAc,YAAY;AAE1B,aAAC,YAAY;AACT,oBAAM,EAAE,gBAAgB,IAAI,MAAM,OAAO,gCAAgC;AACzE,8BAAgB,aAAa,cAAc,IAAI;AAAA,YACnD,GAAG,EAAE,MAAM,MAAM;AAAA,YAAC,CAAC;AACnB,mBAAO;AAAA,UACX,SAAS,aAAa;AAClB,0BAAc,YAAY;AAE1B,6BAAiB,KAAK;AAAA,cAClB,UAAU;AAAA,cACV,OAAO;AAAA,cACP,OAAQ,YAAsB;AAAA,cAC9B,QAAQ,sBAAsB,WAAW,EAAE;AAAA,YAC/C,CAAC;AACD,mBAAO,KAAK,WAAW,YAAY,YAAY,iBAAkB,YAAsB,OAAO,EAAE;AAChG;AAAA,UACJ;AAAA,QACJ;AAAA,MACJ;AAGA,uBAAiB,QAAQ;AAAA,QACrB,UAAU;AAAA,QACV;AAAA,QACA,OAAQ,MAAgB;AAAA,QACxB,QAAQ,WAAW;AAAA,MACvB,CAAC;AAGD,OAAC,YAAY;AACT,cAAM,EAAE,gBAAgB,IAAI,MAAM,OAAO,gCAAgC;AACzE,wBAAgB,OAAO,cAAc,KAAK;AAAA,MAC9C,GAAG,EAAE,MAAM,MAAM;AAAA,MAAC,CAAC;AAGnB,YAAM,iBAAiB,iBAAiB,SAAS,IAC3C,YAAY,iBAAiB,MAAM,eAAe,iBAAiB,IAAI,OAAK,GAAG,EAAE,QAAQ,IAAI,EAAE,KAAK,KAAK,EAAE,MAAM,GAAG,EAAE,KAAK,IAAI,CAAC,KAChI;AACN,YAAM,aAAa,IAAI,MAAM,yBAAyB,QAAQ,GAAG,cAAc,EAAE;AACjF,aAAO,OAAO,YAAY;AAAA,QACtB,QAAQ,WAAW;AAAA,QACnB,UAAU;AAAA,QACV;AAAA,QACA,OAAO;AAAA,QACP,gBAAgB,WAAW;AAAA;AAAA,QAE3B;AAAA,MACJ,CAAC;AACD,YAAM;AAAA,IACV;AAAA,EACJ;AAGA,QAAM,aAAa,IAAI,MAAM,YAAY,YAAY,IAAI,KAAK,2BAA2B;AAC7F;AAKA,gBAAuB,WAAW,SAAuD;AACrF,QAAM,UAAU,QAAQ,SAAS;AACjC,QAAM,EAAE,UAAU,MAAM,IAAI,aAAa,OAAO;AAChD,QAAM,eAAe,SAAS;AAE9B,SAAO,KAAK,WAAW,iBAAiB,SAAS,WAAW,YAAY,KAAK,GAAG;AAKhF,MAAI,CAAC,SAAS,aAAa,GAAG;AAC1B,UAAM,WAAW,YAAY,YAAY,mCACrC,aAAa,YAAY,EAAE,QAAQ,MAAM,GAAG,CAChD,8DAAyD,YAAY;AACrE,WAAO,KAAK,WAAW,QAAQ;AAC/B,UAAM,EAAE,MAAM,SAAS,OAAO,SAAS;AACvC;AAAA,EACJ;AAGA,MAAI,CAAC,WAAW,YAAY,GAAG;AAC3B,UAAM,KAAK,kBAAkB,YAAY;AACzC,UAAM;AAAA,MACF,MAAM;AAAA,MACN,OAAO,kCAAkC,YAAY,IAAI,KAAK,KAAK,GAAG,YAAY,yBAC9E,KAAK,OAAO,uBAAuB,gBAAgB,KAAK,IAAI,IAAI,GAAG,cAAe,GAAI,CAC1F;AAAA,IACJ;AACA;AAAA,EACJ;AAEA,MAAI,YAA0B;AAC9B,QAAM,aAAa,aAAa;AAMhC,MAAI,yBAAyB;AAC7B,MAAI,4BAA4B;AAEhC,WAAS,UAAU,GAAG,WAAW,YAAY,WAAW;AACpD,QAAI;AAGA,UAAI,kBAAkB;AACtB,uBAAiB,SAAS,SAAS,WAAW,EAAE,GAAG,SAAS,MAAM,CAAC,GAAG;AAClE,YAAI,CAAC,mBAAmB,MAAM,SAAS,WAAW,YAAY,GAAG;AAC7D,wBAAc,YAAY;AAC1B,4BAAkB;AAAA,QACtB;AACA,4BAAoB;AACpB,cAAM;AAAA,MACV;AAGA,UAAI,UAAU,GAAG;AACb,eAAO,KAAK,WAAW,GAAG,SAAS,WAAW,IAAI,KAAK,2BAA2B,OAAO,mBAAmB;AAAA,MAChH;AACA;AAAA,IACJ,SAAS,OAAO;AACZ,kBAAY;AAGZ,YAAM,aAAa,sBAAsB,KAAK;AAC9C,UAAI,2BAA2B,UAAU,GAAG;AACxC,sBAAc,YAAY;AAAA,MAC9B;AAEA,YAAM,WAAW,2BAA2B,OAAgB,cAAc,OAAO,OAAO;AAGxF,UAAI,WAAW,aAAa,UAAU,YAAY;AAC9C,cAAM,eAAe,KAAK,IAAI,WAAW,YAAY,sBAAsB,OAAO,CAAC;AACnF,eAAO,KAAK,WAAW,GAAG,QAAQ,KAAK,WAAW,MAAM,+BAA0B,KAAK,MAAM,YAAY,CAAC,IAAI;AAM9G,cAAM;AAAA,UACF,MAAM;AAAA,UACN,SAAS,UAAU;AAAA,UACnB;AAAA,UACA,QAAQ,WAAW;AAAA,UACnB,UAAU;AAAA,UACV;AAAA,UACA,SAAS,KAAK,MAAM,YAAY;AAAA,QACpC;AAEA,cAAM,MAAM,YAAY;AACxB;AAAA,MACJ;AAGA,UAAI,CAAC,WAAW,WAAW;AACvB,eAAO,MAAM,WAAW,GAAG,QAAQ,oCAA+B,WAAW,MAAM,GAAG;AAAA,MAC1F,OAAO;AACH,eAAO,MAAM,WAAW,GAAG,QAAQ,2CAAsC,WAAW,MAAM,GAAG;AAAA,MACjG;AAGA,UAAI,CAAC,2BAA2B,WAAW,aAAa,WAAW,iBAAiB;AAChF,iCAAyB;AACzB,cAAM,cAAc,MAAM,uBAAuB,SAAS,SAAS,KAAc;AACjF,YAAI,aAAa;AACb,gBAAM;AAAA,YACF,MAAM;AAAA,YACN,kBAAkB;AAAA,YAClB,eAAe;AAAA,YACf,OAAQ,MAAgB;AAAA,UAC5B;AACA,iBAAO;AACP;AAAA,QACJ;AAAA,MACJ;AAGA,YAAM,SAAS,WAAW;AAC1B,UAAI,OAAO,MAAM,SAAS;AACtB,cAAM,OAAO,gBAAgB,OAAO;AACpC,YAAI,MAAM;AACN,cAAI;AACA,kBAAM,UAAU,MAAM,QAAQ,QAAQ,QAAQ,IACxC,QAAQ,SAAS,IAAI,OAAK,EAAE,OAAO,EAAE,KAAK,IAAI,IAC7C,QAA+C,WAAqB;AAC3E,kBAAM,SAAS,MAAM,SAAS,MAAM,SAAS,OAAO;AACpD,kBAAM,EAAE,MAAM,QAAiB,SAAS,OAAO,QAAQ;AACvD,kBAAM,EAAE,MAAM,OAAgB;AAC9B;AAAA,UACJ,SAAS,SAAS;AACd,mBAAO,KAAK,WAAW,+BAAgC,QAAkB,OAAO,EAAE;AAAA,UACtF;AAAA,QACJ;AAAA,MACJ;AAMA,UAAI,CAAC,2BAA2B;AAC5B,oCAA4B;AAC5B,cAAM,gBAAgB,iBAAiB,YAAY;AACnD,YAAI,aAAa;AAEjB,mBAAW,gBAAgB,eAAe;AACtC,cAAI,iBAAiB,aAAc;AAEnC,cAAI,CAAC,WAAW,cAAc,IAAI,GAAG;AACjC,mBAAO,MAAM,WAAW,4BAA4B,YAAY,8BAAyB;AACzF;AAAA,UACJ;AAEA,gBAAM,WAAW,UAAU,IAAI,YAAY;AAC3C,cAAI,CAAC,SAAU;AAEf,cAAI;AACA,kBAAM,UAAU,MAAM,SAAS,YAAY;AAC3C,gBAAI,CAAC,QAAS;AAEd,kBAAM,SAAS,MAAM,SAAS,WAAW;AACzC,gBAAI,OAAO,WAAW,EAAG;AAEzB,kBAAM,iBAAiB,MAAM,MAAM,GAAG,EAAE,CAAC;AACzC,kBAAM,YAAY,OAAO,KAAK,OAAK,EAAE,WAAW,cAAc,CAAC,KAAK,OAAO,CAAC;AAE5E,mBAAO,KAAK,WAAW,4BAA4B,YAAY,IAAI,KAAK,WAAM,YAAY,IAAI,SAAS,EAAE;AAGzG,kBAAM;AAAA,cACF,MAAM;AAAA,cACN,kBAAkB;AAAA,cAClB,eAAe;AAAA,cACf,OAAO;AAAA,YACX;AAKA,gBAAI,WAAW;AACf,gBAAI;AACA,+BAAiB,SAAS,SAAS,WAAW,EAAE,GAAG,SAAS,OAAO,UAAU,CAAC,GAAG;AAC7E,oBAAI,MAAM,SAAS,WAAW,CAAC,UAAU;AACrC,gCAAc,YAAY;AAC1B,6BAAW;AAAA,gBACf;AACA,sBAAM;AAAA,cACV;AACA,kBAAI,CAAC,SAAU,eAAc,YAAY;AAAA,YAC7C,SAAS,UAAU;AACf,kBAAI,CAAC,SAAU,eAAc,YAAY;AACzC,oBAAM;AAAA,YACV;AACA,yBAAa;AACb;AAAA,UACJ,SAAS,aAAa;AAClB,0BAAc,YAAY;AAC1B,mBAAO,KAAK,WAAW,mBAAmB,YAAY,iBAAkB,YAAsB,OAAO,EAAE;AACvG;AAAA,UACJ;AAAA,QACJ;AAEA,YAAI,WAAY;AAAA,MACpB;AAGA,YAAM,EAAE,MAAM,SAAS,OAAO,mCAAmC,QAAQ,GAAG;AAC5E;AAAA,IACJ;AAAA,EACJ;AAGA,QAAM,EAAE,MAAM,SAAS,OAAO,WAAW,WAAW,qCAAqC;AAC7F;AAGA,eAAsB,iBAAmD;AACrE,gBAAc;AACd,QAAM,UAAU,MAAM,KAAK,UAAU,QAAQ,CAAC;AAC9C,QAAM,UAAU,MAAM,QAAQ;AAAA,IAC1B,QAAQ,IAAI,CAAC,CAAC,EAAE,QAAQ,MAAM,SAAS,YAAY,CAAC;AAAA,EACxD;AACA,QAAM,UAAmC,CAAC;AAC1C,WAAS,IAAI,GAAG,IAAI,QAAQ,QAAQ,KAAK;AACrC,UAAM,CAAC,IAAI,IAAI,QAAQ,CAAC;AACxB,UAAM,UAAU,QAAQ,CAAC;AACzB,YAAQ,IAAI,IAAI,QAAQ,WAAW,cAAc,QAAQ,QAAQ;AAAA,EACrE;AACA,SAAO;AACX;","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// v6.0.1 — Model-agnostic default picker.\nimport { getDefaultModelId } from './defaultModel.js';\n\nconst COMPONENT = 'Router';\n\n/** Build failover order from all registered providers, sorted by capability priority */\nfunction getFailoverOrder(excludeProvider: string): string[] {\n const priority: Record<string, number> = {\n anthropic: 100,\n openai: 90,\n google: 80,\n openrouter: 75,\n groq: 70,\n together: 65,\n deepseek: 60,\n xai: 55,\n mistral: 50,\n cerebras: 45,\n cohere: 40,\n 'cohere-v2': 40,\n fireworks: 35,\n perplexity: 30,\n 'claude-code': 15,\n ollama: 10,\n };\n initProviders();\n return Array.from(providers.keys())\n .filter(name => name !== excludeProvider)\n .sort((a, b) => (priority[b] ?? 25) - (priority[a] ?? 25));\n}\n\n// ── Chain-of-thought stripping ──────────────────────────────────\n// Some local models (qwen, glm, deepseek, etc.) leak their internal\n// reasoning into the response. This runs on EVERY chat() response so\n// no consumer (FB posts, Messenger, comments, web chat) ever sees it.\n\nfunction stripThinkingFromResponse(text: string): string {\n let cleaned = text;\n\n // 1. Remove <think>...</think> blocks (deepseek, qwen thinking mode)\n cleaned = cleaned.replace(/<think>[\\s\\S]*?<\\/think>/gi, '');\n\n // 2. Remove ```thinking ... ``` blocks\n cleaned = cleaned.replace(/```thinking[\\s\\S]*?```/gi, '');\n\n // 3. Cut at \"multiple draft\" boundaries — models often generate several\n // versions inline: \"Let me try another version:\", \"Here's another:\", etc.\n const draftBoundary = /\\n+[\"']?\\n*(Let me (try|make|write|do|craft)|Here'?s? (another|a better|a more)|Another (version|option|take|attempt)|Or (maybe|how about|alternatively)|Version \\d|Option \\d|Draft \\d|---)/i;\n const draftMatch = cleaned.match(draftBoundary);\n if (draftMatch?.index !== undefined && draftMatch.index > 20) {\n cleaned = cleaned.slice(0, draftMatch.index);\n }\n\n // 4. If the response starts with meta-reasoning, extract just the reply\n const reasoningStart = /^(The user wants|The comment|I need to|I should|Let me (think|craft|write|consider|analyze)|OK so|Alright,|Hmm,|This is a)/i;\n if (reasoningStart.test(cleaned.trim())) {\n const parts = cleaned.split(/\\n{2,}|^---$/m);\n const replyParts = parts.filter(p => {\n const trimmed = p.trim();\n if (!trimmed) return false;\n if (reasoningStart.test(trimmed)) return false;\n if (/^(Wait|Actually|But |So |Since |That works|That's about|Let me (count|check|think|try))/i.test(trimmed)) return false;\n if (/\\b(characters|under \\d+ char|personality|mentioned|the rules)\\b/i.test(trimmed)) return false;\n return true;\n });\n if (replyParts.length > 0) {\n cleaned = replyParts.join('\\n\\n');\n }\n }\n\n // 5. Remove wrapping quotes that some models add\n cleaned = cleaned.trim().replace(/^[\"']|[\"']$/g, '').trim();\n\n return cleaned;\n}\n\n// ── Provider name normalization ─────────────────────────────────\nconst PROVIDER_ALIASES: Record<string, string> = {\n 'z.ai': 'xai',\n 'zai': 'xai',\n 'grok': 'xai',\n 'local': 'ollama',\n 'vertex': 'google',\n 'vertex-ai': 'google',\n 'azure-openai': 'azure',\n 'aws': 'bedrock',\n 'amazon': 'bedrock',\n 'litellm-proxy': 'litellm',\n 'hf': 'huggingface',\n 'hugging-face': 'huggingface',\n '01ai': 'yi',\n '01.ai': 'yi',\n 'glm': 'zhipu',\n 'bigmodel': 'zhipu',\n 'pi': 'inflection',\n 'octoai': 'octo',\n 'nim': 'nvidia',\n 'nvidia-nim': 'nvidia',\n};\n\n/** Normalize provider names for consistency (e.g. \"grok\" → \"xai\", \"local\" → \"ollama\") */\nexport function normalizeProvider(name: string): string {\n const lower = name.toLowerCase();\n return PROVIDER_ALIASES[lower] || lower;\n}\n\n/** Provider registry */\nconst providers: Map<string, LLMProvider> = new Map();\nlet initialized = false;\n\nfunction initProviders(): void {\n if (initialized) return;\n // Core providers (custom implementations)\n providers.set('anthropic', new AnthropicProvider());\n providers.set('openai', new OpenAIProvider());\n providers.set('google', new GoogleProvider());\n providers.set('ollama', new OllamaProvider());\n providers.set('claude-code', new ClaudeCodeProvider());\n // OpenAI-compatible providers (Groq, Mistral, OpenRouter, xAI, etc.)\n for (const preset of PROVIDER_PRESETS) {\n providers.set(preset.name, new OpenAICompatProvider(preset));\n }\n initialized = true;\n}\n\n/** Get a provider by name */\nexport function getProvider(name: string): LLMProvider | undefined {\n initProviders();\n return providers.get(name);\n}\n\n/** Get all registered providers */\nexport function getAllProviders(): Map<string, LLMProvider> {\n initProviders();\n return providers;\n}\n\n/** Resolve a model alias (e.g. \"fast\" → \"openai/gpt-4o-mini\") */\nfunction resolveAlias(modelId: string): string {\n const config = loadConfig();\n const aliases = config.agent.modelAliases;\n if (aliases && aliases[modelId]) {\n const resolved = aliases[modelId];\n logger.debug(COMPONENT, `Alias \"${modelId}\" → \"${resolved}\"`);\n return resolved;\n }\n return modelId;\n}\n\n\n/** Resolve the provider and model from a model ID like \"anthropic/claude-3\" or alias like \"fast\" */\nexport function resolveModel(modelId: string): { provider: LLMProvider; model: string } {\n initProviders();\n // First resolve aliases\n const resolved = resolveAlias(modelId);\n const { provider: rawProviderName, model } = LLMProvider.parseModelId(resolved);\n\n\n // Normalize provider name (e.g. \"grok\" → \"xai\", \"local\" → \"ollama\")\n const providerName = normalizeProvider(rawProviderName);\n const provider = providers.get(providerName);\n if (!provider) {\n throw new Error(`Unknown provider: ${providerName}. Available: ${Array.from(providers.keys()).join(', ')}`);\n }\n return { provider, model };\n}\n\n/**\n * Non-throwing variant of resolveModel — returns null on an unknown\n * provider instead of throwing. Used by gateway endpoints to fail-fast\n * with a helpful 400 BEFORE the agent loop builds the prompt and burns\n * tokens. v5.5.30+. Bug from 2026-05-08 audit: requests with bad model\n * IDs (e.g. typoed providers) used to crash deep inside the agent loop\n * after prompt assembly, returning 500 with a stack trace.\n */\nexport function tryResolveModel(modelId: string): { provider: LLMProvider; model: string } | null {\n try { return resolveModel(modelId); } catch { return null; }\n}\n\n/** List of provider names known to this gateway (for \"did you mean\" suggestions). */\nexport function getKnownProviderNames(): string[] {\n initProviders();\n return Array.from(providers.keys()).sort();\n}\n\n/** Check if a model is allowed by the allowlist. Empty list = all allowed. */\nexport function isModelAllowed(modelId: string): boolean {\n const config = loadConfig();\n const allowedModels = config.agent.allowedModels;\n if (!allowedModels || allowedModels.length === 0) return true;\n\n // Resolve alias first\n const resolved = resolveAlias(modelId);\n\n for (const pattern of allowedModels) {\n if (pattern === resolved) return true;\n // Wildcard support: \"openai/*\" matches \"openai/gpt-4o\"\n if (pattern.endsWith('/*')) {\n const prefix = pattern.slice(0, -1); // \"openai/\"\n if (resolved.startsWith(prefix)) return true;\n }\n }\n return false;\n}\n\n/** Discovered model info */\nexport interface DiscoveredModel {\n id: string; // Full ID e.g. \"ollama/llama3.1\"\n provider: string; // Provider name e.g. \"ollama\"\n model: string; // Model name e.g. \"llama3.1\"\n displayName: string; // Provider display name e.g. \"Ollama (Local)\"\n source: 'static' | 'live'; // Whether discovered via live API or hardcoded list\n /** True if the provider has the credentials it needs to actually serve a request for this model. */\n keyConfigured: boolean;\n}\n\n/** Cache for discovered models (refreshed on demand, 60s TTL) */\nlet modelCache: { models: DiscoveredModel[]; timestamp: number } | null = null;\nconst MODEL_CACHE_TTL = 60_000; // 60 seconds\n\n/**\n * Discover all available models across all providers.\n * Queries each provider's listModels() — for Ollama this hits the local API\n * to find actually-installed models. Results are cached for 60s.\n */\nexport async function discoverAllModels(forceRefresh = false): Promise<DiscoveredModel[]> {\n initProviders();\n\n if (!forceRefresh && modelCache && (Date.now() - modelCache.timestamp) < MODEL_CACHE_TTL) {\n return modelCache.models;\n }\n\n const discovered: DiscoveredModel[] = [];\n const health = await healthCheckAll();\n\n const tasks = Array.from(providers.entries()).map(async ([name, provider]) => {\n try {\n const models = await provider.listModels();\n const isLive = health[name] === true;\n const keyConfigured = provider.isConfigured();\n for (const model of models) {\n discovered.push({\n id: `${name}/${model}`,\n provider: name,\n model,\n displayName: provider.displayName,\n source: (name === 'ollama' && isLive) ? 'live' : 'static',\n keyConfigured,\n });\n }\n } catch (err) {\n logger.debug(COMPONENT, `Failed to list models for ${name}: ${(err as Error).message}`);\n }\n });\n\n await Promise.all(tasks);\n\n modelCache = { models: discovered, timestamp: Date.now() };\n logger.info(COMPONENT, `Discovered ${discovered.length} models across ${providers.size} providers`);\n return discovered;\n}\n\n/** Get current model aliases from config */\nexport function getModelAliases(): Record<string, string> {\n const config = loadConfig();\n return config.agent.modelAliases || {};\n}\n\n// ── Circuit Breaker ─────────────────────────────────────────────\n/** Circuit breaker states for each provider */\ntype CircuitState = 'closed' | 'open' | 'half-open';\n\ninterface CircuitBreakerState {\n state: CircuitState;\n failureCount: number;\n lastFailureTime: number | null;\n lastSuccessTime: number | null;\n openSince: number | null;\n}\n\n/** Circuit breaker configuration — tuned for cloud model tolerance */\nconst CIRCUIT_BREAKER_CONFIG = {\n failureThreshold: 8, // Number of failures before opening circuit (was 5 — too aggressive for cloud)\n resetTimeout: 60000, // 60s before trying again (was 30s — cloud models need recovery time)\n monitoringWindow: 120000, // 120s window for counting failures (was 60s — cloud latency spikes are normal)\n successThreshold: 2, // Successes needed in half-open to close circuit (was 3)\n};\n\n/** Track circuit breaker state per provider */\nconst circuitBreakers = new Map<string, CircuitBreakerState>();\n\n// Prune stale closed circuit breakers every 5 minutes to prevent unbounded growth\nsetInterval(() => {\n const now = Date.now();\n for (const [name, state] of circuitBreakers) {\n if (state.state === 'closed' && state.lastFailureTime && now - state.lastFailureTime > 600_000) {\n circuitBreakers.delete(name);\n }\n }\n}, 300_000);\n\n\n/**\n * G2: Cooldown-aware probe throttling (OpenClaw pattern).\n * When a provider is rate-limited, don't probe it again for MIN_PROBE_INTERVAL_MS.\n * Prevents cascade failures during provider outages.\n */\nconst MIN_PROBE_INTERVAL_MS = 30000; // 30s between probes\nconst providerRateLimitCooldowns = new Map<string, number>(); // provider → timestamp of last rate-limit\n\n/** Record that a provider returned a rate-limit error */\nfunction recordRateLimitCooldown(providerName: string): void {\n providerRateLimitCooldowns.set(providerName, Date.now());\n}\n\n/** Check if a provider is still in its rate-limit cooldown window */\nfunction isInRateLimitCooldown(providerName: string): boolean {\n const lastRateLimit = providerRateLimitCooldowns.get(providerName);\n if (!lastRateLimit) return false;\n const elapsed = Date.now() - lastRateLimit;\n if (elapsed >= MIN_PROBE_INTERVAL_MS) {\n providerRateLimitCooldowns.delete(providerName); // Cooldown expired\n return false;\n }\n return true;\n}\n\n/**\n * Get or create circuit breaker state for a provider.\n */\nfunction getCircuitBreaker(providerName: string): CircuitBreakerState {\n if (!circuitBreakers.has(providerName)) {\n circuitBreakers.set(providerName, {\n state: 'closed',\n failureCount: 0,\n lastFailureTime: null,\n lastSuccessTime: null,\n openSince: null,\n });\n }\n return circuitBreakers.get(providerName)!;\n}\n\n/**\n * Record a successful request for a provider.\n * Resets failure count and updates state appropriately.\n */\nfunction recordSuccess(providerName: string): void {\n const cb = getCircuitBreaker(providerName);\n cb.lastSuccessTime = Date.now();\n\n if (cb.state === 'half-open') {\n // In half-open state, success reduces the counter\n cb.failureCount = Math.max(0, cb.failureCount - 1);\n // If we've had enough successes, close the circuit\n if (cb.failureCount <= 0) {\n cb.state = 'closed';\n cb.openSince = null;\n cb.failureCount = 0;\n logger.info(COMPONENT, `[CircuitBreaker] ${providerName} circuit CLOSED after successful recovery`);\n }\n } else if (cb.state === 'closed') {\n // In closed state, reset the failure count on success\n cb.failureCount = 0;\n }\n}\n\n/**\n * Record a failed request for a provider.\n * Opens circuit if failure threshold is exceeded.\n */\nfunction recordFailure(providerName: string): void {\n const cb = getCircuitBreaker(providerName);\n const now = Date.now();\n cb.lastFailureTime = now;\n\n // Only count failures within the monitoring window\n const windowStart = now - CIRCUIT_BREAKER_CONFIG.monitoringWindow;\n if (cb.lastFailureTime && cb.lastFailureTime < windowStart) {\n // Reset if outside monitoring window\n cb.failureCount = 1;\n } else {\n cb.failureCount++;\n }\n\n // Check if we should open the circuit\n if (cb.failureCount >= CIRCUIT_BREAKER_CONFIG.failureThreshold && cb.state === 'closed') {\n cb.state = 'open';\n cb.openSince = now;\n logger.warn(COMPONENT, `[CircuitBreaker] ${providerName} circuit OPENED after ${cb.failureCount} failures`);\n }\n}\n\n/**\n * Check if a provider's circuit breaker allows requests.\n * Returns true if closed or if half-open (time to test).\n * Returns false if open and still in timeout period.\n */\nfunction canRequest(providerName: string, isFallbackProbe = false): boolean {\n // G2: Rate-limit cooldown only blocks FALLBACK probes, not primary model retries.\n // Primary model has its own backoff logic — don't double-gate it.\n if (isFallbackProbe && isInRateLimitCooldown(providerName)) {\n logger.debug(COMPONENT, `[RateLimitCooldown] ${providerName} still cooling down — skipping fallback probe`);\n return false;\n }\n\n const cb = getCircuitBreaker(providerName);\n\n if (cb.state === 'closed') {\n return true;\n }\n\n if (cb.state === 'open') {\n const now = Date.now();\n if (cb.openSince && (now - cb.openSince) >= CIRCUIT_BREAKER_CONFIG.resetTimeout) {\n // Timeout expired, transition to half-open\n cb.state = 'half-open';\n cb.failureCount = CIRCUIT_BREAKER_CONFIG.successThreshold; // Need this many successes to close\n logger.info(COMPONENT, `[CircuitBreaker] ${providerName} circuit transitioned to HALF-OPEN (testing)`);\n return true;\n }\n return false; // Still open, don't try\n }\n\n // half-open: allow testing\n return true;\n}\n\n/**\n * Get circuit breaker status for all providers (for health dashboards).\n */\nexport function getCircuitBreakerStatus(): Record<string, { state: CircuitState; failureCount: number; openSince?: number }> {\n const status: Record<string, { state: CircuitState; failureCount: number; openSince?: number }> = {};\n for (const [providerName, cb] of circuitBreakers) {\n status[providerName] = {\n state: cb.state,\n failureCount: cb.failureCount,\n ...(cb.openSince !== null ? { openSince: cb.openSince } : {}),\n };\n }\n return status;\n}\n\n/**\n * Reset all circuit breaker state (for testing).\n * NOT exported to production API - test use only.\n */\nexport function __resetCircuitBreakers__(): void {\n circuitBreakers.clear();\n lastFallbackEvent = null;\n}\n\nexport function resetCircuitBreaker(providerName: string): void {\n const cb = circuitBreakers.get(providerName);\n if (cb) {\n cb.state = 'closed';\n cb.failureCount = 0;\n cb.openSince = null;\n }\n}\n\n// ── Fallback chain state ─────────────────────────────────────────\n/** Tracks the most recent fallback event for dashboard display */\nlet lastFallbackEvent: { primary: string; active: string; reason: string; timestamp: number } | null = null;\n\n/** Get the current fallback state (for dashboard display) */\nexport function getFallbackState(): { primary: string; active: string; reason: string; timestamp: number } | null {\n // Expire after 5 minutes\n if (lastFallbackEvent && (Date.now() - lastFallbackEvent.timestamp) > 300_000) {\n lastFallbackEvent = null;\n }\n return lastFallbackEvent;\n}\n\n/** Retry configuration with exponential backoff */\nconst RETRY_CONFIG = {\n maxRetries: 4, // 4 retries (was 3) — cloud APIs need more chances\n initialDelayMs: 1500, // 1.5s initial (was 1s) — give cloud APIs breathing room\n maxDelayMs: 45000, // 45s cap (was 30s) — cloud models can take longer to recover\n backoffMultiplier: 2,\n jitter: true,\n};\n\n/**\n * Monotonic counter seed for decorrelated jitter. Without this, two retries\n * triggered in the same millisecond can receive identical Math.random() values\n * if V8 happens to share a seed under load — that's exactly the thundering\n * herd we're trying to avoid.\n */\nlet _jitterCounter = 0;\n\n/**\n * Calculate delay with exponential backoff + asymmetric additive jitter.\n *\n * Ported from Hermes `agent/retry_utils.py:jittered_backoff` — proven to\n * decorrelate concurrent retries across multiple sessions hitting the same\n * rate-limited provider simultaneously.\n *\n * Formula:\n * base_delay = min(initial * multiplier^attempt, max)\n * jitter = random_uniform(0, jitter_ratio * base_delay)\n * final = base_delay + jitter\n *\n * Key difference from the previous TITAN implementation:\n * - Old: jitter was ±20% centered on base (could reduce delay below base)\n * - New: jitter is 0..+50% of base (only extends delay, never shortens)\n * This matters for rate-limit recovery — we never want to retry EARLIER than\n * the exponential schedule intended.\n *\n * The counter-seeded PRNG guarantees two concurrent retries get different\n * jitter values even in the same millisecond.\n */\nfunction calculateBackoffDelay(attempt: number): number {\n const exponentialDelay = RETRY_CONFIG.initialDelayMs * Math.pow(RETRY_CONFIG.backoffMultiplier, attempt);\n const cappedDelay = Math.min(exponentialDelay, RETRY_CONFIG.maxDelayMs);\n\n if (!RETRY_CONFIG.jitter) return cappedDelay;\n\n // Counter-seeded jitter — decorrelates concurrent callers.\n _jitterCounter = (_jitterCounter + 1) >>> 0;\n const seed = (Date.now() ^ (_jitterCounter * 0x9e3779b9)) >>> 0;\n // Simple xorshift from the seed — fast, good enough for jitter.\n let s = seed || 1;\n s ^= s << 13; s >>>= 0;\n s ^= s >>> 17;\n s ^= s << 5; s >>>= 0;\n const rand01 = (s >>> 0) / 0xffffffff; // [0, 1)\n\n const jitterRatio = 0.5; // up to +50% of base\n const jitter = rand01 * jitterRatio * cappedDelay;\n return cappedDelay + jitter;\n}\n\n/** Parse retry-after header value (seconds or HTTP date) */\nfunction parseRetryAfter(header: string | null): number | null {\n if (!header) return null;\n\n // Try parsing as seconds\n const seconds = parseInt(header, 10);\n if (!isNaN(seconds)) {\n return Math.min(seconds * 1000, RETRY_CONFIG.maxDelayMs); // Cap at max delay\n }\n\n // Try parsing as HTTP date\n const date = new Date(header);\n if (!isNaN(date.getTime())) {\n const delay = date.getTime() - Date.now();\n return Math.max(1000, Math.min(delay, RETRY_CONFIG.maxDelayMs)); // Min 1s, max configured cap\n }\n\n return null;\n}\n\n/**\n * Check if an error is retryable using the centralized error taxonomy.\n */\nfunction isRetryableError(error: unknown): boolean {\n return classifyProviderError(error).retryable;\n}\n\n/**\n * Extract HTTP status code from an error object if present.\n */\nfunction getErrorStatus(error: unknown): number | undefined {\n return classifyProviderError(error).httpStatus;\n}\n\n/** Try the fallback chain for a chat request. Returns null if chain is empty or exhausted. */\nasync function tryFallbackChain(\n options: ChatOptions,\n primaryModelId: string,\n originalError: Error,\n): Promise<ChatResponse | null> {\n const config = loadConfig();\n const chain = config.agent.fallbackChain;\n if (!chain || chain.length === 0) return null;\n\n const maxRetries = config.agent.fallbackMaxRetries ?? 3;\n let attempts = 0;\n\n for (const fallbackModelId of chain) {\n if (attempts >= maxRetries) break;\n if (fallbackModelId === primaryModelId) continue;\n\n attempts++;\n try {\n const { provider: fbProvider, model: fbModel } = resolveModel(fallbackModelId);\n const fbProviderName = fbProvider.name;\n\n // Check circuit breaker + rate-limit cooldown for fallback provider\n if (!canRequest(fbProviderName, true)) {\n const cb = getCircuitBreaker(fbProviderName);\n logger.warn(COMPONENT, `Skipping fallback ${fallbackModelId} — circuit breaker OPEN (${cb.failureCount} failures)`);\n continue;\n }\n\n logger.warn(COMPONENT, `Model ${primaryModelId} failed (${originalError.message}), falling back to ${fallbackModelId}`);\n const result = await fbProvider.chat({ ...options, model: fbModel });\n\n // Record success for circuit breaker\n recordSuccess(fbProviderName);\n\n lastFallbackEvent = {\n primary: primaryModelId,\n active: fallbackModelId,\n reason: originalError.message,\n timestamp: Date.now(),\n };\n return result;\n } catch (chainErr) {\n // Record failure for circuit breaker\n try {\n const { provider: fbProvider } = resolveModel(fallbackModelId);\n recordFailure(fbProvider.name);\n } catch {\n // Ignore if we can't resolve the provider for recording\n }\n logger.warn(COMPONENT, `Fallback model ${fallbackModelId} also failed: ${(chainErr as Error).message}`);\n continue;\n }\n }\n return null;\n}\n\n/**\n * Try the fallback chain for a streaming request. Returns an async generator\n * or null if no fallback could be attempted.\n *\n * Circuit-breaker accounting (fix for Phase X / streaming optimism bug):\n * The pre-fix version called `recordSuccess(fbProviderName)` immediately\n * after acquiring the generator — *before* a single chunk was emitted.\n * That meant a fallback provider that opened a stream and then errored\n * mid-flight was recorded as a success, lying to the breaker.\n *\n * This version returns a wrapped generator that:\n * - records success only after a `done` chunk OR the underlying\n * generator completes without throwing (real outcome)\n * - records failure if the underlying stream throws or yields an\n * `error` chunk after the first chunk\n */\nasync function tryFallbackChainStream(\n options: ChatOptions,\n primaryModelId: string,\n originalError: Error,\n): Promise<AsyncGenerator<ChatStreamChunk> | null> {\n const config = loadConfig();\n const chain = config.agent.fallbackChain;\n if (!chain || chain.length === 0) return null;\n\n const maxRetries = config.agent.fallbackMaxRetries ?? 3;\n let attempts = 0;\n\n for (const fallbackModelId of chain) {\n if (attempts >= maxRetries) break;\n if (fallbackModelId === primaryModelId) continue;\n\n attempts++;\n let fbProviderName: string;\n let gen: AsyncGenerator<ChatStreamChunk>;\n\n try {\n const { provider: fbProvider, model: fbModel } = resolveModel(fallbackModelId);\n fbProviderName = fbProvider.name;\n\n // Check circuit breaker + rate-limit cooldown for fallback provider\n if (!canRequest(fbProviderName, true)) {\n const cb = getCircuitBreaker(fbProviderName);\n logger.warn(COMPONENT, `Skipping stream fallback ${fallbackModelId} — circuit breaker OPEN (${cb.failureCount} failures)`);\n continue;\n }\n\n logger.warn(COMPONENT, `Stream model ${primaryModelId} failed (${originalError.message}), falling back to ${fallbackModelId}`);\n gen = fbProvider.chatStream({ ...options, model: fbModel });\n } catch (chainErr) {\n // Setup failure (resolveModel threw, etc.) — record breaker failure\n try {\n const { provider: fbProvider } = resolveModel(fallbackModelId);\n recordFailure(fbProvider.name);\n } catch {\n // Ignore if we can't resolve the provider for recording\n }\n logger.warn(COMPONENT, `Fallback stream model ${fallbackModelId} setup failed: ${(chainErr as Error).message}`);\n continue;\n }\n\n lastFallbackEvent = {\n primary: primaryModelId,\n active: fallbackModelId,\n reason: originalError.message,\n timestamp: Date.now(),\n };\n\n return monitorStreamForBreaker(gen, fbProviderName);\n }\n return null;\n}\n\n/**\n * Wrap a chat stream so circuit-breaker bookkeeping reflects real outcomes —\n * success only after a clean stream end, failure on error chunks or thrown\n * errors mid-stream. Hoisted to module scope so ESLint's `no-inner-declarations`\n * is happy and so the same wrapper can be reused by chatStream's priority\n * failover path below.\n */\nasync function* monitorStreamForBreaker(\n inner: AsyncGenerator<ChatStreamChunk>,\n providerName: string,\n): AsyncGenerator<ChatStreamChunk> {\n let recorded = false;\n try {\n for await (const chunk of inner) {\n if (chunk.type === 'error') {\n if (!recorded) { recordFailure(providerName); recorded = true; }\n }\n yield chunk;\n }\n if (!recorded) recordSuccess(providerName);\n } catch (innerErr) {\n if (!recorded) { recordFailure(providerName); recorded = true; }\n throw innerErr;\n }\n}\n\n/** Route a chat request to a mesh peer */\nasync function meshChat(peer: MeshPeer, modelId: string, message: string): Promise<ChatResponse> {\n const requestId = randomBytes(8).toString('hex');\n const config = loadConfig();\n const timeoutMs = config.mesh?.taskTimeoutMs || 120_000;\n logger.info(COMPONENT, `Routing \"${modelId}\" to mesh peer ${peer.hostname} (${peer.nodeId.slice(0, 8)}...)`);\n const result = await routeTaskToNode(peer.nodeId, requestId, message, modelId, timeoutMs) as Record<string, unknown>;\n if (result.error) {\n throw new Error(`Mesh peer error: ${result.error}`);\n }\n // Fire-and-forget analytics\n (async () => {\n const { trackModelUsage } = await import('../analytics/featureTracker.js');\n trackModelUsage(modelId, 'mesh', true);\n })().catch(() => {});\n return result as unknown as ChatResponse;\n}\n\n/**\n * Enhanced error message with provider and model context.\n */\nfunction createEnhancedErrorMessage(error: Error, providerName: string, model: string, attempt: number): string {\n const status = getErrorStatus(error);\n const statusInfo = status ? `[HTTP ${status}] ` : '';\n\n return [\n `Provider ${providerName}/${model} failed`,\n statusInfo + error.message,\n attempt > 0 ? `(attempt ${attempt + 1})` : null,\n ].filter(Boolean).join(': ');\n}\n\n/**\n * Send a chat request with exponential backoff retry and circuit breaker protection.\n * Automatically routes to the correct provider with error recovery and fallback chain.\n */\nexport async function chat(options: ChatOptions): Promise<ChatResponse> {\n // v6.0.1 — use the credential-aware default when no model is supplied.\n const modelId = options.model || getDefaultModelId();\n const { provider, model } = resolveModel(modelId);\n const providerName = provider.name;\n\n logger.info(COMPONENT, `Routing to ${provider.displayName} (model: ${model})`);\n\n // Fail-fast: reject before the circuit breaker if the provider has no\n // configured credentials. Without this guard, picking a model from a\n // provider you haven't configured a key for sends N requests that can\n // never succeed, trips the circuit breaker, and locks the provider out\n // for the reset window. (Real incident, 2026-05-10: openrouter circuit\n // tripped after 8 failures because OPENROUTER_API_KEY wasn't set.)\n if (!provider.isConfigured()) {\n const errorMsg = `Provider ${providerName} has no API key configured. Set ${\n providerName.toUpperCase().replace(/-/g, '_')\n }_API_KEY in env or via Settings → Integrations to use ${providerName} models.`;\n logger.warn(COMPONENT, errorMsg);\n const enhancedError = new Error(errorMsg);\n Object.assign(enhancedError, { status: 401, provider: providerName, model, missingKey: true });\n throw enhancedError;\n }\n\n // G4: Track fallback attempts for structured error reporting (OpenClaw pattern)\n const fallbackAttempts: Array<{ provider: string; model: string; error: string; reason: string }> = [];\n\n // Check circuit breaker before attempting request\n if (!canRequest(providerName)) {\n const cb = getCircuitBreaker(providerName);\n const errorMsg = `Circuit breaker OPEN for ${providerName}/${model} (${cb.failureCount} failures, reset in ${\n cb.openSince ? Math.round((CIRCUIT_BREAKER_CONFIG.resetTimeout - (Date.now() - cb.openSince)) / 1000) : 'unknown'\n }s)`;\n logger.warn(COMPONENT, errorMsg);\n const enhancedError = new Error(errorMsg);\n Object.assign(enhancedError, { status: 503, provider: providerName, model });\n throw enhancedError;\n }\n\n let lastError: Error | null = null;\n const maxRetries = RETRY_CONFIG.maxRetries;\n\n // Gap 1 (plan-this-logical-ocean): one-shot compression on CONTEXT_OVERFLOW.\n // The error taxonomy classifies overflows and sets `shouldCompress: true`,\n // but nothing used to act on it — the hint was dead code. Now we compact\n // options.messages via buildSmartContext and retry the SAME provider once\n // before falling through to model fallback / cross-provider failover.\n let compressionRetried = false;\n let thinkingStripped = false;\n\n // v4.13 ancestor-extraction (Hermes rate_limit_tracker): proactive backoff\n // before even sending the request. If the last response from this provider\n // indicated the quota window is nearly depleted, hold off briefly instead\n // of firing the request and getting a 429.\n try {\n const backoff = shouldBackOff(providerName);\n if (backoff) {\n logger.info(COMPONENT, `[RateLimit] Proactive backoff on ${providerName}: ${backoff.reason} — waiting ${Math.round(backoff.backoffMs)}ms`);\n await sleep(backoff.backoffMs);\n }\n } catch { /* never block on tracker errors */ }\n\n // Attempt request with retry logic\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n try {\n const result = await provider.chat({ ...options, model });\n\n // Strip chain-of-thought leakage from model responses\n if (result.content) {\n result.content = stripThinkingFromResponse(result.content);\n }\n\n // Record success for circuit breaker\n recordSuccess(providerName);\n lastFallbackEvent = null; // Clear fallback state on primary success\n\n // Log if this was a retry that succeeded\n if (attempt > 0) {\n logger.info(COMPONENT, `${provider.displayName}/${model} recovered after ${attempt} retry attempt(s)`);\n }\n\n // Fire-and-forget analytics\n (async () => {\n const { trackModelUsage } = await import('../analytics/featureTracker.js');\n trackModelUsage(model, providerName, true);\n })().catch(() => {});\n\n return result;\n } catch (error) {\n lastError = error as Error;\n\n // Classify error using centralized taxonomy\n const classified = classifyProviderError(error);\n\n // Only affect circuit breaker for genuine provider instability\n if (shouldAffectCircuitBreaker(classified)) {\n recordFailure(providerName);\n }\n\n // noFallback: caller (e.g. ModelProbe) requires the target model to\n // answer or the request to fail cleanly. Skip retries, fallback\n // chain, mesh routing, and provider failover entirely — otherwise\n // we would silently probe a different model and poison the caller's\n // data with unrelated capabilities.\n if (options.noFallback) {\n const errorMsg = createEnhancedErrorMessage(error as Error, providerName, model, attempt);\n const noFallbackError = new Error(\n `Probe target ${providerName}/${model} unreachable (noFallback=true): ${errorMsg}`\n );\n Object.assign(noFallbackError, {\n status: classified.httpStatus,\n provider: providerName,\n model,\n cause: error,\n failoverReason: classified.reason,\n noFallback: true,\n });\n throw noFallbackError;\n }\n\n // G2: Record rate-limit cooldown to prevent probe hammering\n if (classified.reason === FailoverReason.RATE_LIMIT) {\n recordRateLimitCooldown(providerName);\n }\n\n // Exhaust credential in pool if rotation is recommended\n if (classified.shouldRotateCredential) {\n const pool = getExistingPool(providerName);\n if (pool) {\n // Find which credential was used and exhaust it\n const status = pool.status();\n const lastUsed = status.find(s => s.available);\n if (lastUsed) {\n pool.exhaust(lastUsed.name, classified.cooldownMs || 60000);\n }\n }\n }\n\n // Gap 1: act on shouldCompress hint BEFORE generic retry/fallback.\n // On CONTEXT_OVERFLOW (or any future reason that sets shouldCompress),\n // compact options.messages via buildSmartContext and retry the same\n // provider+model once. Only fires on the FIRST such error per call —\n // if the compacted request still overflows, we drop through to the\n // normal retry/fallback ladder instead of shrinking forever.\n if (classified.shouldCompress && !compressionRetried && Array.isArray(options.messages)) {\n compressionRetried = true;\n const beforeCount = options.messages.length;\n // Conservative target — most of the whitelisted Ollama cloud\n // models have >=32K context; 24K leaves headroom for the\n // completion itself and any tool schemas the provider adds.\n const compactTokens = 24000;\n try {\n const compacted = buildSmartContext(options.messages, compactTokens);\n if (compacted.length > 0 && compacted.length <= beforeCount) {\n options = { ...options, messages: compacted };\n logger.info(\n COMPONENT,\n `[Router] ${classified.reason} — compacted context ${beforeCount}→${compacted.length} msgs, retrying ${providerName}/${model}`,\n );\n // Retry immediately — no backoff needed, we changed the input\n continue;\n }\n logger.warn(COMPONENT, `[Router] Compression produced empty/larger output — skipping compress retry`);\n } catch (compErr) {\n logger.warn(COMPONENT, `[Router] Compression failed: ${(compErr as Error).message} — falling through`);\n }\n }\n\n // Gap 2: act on THINKING_NOT_SUPPORTED — strip thinking options and retry\n // once on the same provider. This handles models like titan-qwen3.5:4b\n // that return HTTP 400 \"does not support thinking\". We mutate options only\n // once so a second THINKING_NOT_SUPPORTED falls through to normal retry ladder.\n if (classified.reason === FailoverReason.THINKING_NOT_SUPPORTED && !thinkingStripped) {\n thinkingStripped = true;\n const providerOpts = options.providerOptions ? { ...options.providerOptions } : {};\n // Remove Ollama/OpenAI-compat thinking keys\n delete (providerOpts as Record<string, unknown>).think;\n delete (providerOpts as Record<string, unknown>).thinking;\n delete (providerOpts as Record<string, unknown>).thinking_mode;\n delete (providerOpts as Record<string, unknown>).budget_tokens;\n delete (providerOpts as Record<string, unknown>).enable_thinking;\n options = { ...options, providerOptions: providerOpts };\n logger.info(COMPONENT, `[Router] THINKING_NOT_SUPPORTED — stripped thinking flags, retrying ${providerName}/${model}`);\n continue;\n }\n\n const errorMsg = createEnhancedErrorMessage(error as Error, providerName, model, attempt);\n\n // Check if we should retry\n if (classified.retryable && attempt < maxRetries) {\n // Use taxonomy cooldown or calculate backoff, whichever is larger\n let retryDelayMs = Math.max(classified.cooldownMs, calculateBackoffDelay(attempt));\n\n // Hunt Finding #37 (2026-04-14): previous code tried\n // `(error as Response)?.headers?.get?.('Retry-After')` which\n // always returned undefined at runtime because the error is\n // an Error object, not a Response. Retry-After headers were\n // never actually respected. Providers now attach retryAfterMs\n // to the thrown error via createProviderError().\n const errWithHints = error as { retryAfterMs?: number | null; headers?: { get?(k: string): string | null } };\n if (typeof errWithHints.retryAfterMs === 'number' && errWithHints.retryAfterMs > 0) {\n retryDelayMs = errWithHints.retryAfterMs;\n logger.info(COMPONENT, `[RateLimit] Respecting Retry-After: ${Math.round(retryDelayMs / 1000)}s`);\n } else {\n // Back-compat: old-style error that happens to wrap a Response\n const retryAfter = errWithHints.headers?.get?.('Retry-After');\n if (retryAfter) {\n const parsed = parseRetryAfter(retryAfter);\n if (parsed !== null) {\n retryDelayMs = parsed;\n logger.info(COMPONENT, `[RateLimit] Respecting Retry-After (legacy): ${Math.round(retryDelayMs / 1000)}s`);\n }\n }\n }\n\n logger.warn(COMPONENT, `${errorMsg} [${classified.reason}] — retrying in ${Math.round(retryDelayMs)}ms`);\n await sleep(retryDelayMs);\n continue;\n }\n\n // Not retryable or max retries exceeded\n if (!classified.retryable) {\n logger.error(COMPONENT, `${errorMsg} — not retryable [${classified.reason}] (${classified.httpStatus ? `HTTP ${classified.httpStatus}` : 'unknown error'})`);\n } else {\n logger.error(COMPONENT, `${errorMsg} — max retries (${maxRetries}) exceeded [${classified.reason}]`);\n }\n\n // Try configured fallback chain first (model-level fallback)\n if (classified.retryable || classified.shouldFallback) {\n const chainResult = await tryFallbackChain(options, modelId, error as Error);\n if (chainResult) {\n logger.info(COMPONENT, `Fallback chain recovered from ${providerName}/${model} failure [${classified.reason}]`);\n return chainResult;\n }\n }\n\n // Try mesh peers before local failover\n const config = loadConfig();\n if (config.mesh?.enabled) {\n const peer = findModelOnMesh(modelId);\n if (peer) {\n try {\n const message = Array.isArray(options.messages)\n ? options.messages.map(m => m.content).join('\\n')\n : (options as unknown as Record<string, unknown>).message as string || '';\n return await meshChat(peer, modelId, message);\n } catch (meshErr) {\n logger.warn(COMPONENT, `Mesh routing failed: ${(meshErr as Error).message}`);\n }\n }\n }\n\n // Attempt failover to other providers (only on first failure, not after retries)\n if (attempt === 0) {\n const failoverOrder = getFailoverOrder(providerName);\n for (const fallbackName of failoverOrder) {\n if (fallbackName === providerName) continue;\n\n // Check circuit breaker + rate-limit cooldown for fallback provider\n if (!canRequest(fallbackName, true)) {\n logger.debug(COMPONENT, `Skipping fallback ${fallbackName} — circuit breaker OPEN`);\n continue;\n }\n\n const fallback = providers.get(fallbackName);\n if (!fallback) continue;\n\n let fbModelName = 'unknown';\n try {\n const healthy = await fallback.healthCheck();\n if (!healthy) continue;\n\n const models = await fallback.listModels();\n if (models.length === 0) continue;\n\n // Prefer a model with a similar name prefix (e.g. claude-* → claude-*)\n const originalPrefix = model.split('-')[0];\n fbModelName = models.find(m => m.startsWith(originalPrefix)) || models[0];\n\n logger.warn(COMPONENT, `Failing over from ${providerName}/${model} → ${fallbackName}/${fbModelName}`);\n const result = await fallback.chat({ ...options, model: fbModelName });\n recordSuccess(fallbackName); // Record success for the fallback provider\n // Fire-and-forget analytics\n (async () => {\n const { trackModelUsage } = await import('../analytics/featureTracker.js');\n trackModelUsage(fbModelName, fallbackName, true);\n })().catch(() => {});\n return result;\n } catch (fallbackErr) {\n recordFailure(fallbackName); // Record failure for the fallback provider too\n // G4: Record fallback attempt for structured error chain\n fallbackAttempts.push({\n provider: fallbackName,\n model: fbModelName,\n error: (fallbackErr as Error).message,\n reason: classifyProviderError(fallbackErr).reason,\n });\n logger.warn(COMPONENT, `Fallback ${fallbackName} also failed: ${(fallbackErr as Error).message}`);\n continue;\n }\n }\n }\n\n // G4: Record the primary attempt too\n fallbackAttempts.unshift({\n provider: providerName,\n model,\n error: (error as Error).message,\n reason: classified.reason,\n });\n\n // Fire-and-forget analytics\n (async () => {\n const { trackModelUsage } = await import('../analytics/featureTracker.js');\n trackModelUsage(model, providerName, false);\n })().catch(() => {});\n\n // All recovery options exhausted, throw enhanced error\n const attemptSummary = fallbackAttempts.length > 1\n ? ` | Tried ${fallbackAttempts.length} providers: ${fallbackAttempts.map(a => `${a.provider}/${a.model} [${a.reason}]`).join(', ')}`\n : '';\n const finalError = new Error(`All providers failed: ${errorMsg}${attemptSummary}`);\n Object.assign(finalError, {\n status: classified.httpStatus,\n provider: providerName,\n model,\n cause: error,\n failoverReason: classified.reason,\n // G4: Structured fallback attempt chain (OpenClaw FallbackSummaryError pattern)\n fallbackAttempts,\n });\n throw finalError;\n }\n }\n\n // Should never reach here, but TypeScript requires it\n throw lastError || new Error(`Provider ${providerName}/${model} failed after all retries`);\n}\n\n/**\n * Send a streaming chat request with exponential backoff retry and circuit breaker protection.\n */\nexport async function* chatStream(options: ChatOptions): AsyncGenerator<ChatStreamChunk> {\n // v6.0.1 — use the credential-aware default when no model is supplied.\n const modelId = options.model || getDefaultModelId();\n const { provider, model } = resolveModel(modelId);\n const providerName = provider.name;\n\n logger.info(COMPONENT, `Streaming via ${provider.displayName} (model: ${model})`);\n\n // Fail-fast: see chat() for full reasoning. Reject before the circuit\n // breaker if the provider has no configured credentials, so picking\n // an unconfigured model can't trip the breaker.\n if (!provider.isConfigured()) {\n const errorMsg = `Provider ${providerName} has no API key configured. Set ${\n providerName.toUpperCase().replace(/-/g, '_')\n }_API_KEY in env or via Settings → Integrations to use ${providerName} models.`;\n logger.warn(COMPONENT, errorMsg);\n yield { type: 'error', error: errorMsg };\n return;\n }\n\n // Check circuit breaker before attempting request\n if (!canRequest(providerName)) {\n const cb = getCircuitBreaker(providerName);\n yield {\n type: 'error',\n error: `[CircuitBreaker] Circuit OPEN: ${providerName}/${model} (${cb.failureCount} failures, testing in ${\n Math.round((CIRCUIT_BREAKER_CONFIG.resetTimeout - (Date.now() - cb.openSince!)) / 1000)\n }s)`,\n };\n return;\n }\n\n let lastError: Error | null = null;\n const maxRetries = RETRY_CONFIG.maxRetries;\n\n // Once-per-call latches so we don't repeat failover work after a retry\n // burst — both fallback paths can be reached on any exhausted-retry\n // attempt, but each is attempted at most once per chatStream invocation\n // (Task 4: prevent infinite-loop recovery, formerly attempt===0 gate).\n let fallbackChainAttempted = false;\n let priorityFailoverAttempted = false;\n\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n try {\n // Stream from provider — record success only on first non-error\n // chunk so we don't claim success for a stream that never produced.\n let recordedSuccess = false;\n for await (const chunk of provider.chatStream({ ...options, model })) {\n if (!recordedSuccess && chunk.type !== 'error' && attempt === 0) {\n recordSuccess(providerName);\n recordedSuccess = true;\n }\n lastFallbackEvent = null;\n yield chunk;\n }\n\n // Log if this was a retry that succeeded\n if (attempt > 0) {\n logger.info(COMPONENT, `${provider.displayName}/${model} stream recovered after ${attempt} retry attempt(s)`);\n }\n return;\n } catch (error) {\n lastError = error as Error;\n\n // Classify error using centralized taxonomy\n const classified = classifyProviderError(error);\n if (shouldAffectCircuitBreaker(classified)) {\n recordFailure(providerName);\n }\n\n const errorMsg = createEnhancedErrorMessage(error as Error, providerName, model, attempt);\n\n // Check if we should retry\n if (classified.retryable && attempt < maxRetries) {\n const retryDelayMs = Math.max(classified.cooldownMs, calculateBackoffDelay(attempt));\n logger.warn(COMPONENT, `${errorMsg} [${classified.reason}] — streaming retry in ${Math.round(retryDelayMs)}ms`);\n\n // Task 2: emit a dedicated `retry` event instead of leaking a\n // text chunk (e.g. \"[Retrying request (1/4) due to ...]\") into\n // the user-visible stream. UI consumers should display this\n // as a status indicator, never forward to the assistant message.\n yield {\n type: 'retry' as const,\n attempt: attempt + 1,\n maxRetries,\n reason: classified.reason,\n provider: providerName,\n model,\n delayMs: Math.round(retryDelayMs),\n };\n\n await sleep(retryDelayMs);\n continue;\n }\n\n // Not retryable or max retries exceeded\n if (!classified.retryable) {\n logger.error(COMPONENT, `${errorMsg} — streaming not retryable [${classified.reason}]`);\n } else {\n logger.error(COMPONENT, `${errorMsg} — streaming max retries exceeded [${classified.reason}]`);\n }\n\n // Try configured fallback chain first (once per chatStream call)\n if (!fallbackChainAttempted && (classified.retryable || classified.shouldFallback)) {\n fallbackChainAttempted = true;\n const chainStream = await tryFallbackChainStream(options, modelId, error as Error);\n if (chainStream) {\n yield {\n type: 'failover' as const,\n originalProvider: providerName,\n originalModel: model,\n error: (error as Error).message,\n };\n yield* chainStream;\n return;\n }\n }\n\n // Try mesh peers (non-streaming fallback for now)\n const config = loadConfig();\n if (config.mesh?.enabled) {\n const peer = findModelOnMesh(modelId);\n if (peer) {\n try {\n const message = Array.isArray(options.messages)\n ? options.messages.map(m => m.content).join('\\n')\n : (options as unknown as Record<string, unknown>).message as string || '';\n const result = await meshChat(peer, modelId, message);\n yield { type: 'text' as const, content: result.content };\n yield { type: 'done' as const };\n return;\n } catch (meshErr) {\n logger.warn(COMPONENT, `Mesh stream routing failed: ${(meshErr as Error).message}`);\n }\n }\n }\n\n // Task 4: priority-failover loop now runs on ANY exhausted-retry\n // path, not just attempt === 0. The `priorityFailoverAttempted`\n // latch ensures it executes at most once per chatStream call so\n // we don't loop through the failover order on every retry burst.\n if (!priorityFailoverAttempted) {\n priorityFailoverAttempted = true;\n const failoverOrder = getFailoverOrder(providerName);\n let failedOver = false;\n\n for (const fallbackName of failoverOrder) {\n if (fallbackName === providerName) continue;\n\n if (!canRequest(fallbackName, true)) {\n logger.debug(COMPONENT, `Skipping stream fallback ${fallbackName} — circuit breaker OPEN`);\n continue;\n }\n\n const fallback = providers.get(fallbackName);\n if (!fallback) continue;\n\n try {\n const healthy = await fallback.healthCheck();\n if (!healthy) continue;\n\n const models = await fallback.listModels();\n if (models.length === 0) continue;\n\n const originalPrefix = model.split('-')[0];\n const preferred = models.find(m => m.startsWith(originalPrefix)) || models[0];\n\n logger.warn(COMPONENT, `Stream failing over from ${providerName}/${model} → ${fallbackName}/${preferred}`);\n\n // Notify consumer about failover\n yield {\n type: 'failover' as const,\n originalProvider: providerName,\n originalModel: model,\n error: errorMsg,\n };\n\n // Wrap the failover stream so we record actual outcome,\n // not just optimistic success-on-generator-acquire (Task 3\n // applied here too — same pattern as tryFallbackChainStream).\n let recorded = false;\n try {\n for await (const chunk of fallback.chatStream({ ...options, model: preferred })) {\n if (chunk.type === 'error' && !recorded) {\n recordFailure(fallbackName);\n recorded = true;\n }\n yield chunk;\n }\n if (!recorded) recordSuccess(fallbackName);\n } catch (innerErr) {\n if (!recorded) recordFailure(fallbackName);\n throw innerErr;\n }\n failedOver = true;\n break;\n } catch (fallbackErr) {\n recordFailure(fallbackName);\n logger.warn(COMPONENT, `Stream fallback ${fallbackName} also failed: ${(fallbackErr as Error).message}`);\n continue;\n }\n }\n\n if (failedOver) return;\n }\n\n // All recovery options exhausted\n yield { type: 'error', error: `All streaming providers failed: ${errorMsg}` };\n return;\n }\n }\n\n // Should never reach here\n yield { type: 'error', error: lastError?.message || 'Streaming failed after all retries' };\n}\n\n/** Health check all providers */\nexport async function healthCheckAll(): Promise<Record<string, boolean>> {\n initProviders();\n const entries = Array.from(providers.entries());\n const settled = await Promise.allSettled(\n entries.map(([, provider]) => provider.healthCheck())\n );\n const results: Record<string, boolean> = {};\n for (let i = 0; i < entries.length; i++) {\n const [name] = entries[i];\n const outcome = settled[i];\n results[name] = outcome.status === 'fulfilled' ? outcome.value : false;\n }\n return results;\n}\n"],"mappings":"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","names":["errorMsg"]}
|
package/dist/utils/constants.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { homedir } from "os";
|
|
3
3
|
import { join } from "path";
|
|
4
|
-
const TITAN_VERSION = "6.0.
|
|
4
|
+
const TITAN_VERSION = "6.0.2";
|
|
5
5
|
const TITAN_CODENAME = "Living Canvas";
|
|
6
6
|
const TITAN_NAME = "TITAN";
|
|
7
7
|
const TITAN_FULL_NAME = "The Intelligent Task Automation Network";
|