Created
March 25, 2026 03:37
-
-
Save QiangF/5080eb98fa3c70379aa9517720b7f8f1 to your computer and use it in GitHub Desktop.
http/2 429 error
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| HTTP/2 429 | |
| vary: Origin | |
| vary: X-Origin | |
| vary: Referer | |
| content-type: application/json; charset=UTF-8 | |
| content-encoding: gzip | |
| date: Wed, 25 Mar 2026 03:22:37 GMT | |
| server: scaffolding on HTTPServer2 | |
| content-length: 581 | |
| x-xss-protection: 0 | |
| x-frame-options: SAMEORIGIN | |
| x-content-type-options: nosniff | |
| server-timing: gfet4t7; dur=467 | |
| alt-svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 | |
| [{ | |
| "error": { | |
| "code": 429, | |
| "message": "You exceeded your current quota, please check your plan and billing details. For more information on this error, head to: https://ai.google.dev/gemini-api/docs/rate-limits. To monitor your current usage, head to: https://ai.dev/rate-limit. \n* Quota exceeded for metric: generativelanguage.googleapis.com/generate_content_free_tier_requests, limit: 20, model: gemini-2.5-flash\nPlease retry in 22.906018063s.", | |
| "status": "RESOURCE_EXHAUSTED", | |
| "details": [ | |
| { | |
| "@type": "type.googleapis.com/google.rpc.Help", | |
| "links": [ | |
| { | |
| "description": "Learn more about Gemini API quotas", | |
| "url": "https://ai.google.dev/gemini-api/docs/rate-limits" | |
| } | |
| ] | |
| }, | |
| { | |
| "@type": "type.googleapis.com/google.rpc.QuotaFailure", | |
| "violations": [ | |
| { | |
| "quotaMetric": "generativelanguage.googleapis.com/generate_content_free_tier_requests", | |
| "quotaId": "GenerateRequestsPerDayPerProjectPerModel-FreeTier", | |
| "quotaDimensions": { | |
| "location": "global", | |
| "model": "gemini-2.5-flash" | |
| }, | |
| "quotaValue": "20" | |
| } | |
| ] | |
| }, | |
| { | |
| "@type": "type.googleapis.com/google.rpc.RetryInfo", | |
| "retryDelay": "22s" | |
| } | |
| ] | |
| } | |
| } | |
| ] |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| (use-package gptel-gemini-key-rotation | |
| :ensure nil | |
| :after (gptel) | |
| :demand t | |
| :config | |
| ;; (gptel-backends-llama-swap) | |
| ;; (gptel-backends-vibe-proxy) | |
| (require 'gptel-gemini-key-rotation) | |
| (require 'gptel-gemini) | |
| (gptel-gemini-key-rotation-setup) | |
| (setq gptel-model 'gemini-2.5-flash | |
| gptel-backend | |
| (gptel-make-gemini-rotating "Gemini" :stream t | |
| :models '(gemini-2.5-flash gemini-2.5-pro gemini-2.5-flash-lite)))) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| ;;; gptel-gemini-key-rotation.el --- Automatic Gemini API key rotation for gptel on errors -*- lexical-binding: t; -*- | |
| ;; This package provides automatic API key rotation for Gemini backends in gptel. | |
| ;; When a request fails (e.g., due to rate limiting or invalid key), it automatically | |
| ;; tries the next available key from authinfo. | |
| (require 'gptel) | |
| (require 'auth-source) | |
| ;;; Configuration | |
| (defgroup gptel-gemini-key-rotation nil | |
| "Automatic API key rotation for Gemini backends in gptel." | |
| :group 'gptel) | |
| (defcustom gptel-gemini-key-rotation-enabled t | |
| "Enable automatic key rotation for Gemini backends." | |
| :type 'boolean | |
| :group 'gptel-gemini-key-rotation) | |
| (defcustom gptel-gemini-authinfo-host "generativelanguage.googleapis.com" | |
| "Host identifier for Gemini API keys in authinfo. | |
| This is the 'machine' field in ~/.authinfo entries." | |
| :type 'string | |
| :group 'gptel-gemini-key-rotation) | |
| (defcustom gptel-gemini-authinfo-user "apikey" | |
| "User identifier for Gemini API keys in authinfo. | |
| This is the 'login' field in ~/.authinfo entries." | |
| :type 'string | |
| :group 'gptel-gemini-key-rotation) | |
| (defcustom gptel-gemini-key-rotation-errors | |
| '("401" "403" "429" "key" "unauthorized" "forbidden" "rate limit" "quota") | |
| "List of error strings that trigger key rotation. | |
| If any of these strings appear in the error message/status, rotation is attempted." | |
| :type '(repeat string) | |
| :group 'gptel-gemini-key-rotation) | |
| ;;; Internal Variables | |
| (defvar gptel-gemini--key-ring nil | |
| "Cache of available Gemini API keys from authinfo.") | |
| (defvar gptel-gemini--current-key-index 0 | |
| "Index of the currently active key in the key ring.") | |
| (defvar gptel-gemini--rotation-in-progress nil | |
| "Flag to prevent recursive rotation attempts.") | |
| ;;; Key Management | |
| (defun gptel-gemini--fetch-all-keys () | |
| "Fetch all Gemini API keys from authinfo. | |
| Returns a list of (SECRET . METADATA) pairs." | |
| (let ((results (auth-source-search | |
| :host gptel-gemini-authinfo-host | |
| :user gptel-gemini-authinfo-user | |
| :require '(:secret) | |
| :max 100))) | |
| (mapcar (lambda (entry) | |
| (let ((secret (plist-get entry :secret))) | |
| ;; Handle both string secrets and function secrets | |
| (when (functionp secret) | |
| (setq secret (funcall secret))) | |
| (cons secret entry))) | |
| results))) | |
| (defun gptel-gemini--refresh-key-ring () | |
| "Refresh the key ring from authinfo." | |
| (setq gptel-gemini--key-ring (gptel-gemini--fetch-all-keys)) | |
| (setq gptel-gemini--current-key-index 0) | |
| (message "Gemini key ring refreshed: %d keys available" | |
| (length gptel-gemini--key-ring))) | |
| (defun gptel-gemini--get-current-key () | |
| "Get the currently active API key." | |
| (unless gptel-gemini--key-ring | |
| (gptel-gemini--refresh-key-ring)) | |
| (when gptel-gemini--key-ring | |
| (car (nth gptel-gemini--current-key-index gptel-gemini--key-ring)))) | |
| (defun gptel-gemini--rotate-key () | |
| "Rotate to the next available API key. | |
| Returns the new key if rotation successful, nil if no more keys available." | |
| (setq gptel-gemini--current-key-index (1+ gptel-gemini--current-key-index)) | |
| (if (>= gptel-gemini--current-key-index (length gptel-gemini--key-ring)) | |
| (progn | |
| (message "Gemini key rotation: exhausted all available keys") | |
| nil) | |
| (let ((new-key (gptel-gemini--get-current-key))) | |
| (message "Gemini key rotation: switched to key %d/%d" | |
| (1+ gptel-gemini--current-key-index) | |
| (length gptel-gemini--key-ring)) | |
| new-key))) | |
| (defun gptel-gemini--should-rotate-p (info) | |
| "Determine if we should rotate keys based on error INFO." | |
| (and gptel-gemini-key-rotation-enabled | |
| (not gptel-gemini--rotation-in-progress) | |
| gptel-gemini--key-ring | |
| (> (length gptel-gemini--key-ring) 1) | |
| (let ((status (plist-get info :http-status)) | |
| (error-msg (format "%s" (plist-get info :error)))) | |
| (cl-some (lambda (pattern) | |
| (or (and status (string-match-p (regexp-quote pattern) status)) | |
| (string-match-p (regexp-quote pattern) error-msg))) | |
| gptel-gemini-key-rotation-errors)))) | |
| ;;; Backend Integration | |
| (defun gptel-gemini--make-rotating-key-function () | |
| "Create a function that returns the current key from the rotating key ring. | |
| This function is suitable for use as the :key parameter in `gptel-make-gemini'." | |
| (lambda () | |
| (or (gptel-gemini--get-current-key) | |
| (progn | |
| (gptel-gemini--refresh-key-ring) | |
| (gptel-gemini--get-current-key))))) | |
| (defun gptel-make-gemini-rotating (name &rest args) | |
| "Create a Gemini backend with automatic key rotation. | |
| NAME is the backend name. ARGS are passed to `gptel-make-gemini', | |
| but :key is automatically set to use the rotating key ring. | |
| Example: | |
| (gptel-make-gemini-rotating \"Gemini-Rotating\" | |
| :stream t | |
| :models \\='(gemini-1.5-flash gemini-1.5-pro))" | |
| (unless gptel-gemini--key-ring | |
| (gptel-gemini--refresh-key-ring)) | |
| ;; Remove :key from args if provided, we use our own | |
| (setq args (plist-put args :key (gptel-gemini--make-rotating-key-function))) | |
| (apply #'gptel-make-gemini name args)) | |
| ;;; Error Handling Integration | |
| (defun gptel-gemini--handle-error (info) | |
| "Handle gptel errors by rotating keys if appropriate. | |
| INFO is the plist containing error information." | |
| (when (gptel-gemini--should-rotate-p info) | |
| (let ((gptel-gemini--rotation-in-progress t)) | |
| (if-let ((new-key (gptel-gemini--rotate-key))) | |
| (progn | |
| ;; Force backend to pick up new key by clearing any cached value | |
| (when-let ((backend (plist-get info :backend))) | |
| ;; Update the backend's key slot if it exists | |
| (when (slot-exists-p backend 'key) | |
| (setf (slot-value backend 'key) | |
| (gptel-gemini--make-rotating-key-function)))) | |
| ;; Retry the request | |
| (message "Retrying request with rotated key...") | |
| (run-with-timer 0.5 nil #'gptel-send)) | |
| (message "No more Gemini keys available. Please check your authinfo."))))) | |
| ;;; Hook Installation | |
| (defun gptel-gemini-key-rotation-setup () | |
| "Set up automatic key rotation hooks." | |
| (interactive) | |
| ;; Initialize key ring | |
| (gptel-gemini--refresh-key-ring) | |
| ;; not triggered on response with error "code": 429 | |
| (advice-add 'gptel--handle-error :around #'gptel-gemini--check-error-and-rotate) | |
| (message "Gemini key rotation enabled with %d keys" | |
| (length gptel-gemini--key-ring))) | |
| ;; Method 1: Advice on gptel--handle-error (receives FSM with full info) | |
| (defun gptel-gemini--check-error-and-rotate (orig-fun fsm) | |
| "Wrap gptel--handle-error to rotate keys on error." | |
| (let* ((info (gptel-fsm-info fsm)) | |
| (error-data (plist-get info :error))) | |
| (when (and error-data | |
| (gptel-gemini--should-rotate-p info)) | |
| (gptel-gemini--handle-error info)) | |
| (funcall orig-fun fsm))) | |
| ;; Method 2: Wrap gptel-request to intercept errors | |
| ;; (advice-add 'gptel-request :around #'gptel-gemini--wrap-request) | |
| (defun gptel-gemini--wrap-request (orig-fun &rest args) | |
| "Wrap gptel-request to handle key rotation on errors." | |
| ;; args is (prompt :key val :key2 val2 ...) | |
| ;; Extract plist portion (everything after prompt) | |
| (if (null args) | |
| (apply orig-fun args) | |
| (let* ((prompt (car args)) | |
| (plist (cdr args)) | |
| (callback (plist-get args :callback)) | |
| (wrapped-callback | |
| (lambda (response info) | |
| (when (and (not (stringp response)) | |
| (not (eq response t)) ; t means success with streaming | |
| (gptel-gemini--should-rotate-p info)) | |
| (gptel-gemini--handle-error info)) | |
| (when callback | |
| (funcall callback response info)))) | |
| (new-plist (plist-put plist :callback wrapped-callback))) | |
| (apply orig-fun prompt new-plist)))) | |
| ;;; Utility Commands | |
| (defun gptel-gemini-force-rotate () | |
| "Manually force key rotation." | |
| (interactive) | |
| (if-let ((new-key (gptel-gemini--rotate-key))) | |
| (message "Manually rotated to key %d" | |
| (1+ gptel-gemini--current-key-index)) | |
| (message "No more keys available"))) | |
| (defun gptel-gemini-reset-key-ring () | |
| "Reset key ring to start from the first key again." | |
| (interactive) | |
| (setq gptel-gemini--current-key-index 0) | |
| (message "Key ring reset to key 1")) | |
| (provide 'gptel-gemini-key-rotation) | |
| ;;; gptel-gemini-key-rotation.el ends here |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment