From 00523b841248453b9f2ca3c4eebdd77365391092 Mon Sep 17 00:00:00 2001 From: Brett Date: Fri, 17 Jul 2026 13:32:01 -0500 Subject: [PATCH] feat(ai/recipes/litellm): declare chat + expansion touchpoints (#2208) The litellm recipe shipped only an `embedding` touchpoint. `getProviderCapabilities()` in `src/core/ai/capabilities.ts` throws when `recipe.touchpoints.chat` is missing, `classifyCapabilities()` returns `'unknown'`, and `enforceSubagentCapable()` in `src/core/model-config.ts` silently falls back to `TIER_DEFAULTS.subagent` (anthropic). Any brain that routes paid traffic through a litellm-style proxy AND has no `ANTHROPIC_API_KEY` then sees every subagent loop dispatch throw `AIConfigError: Anthropic ... requires ANTHROPIC_API_KEY`. The user's explicit `models.tier.subagent = litellm:*` choice is overridden without their knowledge. Declare `chat` and `expansion` touchpoints mirroring the openai recipe's shape: `models: []` (litellm proxies arbitrary backends; allowlist is intentionally empty, since `assertTouchpoint` already skips allowlist checks for `tier: 'openai-compat'`), `supports_tools: true`, `supports_subagent_loop: true`, `supports_prompt_cache: false` (OpenAI-compat backends don't honor Anthropic-style `cache_control`), `max_context_tokens: 200_000` (conservative GPT-5-family default; per-deployment override needed for smaller-context backends), costs `undefined` (varies by proxied provider). Reproduction (deterministic): 1. Fresh brain with no `ANTHROPIC_API_KEY` in env. 2. `gbrain config set models.tier.subagent litellm:gpt-5.4` (or any `litellm:*` string). 3. `gbrain models` warns and falls back to `anthropic:claude-sonnet-4-6`. 4. Submit any subagent job; throws `AIConfigError: Anthropic ... requires ANTHROPIC_API_KEY`. After the patch, `classifyCapabilities('litellm:gpt-5.4', recipe)` returns `degraded:no_caching` (chat-capable, no Anthropic-style prompt cache). `enforceSubagentCapable` no longer steals the model choice. The subagent loop emits a one-time `degraded:no_caching` warn about prompt-cache absence; cost scales linearly with conversation length, accepted trade for the proxy path. --- src/core/ai/recipes/litellm-proxy.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/core/ai/recipes/litellm-proxy.ts b/src/core/ai/recipes/litellm-proxy.ts index 138e0746b..08ccc1b2c 100644 --- a/src/core/ai/recipes/litellm-proxy.ts +++ b/src/core/ai/recipes/litellm-proxy.ts @@ -41,6 +41,21 @@ export const litellmProxy: Recipe = { // mismatched-dim responses pre-storage). supports_multimodal: true, }, + expansion: { + models: [], + cost_per_1m_tokens_usd: undefined, + price_last_verified: '2026-06-14', + }, + chat: { + models: [], + supports_tools: true, + supports_subagent_loop: true, + supports_prompt_cache: false, + max_context_tokens: 200_000, + cost_per_1m_input_usd: undefined, + cost_per_1m_output_usd: undefined, + price_last_verified: '2026-06-14', + }, }, setup_hint: 'Run LiteLLM (https://docs.litellm.ai) in front of any provider; set LITELLM_BASE_URL (include the /v1 suffix if your proxy serves the OpenAI route there, e.g. http://localhost:4000/v1) + pass --embedding-model litellm: and --embedding-dimensions .', };