Add initial llm-bridge project
CI / fmt, clippy and tests (ubuntu-latest) (push) Successful in 21m0s
CI / browser end-to-end (Chromium + fixture) (push) Skipped
CI / fmt, clippy and tests (windows-latest) (push) Canceled after 0s

This commit is contained in:
2026-09-18 18:01:07 -04:00
commit 7b5aa6fcc9
58 changed files with 19914 additions and 0 deletions
+79
View File
@@ -0,0 +1,79 @@
# 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]"