80 lines
3.0 KiB
TOML
80 lines
3.0 KiB
TOML
# Claude (claude.ai) provider configuration, driven entirely by this file.
|
|
# Selectors change when the web UI is updated: edit them here (reloaded live),
|
|
# never in the Rust source. Validate with: llm-gateway selftest claude
|
|
#
|
|
# The values below come from the public claude.ai DOM observed in 2026 and from
|
|
# published selector lists (May 2026). They are a starting point, not a
|
|
# guarantee: run 'llm-gateway selftest claude' before relying on them.
|
|
|
|
[provider]
|
|
name = "claude"
|
|
display_name = "Anthropic Claude"
|
|
web_url = "https://claude.ai/new"
|
|
new_conversation_url = "https://claude.ai/new"
|
|
# Captures the web conversation id from the current URL (one capture group).
|
|
conversation_url_pattern = '^https://claude\.ai/chat/([0-9a-zA-Z-]{8,})'
|
|
default_models = ["claude-sonnet-4", "claude-opus-4", "claude-haiku-4"]
|
|
enabled = true
|
|
|
|
[selectors]
|
|
# Claude uses ProseMirror, so the editor is a contenteditable div rather than a
|
|
# textarea. The fallbacks cover the composer variants seen behind A/B tests.
|
|
input_field = "div.ProseMirror[contenteditable='true'], div[contenteditable='true'][data-placeholder], [data-testid='composer-input']"
|
|
send_button = "button[aria-label='Send Message'], button[aria-label='Send message'], button[data-testid='send-button'], button[type='submit']"
|
|
# One node per assistant answer, in document order.
|
|
response_container = "div.font-claude-response, div[data-is-streaming], div.font-claude-message"
|
|
streaming_indicator = "button[aria-label='Stop response'], button[aria-label='Stop Response']"
|
|
file_input = "input[type='file']"
|
|
new_chat_button = "a[href='/new'], button[aria-label='New chat']"
|
|
login_page_indicator = "a[href*='/login'], button[data-testid='login-button'], form[action*='login']"
|
|
captcha_indicator = "iframe[src*='challenges.cloudflare.com'], iframe[title*='challenge'], div#challenge-form"
|
|
error_banner = "[role='alert'], div[data-testid='error']"
|
|
# Relative to the answer node: the hover toolbar button that copies the answer
|
|
# as Markdown.
|
|
copy_button = "button[aria-label='Copy'], button[data-testid='copy-button']"
|
|
|
|
[login]
|
|
url_patterns = ["/login", "/magic-link", "accounts.google.com"]
|
|
# Only consulted when the prompt field is missing, so a chat page that happens
|
|
# to mention "log in" is never misclassified.
|
|
text_patterns = ["sign in", "log in", "continue with google"]
|
|
|
|
[rate_limit]
|
|
text_patterns = [
|
|
"message limit",
|
|
"rate limit",
|
|
"too many requests",
|
|
"you've reached",
|
|
"limite de messages",
|
|
"trop de requêtes",
|
|
"réessayez plus tard",
|
|
]
|
|
|
|
[timeouts]
|
|
page_load = 30
|
|
response_generation = 120
|
|
response_timeout = 180
|
|
|
|
[input]
|
|
poll_interval_ms = 400
|
|
quiet_ms = 1500
|
|
inject_method = "auto"
|
|
|
|
[options]
|
|
supports_images = true
|
|
supports_streaming = true
|
|
supports_new_chat = true
|
|
|
|
# Multi-account: each account is a separate browser profile, so several
|
|
# subscriptions can be driven side by side. Declare them like this and address
|
|
# them with model = "claude@work".
|
|
#
|
|
# [[accounts]]
|
|
# id = "perso"
|
|
# email = "[email protected]"
|
|
# default = true
|
|
#
|
|
# [[accounts]]
|
|
# id = "work"
|
|
# email = "[email protected]"
|