Files
hack/tools/scripts/llm-key-hunter/results/_cache/content/bhengubv_circleai.json
T
OpenCode 68a9071cf1 feat: reconcile local master with origin (exa/hot-platform hunters, leaks ledger, latest results)
Local branch had diverged from origin/master (sibling commits on the same base). Rewrote local history linearly on top of origin/master, folding in all local content: exa/hot-platform discovery hunters, leaks ledger, nightly sweep orchestrator, updated .gitignore and skill docs, plus the latest hunt output and vault state. Remote-only files (vault keys, channel scripts) were restored rather than dropped, so the resulting tree is a full union of both sides.
2026-08-07 12:53:47 +08:00

1 line
47 KiB
JSON

{"repo": "bhengubv/circleai", "files": {"src/CircleAI.Hosting.CloudFallback/Options.cs": {"cbdc9a66fed3405215524d3d9e70a56a4b96d0b0": {"t": 1785746197.801123, "n": 3440, "h": "61a044fd23e4ef09", "c": "// Options.cs\n//\n// (3.2.0) Per-provider option shapes. Lifted from Concierge with no\n// substantive change — the host owns the factory, the key comes from\n// IConfiguration / env, the runtime never embeds it.\n\nusing System;\n\nnamespace CircleAI.Hosting.CloudFallback;\n\n/// <summary>(3.2.0) OpenAI Chat Completions options. Defaults match Concierge's working config.</summary>\npublic sealed class OpenAiChatOptions\n{\n public Uri BaseAddress { get; init; } = new(\"https://api.openai.com\");\n public string? ApiKey { get; init; }\n public string Model { get; init; } = \"gpt-4o-mini\";\n public float Temperature { get; init; } = 0.7f;\n public int MaxTokens { get; init; } = 1024;\n}\n\n/// <summary>(3.2.0) Anthropic Messages options.</summary>\npublic sealed class AnthropicChatOptions\n{\n public Uri BaseAddress { get; init; } = new(\"https://api.anthropic.com\");\n public string? ApiKey { get; init; }\n public string Model { get; init; } = \"claude-3-5-sonnet-latest\";\n public float Temperature { get; init; } = 0.7f;\n public int MaxTokens { get; init; } = 1024;\n public string AnthropicVersion { get; init; } = \"2023-06-01\";\n}\n\n/// <summary>(3.2.0) Google Gemini options.</summary>\npublic sealed class GeminiChatOptions\n{\n public Uri BaseAddress { get; init; } = new(\"https://generativelanguage.googleapis.com\");\n public string? ApiKey { get; init; }\n public string Model { get; init; } = \"gemini-2.0-flash\";\n public float Temperature { get; init; } = 0.7f;\n public int MaxOutputTokens { get; init; } = 1024;\n}\n\n/// <summary>(3.3.0) Groq Chat Completions options. OpenAI-compatible at <c>/openai/v1/chat/completions</c>.</summary>\npublic sealed class GroqChatOptions\n{\n public Uri BaseAddress { get; init; } = new(\"https://api.groq.com\");\n public string? ApiKey { get; init; }\n public string Model { get; init; } = \"llama-3.3-70b-versatile\";\n public float Temperature { get; init; } = 0.7f;\n public int MaxTokens { get; init; } = 1024;\n}\n\n/// <summary>(3.3.0) Cerebras options. OpenAI-compatible at <c>/v1/chat/completions</c>.</summary>\npublic sealed class CerebrasChatOptions\n{\n public Uri BaseAddress { get; init; } = new(\"https://api.cerebras.ai\");\n public string? ApiKey { get; init; }\n public string Model { get; init; } = \"llama3.3-70b\";\n public float Temperature { get; init; } = 0.7f;\n public int MaxTokens { get; init; } = 1024;\n}\n\n/// <summary>(3.3.0) Together AI options. OpenAI-compatible at <c>/v1/chat/completions</c>.</summary>\npublic sealed class TogetherChatOptions\n{\n public Uri BaseAddress { get; init; } = new(\"https://api.together.xyz\");\n public string? ApiKey { get; init; }\n public string Model { get; init; } = \"meta-llama/Llama-3.3-70B-Instruct-Turbo\";\n public float Temperature { get; init; } = 0.7f;\n public int MaxTokens { get; init; } = 1024;\n}\n\n/// <summary>(3.3.0) DeepSeek options. OpenAI-compatible at <c>/v1/chat/completions</c>.</summary>\npublic sealed class DeepSeekChatOptions\n{\n public Uri BaseAddress { get; init; } = new(\"https://api.deepseek.com\");\n public string? ApiKey { get; init; }\n public string Model { get; init; } = \"deepseek-chat\";\n public float Temperature { get; init; } = 0.7f;\n public int MaxTokens { get; init; } = 1024;\n}\n"}, "b0cd8d037994df685951e71fc66a45544e6fdd1f": {"t": 1785760532.965991, "n": 3440, "h": "61a044fd23e4ef09", "c": "// Options.cs\n//\n// (3.2.0) Per-provider option shapes. Lifted from Concierge with no\n// substantive change — the host owns the factory, the key comes from\n// IConfiguration / env, the runtime never embeds it.\n\nusing System;\n\nnamespace CircleAI.Hosting.CloudFallback;\n\n/// <summary>(3.2.0) OpenAI Chat Completions options. Defaults match Concierge's working config.</summary>\npublic sealed class OpenAiChatOptions\n{\n public Uri BaseAddress { get; init; } = new(\"https://api.openai.com\");\n public string? ApiKey { get; init; }\n public string Model { get; init; } = \"gpt-4o-mini\";\n public float Temperature { get; init; } = 0.7f;\n public int MaxTokens { get; init; } = 1024;\n}\n\n/// <summary>(3.2.0) Anthropic Messages options.</summary>\npublic sealed class AnthropicChatOptions\n{\n public Uri BaseAddress { get; init; } = new(\"https://api.anthropic.com\");\n public string? ApiKey { get; init; }\n public string Model { get; init; } = \"claude-3-5-sonnet-latest\";\n public float Temperature { get; init; } = 0.7f;\n public int MaxTokens { get; init; } = 1024;\n public string AnthropicVersion { get; init; } = \"2023-06-01\";\n}\n\n/// <summary>(3.2.0) Google Gemini options.</summary>\npublic sealed class GeminiChatOptions\n{\n public Uri BaseAddress { get; init; } = new(\"https://generativelanguage.googleapis.com\");\n public string? ApiKey { get; init; }\n public string Model { get; init; } = \"gemini-2.0-flash\";\n public float Temperature { get; init; } = 0.7f;\n public int MaxOutputTokens { get; init; } = 1024;\n}\n\n/// <summary>(3.3.0) Groq Chat Completions options. OpenAI-compatible at <c>/openai/v1/chat/completions</c>.</summary>\npublic sealed class GroqChatOptions\n{\n public Uri BaseAddress { get; init; } = new(\"https://api.groq.com\");\n public string? ApiKey { get; init; }\n public string Model { get; init; } = \"llama-3.3-70b-versatile\";\n public float Temperature { get; init; } = 0.7f;\n public int MaxTokens { get; init; } = 1024;\n}\n\n/// <summary>(3.3.0) Cerebras options. OpenAI-compatible at <c>/v1/chat/completions</c>.</summary>\npublic sealed class CerebrasChatOptions\n{\n public Uri BaseAddress { get; init; } = new(\"https://api.cerebras.ai\");\n public string? ApiKey { get; init; }\n public string Model { get; init; } = \"llama3.3-70b\";\n public float Temperature { get; init; } = 0.7f;\n public int MaxTokens { get; init; } = 1024;\n}\n\n/// <summary>(3.3.0) Together AI options. OpenAI-compatible at <c>/v1/chat/completions</c>.</summary>\npublic sealed class TogetherChatOptions\n{\n public Uri BaseAddress { get; init; } = new(\"https://api.together.xyz\");\n public string? ApiKey { get; init; }\n public string Model { get; init; } = \"meta-llama/Llama-3.3-70B-Instruct-Turbo\";\n public float Temperature { get; init; } = 0.7f;\n public int MaxTokens { get; init; } = 1024;\n}\n\n/// <summary>(3.3.0) DeepSeek options. OpenAI-compatible at <c>/v1/chat/completions</c>.</summary>\npublic sealed class DeepSeekChatOptions\n{\n public Uri BaseAddress { get; init; } = new(\"https://api.deepseek.com\");\n public string? ApiKey { get; init; }\n public string Model { get; init; } = \"deepseek-chat\";\n public float Temperature { get; init; } = 0.7f;\n public int MaxTokens { get; init; } = 1024;\n}\n"}, "e5a7472468f4f23a9fa771fd011c2710fc798054": {"t": 1785760534.2551105, "n": 1631, "h": "a58aba95c79b912f", "c": "// Options.cs\n//\n// (3.2.0) Per-provider option shapes. Lifted from Concierge with no\n// substantive change — the host owns the factory, the key comes from\n// IConfiguration / env, the runtime never embeds it.\n\nusing System;\n\nnamespace CircleAI.Hosting.CloudFallback;\n\n/// <summary>(3.2.0) OpenAI Chat Completions options. Defaults match Concierge's working config.</summary>\npublic sealed class OpenAiChatOptions\n{\n public Uri BaseAddress { get; init; } = new(\"https://api.openai.com\");\n public string? ApiKey { get; init; }\n public string Model { get; init; } = \"gpt-4o-mini\";\n public float Temperature { get; init; } = 0.7f;\n public int MaxTokens { get; init; } = 1024;\n}\n\n/// <summary>(3.2.0) Anthropic Messages options.</summary>\npublic sealed class AnthropicChatOptions\n{\n public Uri BaseAddress { get; init; } = new(\"https://api.anthropic.com\");\n public string? ApiKey { get; init; }\n public string Model { get; init; } = \"claude-3-5-sonnet-latest\";\n public float Temperature { get; init; } = 0.7f;\n public int MaxTokens { get; init; } = 1024;\n public string AnthropicVersion { get; init; } = \"2023-06-01\";\n}\n\n/// <summary>(3.2.0) Google Gemini options.</summary>\npublic sealed class GeminiChatOptions\n{\n public Uri BaseAddress { get; init; } = new(\"https://generativelanguage.googleapis.com\");\n public string? ApiKey { get; init; }\n public string Model { get; init; } = \"gemini-2.0-flash\";\n public float Temperature { get; init; } = 0.7f;\n public int MaxOutputTokens { get; init; } = 1024;\n}\n"}, "f2d7627873980592db8a111ec0f32a565eee232d": {"t": 1786067628.9859476, "n": 3440, "h": "61a044fd23e4ef09", "c": "// Options.cs\n//\n// (3.2.0) Per-provider option shapes. Lifted from Concierge with no\n// substantive change — the host owns the factory, the key comes from\n// IConfiguration / env, the runtime never embeds it.\n\nusing System;\n\nnamespace CircleAI.Hosting.CloudFallback;\n\n/// <summary>(3.2.0) OpenAI Chat Completions options. Defaults match Concierge's working config.</summary>\npublic sealed class OpenAiChatOptions\n{\n public Uri BaseAddress { get; init; } = new(\"https://api.openai.com\");\n public string? ApiKey { get; init; }\n public string Model { get; init; } = \"gpt-4o-mini\";\n public float Temperature { get; init; } = 0.7f;\n public int MaxTokens { get; init; } = 1024;\n}\n\n/// <summary>(3.2.0) Anthropic Messages options.</summary>\npublic sealed class AnthropicChatOptions\n{\n public Uri BaseAddress { get; init; } = new(\"https://api.anthropic.com\");\n public string? ApiKey { get; init; }\n public string Model { get; init; } = \"claude-3-5-sonnet-latest\";\n public float Temperature { get; init; } = 0.7f;\n public int MaxTokens { get; init; } = 1024;\n public string AnthropicVersion { get; init; } = \"2023-06-01\";\n}\n\n/// <summary>(3.2.0) Google Gemini options.</summary>\npublic sealed class GeminiChatOptions\n{\n public Uri BaseAddress { get; init; } = new(\"https://generativelanguage.googleapis.com\");\n public string? ApiKey { get; init; }\n public string Model { get; init; } = \"gemini-2.0-flash\";\n public float Temperature { get; init; } = 0.7f;\n public int MaxOutputTokens { get; init; } = 1024;\n}\n\n/// <summary>(3.3.0) Groq Chat Completions options. OpenAI-compatible at <c>/openai/v1/chat/completions</c>.</summary>\npublic sealed class GroqChatOptions\n{\n public Uri BaseAddress { get; init; } = new(\"https://api.groq.com\");\n public string? ApiKey { get; init; }\n public string Model { get; init; } = \"llama-3.3-70b-versatile\";\n public float Temperature { get; init; } = 0.7f;\n public int MaxTokens { get; init; } = 1024;\n}\n\n/// <summary>(3.3.0) Cerebras options. OpenAI-compatible at <c>/v1/chat/completions</c>.</summary>\npublic sealed class CerebrasChatOptions\n{\n public Uri BaseAddress { get; init; } = new(\"https://api.cerebras.ai\");\n public string? ApiKey { get; init; }\n public string Model { get; init; } = \"llama3.3-70b\";\n public float Temperature { get; init; } = 0.7f;\n public int MaxTokens { get; init; } = 1024;\n}\n\n/// <summary>(3.3.0) Together AI options. OpenAI-compatible at <c>/v1/chat/completions</c>.</summary>\npublic sealed class TogetherChatOptions\n{\n public Uri BaseAddress { get; init; } = new(\"https://api.together.xyz\");\n public string? ApiKey { get; init; }\n public string Model { get; init; } = \"meta-llama/Llama-3.3-70B-Instruct-Turbo\";\n public float Temperature { get; init; } = 0.7f;\n public int MaxTokens { get; init; } = 1024;\n}\n\n/// <summary>(3.3.0) DeepSeek options. OpenAI-compatible at <c>/v1/chat/completions</c>.</summary>\npublic sealed class DeepSeekChatOptions\n{\n public Uri BaseAddress { get; init; } = new(\"https://api.deepseek.com\");\n public string? ApiKey { get; init; }\n public string Model { get; init; } = \"deepseek-chat\";\n public float Temperature { get; init; } = 0.7f;\n public int MaxTokens { get; init; } = 1024;\n}\n"}, "937e80e9e8aac88b1509b7334271a0717221797d": {"t": 1786068388.7355642, "n": 3440, "h": "61a044fd23e4ef09", "c": "// Options.cs\n//\n// (3.2.0) Per-provider option shapes. Lifted from Concierge with no\n// substantive change — the host owns the factory, the key comes from\n// IConfiguration / env, the runtime never embeds it.\n\nusing System;\n\nnamespace CircleAI.Hosting.CloudFallback;\n\n/// <summary>(3.2.0) OpenAI Chat Completions options. Defaults match Concierge's working config.</summary>\npublic sealed class OpenAiChatOptions\n{\n public Uri BaseAddress { get; init; } = new(\"https://api.openai.com\");\n public string? ApiKey { get; init; }\n public string Model { get; init; } = \"gpt-4o-mini\";\n public float Temperature { get; init; } = 0.7f;\n public int MaxTokens { get; init; } = 1024;\n}\n\n/// <summary>(3.2.0) Anthropic Messages options.</summary>\npublic sealed class AnthropicChatOptions\n{\n public Uri BaseAddress { get; init; } = new(\"https://api.anthropic.com\");\n public string? ApiKey { get; init; }\n public string Model { get; init; } = \"claude-3-5-sonnet-latest\";\n public float Temperature { get; init; } = 0.7f;\n public int MaxTokens { get; init; } = 1024;\n public string AnthropicVersion { get; init; } = \"2023-06-01\";\n}\n\n/// <summary>(3.2.0) Google Gemini options.</summary>\npublic sealed class GeminiChatOptions\n{\n public Uri BaseAddress { get; init; } = new(\"https://generativelanguage.googleapis.com\");\n public string? ApiKey { get; init; }\n public string Model { get; init; } = \"gemini-2.0-flash\";\n public float Temperature { get; init; } = 0.7f;\n public int MaxOutputTokens { get; init; } = 1024;\n}\n\n/// <summary>(3.3.0) Groq Chat Completions options. OpenAI-compatible at <c>/openai/v1/chat/completions</c>.</summary>\npublic sealed class GroqChatOptions\n{\n public Uri BaseAddress { get; init; } = new(\"https://api.groq.com\");\n public string? ApiKey { get; init; }\n public string Model { get; init; } = \"llama-3.3-70b-versatile\";\n public float Temperature { get; init; } = 0.7f;\n public int MaxTokens { get; init; } = 1024;\n}\n\n/// <summary>(3.3.0) Cerebras options. OpenAI-compatible at <c>/v1/chat/completions</c>.</summary>\npublic sealed class CerebrasChatOptions\n{\n public Uri BaseAddress { get; init; } = new(\"https://api.cerebras.ai\");\n public string? ApiKey { get; init; }\n public string Model { get; init; } = \"llama3.3-70b\";\n public float Temperature { get; init; } = 0.7f;\n public int MaxTokens { get; init; } = 1024;\n}\n\n/// <summary>(3.3.0) Together AI options. OpenAI-compatible at <c>/v1/chat/completions</c>.</summary>\npublic sealed class TogetherChatOptions\n{\n public Uri BaseAddress { get; init; } = new(\"https://api.together.xyz\");\n public string? ApiKey { get; init; }\n public string Model { get; init; } = \"meta-llama/Llama-3.3-70B-Instruct-Turbo\";\n public float Temperature { get; init; } = 0.7f;\n public int MaxTokens { get; init; } = 1024;\n}\n\n/// <summary>(3.3.0) DeepSeek options. OpenAI-compatible at <c>/v1/chat/completions</c>.</summary>\npublic sealed class DeepSeekChatOptions\n{\n public Uri BaseAddress { get; init; } = new(\"https://api.deepseek.com\");\n public string? ApiKey { get; init; }\n public string Model { get; init; } = \"deepseek-chat\";\n public float Temperature { get; init; } = 0.7f;\n public int MaxTokens { get; init; } = 1024;\n}\n"}}, "typescript/src/hosting/cloud_fallback/index.ts": {"937e80e9e8aac88b1509b7334271a0717221797d": {"t": 1786068363.1992455, "n": 27766, "h": "bdec62ece9177eb1", "c": "// hosting/cloud_fallback/index.ts\n//\n// Port of CircleAI.Hosting.CloudFallback:\n// • CloudFallbackChain.cs — IConfigurableChatGenerator + start-of-call\n// ordering chain (first ready generator wins; fail-soft frames are skipped)\n// • BackupBrainOrchestrator.cs — mid-turn failover with degrade/cool-down\n// • ServerSentEventsReader.cs — shared SSE frame reader\n// • Options.cs — per-provider option shapes\n// • OpenAiChatGenerator / AnthropicChatGenerator / GeminiChatGenerator and the\n// OpenAI-compatible base (Groq/Cerebras/Together/DeepSeek)\n//\n// Cloud generators speak the real wire formats but send through the injectable\n// {@link IHttpTransport} seam (no sockets), so tests use the deterministic\n// {@link FakeConfigurableChatGenerator} or a fake transport. The chain and\n// orchestrator — the deterministic failover logic — are ported verbatim.\n\nimport type {\n GenerationOptions,\n IChatGenerator,\n} from \"../../inference/index.js\";\nimport type { ChatMessage } from \"../../models/index.js\";\nimport type { HttpResponse, IHttpTransport } from \"../endpoints.js\";\n\n// ─────────────────────────────────────────────────────────────────────────────\n// IConfigurableChatGenerator\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Reports whether a generator can currently serve calls. Mirrors\n * CircleAI.Hosting.CloudFallback.IConfigurableChatGenerator.\n */\nexport interface IConfigurableChatGenerator extends IChatGenerator {\n /** True when the generator can serve calls (e.g. API key present). */\n readonly isConfigured: boolean;\n /** Display name (e.g. \"OpenAI · gpt-4o-mini\"). */\n readonly engineLabel: string;\n /** Human-readable explanation of the current state. */\n readonly statusMessage: string;\n}\n\nfunction isConfigurable(g: IChatGenerator): g is IConfigurableChatGenerator {\n return typeof (g as Partial<IConfigurableChatGenerator>).isConfigured === \"boolean\";\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// CloudFallbackChain\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Tries an ordered list of {@link IChatGenerator}s and uses the first one ready.\n * Mirrors CircleAI.Hosting.CloudFallback.CloudFallbackChain.\n */\nexport class CloudFallbackChain implements IChatGenerator {\n private readonly generators: readonly IChatGenerator[];\n\n constructor(generators: Iterable<IChatGenerator>) {\n if (!generators) throw new Error(\"generators required\");\n this.generators = [...generators];\n }\n\n get generatorList(): readonly IChatGenerator[] {\n return this.generators;\n }\n\n async generateAsync(\n messages: readonly ChatMessage[],\n options?: GenerationOptions,\n ): Promise<string> {\n for (const g of this.generators) {\n if (!isReady(g)) continue;\n try {\n return await g.generateAsync(messages, options);\n } catch {\n // Fall through to the next generator.\n }\n }\n return \"[CloudFallbackChain: no configured generator could serve the request]\";\n }\n\n async *streamAsync(\n messages: readonly ChatMessage[],\n options?: GenerationOptions,\n ): AsyncGenerator<string> {\n for (const g of this.generators) {\n if (!isReady(g)) continue;\n\n const iterator = g.streamAsync(messages, options)[Symbol.asyncIterator]();\n let yielded = false;\n try {\n for (;;) {\n let res: IteratorResult<string>;\n try {\n res = await iterator.next();\n } catch {\n // Faulted mid-stream; move on iff nothing yielded yet.\n if (yielded) return;\n break;\n }\n if (res.done) return;\n\n const chunk = res.value;\n if (!yielded && isFailSoftFrame(chunk)) {\n // Generator declined the call (e.g. no API key).\n break;\n }\n yielded = true;\n yield chunk;\n }\n } finally {\n await iterator.return?.(undefined);\n }\n\n if (yielded) return;\n }\n\n yield \"[CloudFallbackChain: no configured generator could serve the request]\";\n }\n\n dispose(): void {\n for (const g of this.generators) {\n try {\n g.dispose();\n } catch {\n /* best effort */\n }\n }\n }\n}\n\nfunction isReady(g: IChatGenerator): boolean {\n return !isConfigurable(g) || g.isConfigured;\n}\n\nfunction isFailSoftFrame(chunk: string): boolean {\n return (\n chunk.startsWith(\"[\") &&\n (chunk.toLowerCase().includes(\"not configured\") ||\n chunk.toLowerCase().includes(\"cloudfallbackchain\"))\n );\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// BackupBrainOrchestrator\n// ─────────────────────────────────────────────────────────────────────────────\n\n/** Health state of one brain in the chain. Mirrors BrainHealth. */\nexport enum BrainHealth {\n Healthy = \"Healthy\",\n Degraded = \"Degraded\",\n CoolingDown = \"CoolingDown\",\n}\n\n/** Snapshot of brain health for monitoring. Mirrors BrainStatus. */\nexport interface BrainStatus {\n readonly label: string;\n readonly health: BrainHealth;\n readonly consecutiveFailures: number;\n}\n\n/** Policy knobs. Mirrors BackupBrainPolicy. Durations are milliseconds. */\nexport interface BackupBrainPolicy {\n /** Consecutive failures that push a brain to degraded. Default 2. */\n readonly degradedAfterFailures?: number;\n /** How long a degraded brain stays out before retry, ms. Default 30 000. */\n readonly coolDownDurationMs?: number;\n /** How many brains to try before giving up on one turn. Default 3. */\n readonly maxRetriesPerTurn?: number;\n}\n\nconst DEFAULT_POLICY: Required<BackupBrainPolicy> = {\n degradedAfterFailures: 2,\n coolDownDurationMs: 30_000,\n maxRetriesPerTurn: 3,\n};\n\nclass BrainEntry {\n consecutive = 0;\n degradedSinceMs = 0;\n isDegraded = false;\n constructor(readonly brain: IChatGenerator) {}\n\n healthAt(nowMs: number, coolDownMs: number): BrainHealth {\n if (!this.isDegraded) return BrainHealth.Healthy;\n if (nowMs - this.degradedSinceMs >= coolDownMs) return BrainHealth.CoolingDown;\n return BrainHealth.Degraded;\n }\n\n recordSuccess(): void {\n this.consecutive = 0;\n this.isDegraded = false;\n }\n\n recordFailure(threshold: number, nowMs: number): void {\n this.consecutive++;\n if (this.consecutive >= threshold) {\n this.isDegraded = true;\n this.degradedSinceMs = nowMs;\n }\n }\n}\n\n/**\n * Wraps an ordered set of brains; switches on failure, retries the primary on\n * cool-down. Mirrors CircleAI.Hosting.CloudFallback.BackupBrainOrchestrator.\n */\nexport class BackupBrainOrchestrator implements IChatGenerator {\n private readonly brains: BrainEntry[];\n private readonly policy: Required<BackupBrainPolicy>;\n private readonly clock: () => number;\n\n constructor(\n brains: Iterable<IChatGenerator>,\n policy: BackupBrainPolicy = {},\n clock?: () => number,\n ) {\n if (!brains) throw new Error(\"brains required\");\n this.brains = [...brains].map((b) => new BrainEntry(b));\n if (this.brains.length === 0) throw new Error(\"At least one brain is required.\");\n this.policy = { ...DEFAULT_POLICY, ...policy };\n this.clock = clock ?? (() => Date.now());\n }\n\n get statuses(): readonly BrainStatus[] {\n const now = this.clock();\n return this.brains.map((e) => {\n const h = e.healthAt(now, this.policy.coolDownDurationMs);\n const label = isConfigurable(e.brain)\n ? e.brain.engineLabel\n : e.brain.constructor.name;\n return { label, health: h, consecutiveFailures: e.consecutive };\n });\n }\n\n async generateAsync(\n messages: readonly ChatMessage[],\n options?: GenerationOptions,\n ): Promise<string> {\n const maxRetries = Math.min(this.policy.maxRetriesPerTurn, this.brains.length);\n const tried = new Set<BrainEntry>();\n for (let attempt = 0; attempt < maxRetries; attempt++) {\n const pick = this.pickAvailable(tried);\n if (pick === null) break;\n tried.add(pick);\n try {\n const result = await pick.brain.generateAsync(messages, options);\n pick.recordSuccess();\n return result;\n } catch {\n pick.recordFailure(this.policy.degradedAfterFailures, this.clock());\n }\n }\n return \"[All brains failed.]\";\n }\n\n async *streamAsync(\n messages: readonly ChatMessage[],\n options?: GenerationOptions,\n ): AsyncGenerator<string> {\n const maxRetries = Math.min(this.policy.maxRetriesPerTurn, this.brains.length);\n const tried = new Set<BrainEntry>();\n for (let attempt = 0; attempt < maxRetries; attempt++) {\n const pick = this.pickAvailable(tried);\n if (pick === null) break;\n tried.add(pick);\n let streamedAny = false;\n let failed = false;\n\n for await (const chunk of iterateStreamSafe(pick, messages, options)) {\n if (chunk === null) {\n failed = true;\n break;\n }\n streamedAny = true;\n yield chunk;\n }\n\n if (failed) {\n pick.recordFailure(this.policy.degradedAfterFailures, this.clock());\n if (!streamedAny) continue; // try the backup\n }\n if (streamedAny) {\n pick.recordSuccess();\n return;\n }\n }\n yield \"[All brains failed.]\";\n }\n\n dispose(): void {\n /* nothing owned */\n }\n\n private pickAvailable(skip: Set<BrainEntry>): BrainEntry | null {\n const now = this.clock();\n for (const e of this.brains) {\n if (skip.has(e)) continue;\n const h = e.healthAt(now, this.policy.coolDownDurationMs);\n if (h === BrainHealth.Healthy || h === BrainHealth.CoolingDown) return e;\n }\n // None healthy — pick first untried brain anyway (degraded might recover).\n for (const e of this.brains) {\n if (!skip.has(e)) return e;\n }\n return null;\n }\n}\n\n/** Mirrors BackupBrainOrchestrator.IterateStreamSafe — null sentinel on fault. */\nasync function* iterateStreamSafe(\n pick: BrainEntry,\n messages: readonly ChatMessage[],\n options: GenerationOptions | undefined,\n): AsyncGenerator<string | null> {\n let iterator: AsyncIterator<string> | null = null;\n try {\n iterator = pick.brain.streamAsync(messages, options)[Symbol.asyncIterator]();\n } catch {\n yield null;\n return;\n }\n\n try {\n for (;;) {\n let res: IteratorResult<string>;\n try {\n res = await iterator.next();\n } catch {\n yield null;\n return;\n }\n if (res.done) return;\n yield res.value;\n }\n } finally {\n await iterator.return?.(undefined);\n }\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// ServerSentEventsReader\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Yields the payload of every `data:` frame from a streaming HTTP response;\n * a `[DONE]` sentinel terminates cleanly. Mirrors ServerSentEventsReader.\n */\nexport async function* readSseFrames(resp: HttpResponse): AsyncGenerator<string> {\n for await (const line of sseLines(resp)) {\n if (!line.startsWith(\"data:\")) continue;\n const payload = line.slice(5).replace(/^\\s+/, \"\");\n if (payload === \"[DONE]\") return;\n yield payload;\n }\n}\n\nasync function* sseLines(resp: HttpResponse): AsyncGenerator<string> {\n const source = resp.sse;\n if (source == null) {\n if (resp.body != null) for (const line of resp.body.split(\"\\n\")) yield line;\n return;\n }\n let buffer = \"\";\n for await (const chunk of source) {\n buffer += chunk;\n let nl: number;\n while ((nl = buffer.indexOf(\"\\n\")) >= 0) {\n yield buffer.slice(0, nl);\n buffer = buffer.slice(nl + 1);\n }\n }\n if (buffer.length > 0) yield buffer;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Provider option shapes (Options.cs)\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface OpenAiChatOptions {\n readonly baseAddress?: string;\n readonly apiKey?: string | null;\n readonly model?: string;\n readonly temperature?: number;\n readonly maxTokens?: number;\n}\nexport interface AnthropicChatOptions {\n readonly baseAddress?: string;\n readonly apiKey?: string | null;\n readonly model?: string;\n readonly temperature?: number;\n readonly maxTokens?: number;\n readonly anthropicVersion?: string;\n}\nexport interface GeminiChatOptions {\n readonly baseAddress?: string;\n readonly apiKey?: string | null;\n readonly model?: string;\n readonly temperature?: number;\n readonly maxOutputTokens?: number;\n}\nexport interface OpenAiCompatibleOptions {\n readonly baseAddress?: string;\n readonly apiKey?: string | null;\n readonly model?: string;\n readonly temperature?: number;\n readonly maxTokens?: number;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// OpenAI-compatible base (Groq / Cerebras / Together / DeepSeek / OpenAI)\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Shared OpenAI-compatible streaming chat generator. Sends via the injected\n * {@link IHttpTransport}. Mirrors OpenAiCompatibleChatGeneratorBase +\n * OpenAiChatGenerator.\n */\nexport class OpenAiCompatibleChatGenerator implements IConfigurableChatGenerator {\n protected readonly transport: IHttpTransport;\n protected readonly options: Required<Omit<OpenAiCompatibleOptions, \"apiKey\">> & {\n apiKey: string | null;\n };\n private readonly id: string;\n private readonly chatCompletionsPath: string;\n\n constructor(\n transport: IHttpTransport,\n options: OpenAiCompatibleOptions,\n id = \"openai\",\n chatCompletionsPath = \"/v1/chat/completions\",\n ) {\n if (!transport) throw new Error(\"transport required\");\n if (!options) throw new Error(\"options required\");\n this.transport = transport;\n this.options = {\n baseAddress: options.baseAddress ?? \"https://api.openai.com\",\n apiKey: options.apiKey ?? null,\n model: options.model ?? \"gpt-4o-mini\",\n temperature: options.temperature ?? 0.7,\n maxTokens: options.maxTokens ?? 1024,\n };\n this.id = id;\n this.chatCompletionsPath = chatCompletionsPath;\n }\n\n get engineLabel(): string {\n return `${cap(this.id)} · ${this.options.model}`;\n }\n get isConfigured(): boolean {\n return this.options.apiKey != null && this.options.apiKey.trim().length > 0;\n }\n get statusMessage(): string {\n return this.isConfigured\n ? `Ready · ${this.options.model}`\n : `${this.id} API key not configured.`;\n }\n\n async generateAsync(\n messages: readonly ChatMessage[],\n options?: GenerationOptions,\n ): Promise<string> {\n let out = \"\";\n for await (const chunk of this.streamAsync(messages, options)) out += chunk;\n return out;\n }\n\n async *streamAsync(\n messages: readonly ChatMessage[],\n options?: GenerationOptions,\n ): AsyncGenerator<string> {\n if (!this.isConfigured) {\n yield `[${this.statusMessage}]`;\n return;\n }\n\n const body = {\n model: this.options.model,\n stream: true,\n temperature: options?.temperature ?? this.options.temperature,\n max_tokens: options?.maxTokens ?? this.options.maxTokens,\n messages: messages.map((m) => ({ role: m.role, content: m.content })),\n };\n\n const resp = await this.transport.sendAsync(\n \"POST\",\n this.chatCompletionsPath,\n { Authorization: `Bearer ${this.options.apiKey}`, \"Content-Type\": \"application/json\" },\n JSON.stringify(body),\n );\n\n if (resp.status < 200 || resp.status >= 300) {\n yield `[${this.id} error ${resp.status}: ${truncate(resp.body ?? \"\", 240)}]`;\n return;\n }\n\n for await (const frame of readSseFrames(resp)) {\n const delta = parseOpenAiDelta(frame);\n if (delta != null && delta.length > 0) yield delta;\n }\n }\n\n dispose(): void {\n /* transport is externally owned */\n }\n}\n\n/** OpenAI (official endpoint / compatible gateways). Mirrors OpenAiChatGenerator. */\nexport class OpenAiChatGenerator extends OpenAiCompatibleChatGenerator {\n constructor(transport: IHttpTransport, options: OpenAiChatOptions) {\n super(transport, options, \"openai\", \"/v1/chat/completions\");\n }\n override get engineLabel(): string {\n return `OpenAI · ${this.options.model}`;\n }\n}\n\n/** Groq — /openai/v1/chat/completions. */\nexport class GroqChatGenerator extends OpenAiCompatibleChatGenerator {\n constructor(transport: IHttpTransport, options: OpenAiCompatibleOptions) {\n super(\n transport,\n { model: \"llama-3.3-70b-versatile\", baseAddress: \"https://api.groq.com\", ...options },\n \"groq\",\n \"/openai/v1/chat/completions\",\n );\n }\n override get engineLabel(): string {\n return `Groq · ${this.options.model}`;\n }\n}\n\n/** Cerebras — /v1/chat/completions. */\nexport class CerebrasChatGenerator extends OpenAiCompatibleChatGenerator {\n constructor(transport: IHttpTransport, options: OpenAiCompatibleOptions) {\n super(\n transport,\n { model: \"llama3.3-70b\", baseAddress: \"https://api.cerebras.ai\", ...options },\n \"cerebras\",\n );\n }\n override get engineLabel(): string {\n return `Cerebras · ${this.options.model}`;\n }\n}\n\n/** Together AI — /v1/chat/completions. */\nexport class TogetherChatGenerator extends OpenAiCompatibleChatGenerator {\n constructor(transport: IHttpTransport, options: OpenAiCompatibleOptions) {\n super(\n transport,\n {\n model: \"meta-llama/Llama-3.3-70B-Instruct-Turbo\",\n baseAddress: \"https://api.together.xyz\",\n ...options,\n },\n \"together\",\n );\n }\n override get engineLabel(): string {\n return `Together · ${this.options.model}`;\n }\n}\n\n/** DeepSeek — /v1/chat/completions. */\nexport class DeepSeekChatGenerator extends OpenAiCompatibleChatGenerator {\n constructor(transport: IHttpTransport, options: OpenAiCompatibleOptions) {\n super(\n transport,\n { model: \"deepseek-chat\", baseAddress: \"https://api.deepseek.com\", ...options },\n \"deepseek\",\n );\n }\n override get engineLabel(): string {\n return `DeepSeek · ${this.options.model}`;\n }\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Anthropic\n// ─────────────────────────────────────────────────────────────────────────────\n\n/** Anthropic Messages API generator. Mirrors AnthropicChatGenerator. */\nexport class AnthropicChatGenerator implements IConfigurableChatGenerator {\n private readonly transport: IHttpTransport;\n private readonly options: Required<Omit<AnthropicChatOptions, \"apiKey\">> & {\n apiKey: string | null;\n };\n\n constructor(transport: IHttpTransport, options: AnthropicChatOptions) {\n if (!transport) throw new Error(\"transport required\");\n if (!options) throw new Error(\"options required\");\n this.transport = transport;\n this.options = {\n baseAddress: options.baseAddress ?? \"https://api.anthropic.com\",\n apiKey: options.apiKey ?? null,\n model: options.model ?? \"claude-3-5-sonnet-latest\",\n temperature: options.temperature ?? 0.7,\n maxTokens: options.maxTokens ?? 1024,\n anthropicVersion: options.anthropicVersion ?? \"2023-06-01\",\n };\n }\n\n get engineLabel(): string {\n return `Anthropic · ${this.options.model}`;\n }\n get isConfigured(): boolean {\n return this.options.apiKey != null && this.options.apiKey.trim().length > 0;\n }\n get statusMessage(): string {\n return this.isConfigured\n ? `Ready · ${this.options.model}`\n : \"Anthropic API key not configured.\";\n }\n\n async generateAsync(\n messages: readonly ChatMessage[],\n options?: GenerationOptions,\n ): Promise<string> {\n let out = \"\";\n for await (const chunk of this.streamAsync(messages, options)) out += chunk;\n return out;\n }\n\n async *streamAsync(\n messages: readonly ChatMessage[],\n options?: GenerationOptions,\n ): AsyncGenerator<string> {\n if (!this.isConfigured) {\n yield `[${this.statusMessage}]`;\n return;\n }\n\n const system = messages\n .filter((m) => m.role.toLowerCase() === \"system\")\n .map((m) => m.content)\n .join(\"\\n\\n\");\n const chat = messages\n .filter((m) => m.role.toLowerCase() !== \"system\")\n .map((m) => ({ role: m.role.toLowerCase(), content: m.content }));\n\n const base = {\n model: this.options.model,\n max_tokens: options?.maxTokens ?? this.options.maxTokens,\n temperature: options?.temperature ?? this.options.temperature,\n stream: true,\n messages: chat,\n };\n const body = system.length === 0 ? base : { ...base, system };\n\n const resp = await this.transport.sendAsync(\n \"POST\",\n \"/v1/messages\",\n {\n \"x-api-key\": this.options.apiKey!,\n \"anthropic-version\": this.options.anthropicVersion,\n \"Content-Type\": \"application/json\",\n },\n JSON.stringify(body),\n );\n\n if (resp.status < 200 || resp.status >= 300) {\n yield `[Anthropic error ${resp.status}: ${truncate(resp.body ?? \"\", 240)}]`;\n return;\n }\n\n for await (const frame of readSseFrames(resp)) {\n const delta = parseAnthropicDelta(frame);\n if (delta != null && delta.length > 0) yield delta;\n }\n }\n\n dispose(): void {\n /* transport is externally owned */\n }\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Gemini\n// ─────────────────────────────────────────────────────────────────────────────\n\n/** Google Gemini streamGenerateContent generator. Mirrors GeminiChatGenerator. */\nexport class GeminiChatGenerator implements IConfigurableChatGenerator {\n private readonly transport: IHttpTransport;\n private readonly options: Required<Omit<GeminiChatOptions, \"apiKey\">> & {\n apiKey: string | null;\n };\n\n constructor(transport: IHttpTransport, options: GeminiChatOptions) {\n if (!transport) throw new Error(\"transport required\");\n if (!options) throw new Error(\"options required\");\n this.transport = transport;\n this.options = {\n baseAddress: options.baseAddress ?? \"https://generativelanguage.googleapis.com\",\n apiKey: options.apiKey ?? null,\n model: options.model ?? \"gemini-2.0-flash\",\n temperature: options.temperature ?? 0.7,\n maxOutputTokens: options.maxOutputTokens ?? 1024,\n };\n }\n\n get engineLabel(): string {\n return `Gemini · ${this.options.model}`;\n }\n get isConfigured(): boolean {\n return this.options.apiKey != null && this.options.apiKey.trim().length > 0;\n }\n get statusMessage(): string {\n return this.isConfigured\n ? `Ready · ${this.options.model}`\n : \"Gemini API key not configured.\";\n }\n\n async generateAsync(\n messages: readonly ChatMessage[],\n options?: GenerationOptions,\n ): Promise<string> {\n let out = \"\";\n for await (const chunk of this.streamAsync(messages, options)) out += chunk;\n return out;\n }\n\n async *streamAsync(\n messages: readonly ChatMessage[],\n options?: GenerationOptions,\n ): AsyncGenerator<string> {\n if (!this.isConfigured) {\n yield `[${this.statusMessage}]`;\n return;\n }\n\n const system = messages\n .filter((m) => m.role.toLowerCase() === \"system\")\n .map((m) => m.content)\n .join(\"\\n\\n\");\n const contents = messages\n .filter((m) => m.role.toLowerCase() !== \"system\")\n .map((m) => ({\n role: m.role.toLowerCase() === \"assistant\" ? \"model\" : m.role.toLowerCase(),\n parts: [{ text: m.content }],\n }));\n\n const generationConfig = {\n temperature: options?.temperature ?? this.options.temperature,\n maxOutputTokens: options?.maxTokens ?? this.options.maxOutputTokens,\n };\n const body =\n system.length === 0\n ? { contents, generationConfig }\n : {\n contents,\n systemInstruction: { parts: [{ text: system }] },\n generationConfig,\n };\n\n const path = `/v1beta/models/${encodeURIComponent(this.options.model)}:streamGenerateContent?alt=sse&key=${encodeURIComponent(this.options.apiKey!)}`;\n const resp = await this.transport.sendAsync(\n \"POST\",\n path,\n { \"Content-Type\": \"application/json\" },\n JSON.stringify(body),\n );\n\n if (resp.status < 200 || resp.status >= 300) {\n yield `[Gemini error ${resp.status}: ${truncate(resp.body ?? \"\", 240)}]`;\n return;\n }\n\n for await (const frame of readSseFrames(resp)) {\n const delta = parseGeminiDelta(frame);\n if (delta != null && delta.length > 0) yield delta;\n }\n }\n\n dispose(): void {\n /* transport is externally owned */\n }\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// FakeConfigurableChatGenerator — deterministic local fake for tests\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Deterministic {@link IConfigurableChatGenerator} for tests. When configured,\n * streams a fixed reply word-by-word; when not, yields the fail-soft frame so\n * a {@link CloudFallbackChain} skips it. Optionally throws to exercise the\n * {@link BackupBrainOrchestrator} failover paths.\n */\nexport class FakeConfigurableChatGenerator implements IConfigurableChatGenerator {\n private readonly reply: string;\n private readonly configured: boolean;\n private readonly throwOnCall: boolean;\n readonly engineLabel: string;\n\n constructor(opts: {\n reply?: string;\n configured?: boolean;\n engineLabel?: string;\n throwOnCall?: boolean;\n } = {}) {\n this.reply = opts.reply ?? \"hello world\";\n this.configured = opts.configured ?? true;\n this.throwOnCall = opts.throwOnCall ?? false;\n this.engineLabel = opts.engineLabel ?? \"Fake · deterministic\";\n }\n\n get isConfigured(): boolean {\n return this.configured;\n }\n get statusMessage(): string {\n return this.configured ? \"Ready · fake\" : \"fake API key not configured.\";\n }\n\n async generateAsync(): Promise<string> {\n if (this.throwOnCall) throw new Error(\"fake failure\");\n if (!this.configured) return `[${this.statusMessage}]`;\n return this.reply;\n }\n\n async *streamAsync(): AsyncGenerator<string> {\n if (this.throwOnCall) throw new Error(\"fake failure\");\n if (!this.configured) {\n yield `[${this.statusMessage}]`;\n return;\n }\n const words = this.reply.split(\" \");\n for (let i = 0; i < words.length; i++) yield i === 0 ? words[i] : ` ${words[i]}`;\n }\n\n dispose(): void {\n /* nothing owned */\n }\n}\n\n// ── delta parsers ──────────────────────────────────────────────────────────────\n\nfunction parseOpenAiDelta(frame: string): string | null {\n try {\n const doc = JSON.parse(frame) as {\n choices?: { delta?: { content?: unknown } }[];\n };\n const c = doc.choices;\n if (Array.isArray(c) && c.length > 0) {\n const content = c[0].delta?.content;\n if (typeof content === \"string\") return content;\n }\n } catch {\n return null;\n }\n return null;\n}\n\nfunction parseAnthropicDelta(frame: string): string | null {\n try {\n const doc = JSON.parse(frame) as {\n type?: string;\n delta?: { text?: unknown };\n };\n if (doc.type === \"content_block_delta\" && typeof doc.delta?.text === \"string\")\n return doc.delta.text;\n } catch {\n return null;\n }\n return null;\n}\n\nfunction parseGeminiDelta(frame: string): string | null {\n try {\n const doc = JSON.parse(frame) as {\n candidates?: { content?: { parts?: { text?: unknown }[] } }[];\n };\n const cand = doc.candidates;\n if (Array.isArray(cand) && cand.length > 0) {\n const parts = cand[0].content?.parts;\n if (Array.isArray(parts) && parts.length > 0 && typeof parts[0].text === \"string\")\n return parts[0].text;\n }\n } catch {\n return null;\n }\n return null;\n}\n\nfunction truncate(value: string, max: number): string {\n return value.length <= max ? value : value.slice(0, max) + \"…\";\n}\n\nfunction cap(s: string): string {\n return s.length === 0 ? s : s[0].toUpperCase() + s.slice(1);\n}\n"}}}}