BYOK: make Anthropic (Claude) usable as a provider#88
Conversation
- postChat: strip temperature/top_p/top_k when the model id starts with 'claude'; recent Claude models (Opus 4.7+, Sonnet 5) reject sampling parameters with a 400 through Anthropic's OpenAI-compatible endpoint, and omitting them is valid on every Claude model - fetchModels: when the host is anthropic.com, authenticate with x-api-key + anthropic-version instead of a bearer token so the Fetch button works; the response shape (data[].id) already matches OpenAI's. Other providers are untouched
📝 WalkthroughWalkthroughCoachEngine now uses Anthropic-specific model discovery authentication and pagination, and removes OpenAI sampling parameters from chat requests targeting Claude models. ChangesAnthropic provider compatibility
Estimated code review effort: 2 (Simple) | ~10 minutes Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
lib/coach/coach_engine.dart (1)
338-350: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winFollow the Models API cursor when
has_moreis true.limit=1000only caps a single page; if the response has more results, keep requesting/models?after_id=...withlast_idor later models never reach Settings.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@lib/coach/coach_engine.dart` around lines 338 - 350, Update the models-fetching flow around the HTTP request and ids parsing to paginate Anthropic responses while has_more is true. Reuse the returned last_id as the after_id query parameter for each subsequent /models request, accumulating ids across pages; keep the existing non-Anthropic single-request behavior and status/error handling intact.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@lib/coach/coach_engine.dart`:
- Around line 336-342: Update the Anthropic host detection in the model-fetching
flow to require an exact `anthropic.com` match or a subdomain ending in
`.anthropic.com`, preventing lookalike domains from being classified as
Anthropic. Extract or reuse this domain-boundary helper in `postChat` so both
request paths apply identical host validation.
- Around line 482-488: Update the sampling-parameter removal in postChat to
require both the Anthropic endpoint and the specific Anthropic model
family/version known to reject these fields, rather than relying on
model.startsWith('claude') alone. Preserve temperature and top_p for Anthropic
models that support them and for non-Anthropic providers, while continuing to
remove all three parameters only for the targeted models.
---
Outside diff comments:
In `@lib/coach/coach_engine.dart`:
- Around line 338-350: Update the models-fetching flow around the HTTP request
and ids parsing to paginate Anthropic responses while has_more is true. Reuse
the returned last_id as the after_id query parameter for each subsequent /models
request, accumulating ids across pages; keep the existing non-Anthropic
single-request behavior and status/error handling intact.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: c52a25e1-294f-49cc-8225-7d9fb7ae7bb7
📒 Files selected for processing (1)
lib/coach/coach_engine.dart
| final isAnthropic = | ||
| (Uri.tryParse(b)?.host ?? '').endsWith('anthropic.com'); | ||
| final resp = await http.get( | ||
| Uri.parse('$b/models'), | ||
| headers: {'Authorization': 'Bearer $apiKey'}, | ||
| Uri.parse(isAnthropic ? '$b/models?limit=1000' : '$b/models'), | ||
| headers: isAnthropic | ||
| ? {'x-api-key': apiKey, 'anthropic-version': '2023-06-01'} | ||
| : {'Authorization': 'Bearer $apiKey'}, |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win
Use a domain-boundary check for Anthropic hosts.
endsWith('anthropic.com') also matches hosts such as evil-anthropic.com, causing the BYOK key to be sent using Anthropic headers to a non-Anthropic endpoint. Require host == 'anthropic.com' || host.endsWith('.anthropic.com') (or an exact allowlist), and reuse the helper in postChat.
Proposed fix
- final isAnthropic =
- (Uri.tryParse(b)?.host ?? '').endsWith('anthropic.com');
+ final host = (Uri.tryParse(b)?.host ?? '').toLowerCase();
+ final isAnthropic =
+ host == 'anthropic.com' || host.endsWith('.anthropic.com');📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| final isAnthropic = | |
| (Uri.tryParse(b)?.host ?? '').endsWith('anthropic.com'); | |
| final resp = await http.get( | |
| Uri.parse('$b/models'), | |
| headers: {'Authorization': 'Bearer $apiKey'}, | |
| Uri.parse(isAnthropic ? '$b/models?limit=1000' : '$b/models'), | |
| headers: isAnthropic | |
| ? {'x-api-key': apiKey, 'anthropic-version': '2023-06-01'} | |
| : {'Authorization': 'Bearer $apiKey'}, | |
| final host = (Uri.tryParse(b)?.host ?? '').toLowerCase(); | |
| final isAnthropic = | |
| host == 'anthropic.com' || host.endsWith('.anthropic.com'); | |
| final resp = await http.get( | |
| Uri.parse(isAnthropic ? '$b/models?limit=1000' : '$b/models'), | |
| headers: isAnthropic | |
| ? {'x-api-key': apiKey, 'anthropic-version': '2023-06-01'} | |
| : {'Authorization': 'Bearer $apiKey'}, |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@lib/coach/coach_engine.dart` around lines 336 - 342, Update the Anthropic
host detection in the model-fetching flow to require an exact `anthropic.com`
match or a subdomain ending in `.anthropic.com`, preventing lookalike domains
from being classified as Anthropic. Extract or reuse this domain-boundary helper
in `postChat` so both request paths apply identical host validation.
| final model = body['model'] as String? ?? ''; | ||
| if (model.startsWith('claude')) { | ||
| body = {...body} | ||
| ..remove('temperature') | ||
| ..remove('top_p') | ||
| ..remove('top_k'); | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Inspect the relevant section and nearby provider handling.
sed -n '430,540p' lib/coach/coach_engine.dart
printf '\n--- SEARCH ---\n'
rg -n "postChat|claude|temperature|top_p|top_k|Anthropic|provider" lib/coach -g '!**/*.g.dart'
printf '\n--- FILE LIST ---\n'
git ls-files lib/coachRepository: OpenStrap/edge
Length of output: 6815
🏁 Script executed:
#!/bin/bash
set -euo pipefail
sed -n '1,220p' lib/coach/coach_config.dart
printf '\n--- ANTHROPIC SCOPING SEARCH ---\n'
rg -n "isAnthropic|anthropic|apiBase|baseUrl|provider" lib/coach/coach_engine.dart lib/coach/coach_config.dartRepository: OpenStrap/edge
Length of output: 5188
🌐 Web query:
Anthropic OpenAI-compatible API temperature top_p support documentation
💡 Result:
Anthropic provides an OpenAI-compatible API interface that supports several standard parameters, including temperature and top_p, though behavior varies by model [1][2]. General Support and Behavior: - Temperature: Generally supported for values between 0 and 1.0 [1][2]. Values greater than 1.0 are typically capped at 1.0 [1][2]. - Top_p: Fully supported [1][2]. Model-Specific Restrictions: Important variations exist depending on the specific model used: - Older and current standard models (e.g., Claude Sonnet 4.6, Haiku 4.5): These models support temperature and top_p within the standard 0–1.0 range [3]. - Newer models (e.g., Claude Opus 4.7, 4.8): These models explicitly do not support temperature, top_p, or top_k [3][4]. Passing any non-default values for these parameters in an API request will result in a 400 error [3][4]. For these models, Anthropic recommends using prompting techniques to control output style and behavior [3][4]. Compatibility Limitations: Anthropic's OpenAI-compatible API is intended primarily for evaluation and testing [3][5]. It acts as a "least-common-denominator" interface and does not support several advanced features available via the native Anthropic Messages API, such as prompt caching, extended thinking, or structured outputs [1][3][6]. For production workloads requiring full feature access, Anthropic recommends using their native SDK and API [3][5]. Top Results: [1][3][4][2]
Citations:
- 1: https://platform.claude.com/docs/en/cli-sdks-libraries/libraries/openai-sdk
- 2: https://doc.jarvisuni.com/claude/api/en/api/openai-sdk.html
- 3: https://awesomeagents.ai/migrations/openai-to-anthropic-api/
- 4: https://docs.anyfast.ai/guides/model-api/anthropic/claude-opus-4-8
- 5: https://docs.openwebui.com/getting-started/quick-start/connect-a-provider/starting-with-anthropic/
- 6: https://tools.yiteai.com/en/books/claude-guide/ch14
Scope the sampling-parameter removal to Anthropic models that actually reject it.
postChat is shared across providers, but this branch keys only off model.startsWith('claude'), so it will silently strip temperature/top_p/top_k from non-Anthropic Claude-named models and from Anthropic models that still support temperature and top_p. Gate this on the Anthropic endpoint and the specific model family/version instead of the model prefix alone.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@lib/coach/coach_engine.dart` around lines 482 - 488, Update the
sampling-parameter removal in postChat to require both the Anthropic endpoint
and the specific Anthropic model family/version known to reject these fields,
rather than relying on model.startsWith('claude') alone. Preserve temperature
and top_p for Anthropic models that support them and for non-Anthropic
providers, while continuing to remove all three parameters only for the targeted
models.
There was a problem hiding this comment.
@alexforia Many providers offer Claude apart from Anthropic, so this seems like a genuine issue. Could you implement this?
Summary by CodeRabbit