# ChatGPT 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 chatgpt [provider] name = "chatgpt" display_name = "OpenAI ChatGPT" web_url = "https://chatgpt.com/" new_conversation_url = "https://chatgpt.com/" # Captures the web conversation id from the current URL (one capture group). conversation_url_pattern = "^https://chatgpt\\.com/c/([0-9a-zA-Z-]{8,})" default_models = ["gpt-4o", "gpt-4o-mini", "gpt-4.1"] enabled = true [selectors] input_field = "div#prompt-textarea, div.ProseMirror[contenteditable='true']" # The id and the data-testid are language independent; the aria-label fallbacks # only match an English UI (see providers/selectors.md, "Localised user # interfaces"). send_button = "button[data-testid='send-button'], button#composer-submit-button, button[aria-label='Send prompt'], button[aria-label='Envoyer la requête']" response_container = "div[data-message-author-role='assistant']" streaming_indicator = "button[data-testid='stop-button']" file_input = "input[type='file']" new_chat_button = "a[data-testid='create-new-chat-button']" # Clicked once the answer has settled, to read the provider's own Markdown # through the clipboard. Looked up inside the answer node, so it must be # relative to it. Leave empty to always convert the DOM instead. copy_button = "button[data-testid='copy-turn-action-button'], button[aria-label='Copy'], button[aria-label='Copier']" login_page_indicator = "button[data-testid='login'], form[action*='login'], a[href*='auth/login'], a[href*='auth/0']" captcha_indicator = "iframe[src*='captcha'], iframe[src*='challenges.cloudflare.com'], div#challenge-form" error_banner = "[data-testid='error-message'], div[role='alert']" [login] url_patterns = ["/auth/login", "/login", "accounts.google.com", "/auth/0"] # The page a signed-out visitor lands on (title "ChatGPT: Chat, Work, Create & # Code with AI") has no prompt field and shows these call to action buttons. # The text patterns are only used when that field is missing, so a signed-in # conversation is never mistaken for a sign-in screen. text_patterns = ["log in", "sign up", "create an account"] [rate_limit] text_patterns = [ "too many requests", "rate limit", "trop de requêtes", "réessayez plus tard", "you've reached", ] [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. A ChatGPT account is a browser profile, so declaring two # accounts gives two independent sessions, each with its own tab pool, driven # side by side. Address them with the model string: # # {"model": "chatgpt"} -> the default account # {"model": "chatgpt@perso"} -> by account id # {"model": "chatgpt@brunocharest.bc@gmail.com"} -> by address # {"model": "chatgpt@perso/gpt-4o"} -> account and model # # Sign each one in once: llm-gateway login chatgpt --account perso # Their profiles live in profiles/chatgpt/accounts//user-data-dir. # # [[accounts]] # id = "perso" # email = "bruno.charest@gmail.com" # label = "Personal" # default = true # # [[accounts]] # id = "pro" # email = "brunocharest.bc@gmail.com" # label = "Work"