[
  {
    "path": ".github/pull_request_template.md",
    "content": "<!---\nBefore adding a new provider, there's a few general guidelines as to what providers would be accepted into the repo, primarily around trustworthiness and legitimacy:\n\n1. Is the provider a legitimate company?\n2. Does the provider provide a proper API service?\n   For example, reverse engineering services such as Claude Code, Codex, GitHub Copilot, Qwen Code, etc are not considered appropriate for this list.\n3. Does the provider have a business model?\n4. Does the provider provide legitimate services?\n   For example, providing commercial models such as Anthropic or OpenAI models for free or at steep discounts can suggest questionable business practices, such as chatbot reverse engineering, API credit resale/theft, or other similar practices.\n\nThese guidelines are not exhaustive, but attempts to put into writing what sort of providers might be included in the list.\n\nFor providers that are trying to add themselves to the list, please note that you are very likely going to see abusive traffic, as free APIs are very common abuse targets.\n--->\n"
  },
  {
    "path": ".github/workflows/readme-change-validator.yml",
    "content": "name: README Change Validator\n\non:\n  pull_request:\n    paths:\n      - 'README.md'\n      - 'src/README_template.md'\n      - 'src/pull_available_models.py'\n\npermissions:\n  issues: write\n\njobs:\n  check-readme-changes:\n    if: github.actor != 'github-actions[bot]'\n    runs-on: ubuntu-latest\n    \n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@v6\n        with:\n          fetch-depth: 0  # Fetch all history to compare changes\n\n      - name: Get changed files\n        id: changed-files\n        run: |\n          README_CHANGED=$(git diff --name-only ${{ github.event.pull_request.base.sha }} ${{ github.sha }} | grep -c \"README.md\" || true)\n          TEMPLATE_CHANGED=$(git diff --name-only ${{ github.event.pull_request.base.sha }} ${{ github.sha }} | grep -c \"src/README_template.md\" || true)\n          SCRIPT_CHANGED=$(git diff --name-only ${{ github.event.pull_request.base.sha }} ${{ github.sha }} | grep -c \"src/pull_available_models.py\" || true)\n          \n          echo \"readme_changed=$README_CHANGED\" >> $GITHUB_OUTPUT\n          echo \"template_changed=$TEMPLATE_CHANGED\" >> $GITHUB_OUTPUT\n          echo \"script_changed=$SCRIPT_CHANGED\" >> $GITHUB_OUTPUT\n\n      - name: Validate README changes\n        run: |\n          if [ \"${{ steps.changed-files.outputs.readme_changed }}\" -gt 0 ] && \\\n             [ \"${{ steps.changed-files.outputs.template_changed }}\" -eq 0 ] && \\\n             [ \"${{ steps.changed-files.outputs.script_changed }}\" -eq 0 ]; then\n            echo \"Error: README.md was modified without corresponding changes in src/README_template.md or src/pull_available_models.py\"\n            echo \"Please update the template or script instead of modifying README.md directly.\"\n            exit 1\n          fi\n\n      - name: Add PR comment if check fails\n        if: failure()\n        uses: actions/github-script@v8\n        with:\n          github-token: ${{ secrets.GITHUB_TOKEN }}\n          script: |\n            github.rest.issues.createComment({\n              owner: context.repo.owner,\n              repo: context.repo.repo,\n              issue_number: context.issue.number,\n              body: '❌ README.md was modified without corresponding changes in `src/README_template.md` or `src/pull_available_models.py`\\n\\nPlease update the template or script instead of modifying README.md directly.'\n            })\n"
  },
  {
    "path": ".github/workflows/update-readme.yml",
    "content": "name: Update README\n\non:\n  schedule:\n    - cron: \"0 0 * * *\"\n  workflow_dispatch:\n\njobs:\n  update-readme:\n    runs-on: ubuntu-latest\n    permissions:\n      contents: write\n      pull-requests: write\n      id-token: write\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@v6\n      - name: Setup Python\n        uses: actions/setup-python@v6\n        with:\n          python-version: \"3.12\"\n      - name: Install dependencies\n        run: |\n          pip install -r src/requirements.txt\n      - id: \"auth\"\n        uses: \"google-github-actions/auth@v3\"\n        with:\n          workload_identity_provider: \"projects/576328904266/locations/global/workloadIdentityPools/github/providers/cheahjs-org\"\n          project_id: ${{ secrets.GCP_PROJECT }}\n      - name: Run script\n        env:\n          GROQ_API_KEY: ${{ secrets.GROQ_API_KEY }}\n          CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}\n          CLOUDFLARE_API_KEY: ${{ secrets.CLOUDFLARE_API_KEY }}\n          HYPERBOLIC_API_KEY: ${{ secrets.HYPERBOLIC_API_KEY }}\n          GCP_PROJECT_ID: ${{ secrets.GCP_PROJECT_ID }}\n          GOOGLE_APPLICATION_CREDENTIALS: ${{ steps.auth.outputs.credentials_file_path }}\n          LAMBDA_API_KEY: ${{ secrets.LAMBDA_API_KEY }}\n          MISTRAL_API_KEY: ${{ secrets.MISTRAL_API_KEY }}\n          SCALEWAY_API_KEY: ${{ secrets.SCALEWAY_API_KEY }}\n          COHERE_API_KEY: ${{ secrets.COHERE_API_KEY }}\n        run: |\n          python -u src/pull_available_models.py\n      - name: Remove credentials\n        run: |\n          rm ${{ steps.auth.outputs.credentials_file_path }}\n      - name: Create Pull Request\n        uses: peter-evans/create-pull-request@v8\n        with:\n          title: \"Update README with latest models\"\n          body: \"This PR updates the README with the latest available models.\"\n          branch: update-readme\n          base: main\n          commit-message: \"Update README with latest models\"\n"
  },
  {
    "path": ".gitignore",
    "content": "venv\n.venv\n.env\n.aider*\n*.json\n*.pyc\n"
  },
  {
    "path": "README.md",
    "content": "<!---\nWARNING: DO NOT EDIT THIS FILE DIRECTLY. IT IS GENERATED BY src/pull_available_models.py\n--->\n# Free LLM API resources\n\nThis lists various services that provide free access or credits towards API-based LLM usage.\n\n> [!NOTE]  \n> Please don't abuse these services, else we might lose them.\n\n> [!WARNING]  \n> This list explicitly excludes any services that are not legitimate (eg reverse engineers an existing chatbot)\n\n- [Free Providers](#free-providers)\n  - [OpenRouter](#openrouter)\n  - [Google AI Studio](#google-ai-studio)\n  - [NVIDIA NIM](#nvidia-nim)\n  - [Mistral (La Plateforme)](#mistral-la-plateforme)\n  - [Mistral (Codestral)](#mistral-codestral)\n  - [HuggingFace Inference Providers](#huggingface-inference-providers)\n  - [Vercel AI Gateway](#vercel-ai-gateway)\n  - [OpenCode Zen](#opencode-zen)\n  - [Cerebras](#cerebras)\n  - [Groq](#groq)\n  - [Cohere](#cohere)\n  - [GitHub Models](#github-models)\n  - [Cloudflare Workers AI](#cloudflare-workers-ai)\n- [Providers with trial credits](#providers-with-trial-credits)\n  - [Fireworks](#fireworks)\n  - [Baseten](#baseten)\n  - [Nebius](#nebius)\n  - [Novita](#novita)\n  - [AI21](#ai21)\n  - [Upstage](#upstage)\n  - [NLP Cloud](#nlp-cloud)\n  - [Alibaba Cloud (International) Model Studio](#alibaba-cloud-international-model-studio)\n  - [Modal](#modal)\n  - [Inference.net](#inferencenet)\n  - [Hyperbolic](#hyperbolic)\n  - [SambaNova Cloud](#sambanova-cloud)\n  - [Scaleway Generative APIs](#scaleway-generative-apis)\n\n## Free Providers\n\n### [OpenRouter](https://openrouter.ai)\n\n**Limits:**\n\n[20 requests/minute<br>50 requests/day<br>Up to 1000 requests/day with $10 lifetime topup](https://openrouter.ai/docs/api/reference/limits)\n\nModels share a common quota.\n\n- [Gemma 3 12B Instruct](https://openrouter.ai/google/gemma-3-12b-it:free)\n- [Gemma 3 27B Instruct](https://openrouter.ai/google/gemma-3-27b-it:free)\n- [Gemma 3 4B Instruct](https://openrouter.ai/google/gemma-3-4b-it:free)\n- [Hermes 3 Llama 3.1 405B](https://openrouter.ai/nousresearch/hermes-3-llama-3.1-405b:free)\n- [Llama 3.2 3B Instruct](https://openrouter.ai/meta-llama/llama-3.2-3b-instruct:free)\n- [Llama 3.3 70B Instruct](https://openrouter.ai/meta-llama/llama-3.3-70b-instruct:free)\n- [Mistral Small 3.1 24B Instruct](https://openrouter.ai/mistralai/mistral-small-3.1-24b-instruct:free)\n- [arcee-ai/trinity-large-preview:free](https://openrouter.ai/arcee-ai/trinity-large-preview:free)\n- [arcee-ai/trinity-mini:free](https://openrouter.ai/arcee-ai/trinity-mini:free)\n- [cognitivecomputations/dolphin-mistral-24b-venice-edition:free](https://openrouter.ai/cognitivecomputations/dolphin-mistral-24b-venice-edition:free)\n- [google/gemma-3n-e2b-it:free](https://openrouter.ai/google/gemma-3n-e2b-it:free)\n- [google/gemma-3n-e4b-it:free](https://openrouter.ai/google/gemma-3n-e4b-it:free)\n- [liquid/lfm-2.5-1.2b-instruct:free](https://openrouter.ai/liquid/lfm-2.5-1.2b-instruct:free)\n- [liquid/lfm-2.5-1.2b-thinking:free](https://openrouter.ai/liquid/lfm-2.5-1.2b-thinking:free)\n- [nvidia/nemotron-3-nano-30b-a3b:free](https://openrouter.ai/nvidia/nemotron-3-nano-30b-a3b:free)\n- [nvidia/nemotron-nano-12b-v2-vl:free](https://openrouter.ai/nvidia/nemotron-nano-12b-v2-vl:free)\n- [nvidia/nemotron-nano-9b-v2:free](https://openrouter.ai/nvidia/nemotron-nano-9b-v2:free)\n- [openai/gpt-oss-120b:free](https://openrouter.ai/openai/gpt-oss-120b:free)\n- [openai/gpt-oss-20b:free](https://openrouter.ai/openai/gpt-oss-20b:free)\n- [qwen/qwen3-4b:free](https://openrouter.ai/qwen/qwen3-4b:free)\n- [qwen/qwen3-coder:free](https://openrouter.ai/qwen/qwen3-coder:free)\n- [qwen/qwen3-next-80b-a3b-instruct:free](https://openrouter.ai/qwen/qwen3-next-80b-a3b-instruct:free)\n- [stepfun/step-3.5-flash:free](https://openrouter.ai/stepfun/step-3.5-flash:free)\n- [z-ai/glm-4.5-air:free](https://openrouter.ai/z-ai/glm-4.5-air:free)\n\n### [Google AI Studio](https://aistudio.google.com)\n\nData is used for training when used outside of the UK/CH/EEA/EU.\n\n<table><thead><tr><th>Model Name</th><th>Model Limits</th></tr></thead><tbody>\n<tr><td>Gemini 3 Flash</td><td>250,000 tokens/minute<br>20 requests/day<br>5 requests/minute</td></tr>\n<tr><td>Gemini 3.1 Flash-Lite</td><td>250,000 tokens/minute<br>500 requests/day<br>15 requests/minute</td></tr>\n<tr><td>Gemini 2.5 Flash</td><td>250,000 tokens/minute<br>20 requests/day<br>5 requests/minute</td></tr>\n<tr><td>Gemini 2.5 Flash-Lite</td><td>250,000 tokens/minute<br>20 requests/day<br>10 requests/minute</td></tr>\n<tr><td>Gemma 3 27B Instruct</td><td>15,000 tokens/minute<br>14,400 requests/day<br>30 requests/minute</td></tr>\n<tr><td>Gemma 3 12B Instruct</td><td>15,000 tokens/minute<br>14,400 requests/day<br>30 requests/minute</td></tr>\n<tr><td>Gemma 3 4B Instruct</td><td>15,000 tokens/minute<br>14,400 requests/day<br>30 requests/minute</td></tr>\n<tr><td>Gemma 3 1B Instruct</td><td>15,000 tokens/minute<br>14,400 requests/day<br>30 requests/minute</td></tr>\n</tbody></table>\n\n### [NVIDIA NIM](https://build.nvidia.com/explore/discover)\n\nPhone number verification required.\nModels tend to be context window limited.\n\n**Limits:** 40 requests/minute\n\n- [Various open models](https://build.nvidia.com/models)\n\n### [Mistral (La Plateforme)](https://console.mistral.ai/)\n\n* Free tier (Experiment plan) requires opting into data training\n* Requires phone number verification.\n\n**Limits (per-model):** 1 request/second, 500,000 tokens/minute, 1,000,000,000 tokens/month\n\n- [Open and Proprietary Mistral models](https://docs.mistral.ai/getting-started/models/models_overview/)\n\n### [Mistral (Codestral)](https://codestral.mistral.ai/)\n\n* Currently free to use\n* Monthly subscription based\n* Requires phone number verification\n\n**Limits:** 30 requests/minute, 2,000 requests/day\n\n- Codestral\n\n### [HuggingFace Inference Providers](https://huggingface.co/docs/inference-providers/en/index)\n\nHuggingFace Serverless Inference limited to models smaller than 10GB. Some popular models are supported even if they exceed 10GB.\n\n**Limits:** [$0.10/month in credits](https://huggingface.co/docs/inference-providers/en/pricing)\n\n- Various open models across supported providers\n\n### [Vercel AI Gateway](https://vercel.com/docs/ai-gateway)\n\nRoutes to various supported providers.\n\n**Limits:** [$5/month](https://vercel.com/docs/ai-gateway/pricing)\n\n\n### [OpenCode Zen](https://opencode.ai/docs/zen/)\n\nAI gateway with curated models.\n\nFree models may use data for improvement.\n\n- Big Pickle Stealth\n- MiniMax M2.5 Free\n- Arcee Large Preview Free\n\n### [Cerebras](https://cloud.cerebras.ai/)\n\n<table><thead><tr><th>Model Name</th><th>Model Limits</th></tr></thead><tbody>\n<tr><td>gpt-oss-120b</td><td>30 requests/minute<br>60,000 tokens/minute<br>900 requests/hour<br>1,000,000 tokens/hour<br>14,400 requests/day<br>1,000,000 tokens/day</td></tr>\n<tr><td>Llama 3.1 8B</td><td>30 requests/minute<br>60,000 tokens/minute<br>900 requests/hour<br>1,000,000 tokens/hour<br>14,400 requests/day<br>1,000,000 tokens/day</td></tr>\n</tbody></table>\n\n### [Groq](https://console.groq.com)\n\n<table><thead><tr><th>Model Name</th><th>Model Limits</th></tr></thead><tbody>\n<tr><td>Allam 2 7B</td><td>7,000 requests/day<br>6,000 tokens/minute</td></tr>\n<tr><td>Llama 3.1 8B</td><td>14,400 requests/day<br>6,000 tokens/minute</td></tr>\n<tr><td>Llama 3.3 70B</td><td>1,000 requests/day<br>12,000 tokens/minute</td></tr>\n<tr><td>Llama 4 Maverick 17B 128E Instruct</td><td>1,000 requests/day<br>6,000 tokens/minute</td></tr>\n<tr><td>Llama 4 Scout Instruct</td><td>1,000 requests/day<br>30,000 tokens/minute</td></tr>\n<tr><td>Whisper Large v3</td><td>7,200 audio-seconds/minute<br>2,000 requests/day</td></tr>\n<tr><td>Whisper Large v3 Turbo</td><td>7,200 audio-seconds/minute<br>2,000 requests/day</td></tr>\n<tr><td>canopylabs/orpheus-arabic-saudi</td><td></td></tr>\n<tr><td>canopylabs/orpheus-v1-english</td><td></td></tr>\n<tr><td>groq/compound</td><td>250 requests/day<br>70,000 tokens/minute</td></tr>\n<tr><td>groq/compound-mini</td><td>250 requests/day<br>70,000 tokens/minute</td></tr>\n<tr><td>meta-llama/llama-guard-4-12b</td><td>14,400 requests/day<br>15,000 tokens/minute</td></tr>\n<tr><td>meta-llama/llama-prompt-guard-2-22m</td><td></td></tr>\n<tr><td>meta-llama/llama-prompt-guard-2-86m</td><td></td></tr>\n<tr><td>moonshotai/kimi-k2-instruct</td><td>1,000 requests/day<br>10,000 tokens/minute</td></tr>\n<tr><td>moonshotai/kimi-k2-instruct-0905</td><td>1,000 requests/day<br>10,000 tokens/minute</td></tr>\n<tr><td>openai/gpt-oss-120b</td><td>1,000 requests/day<br>8,000 tokens/minute</td></tr>\n<tr><td>openai/gpt-oss-20b</td><td>1,000 requests/day<br>8,000 tokens/minute</td></tr>\n<tr><td>openai/gpt-oss-safeguard-20b</td><td>1,000 requests/day<br>8,000 tokens/minute</td></tr>\n<tr><td>qwen/qwen3-32b</td><td>1,000 requests/day<br>6,000 tokens/minute</td></tr>\n</tbody></table>\n\n### [Cohere](https://cohere.com)\n\n**Limits:**\n\n[20 requests/minute<br>1,000 requests/month](https://docs.cohere.com/docs/rate-limits)\n\nModels share a common monthly quota.\n\n- c4ai-aya-expanse-32b\n- c4ai-aya-vision-32b\n- command-a-03-2025\n- command-a-reasoning-08-2025\n- command-a-translate-08-2025\n- command-a-vision-07-2025\n- command-r-08-2024\n- command-r-plus-08-2024\n- command-r7b-12-2024\n- command-r7b-arabic-02-2025\n- tiny-aya-earth\n- tiny-aya-fire\n- tiny-aya-global\n- tiny-aya-water\n\n### [GitHub Models](https://github.com/marketplace/models)\n\nExtremely restrictive input/output token limits.\n\n**Limits:** [Dependent on Copilot subscription tier (Free/Pro/Pro+/Business/Enterprise)](https://docs.github.com/en/github-models/prototyping-with-ai-models#rate-limits)\n\n- AI21 Jamba 1.5 Large\n- Codestral 25.01\n- Cohere Command A\n- Cohere Command R 08-2024\n- Cohere Command R+ 08-2024\n- DeepSeek-R1\n- DeepSeek-R1-0528\n- DeepSeek-V3-0324\n- Grok 3\n- Grok 3 Mini\n- Llama 4 Maverick 17B 128E Instruct FP8\n- Llama 4 Scout 17B 16E Instruct\n- Llama-3.2-11B-Vision-Instruct\n- Llama-3.2-90B-Vision-Instruct\n- Llama-3.3-70B-Instruct\n- MAI-DS-R1\n- Meta-Llama-3.1-405B-Instruct\n- Meta-Llama-3.1-8B-Instruct\n- Ministral 3B\n- Mistral Medium 3 (25.05)\n- Mistral Small 3.1\n- OpenAI GPT-4.1\n- OpenAI GPT-4.1-mini\n- OpenAI GPT-4.1-nano\n- OpenAI GPT-4o\n- OpenAI GPT-4o mini\n- OpenAI Text Embedding 3 (large)\n- OpenAI Text Embedding 3 (small)\n- OpenAI gpt-5\n- OpenAI gpt-5-chat (preview)\n- OpenAI gpt-5-mini\n- OpenAI gpt-5-nano\n- OpenAI o1\n- OpenAI o1-mini\n- OpenAI o1-preview\n- OpenAI o3\n- OpenAI o3-mini\n- OpenAI o4-mini\n- Phi-4\n- Phi-4-mini-instruct\n- Phi-4-mini-reasoning\n- Phi-4-multimodal-instruct\n- Phi-4-reasoning\n\n### [Cloudflare Workers AI](https://developers.cloudflare.com/workers-ai)\n\n**Limits:** [10,000 neurons/day](https://developers.cloudflare.com/workers-ai/platform/pricing/#free-allocation)\n\n- @cf/aisingapore/gemma-sea-lion-v4-27b-it\n- @cf/ibm-granite/granite-4.0-h-micro\n- @cf/openai/gpt-oss-120b\n- @cf/openai/gpt-oss-20b\n- @cf/qwen/qwen3-30b-a3b-fp8\n- @cf/zai-org/glm-4.7-flash\n- DeepSeek R1 Distill Qwen 32B\n- Deepseek Coder 6.7B Base (AWQ)\n- Deepseek Coder 6.7B Instruct (AWQ)\n- Deepseek Math 7B Instruct\n- Discolm German 7B v1 (AWQ)\n- Falcom 7B Instruct\n- Gemma 2B Instruct (LoRA)\n- Gemma 3 12B Instruct\n- Gemma 7B Instruct\n- Gemma 7B Instruct (LoRA)\n- Hermes 2 Pro Mistral 7B\n- Llama 2 13B Chat (AWQ)\n- Llama 2 7B Chat (FP16)\n- Llama 2 7B Chat (INT8)\n- Llama 2 7B Chat (LoRA)\n- Llama 3 8B Instruct\n- Llama 3 8B Instruct (AWQ)\n- Llama 3.1 8B Instruct (AWQ)\n- Llama 3.1 8B Instruct (FP8)\n- Llama 3.2 11B Vision Instruct\n- Llama 3.2 1B Instruct\n- Llama 3.2 3B Instruct\n- Llama 3.3 70B Instruct (FP8)\n- Llama 4 Scout Instruct\n- Llama Guard 3 8B\n- Mistral 7B Instruct v0.1\n- Mistral 7B Instruct v0.1 (AWQ)\n- Mistral 7B Instruct v0.2\n- Mistral 7B Instruct v0.2 (LoRA)\n- Mistral Small 3.1 24B Instruct\n- Neural Chat 7B v3.1 (AWQ)\n- OpenChat 3.5 0106\n- OpenHermes 2.5 Mistral 7B (AWQ)\n- Phi-2\n- Qwen 1.5 0.5B Chat\n- Qwen 1.5 1.8B Chat\n- Qwen 1.5 14B Chat (AWQ)\n- Qwen 1.5 7B Chat (AWQ)\n- Qwen 2.5 Coder 32B Instruct\n- Qwen QwQ 32B\n- SQLCoder 7B 2\n- Starling LM 7B Beta\n- TinyLlama 1.1B Chat v1.0\n- Una Cybertron 7B v2 (BF16)\n- Zephyr 7B Beta (AWQ)\n\n</tbody></table>\n\n\n\n## Providers with trial credits\n\n### [Fireworks](https://fireworks.ai/)\n\n**Credits:** $1\n\n**Models:** [Various open models](https://fireworks.ai/models)\n\n### [Baseten](https://app.baseten.co/)\n\n**Credits:** $30\n\n**Models:** [Any supported model - pay by compute time](https://www.baseten.co/library/)\n\n### [Nebius](https://tokenfactory.nebius.com/)\n\n**Credits:** $1\n\n**Models:** [Various open models](https://tokenfactory.nebius.com/models)\n\n### [Novita](https://novita.ai/?ref=ytblmjc&utm_source=affiliate)\n\n**Credits:** $0.5 for 1 year\n\n**Models:** [Various open models](https://novita.ai/models)\n\n### [AI21](https://studio.ai21.com/)\n\n**Credits:** $10 for 3 months\n\n**Models:** Jamba family of models\n\n### [Upstage](https://console.upstage.ai/)\n\n**Credits:** $10 for 3 months\n\n**Models:** Solar Pro/Mini\n\n### [NLP Cloud](https://nlpcloud.com/home)\n\n**Credits:** $15\n\n**Requirements:** Phone number verification\n\n**Models:** Various open models\n\n### [Alibaba Cloud (International) Model Studio](https://bailian.console.alibabacloud.com/)\n\n**Credits:** 1 million tokens/model\n\n**Models:** [Various open and proprietary Qwen models](https://www.alibabacloud.com/en/product/modelstudio)\n\n### [Modal](https://modal.com)\n\n**Credits:** $5/month upon sign up, $30/month with payment method added\n\n**Models:** Any supported model - pay by compute time\n\n### [Inference.net](https://inference.net)\n\n**Credits:** $1, $25 on responding to email survey\n\n**Models:** Various open models\n\n### [Hyperbolic](https://app.hyperbolic.ai/)\n\n**Credits:** $1\n\n**Models:**\n- DeepSeek V3\n- DeepSeek V3 0324\n- Llama 3.1 405B Base\n- Llama 3.1 405B Instruct\n- Llama 3.1 8B Instruct\n- Llama 3.2 3B Instruct\n- Llama 3.3 70B Instruct\n- Pixtral 12B (2409)\n- Qwen QwQ 32B\n- Qwen2.5 72B Instruct\n- Qwen2.5 Coder 32B Instruct\n- Qwen2.5 VL 72B Instruct\n- Qwen2.5 VL 7B Instruct\n- deepseek-ai/deepseek-r1-0528\n- openai/gpt-oss-120b\n- openai/gpt-oss-120b-turbo\n- openai/gpt-oss-20b\n- qwen/qwen3-235b-a22b\n- qwen/qwen3-235b-a22b-instruct-2507\n- qwen/qwen3-coder-480b-a35b-instruct\n- qwen/qwen3-next-80b-a3b-instruct\n- qwen/qwen3-next-80b-a3b-thinking\n\n### [SambaNova Cloud](https://cloud.sambanova.ai/)\n\n**Credits:** $5 for 3 months\n\n**Models:**\n- E5-Mistral-7B-Instruct\n- Llama 3.1 8B\n- Llama 3.3 70B\n- Llama 3.3 70B\n- Llama-4-Maverick-17B-128E-Instruct\n- Qwen/Qwen3-235B\n- Qwen/Qwen3-32B\n- Whisper-Large-v3\n- deepseek-ai/DeepSeek-R1-0528\n- deepseek-ai/DeepSeek-R1-Distill-Llama-70B\n- deepseek-ai/DeepSeek-V3-0324\n- deepseek-ai/DeepSeek-V3.1\n- deepseek-ai/DeepSeek-V3.1-Terminus\n- deepseek-ai/DeepSeek-V3.2\n- minimaxai/minimax-m2.5\n- openai/gpt-oss-120b\n- tbd\n\n### [Scaleway Generative APIs](https://console.scaleway.com/generative-api/models)\n\n**Credits:** 1,000,000 free tokens\n\n**Models:**\n- BGE-Multilingual-Gemma2\n- DeepSeek R1 Distill Llama 70B\n- Gemma 3 27B Instruct\n- Llama 3.1 8B Instruct\n- Llama 3.3 70B Instruct\n- Mistral Nemo 2407\n- Pixtral 12B (2409)\n- Whisper Large v3\n- devstral-2-123b-instruct-2512\n- gpt-oss-120b\n- holo2-30b-a3b\n- mistral-small-3.2-24b-instruct-2506\n- qwen3-235b-a22b-instruct-2507\n- qwen3-coder-30b-a3b-instruct\n- qwen3-embedding-8b\n- voxtral-small-24b-2507\n\n\n"
  },
  {
    "path": "src/README_template.md",
    "content": "# Free LLM API resources\n\nThis lists various services that provide free access or credits towards API-based LLM usage.\n\n> [!NOTE]  \n> Please don't abuse these services, else we might lose them.\n\n> [!WARNING]  \n> This list explicitly excludes any services that are not legitimate (eg reverse engineers an existing chatbot)\n\n{{TOC}}\n\n## Free Providers\n\n{{MODEL_LIST}}\n\n## Providers with trial credits\n\n{{TRIAL_LIST_MARKDOWN}}\n"
  },
  {
    "path": "src/data.py",
    "content": "MODEL_TO_NAME_MAPPING = {\n    \"@cf/deepseek-ai/deepseek-math-7b-instruct\": \"Deepseek Math 7B Instruct\",\n    \"@cf/defog/sqlcoder-7b-2\": \"SQLCoder 7B 2\",\n    \"@cf/fblgit/una-cybertron-7b-v2-bf16\": \"Una Cybertron 7B v2 (BF16)\",\n    \"@cf/google/gemma-2b-it-lora\": \"Gemma 2B Instruct (LoRA)\",\n    \"@cf/google/gemma-7b-it-lora\": \"Gemma 7B Instruct (LoRA)\",\n    \"@cf/meta-llama/llama-2-7b-chat-hf-lora\": \"Llama 2 7B Chat (LoRA)\",\n    \"@cf/meta/llama-2-7b-chat-fp16\": \"Llama 2 7B Chat (FP16)\",\n    \"@cf/meta/llama-2-7b-chat-int8\": \"Llama 2 7B Chat (INT8)\",\n    \"@cf/meta/llama-3-8b-instruct-awq\": \"Llama 3 8B Instruct (AWQ)\",\n    \"@cf/meta/llama-3-8b-instruct\": \"Llama 3 8B Instruct\",\n    \"@cf/meta/llama-3.1-8b-instruct-awq\": \"Llama 3.1 8B Instruct (AWQ)\",\n    \"@cf/meta/llama-3.1-8b-instruct-fp8\": \"Llama 3.1 8B Instruct (FP8)\",\n    \"@cf/meta/llama-3.1-8b-instruct\": \"Llama 3.1 8B Instruct\",\n    \"@cf/microsoft/phi-2\": \"Phi-2\",\n    \"@cf/mistral/mistral-7b-instruct-v0.1-vllm\": \"Mistral 7B Instruct v0.1\",\n    \"@cf/mistral/mistral-7b-instruct-v0.1\": \"Mistral 7B Instruct v0.1\",\n    \"@cf/mistral/mistral-7b-instruct-v0.2-lora\": \"Mistral 7B Instruct v0.2 (LoRA)\",\n    \"@cf/openchat/openchat-3.5-0106\": \"OpenChat 3.5 0106\",\n    \"@cf/qwen/qwen1.5-0.5b-chat\": \"Qwen 1.5 0.5B Chat\",\n    \"@cf/qwen/qwen1.5-1.8b-chat\": \"Qwen 1.5 1.8B Chat\",\n    \"@cf/qwen/qwen1.5-14b-chat-awq\": \"Qwen 1.5 14B Chat (AWQ)\",\n    \"@cf/qwen/qwen1.5-7b-chat-awq\": \"Qwen 1.5 7B Chat (AWQ)\",\n    \"@cf/thebloke/discolm-german-7b-v1-awq\": \"Discolm German 7B v1 (AWQ)\",\n    \"@cf/tiiuae/falcon-7b-instruct\": \"Falcom 7B Instruct\",\n    \"@cf/tinyllama/tinyllama-1.1b-chat-v1.0\": \"TinyLlama 1.1B Chat v1.0\",\n    \"@hf/google/gemma-7b-it\": \"Gemma 7B Instruct\",\n    \"@hf/meta-llama/meta-llama-3-8b-instruct\": \"Llama 3 8B Instruct\",\n    \"@hf/mistral/mistral-7b-instruct-v0.2\": \"Mistral 7B Instruct v0.2\",\n    \"@hf/nexusflow/starling-lm-7b-beta\": \"Starling LM 7B Beta\",\n    \"@hf/nousresearch/hermes-2-pro-mistral-7b\": \"Hermes 2 Pro Mistral 7B\",\n    \"@hf/thebloke/deepseek-coder-6.7b-base-awq\": \"Deepseek Coder 6.7B Base (AWQ)\",\n    \"@hf/thebloke/deepseek-coder-6.7b-instruct-awq\": \"Deepseek Coder 6.7B Instruct (AWQ)\",\n    \"@hf/thebloke/llama-2-13b-chat-awq\": \"Llama 2 13B Chat (AWQ)\",\n    \"@hf/thebloke/llamaguard-7b-awq\": \"LlamaGuard 7B (AWQ)\",\n    \"@hf/thebloke/mistral-7b-instruct-v0.1-awq\": \"Mistral 7B Instruct v0.1 (AWQ)\",\n    \"@hf/thebloke/neural-chat-7b-v3-1-awq\": \"Neural Chat 7B v3.1 (AWQ)\",\n    \"@hf/thebloke/openhermes-2.5-mistral-7b-awq\": \"OpenHermes 2.5 Mistral 7B (AWQ)\",\n    \"@hf/thebloke/zephyr-7b-beta-awq\": \"Zephyr 7B Beta (AWQ)\",\n    \"codellama-13b-instruct-hf\": \"CodeLlama 13B Instruct\",\n    \"distil-whisper-large-v3-en\": \"Distil Whisper Large v3\",\n    \"gemma-7b-it\": \"Gemma 7B Instruct (Deprecated)\",\n    \"gemma2-9b-it\": \"Gemma 2 9B Instruct\",\n    \"google/gemma-2-9b-it:free\": \"Gemma 2 9B Instruct\",\n    \"google/gemma-7b-it:free\": \"Gemma 7B Instruct\",\n    \"gryphe/mythomist-7b:free\": \"Mythomist 7B\",\n    \"huggingfaceh4/zephyr-7b-beta:free\": \"Zephyr 7B Beta\",\n    \"llama-2-13b-chat-hf\": \"Llama 2 13B Chat\",\n    \"llama-3-70b-instruct\": \"Llama 3 70B Instruct\",\n    \"llama-3-8b-instruct\": \"Llama 3 8B Instruct\",\n    \"llama-3.1-405b-reasoning\": \"Llama 3.1 405B\",\n    \"llama-3.1-70b-versatile\": \"Llama 3.1 70B\",\n    \"llama-3.1-8b-instant\": \"Llama 3.1 8B\",\n    \"llama-guard-3-8b\": \"Llama Guard 3 8B\",\n    \"llama3-70b-8192\": \"Llama 3 70B\",\n    \"llama3-8b-8192\": \"Llama 3 8B\",\n    \"llama3-groq-70b-8192-tool-use-preview\": \"Llama 3 70B - Groq Tool Use Preview\",\n    \"llama3-groq-8b-8192-tool-use-preview\": \"Llama 3 8B - Groq Tool Use Preview\",\n    \"meta-llama/llama-3-8b-instruct:free\": \"Llama 3 8B Instruct\",\n    \"meta-llama/llama-3.1-8b-instruct:free\": \"Llama 3.1 8B Instruct\",\n    \"meta-llama/meta-llama-3-70b-instruct\": \"Llama 3 70B Instruct\",\n    \"meta-llama/meta-llama-3.1-405b\": \"Llama 3.1 405B Base\",\n    \"meta-llama/meta-llama-3.1-405b-fp8\": \"Llama 3.1 405B Base (FP8)\",\n    \"meta-llama/meta-llama-3.1-405b-instruct\": \"Llama 3.1 405B Instruct\",\n    \"meta-llama/meta-llama-3.1-70b-instruct\": \"Llama 3.1 70B Instruct\",\n    \"meta-llama/meta-llama-3.1-8b-instruct\": \"Llama 3.1 8B Instruct\",\n    \"microsoft/phi-3-medium-128k-instruct:free\": \"Phi-3 Medium 128k Instruct\",\n    \"microsoft/phi-3-mini-128k-instruct:free\": \"Phi-3 Mini 128k Instruct\",\n    \"mistral-7b-instruct\": \"Mistral 7B Instruct\",\n    \"mistralai/mistral-7b-instruct:free\": \"Mistral 7B Instruct\",\n    \"mixtral-8x22b-instruct\": \"Mixtral 8x22B Instruct\",\n    \"mixtral-8x7b-32768\": \"Mixtral 8x7B\",\n    \"mixtral-8x7b-instruct\": \"Mixtral 8x7B Instruct\",\n    \"nousresearch/hermes-3-llama-3.1-70b\": \"Hermes 3 Llama 3.1 70B\",\n    \"nousresearch/nous-capybara-7b:free\": \"Nous Capybara 7B\",\n    \"openchat/openchat-7b:free\": \"OpenChat 7B\",\n    \"qwen/qwen-2-7b-instruct:free\": \"Qwen 2 7B Instruct\",\n    \"qwen/qwen2-72b-instruct\": \"Qwen 2 72B Instruct\",\n    \"undi95/toppy-m-7b:free\": \"Toppy M 7B\",\n    \"whisper-large-v3\": \"Whisper Large v3\",\n    \"whisper-large-v3-turbo\": \"Whisper Large v3 Turbo\",\n    \"01-ai/yi-34b-chat\": \"Yi 34B Chat\",\n    \"01-ai/yi-1.5-34b-chat\": \"Yi 1.5 34B Chat\",\n    \"nousresearch/hermes-3-llama-3.1-70b-fp8\": \"Hermes 3 Llama 3.1 70B (FP8)\",\n    \"nousresearch/hermes-3-llama-3.1-405b:free\": \"Hermes 3 Llama 3.1 405B\",\n    \"llava-v1.5-7b-4096-preview\": \"LLaVA 1.5 7B\",\n    \"mattshumer/reflection-llama-3.1-70b\": \"Reflection Llama 3.1 70B\",\n    \"mattshumer/reflection-70b:free\": \"Reflection Llama 3.1 70B\",\n    \"mattshumer/reflection-llama-3.1-70b-completions\": \"Reflection Llama 3.1 70B Completions\",\n    \"deepseek-ai/deepseek-v2.5\": \"DeepSeek V2.5\",\n    \"mistralai/pixtral-12b-2409\": \"Pixtral 12B (2409)\",\n    \"qwen/qwen2-vl-7b-instruct\": \"Qwen2-VL 7B Instruct\",\n    \"mistralai/pixtral-12b:free\": \"Pixtral 12B\",\n    \"qwen/qwen-2-vl-7b-instruct:free\": \"Qwen2-VL 7B Instruct\",\n    \"qwen/qwen2-vl-72b-instruct\": \"Qwen2-VL 72B Instruct\",\n    \"qwen/qwen2.5-72b-instruct\": \"Qwen2.5 72B Instruct\",\n    \"llama-3.2-90b-text-preview\": \"Llama 3.2 90B (Text Only)\",\n    \"llama-3.2-3b-preview\": \"Llama 3.2 3B\",\n    \"llama-3.2-11b-text-preview\": \"Llama 3.2 11B (Text Only)\",\n    \"llama-3.2-1b-preview\": \"Llama 3.2 1B\",\n    \"@cf/meta/llama-3.2-1b-instruct\": \"Llama 3.2 1B Instruct\",\n    \"meta-llama/llama-3.2-11b-vision-instruct:free\": \"Llama 3.2 11B Vision Instruct\",\n    \"@cf/meta/llama-3.2-11b-vision-instruct\": \"Llama 3.2 11B Vision Instruct\",\n    \"@cf/meta/llama-3.2-3b-instruct\": \"Llama 3.2 3B Instruct\",\n    \"meta-llama/llama-3.2-90b-vision-instruct\": \"Llama 3.2 90B Vision Instruct\",\n    \"meta-llama/llama-3.2-3b-instruct\": \"Llama 3.2 3B Instruct\",\n    \"llama-3.2-11b-vision-preview\": \"Llama 3.2 11B Vision\",\n    \"llama-3.2-90b-vision-preview\": \"Llama 3.2 90B Vision\",\n    \"meta-llama/llama-3.2-90b-vision\": \"Llama 3.2 90B Vision\",\n    \"meta-llama/llama-3.1-70b-instruct:free\": \"Llama 3.1 70B Instruct\",\n    \"meta-llama/llama-3.2-1b-instruct:free\": \"Llama 3.2 1B Instruct\",\n    \"liquid/lfm-40b:free\": \"Liquid LFM 40B\",\n    \"meta-llama/llama-3.2-3b-instruct:free\": \"Llama 3.2 3B Instruct\",\n    \"meta-llama/llama-3.1-405b-instruct:free\": \"Llama 3.1 405B Instruct\",\n    \"mathstral-7b-v0.1\": \"Mathstral 7B v0.1\",\n    \"llama-3.1-70b-instruct\": \"Llama 3.1 70B Instruct\",\n    \"gryphe/mythomax-l2-13b:free\": \"Mythomax L2 13B\",\n    \"meta-llama/llama-3.2-90b-vision-instruct:free\": \"Llama 3.2 90B Vision Instruct\",\n    \"mamba-codestral-7b-v0-1\": \"Codestral Mamba 7B v0.1\",\n    \"hermes3-70b\": \"Hermes 3 70B\",\n    \"llama3.1-nemotron-70b-instruct\": \"Llama 3.1 Nemotron 70B Instruct\",\n    \"llama3.2-3b-instruct\": \"Llama 3.2 3B Instruct\",\n    \"llama3.1-8b-instruct\": \"Llama 3.1 8B Instruct\",\n    \"llama3.1-70b-instruct-fp8\": \"Llama 3.1 70B Instruct (FP8)\",\n    \"llama3.1-405b-instruct-fp8\": \"Llama 3.1 405B Instruct (FP8)\",\n    \"hermes3-405b\": \"Hermes 3 405B\",\n    \"deepseek-coder-v2-lite-instruct\": \"DeepSeek Coder v2 Lite Instruct\",\n    \"hermes3-8b\": \"Hermes 3 8B\",\n    \"dracarys2-72b-instruct\": \"Dracarys 2 72B Instruct\",\n    \"lfm-40b\": \"Liquid LFM 40B\",\n    \"qwen/qwen2.5-coder-32b-instruct\": \"Qwen2.5 Coder 32B Instruct\",\n    \"thedrummer/unslopnemo-12b:free\": \"UnslopNemo 12B\",\n    \"mistral-nemo-instruct-2407\": \"Mistral Nemo 2407\",\n    \"google/gemini-exp-1121:free\": \"Gemini Experimental 1121\",\n    \"meta-llama/llama-3.1-70b-instruct-fp8\": \"Llama 3.1 70B Instruct (FP8)\",\n    \"google/learnlm-1.5-pro-experimental:free\": \"LearnLM 1.5 Pro Experimental\",\n    \"google/gemini-exp-1114:free\": \"Gemini Experimental 1114\",\n    \"qwen25-coder-32b-instruct\": \"Qwen2.5 Coder 32B Instruct\",\n    \"qwen/qwq-32b-preview\": \"Qwen QwQ 32B Preview\",\n    \"meta-llama/llama-3.3-70b-instruct\": \"Llama 3.3 70B Instruct\",\n    \"llama-3.3-70b-versatile\": \"Llama 3.3 70B\",\n    \"google/gemini-exp-1206:free\": \"Gemini Experimental 1206\",\n    \"llama3.1-nemotron-70b-instruct-fp8\": \"Llama 3.1 Nemotron 70B Instruct (FP8)\",\n    \"llama-3.3-70b-specdec\": \"Llama 3.3 70B (Speculative Decoding)\",\n    \"@cf/meta/llama-3.3-70b-instruct-fp8-fast\": \"Llama 3.3 70B Instruct (FP8)\",\n    \"google/gemini-2.0-flash-exp:free\": \"Gemini 2.0 Flash Experimental\",\n    \"qwen2.5-coder-32b-instruct\": \"Qwen2.5 Coder 32B Instruct\",\n    \"bge-multilingual-gemma2\": \"BGE-Multilingual-Gemma2\",\n    \"pixtral-12b-2409\": \"Pixtral 12B (2409)\",\n    \"google/gemini-2.0-flash-thinking-exp:free\": \"Gemini 2.0 Flash Thinking Experimental\",\n    \"sentence-t5-xxl\": \"sentence-t5-xxl\",\n    \"meta-llama/meta-llama-3.1-405b-instruct-virtuals\": \"Llama 3.1 405B Instruct Virtuals\",\n    \"llama-3.1-8b-instruct\": \"Llama 3.1 8B Instruct\",\n    \"deepseek-ai/deepseek-v3\": \"DeepSeek V3\",\n    \"llava-next-mistral-7b\": \"Llava Next Mistral 7B\",\n    \"llama-3.3-70b-instruct\": \"Llama 3.3 70B Instruct\",\n    \"google/gemini-2.0-flash-thinking-exp-1219:free\": \"Gemini 2.0 Flash Thinking Experimental 1219\",\n    \"sophosympatheia/rogue-rose-103b-v0.2:free\": \"Rogue Rose 103B v0.2\",\n    \"deepseek-ai/deepseek-r1\": \"DeepSeek R1\",\n    \"deepseek-ai/deepseek-r1-zero\": \"DeepSeek R1-Zero\",\n    \"deepseek/deepseek-r1:free\": \"DeepSeek R1\",\n    \"deepseek-r1-distill-llama-70b\": \"DeepSeek R1 Distill Llama 70B\",\n    \"@cf/deepseek-ai/deepseek-r1-distill-qwen-32b\": \"DeepSeek R1 Distill Qwen 32B\",\n    \"deepseek-ai/janus-pro-7b\": \"DeepSeek Janus Pro 7B\",\n    \"deepseek-r1-distill-llama-8b\": \"DeepSeek R1 Distill Llama 8B\",\n    \"nvidia/llama-3.1-nemotron-70b-instruct:free\": \"Llama 3.1 Nemotron 70B Instruct\",\n    \"deepseek/deepseek-r1-distill-llama-70b:free\": \"DeepSeek R1 Distill Llama 70B\",\n    \"qwen/qwen2.5-vl-72b-instruct:free\": \"Qwen2.5 VL 72B Instruct\",\n    \"google/gemini-2.0-flash-lite-preview-02-05:free\": \"Gemini 2.0 Flash Lite Preview 02-05\",\n    \"qwen/qwen-vl-plus:free\": \"Qwen VL Plus\",\n    \"google/gemini-2.0-pro-exp-02-05:free\": \"Gemini 2.0 Pro Experimental 02-05\",\n    \"deepseek-r1\": \"DeepSeek R1\",\n    \"meta-llama/llama-3.3-70b-instruct:free\": \"Llama 3.3 70B Instruct\",\n    \"deepseek/deepseek-chat:free\": \"DeepSeek V3\",\n    \"deepseek-r1-distill-qwen-32b\": \"DeepSeek R1 Distill Qwen 32B\",\n    \"mistralai/mistral-nemo:free\": \"Mistral Nemo\",\n    \"allam-2-7b\": \"Allam 2 7B\",\n    \"mistralai/mistral-small-24b-instruct-2501:free\": \"Mistral Small 24B Instruct 2501\",\n    \"qwen-2.5-32b\": \"Qwen 2.5 32B\",\n    \"cognitivecomputations/dolphin3.0-r1-mistral-24b:free\": \"Dolphin 3.0 R1 Mistral 24B\",\n    \"qwen-2.5-coder-32b\": \"Qwen 2.5 Coder 32B\",\n    \"cognitivecomputations/dolphin3.0-mistral-24b:free\": \"Dolphin 3.0 Mistral 24B\",\n    \"deepseek-r1-671b\": \"DeepSeek R1\",\n    \"@cf/meta/llama-guard-3-8b\": \"Llama Guard 3 8B\",\n    \"mistral-saba-24b\": \"Mistral Saba 24B\",\n    \"deepseek/deepseek-r1-zero:free\": \"DeepSeek R1 Zero\",\n    \"nousresearch/deephermes-3-llama-3-8b-preview:free\": \"DeepHermes 3 Llama 3 8B Preview\",\n    \"qwen-qwq-32b\": \"Qwen QwQ 32B\",\n    \"qwen/qwq-32b\": \"Qwen QwQ 32B\",\n    \"qwen/qwq-32b:free\": \"Qwen QwQ 32B\",\n    \"qwen/qwen2.5-vl-7b-instruct\": \"Qwen2.5 VL 7B Instruct\",\n    \"qwen/qwen-2.5-coder-32b-instruct:free\": \"Qwen2.5 Coder 32B Instruct\",\n    \"mistral-7b-instruct-v0.3\": \"Mistral 7B Instruct v0.3\",\n    \"moonshotai/moonlight-16b-a3b-instruct:free\": \"Moonlight-16B-A3B-Instruct\",\n    \"google/gemma-3-27b-it:free\": \"Gemma 3 27B Instruct\",\n    \"qwen/qwen-2.5-72b-instruct:free\": \"Qwen 2.5 72B Instruct\",\n    \"rekaai/reka-flash-3:free\": \"Reka Flash 3\",\n    \"deepseek/deepseek-r1-distill-qwen-32b:free\": \"DeepSeek R1 Distill Qwen 32B\",\n    \"deepseek/deepseek-r1-distill-qwen-14b:free\": \"DeepSeek R1 Distill Qwen 14B\",\n    \"qwen/qwen2.5-vl-72b-instruct\": \"Qwen2.5 VL 72B Instruct\",\n    \"qwen/qwq-32b-preview:free\": \"Qwen QwQ 32B Preview\",\n    \"google/gemma-3-12b-it:free\": \"Gemma 3 12B Instruct\",\n    \"google/gemma-3-1b-it:free\": \"Gemma 3 1B Instruct\",\n    \"google/gemma-3-4b-it:free\": \"Gemma 3 4B Instruct\",\n    \"open-r1/olympiccoder-32b:free\": \"OlympicCoder 32B\",\n    \"open-r1/olympiccoder-7b:free\": \"OlympicCoder 7B\",\n    \"featherless/qwerky-72b:free\": \"Featherless Qwerky 72B\",\n    \"qwen/qwen2.5-vl-32b-instruct:free\": \"Qwen 2.5 VL 32B Instruct\",\n    \"deepseek/deepseek-chat-v3-0324:free\": \"DeepSeek V3 0324\",\n    \"qwen/qwen-2.5-vl-7b-instruct:free\": \"Qwen 2.5 VL 7B Instruct\",\n    \"deepseek-ai/deepseek-v3-0324\": \"DeepSeek V3 0324\",\n    \"allenai/molmo-7b-d:free\": \"Molmo 7B D\",\n    \"qwen/qwen2.5-vl-3b-instruct:free\": \"Qwen 2.5 VL 3B Instruct\",\n    \"google/gemini-2.5-pro-exp-03-25:free\": \"Gemini 2.5 Pro Experimental 03-25\",\n    \"mistralai/mistral-small-3.1-24b-instruct:free\": \"Mistral Small 3.1 24B Instruct\",\n    \"bytedance-research/ui-tars-72b:free\": \"Bytedance UI Tars 72B\",\n    \"meta-llama-3_3-70b-instruct\": \"Llama 3.3 70B Instruct\",\n    \"mixtral-8x7b-instruct-v0.1\": \"Mixtral 8x7B Instruct v0.1\",\n    \"deepseek/deepseek-v3-base:free\": \"DeepSeek V3 Base\",\n    \"qwen2.5-vl-72b-instruct\": \"Qwen 2.5 VL 72B Instruct\",\n    \"meta-llama-3_1-70b-instruct\": \"Llama 3.1 70B Instruct\",\n    \"qwen/qwen-2.5-7b-instruct:free\": \"Qwen 2.5 7B Instruct\",\n    \"mamba-codestral-7b-v0.1\": \"Mamba Codestral 7B v0.1\",\n    \"meta-llama/llama-4-scout-17b-16e-instruct\": \"Llama 4 Scout Instruct\",\n    \"@cf/meta/llama-4-scout-17b-16e-instruct\": \"Llama 4 Scout Instruct\",\n    \"meta-llama/llama-4-scout:free\": \"Llama 4 Scout\",\n    \"meta-llama/llama-4-maverick:free\": \"Llama 4 Maverick\",\n    \"rekaai/reka-flash-3\": \"Reka Flash 3\",\n    \"cognitivecomputations/dolphin3.0-mistral-24b\": \"Dolphin 3.0 Mistral 24B\",\n    \"unsloth/gemma-3-12b-it\": \"Gemma 3 12B Instruct\",\n    \"chutesai/llama-4-maverick-17b-128e-instruct-fp8\": \"Llama 4 Maverick 17B 128E Instruct FP8\",\n    \"unsloth/gemma-3-1b-it\": \"Gemma 3 1B Instruct\",\n    \"deepseek-ai/deepseek-v3-base\": \"DeepSeek V3 Base\",\n    \"unsloth/gemma-3-4b-it\": \"Gemma 3 4B Instruct\",\n    \"open-r1/olympiccoder-32b\": \"OlympicCoder 32B\",\n    \"chutesai/llama-4-scout-17b-16e-instruct\": \"Llama 4 Scout 17B 16E Instruct\",\n    \"cognitivecomputations/dolphin3.0-r1-mistral-24b\": \"Dolphin 3.0 R1 Mistral 24B\",\n    \"open-r1/olympiccoder-7b\": \"OlympicCoder 7B\",\n    \"nousresearch/deephermes-3-llama-3-8b-preview\": \"DeepHermes 3 Llama 3 8B Preview\",\n    \"chutesai/mistral-small-3.1-24b-instruct-2503\": \"Mistral Small 3.1 24B Instruct 2503\",\n    \"qwen/qwen2.5-vl-32b-instruct\": \"Qwen 2.5 VL 32B Instruct\",\n    \"nvidia/llama-3_1-nemotron-ultra-253b-v1\": \"Llama 3.1 Nemotron Ultra 253B v1\",\n    \"nvidia/llama-3.1-nemotron-ultra-253b-v1:free\": \"Llama 3.1 Nemotron Ultra 253B v1\",\n    \"nvidia/llama-3.1-nemotron-nano-8b-v1\": \"Llama 3.1 Nemotron Nano 8B v1\",\n    \"mistral-small-3.1-24b-instruct-2503\": \"Mistral Small 3.1 24B Instruct 2503\",\n    \"nvidia/llama-3_3-nemotron-super-49b-v1\": \"Llama 3.3 Nemotron Super 49B v1\",\n    \"gemma-3-27b-it\": \"Gemma 3 27B Instruct\",\n    \"nvidia/llama-3.3-nemotron-super-49b-v1:free\": \"Llama 3.3 Nemotron Super 49B v1\",\n    \"nvidia/llama-3.1-nemotron-nano-8b-v1:free\": \"Llama 3.1 Nemotron Nano 8B v1\",\n    \"meta-llama/llama-4-maverick-17b-128e-instruct\": \"Llama 4 Maverick 17B 128E Instruct\",\n    \"moonshotai/kimi-vl-a3b-thinking:free\": \"Kimi VL A3B Thinking\",\n    \"moonshotai/kimi-vl-a3b-thinking\": \"Kimi VL A3B Thinking\",\n    \"@cf/mistralai/mistral-small-3.1-24b-instruct\": \"Mistral Small 3.1 24B Instruct\",\n    \"@cf/google/gemma-3-12b-it\": \"Gemma 3 12B Instruct\",\n    \"@cf/qwen/qwq-32b\": \"Qwen QwQ 32B\",\n    \"@cf/qwen/qwen2.5-coder-32b-instruct\": \"Qwen 2.5 Coder 32B Instruct\",\n    \"arliai/qwq-32b-arliai-rpr-v1:free\": \"QwQ 32B ArliAI RpR v1\",\n    \"agentica-org/deepcoder-14b-preview:free\": \"DeepCoder 14B Preview\",\n    \"agentica-org/deepcoder-14b-preview\": \"DeepCoder 14B Preview\",\n    \"arliai/qwq-32b-arliai-rpr-v1\": \"QwQ 32B ArliAI RpR v1\",\n    \"shisa-ai/shisa-v2-llama3.3-70b:free\": \"Shisa V2 Llama 3.3 70B\",\n    \"compound-beta-mini\": \"Groq compound-beta-mini\",\n    \"compound-beta\": \"Groq compound-beta\",\n    \"shisa-ai/shisa-v2-llama3.3-70b\": \"Shisa V2 Llama 3.3 70B\",\n}\n\nHYPERBOLIC_IGNORED_MODELS = {\n    \"Wifhat\",\n    \"FLUX.1-dev\",\n    \"StableDiffusion\",\n    \"Monad\",\n    \"TTS\",\n    \"deepseek-ai/Janus-Pro-7B\",\n    \"test\",\n    \"SDXL1.0-base\",\n    # Ignore DeepSeek R1 and R1-Zero because they are not available in the free tier.\n    \"deepseek-ai/DeepSeek-R1\",\n    \"deepseek-ai/DeepSeek-R1-Zero\",\n}\n\nLAMBDA_IGNORED_MODELS = {\"lfm-40b-vllm\", \"hermes3-405b-fp8-128k\"}\n\nOPENROUTER_IGNORED_MODELS = {\n    # Ignore gemini experimental free models because rate limits mean they are unusable.\n    \"google/gemini-exp-1121:free\",\n    \"google/learnlm-1.5-pro-experimental:free\",\n    \"google/gemini-exp-1114:free\",\n    \"google/gemini-exp-1206:free\",\n    \"google/gemini-2.0-flash-exp:free\",\n    \"google/gemini-2.0-flash-thinking-exp:free\",\n    \"google/gemini-2.0-flash-thinking-exp-1219:free\",\n    \"google/gemini-flash-1.5-exp:free\",\n    \"google/gemini-2.0-pro-exp-02-05:free\",\n}\n"
  },
  {
    "path": "src/pull_available_models.py",
    "content": "#!/usr/bin/env python3\n\nfrom collections import defaultdict\nimport logging\nimport json\nimport requests\nimport os\nfrom dotenv import load_dotenv\nfrom google.cloud import cloudquotas_v1\nfrom mistralai import Mistral\nfrom concurrent.futures import ThreadPoolExecutor\nimport time\nimport re\n\nfrom data import (\n    MODEL_TO_NAME_MAPPING,\n    HYPERBOLIC_IGNORED_MODELS,\n    LAMBDA_IGNORED_MODELS,\n    OPENROUTER_IGNORED_MODELS,\n)\n\n\nload_dotenv()\nscript_dir = os.path.dirname(os.path.abspath(__file__))\n\n# Global clients\nmistral_client = Mistral(api_key=os.environ[\"MISTRAL_API_KEY\"])\nlast_mistral_request_time = 0\n\n\ndef create_logger(provider_name):\n    logger = logging.getLogger(provider_name)\n    logger.setLevel(logging.DEBUG)\n    handler = logging.StreamHandler()\n    formatter = logging.Formatter(f\"{provider_name}: %(message)s\")\n    handler.setFormatter(formatter)\n    logger.addHandler(handler)\n    return logger\n\n\nMISSING_MODELS = set()\n\n\ndef get_model_name(id):\n    id = id.lower()\n    if id in MODEL_TO_NAME_MAPPING:\n        return MODEL_TO_NAME_MAPPING[id]\n    MISSING_MODELS.add(id)\n    return id\n\n\ndef get_groq_limits_for_stt_model(model_id, logger):\n    logger.info(f\"Getting limits for STT model {model_id}...\")\n    try:\n        r = requests.post(\n            \"https://api.groq.com/openai/v1/audio/transcriptions\",\n            headers={\n                \"Authorization\": f'Bearer {os.environ[\"GROQ_API_KEY\"]}',\n            },\n            data={\n                \"model\": model_id,\n            },\n            files={\n                \"file\": open(os.path.join(script_dir, \"1-second-of-silence.mp3\"), \"rb\"),\n            },\n        )\n    except Exception as e:\n        logger.error(f\"Failed to get limits for model {model_id}: {e}\")\n        return {}\n    try:\n        r.raise_for_status()\n    except Exception as e:\n        logger.error(f\"Failed to get limits for model {model_id}: {e}\")\n        logger.error(r.text)\n        return {}\n    audio_seconds_per_minute = int(r.headers[\"x-ratelimit-limit-audio-seconds\"])\n    rpd = int(r.headers[\"x-ratelimit-limit-requests\"])\n    return {\n        \"audio-seconds/minute\": audio_seconds_per_minute,\n        \"requests/day\": rpd,\n    }\n\n\ndef get_groq_limits_for_model(model_id, script_dir, logger):\n    if \"whisper\" in model_id:\n        return get_groq_limits_for_stt_model(model_id, logger)\n    if \"tts\" in model_id:\n        return None\n    logger.info(f\"Getting limits for chat model {model_id}...\")\n\n    try:\n        r = requests.post(\n            \"https://api.groq.com/openai/v1/chat/completions\",\n            headers={\n                \"Authorization\": f'Bearer {os.environ[\"GROQ_API_KEY\"]}',\n                \"Content-Type\": \"application/json\",\n            },\n            json={\n                \"model\": model_id,\n                \"messages\": [{\"role\": \"user\", \"content\": \"Hi!\"}],\n                \"max_tokens\": 1,\n                \"stream\": True,\n            },\n            stream=True,\n        )\n    except Exception as e:\n        logger.error(f\"Failed to get limits for model {model_id}: {e}\")\n        return {}\n    try:\n        r.raise_for_status()\n        rpd = int(r.headers[\"x-ratelimit-limit-requests\"])\n        tpm = int(r.headers[\"x-ratelimit-limit-tokens\"])\n        return {\"requests/day\": rpd, \"tokens/minute\": tpm}\n    except Exception as e:\n        logger.error(f\"Failed to get limits for model {model_id}: {e}\")\n        logger.error(r.text)\n        return {}\n\n\ndef fetch_groq_models(logger):\n    logger.info(\"Fetching Groq models...\")\n    r = requests.get(\n        \"https://api.groq.com/openai/v1/models\",\n        headers={\n            \"Authorization\": f'Bearer {os.environ[\"GROQ_API_KEY\"]}',\n            \"Content-Type\": \"application/json\",\n        },\n    )\n    r.raise_for_status()\n    models = r.json()[\"data\"]\n    logger.debug(json.dumps(models, indent=4))\n    ret_models = []\n    with ThreadPoolExecutor() as executor:\n        futures = []\n        for model in models:\n            future = executor.submit(\n                get_groq_limits_for_model, model[\"id\"], script_dir, logger\n            )\n            futures.append((model, future))\n\n        for model, future in futures:\n            limits = future.result()\n            if limits is None:\n                continue\n            ret_models.append(\n                {\n                    \"id\": model[\"id\"],\n                    \"name\": get_model_name(model[\"id\"]),\n                    \"limits\": limits,\n                }\n            )\n    ret_models = sorted(ret_models, key=lambda x: x[\"name\"])\n    return ret_models\n\n\ndef fetch_kluster_models(logger):\n    logger.info(\"Fetching Kluster models...\")\n    try:\n        r = requests.get(\n            \"https://api.kluster.ai/v1/models\",\n            headers={\n                \"Content-Type\": \"application/json\",\n            },\n            timeout=10,\n        )\n        r.raise_for_status()\n\n        # Parse the JSON response\n        response = r.json()\n\n        # Based on the paste-2.txt example, the structure should be:\n        # {\"object\":\"list\",\"data\":[{model1}, {model2}, ...]}\n        if isinstance(response, dict) and \"data\" in response:\n            models = response[\"data\"]\n        else:\n            models = response\n\n        logger.info(f\"Fetched {len(models)} models from Kluster\")\n\n        ret_models = []\n        for model in models:\n            # Extract fields from the model object\n            model_id = model.get(\"id\")\n            model_name = model.get(\"name\", model_id)\n\n            # Skip models without an ID\n            if not model_id:\n                continue\n\n            ret_models.append(\n                {\n                    \"id\": model_id,\n                    \"name\": model_name,  # Use actual name rather than lookup, as these are official names\n                }\n            )\n\n        logger.debug(json.dumps(ret_models, indent=4))\n        ret_models = sorted(ret_models, key=lambda x: x[\"name\"])\n        return ret_models\n\n    except requests.exceptions.RequestException as e:\n        logger.error(f\"Error fetching Kluster models: {e}\")\n        return []\n    except json.JSONDecodeError as e:\n        logger.error(f\"Error decoding JSON from Kluster API: {e}\")\n        logger.error(f\"Response text: {r.text}\")\n        return []\n\n\ndef fetch_openrouter_models(logger):\n    logger.info(\"Fetching OpenRouter models...\")\n    r = requests.get(\n        \"https://openrouter.ai/api/v1/models\",\n        headers={\n            \"Content-Type\": \"application/json\",\n        },\n    )\n    r.raise_for_status()\n    models = r.json()[\"data\"]\n    logger.info(f\"Fetched {len(models)} models from OpenRouter\")\n    ret_models = []\n    for model in models:\n        pricing = float(model.get(\"pricing\", {}).get(\"completion\", \"1\")) + float(\n            model.get(\"pricing\", {}).get(\"prompt\", \"1\")\n        )\n        if pricing != 0:\n            continue\n        if \":free\" not in model[\"id\"]:\n            continue\n        if model[\"id\"].lower() in OPENROUTER_IGNORED_MODELS:\n            logger.debug(f\"Ignoring model {model['id']}\")\n            continue\n        ret_models.append(\n            {\n                \"id\": model[\"id\"],\n                \"name\": get_model_name(model[\"id\"]),\n                \"limits\": {\n                    \"requests/minute\": 20,\n                    \"requests/day\": 50,\n                },\n            }\n        )\n    ret_models = sorted(ret_models, key=lambda x: x[\"name\"])\n    return ret_models\n\n\ndef fetch_cloudflare_models(logger):\n    logger.info(\"Fetching Cloudflare models...\")\n    r = requests.get(\n        f\"https://api.cloudflare.com/client/v4/accounts/{os.environ['CLOUDFLARE_ACCOUNT_ID']}/ai/models/search?search=Text+Generation\",\n        headers={\n            \"Authorization\": f'Bearer {os.environ[\"CLOUDFLARE_API_KEY\"]}',\n            \"Content-Type\": \"application/json\",\n        },\n    )\n    r.raise_for_status()\n    models = r.json()[\"result\"]\n    logger.info(f\"Fetched {len(models)} models from Cloudflare\")\n    ret_models = []\n    for model in models:\n        ret_models.append(\n            {\n                \"id\": model[\"name\"],\n                \"name\": get_model_name(model[\"name\"]),\n            }\n        )\n    ret_models = sorted(ret_models, key=lambda x: x[\"name\"])\n    return ret_models\n\n\ndef fetch_ovh_models(logger):\n    logger.info(\"Fetching OVH models...\")\n    r = requests.get(\n        \"https://endpoints-backend.ai.cloud.ovh.net/rest/v1/models_v2\",\n        params={\"select\": \"*\", \"order\": \"id.desc\", \"offset\": \"0\", \"limit\": \"100\"},\n        headers={\n            \"accept\": \"*/*\",\n            \"accept-language\": \"en-GB,en-US;q=0.9,en;q=0.8\",\n            \"accept-profile\": \"public\",\n            \"apikey\": \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.ewogICJyb2xlIjogImFub24iLAogICJpc3MiOiAic3VwYWJhc2UiLAogICJpYXQiOiAxNzEwNzE2NDAwLAogICJleHAiOiAxODY4NDgyODAwCn0.Jty_eO4oWqLm4Lx_LfbpRW5WESXYXtT2humbBq2Pal8\",\n            \"authorization\": \"Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.ewogICJyb2xlIjogImFub24iLAogICJpc3MiOiAic3VwYWJhc2UiLAogICJpYXQiOiAxNzEwNzE2NDAwLAogICJleHAiOiAxODY4NDgyODAwCn0.Jty_eO4oWqLm4Lx_LfbpRW5WESXYXtT2humbBq2Pal8\",\n            \"priority\": \"u=1, i\",\n            \"sec-ch-ua\": '\"Not/A)Brand\";v=\"8\", \"Chromium\";v=\"126\", \"Google Chrome\";v=\"126\"',\n            \"sec-ch-ua-mobile\": \"?0\",\n            \"sec-ch-ua-platform\": '\"macOS\"',\n            \"sec-fetch-dest\": \"empty\",\n            \"sec-fetch-mode\": \"cors\",\n            \"sec-fetch-site\": \"same-site\",\n            \"x-client-info\": \"supabase-js-web/2.39.7\",\n        },\n    )\n    r.raise_for_status()\n    models = list(filter(lambda x: x[\"available\"] and \"LLM\" in x[\"category\"], r.json()))\n    logger.info(f\"Fetched {len(models)} models from OVH\")\n    ret_models = []\n    for model in models:\n        ret_models.append(\n            {\n                \"id\": model[\"name\"],\n                \"name\": get_model_name(model[\"name\"]),\n                \"limits\": {\n                    \"requests/minute\": 12,\n                },\n            }\n        )\n    ret_models = sorted(ret_models, key=lambda x: x[\"name\"])\n    return ret_models\n\n\ndef fetch_hyperbolic_models(logger):\n    logger.info(\"Fetching Hyperbolic models from API...\")\n    r = requests.get(\n        \"https://api.hyperbolic.xyz/v1/models\",\n        headers={\n            \"accept\": \"application/json\",\n            \"authorization\": f\"Bearer {os.environ['HYPERBOLIC_API_KEY']}\",\n        },\n    )\n    r.raise_for_status()\n    models = r.json()[\"data\"]\n    logger.info(f\"Fetched {len(models)} models from Hyperbolic's API\")\n    ret_models = []\n    for model in models:\n        if model[\"id\"] in HYPERBOLIC_IGNORED_MODELS:\n            logger.debug(f\"Ignoring model {model['id']}\")\n            continue\n        ret_models.append(\n            {\n                \"id\": model[\"id\"],\n                \"name\": get_model_name(model[\"id\"]),\n                \"limits\": {\n                    \"requests/minute\": 60,\n                },\n            }\n        )\n    logger.debug(json.dumps(ret_models, indent=4))\n    return sorted(ret_models, key=lambda x: x[\"name\"])\n\n\ndef fetch_github_models(logger):\n    logger.info(\"Fetching GitHub models...\")\n    all_models_data = []\n    page = 1\n    total_pages = 1  # Initialize with 1 to start the loop\n\n    while page <= total_pages:\n        try:\n            url = f\"https://github.com/marketplace?type=models&page={page}\"\n            logger.info(f\"Fetching from {url}\")\n            r = requests.get(\n                url,\n                headers={\n                    \"Accept\": \"application/json\",\n                    \"Content-Type\": \"application/json\",\n                    \"x-requested-with\": \"XMLHttpRequest\",\n                },\n            )\n            r.raise_for_status()\n            data = r.json()\n\n            current_page_models = data.get(\"results\", [])\n            if not current_page_models:\n                logger.info(f\"No models found on page {page}. Stopping.\")\n                break\n\n            all_models_data.extend(current_page_models)\n\n            total_pages = data.get(\"totalPages\", 0)\n            logger.info(\n                f\"Fetched page {page}/{total_pages}. Found {len(current_page_models)} models on this page.\"\n            )\n\n            if page >= total_pages:\n                break\n            page += 1\n            time.sleep(0.5)  # Be respectful to the API\n\n        except requests.exceptions.RequestException as e:\n            logger.error(f\"Error fetching GitHub models on page {page}: {e}\")\n            if (\n                r.status_code == 404 and page == 1\n            ):  # If first page is 404, likely endpoint changed or no models\n                logger.error(\n                    \"Initial request failed (404), assuming no models or endpoint issue.\"\n                )\n                return []\n            elif (\n                r.status_code == 404\n            ):  # If a subsequent page is 404, means we've gone past the last page\n                logger.info(f\"Reached end of pages (404 on page {page}).\")\n                break\n            # For other errors, break or implement retry logic if desired\n            break\n        except json.JSONDecodeError as e:\n            logger.error(\n                f\"Error decoding JSON from GitHub models API on page {page}: {e}\"\n            )\n            logger.error(f\"Response text: {r.text}\")\n            break\n\n    logger.info(\n        f\"Fetched a total of {len(all_models_data)} models from GitHub over {page-1 if page > 1 else 1} page(s).\"\n    )\n    ret_models = []\n    for model_data in all_models_data:\n        # Ensure model_data is a dictionary and has the required keys\n        if (\n            isinstance(model_data, dict)\n            and \"name\" in model_data\n            and \"friendly_name\" in model_data\n        ):\n            ret_models.append(\n                {\n                    \"id\": model_data[\n                        \"name\"\n                    ],  # Using 'name' as id, can be changed if another field is more suitable\n                    \"name\": model_data[\"friendly_name\"],\n                }\n            )\n        else:\n            logger.warning(f\"Skipping malformed model data: {model_data}\")\n\n    ret_models = sorted(ret_models, key=lambda x: x[\"name\"])\n    return ret_models\n\n\ndef fetch_gemini_limits(logger):\n    logger.info(\"Fetching Gemini limits...\")\n    client = cloudquotas_v1.CloudQuotasClient()\n    request = cloudquotas_v1.ListQuotaInfosRequest(\n        parent=f\"projects/{os.environ[\"GCP_PROJECT_ID\"]}/locations/global/services/generativelanguage.googleapis.com\"\n    )\n    pager = client.list_quota_infos(request=request)\n    models = defaultdict(dict)\n    for quota in pager:\n        if (\n            quota.metric\n            == \"generativelanguage.googleapis.com/generate_content_free_tier_input_token_count\"\n        ):\n            for dimension in quota.dimensions_infos:\n                if dimension.details.value == -1:\n                    # -1 means unlimited\n                    continue\n                models[dimension.dimensions.get(\"model\")][\n                    f\"tokens/{quota.refresh_interval}\"\n                ] = dimension.details.value\n        elif (\n            quota.metric\n            == \"generativelanguage.googleapis.com/generate_content_free_tier_requests\"\n        ):\n            for dimension in quota.dimensions_infos:\n                if dimension.details.value == -1:\n                    # -1 means unlimited\n                    continue\n                models[dimension.dimensions.get(\"model\")][\n                    f\"requests/{quota.refresh_interval}\"\n                ] = dimension.details.value\n    logger.debug(json.dumps(models, indent=4))\n    return models\n\n\ndef fetch_lambda_models(logger):\n    logger.info(\"Fetching Lambda Labs models...\")\n    r = requests.get(\n        \"https://api.lambdalabs.com/v1/models\",\n        headers={\n            \"Authorization\": f\"Bearer {os.environ['LAMBDA_API_KEY']}\",\n        },\n    )\n    r.raise_for_status()\n    models = r.json()[\"data\"]\n    logger.info(f\"Fetched {len(models)} models from Lambda Labs\")\n    ret_models = []\n    for model in models:\n        if model[\"id\"] in LAMBDA_IGNORED_MODELS:\n            logger.debug(f\"Ignoring model {model['id']}\")\n            continue\n        ret_models.append(\n            {\n                \"id\": model[\"id\"],\n                \"name\": get_model_name(model[\"id\"]),\n            }\n        )\n    ret_models = sorted(ret_models, key=lambda x: x[\"name\"])\n    return ret_models\n\n\ndef rate_limited_mistral_chat(client, **kwargs):\n    global last_mistral_request_time\n\n    # Ensure at least 1 second between requests\n    current_time = time.time()\n    time_since_last = current_time - last_mistral_request_time\n    if time_since_last < 1:\n        time.sleep(1 - time_since_last)\n\n    response = client.chat.complete(**kwargs)\n    last_mistral_request_time = time.time()\n    return response\n\n\ndef fetch_samba_models(logger):\n    logger.info(\"Fetching SambaNova models...\")\n    r = requests.get(\"https://cloud.sambanova.ai/api/pricing\")\n    r.raise_for_status()\n    models = r.json()[\"prices\"]\n    logger.info(f\"Fetched {len(models)} models from SambaNova\")\n    ret_models = []\n    for model in models:\n        ret_models.append(\n            {\n                \"id\": model[\"model_id\"],\n                \"name\": model[\"model_name\"] or model[\"model_id\"],\n            }\n        )\n    ret_models = sorted(ret_models, key=lambda x: x[\"name\"])\n    return ret_models\n\n\ndef fetch_scaleway_models(logger):\n    logger.info(\"Fetching Scaleway models...\")\n    r = requests.get(\n        \"https://api.scaleway.ai/v1/models\",\n        headers={\"Authorization\": f\"Bearer {os.environ['SCALEWAY_API_KEY']}\"},\n    )\n    r.raise_for_status()\n    models = r.json()[\"data\"]\n    logger.info(f\"Fetched {len(models)} models from Scaleway\")\n    ret_models = []\n    for model in models:\n        ret_models.append(\n            {\n                \"id\": model[\"id\"],\n                \"name\": get_model_name(model[\"id\"]),\n            }\n        )\n    ret_models = sorted(ret_models, key=lambda x: x[\"name\"])\n    return ret_models\n\n\ndef fetch_cohere_models(logger):\n    logger.info(\"Fetching Cohere models...\")\n    headers = {\n        \"accept\": \"application/json\",\n        \"Authorization\": f\"Bearer {os.environ['COHERE_API_KEY']}\",\n    }\n    params = {}\n    all_models = []\n    page = 1\n\n    try:\n        while True:\n            response = requests.get(\n                \"https://api.cohere.com/v1/models\",\n                headers=headers,\n                params=params or None,\n                timeout=10,\n            )\n            response.raise_for_status()\n            payload = response.json()\n            models = payload.get(\"models\", [])\n            logger.info(f\"Fetched {len(models)} models from Cohere (page {page})\")\n            all_models.extend(models)\n            next_token = payload.get(\"next_page_token\")\n            if not next_token:\n                break\n            params[\"page_token\"] = next_token\n            page += 1\n    except requests.exceptions.RequestException as exc:\n        logger.error(f\"Error fetching Cohere models: {exc}\")\n        return []\n    except json.JSONDecodeError as exc:\n        logger.error(f\"Error decoding Cohere API response: {exc}\")\n        return []\n\n    ret_models = []\n    for model in all_models:\n        model_id = model.get(\"name\")\n        if not model_id:\n            continue\n        if model.get(\"is_deprecated\"):\n            logger.debug(f\"Skipping deprecated Cohere model {model_id}\")\n            continue\n        endpoints = set(model.get(\"endpoints\") or []) | set(\n            model.get(\"default_endpoints\") or []\n        )\n        if \"chat\" not in endpoints:\n            logger.debug(f\"Skipping non-chat Cohere model {model_id}\")\n            continue\n        ret_models.append(\n            {\n                \"id\": model_id,\n                \"name\": get_model_name(model_id),\n            }\n        )\n\n    logger.info(f\"Found {len(ret_models)} Cohere chat models\")\n    return sorted(ret_models, key=lambda x: x[\"name\"])\n\n\ndef fetch_chutes_models(logger):\n    logger.info(\"Fetching Chutes models...\")\n    r = requests.get(\n        \"https://api.chutes.ai/chutes/?include_public=true&limit=1000\",\n        headers={\n            \"Content-Type\": \"application/json\",\n        },\n    )\n    r.raise_for_status()\n    models = r.json()[\"items\"]\n    logger.info(f\"Fetched {len(models)} models from Chutes\")\n\n    # Filter for free models based on per_million_token price\n    free_models = []\n    for model in models:\n        price_info = model.get(\"current_estimated_price\", {})\n        # Check if per_million_tokens field exists and is set to 0 for USD\n        if price_info.get(\"per_million_tokens\", {}).get(\"usd\", 1) == 0:\n            model_name = model.get(\"name\", \"Unknown model\")\n            free_models.append(\n                {\n                    \"id\": model_name,\n                    \"name\": get_model_name(model_name),\n                    \"description\": model.get(\"tagline\", \"\"),\n                }\n            )\n\n    logger.info(f\"Found {len(free_models)} free models from Chutes\")\n    return sorted(free_models, key=lambda x: x[\"name\"])\n\n\ndef get_human_limits(model, seperator=\"<br>\"):\n    if \"limits\" not in model:\n        return \"\"\n    limits = model[\"limits\"]\n    return seperator.join([f\"{value:,} {key}\" for key, value in limits.items()])\n\n\ndef generate_toc(markdown):\n    toc_lines = []\n    # Find all ## and ### headings, but skip the main title (# ...)\n    headings = re.findall(r\"^(#{2,3}) +(.+)\", markdown, re.MULTILINE)\n    for hashes, title in headings:\n        # Remove markdown links for anchor text, keep display text\n        display = re.sub(r\"\\[(.*?)\\]\\([^)]*\\)\", r\"\\1\", title)\n        # Build anchor (GitHub style)\n        anchor = display.lower()\n        anchor = re.sub(r\"[^a-z0-9 \\-_]\", \"\", anchor)\n        anchor = anchor.replace(\" \", \"-\")\n        anchor = anchor.replace(\"--\", \"-\")\n        anchor = anchor.strip(\"-\")\n        indent = \"  \" if len(hashes) == 3 else \"\"\n        toc_lines.append(f\"{indent}- [{display}](#{anchor})\")\n    return \"\\n\".join(toc_lines)\n\n\ndef main():\n    logger = create_logger(\"Main\")\n    groq_logger = create_logger(\"Groq\")\n    openrouter_logger = create_logger(\"OpenRouter\")\n    google_ai_studio_logger = create_logger(\"Google AI Studio\")\n    cloudflare_logger = create_logger(\"Cloudflare\")\n    github_logger = create_logger(\"GitHub\")\n    hyperbolic_logger = create_logger(\"Hyperbolic\")\n    samba_logger = create_logger(\"SambaNova\")\n    scaleway_logger = create_logger(\"Scaleway\")\n    cohere_logger = create_logger(\"Cohere\")\n\n    fetch_concurrently = os.getenv(\"FETCH_CONCURRENTLY\", \"false\").lower() == \"true\"\n\n    if fetch_concurrently:\n        with ThreadPoolExecutor() as executor:\n            futures = [\n                executor.submit(fetch_gemini_limits, google_ai_studio_logger),\n                executor.submit(fetch_openrouter_models, openrouter_logger),\n                executor.submit(fetch_hyperbolic_models, hyperbolic_logger),\n                executor.submit(fetch_cloudflare_models, cloudflare_logger),\n                executor.submit(fetch_github_models, github_logger),\n                executor.submit(fetch_samba_models, samba_logger),\n                executor.submit(fetch_scaleway_models, scaleway_logger),\n                executor.submit(fetch_cohere_models, cohere_logger),\n            ]\n            (\n                gemini_models,\n                openrouter_models,\n                hyperbolic_models,\n                cloudflare_models,\n                github_models,\n                samba_models,\n                scaleway_models,\n                cohere_models,\n            ) = [f.result() for f in futures]\n\n            # Fetch groq models after others complete\n            groq_models = fetch_groq_models(groq_logger)\n    else:\n        gemini_models = fetch_gemini_limits(google_ai_studio_logger)\n        openrouter_models = fetch_openrouter_models(openrouter_logger)\n        hyperbolic_models = fetch_hyperbolic_models(hyperbolic_logger)\n        cloudflare_models = fetch_cloudflare_models(cloudflare_logger)\n        github_models = fetch_github_models(github_logger)\n        samba_models = fetch_samba_models(samba_logger)\n        scaleway_models = fetch_scaleway_models(scaleway_logger)\n        cohere_models = fetch_cohere_models(cohere_logger)\n        groq_models = fetch_groq_models(groq_logger)\n\n    # Initialize markdown string for free providers\n    model_list_markdown = \"\"\n\n    # --- OpenRouter ---\n    model_list_markdown += \"### [OpenRouter](https://openrouter.ai)\\n\\n\"\n    if openrouter_models:\n        provider_limits = get_human_limits(openrouter_models[0])\n        model_list_markdown += \"**Limits:**\\n\\n\"\n        model_list_markdown += f\"[{provider_limits}<br>Up to 1000 requests/day with $10 lifetime topup](https://openrouter.ai/docs/api/reference/limits)\\n\\n\"\n        model_list_markdown += \"Models share a common quota.\\n\\n\"\n        for model in openrouter_models:\n            model_list_markdown += (\n                f\"- [{model['name']}](https://openrouter.ai/{model['id']})\\n\"\n            )\n    model_list_markdown += \"\\n\"\n\n    # --- Google AI Studio ---\n    model_list_markdown += \"### [Google AI Studio](https://aistudio.google.com)\\n\\n\"\n    model_list_markdown += (\n        \"Data is used for training when used outside of the UK/CH/EEA/EU.\\n\\n\"\n    )\n    model_list_markdown += \"<table><thead><tr><th>Model Name</th><th>Model Limits</th></tr></thead><tbody>\\n\"\n\n    gemini_text_models = [\n        {\n            \"id\": \"gemini-3-flash-preview\",\n            \"name\": \"Gemini 3 Flash\",\n            \"limits\": gemini_models.get(\"gemini-3-flash\", {}),\n        },\n        {\n            \"id\": \"gemini-3.1-flash-lite-preview\",\n            \"name\": \"Gemini 3.1 Flash-Lite\",\n            \"limits\": gemini_models.get(\"gemini-3.1-flash-lite\", {}),\n        },\n        {\n            \"id\": \"gemini-2.5-flash\",\n            \"name\": \"Gemini 2.5 Flash\",\n            \"limits\": gemini_models.get(\"gemini-2.5-flash\", {}),\n        },\n        {\n            \"id\": \"gemini-2.5-flash-lite\",\n            \"name\": \"Gemini 2.5 Flash-Lite\",\n            \"limits\": gemini_models.get(\"gemini-2.5-flash-lite\", {}),\n        },\n        {\n            \"id\": \"gemma-3-27b-it\",\n            \"name\": \"Gemma 3 27B Instruct\",\n            \"limits\": gemini_models.get(\"gemma-3-27b\", {}),\n        },\n        {\n            \"id\": \"gemma-3-12b-it\",\n            \"name\": \"Gemma 3 12B Instruct\",\n            \"limits\": gemini_models.get(\"gemma-3-12b\", {}),\n        },\n        {\n            \"id\": \"gemma-3-4b-it\",\n            \"name\": \"Gemma 3 4B Instruct\",\n            \"limits\": gemini_models.get(\"gemma-3-4b\", {}),\n        },\n        {\n            \"id\": \"gemma-3-1b-it\",\n            \"name\": \"Gemma 3 1B Instruct\",\n            \"limits\": gemini_models.get(\"gemma-3-1b\", {}),\n        },\n    ]\n\n    # Write text models to table\n    for model in gemini_text_models:\n        limits_str = get_human_limits(model)\n        model_list_markdown += (\n            f\"<tr><td>{model['name']}</td><td>{limits_str}</td></tr>\\n\"\n        )\n\n    model_list_markdown += \"</tbody></table>\\n\\n\"\n\n    # --- NVIDIA NIM ---\n    model_list_markdown += (\n        \"### [NVIDIA NIM](https://build.nvidia.com/explore/discover)\\n\\n\"\n    )\n    model_list_markdown += \"Phone number verification required.\\n\"\n    model_list_markdown += \"Models tend to be context window limited.\\n\\n\"\n    model_list_markdown += \"**Limits:** 40 requests/minute\\n\\n\"\n    model_list_markdown += \"- [Various open models](https://build.nvidia.com/models)\\n\"\n    model_list_markdown += \"\\n\"\n\n    # --- Mistral (La Plateforme) ---\n    model_list_markdown += (\n        \"### [Mistral (La Plateforme)](https://console.mistral.ai/)\\n\\n\"\n    )\n    model_list_markdown += (\n        \"* Free tier (Experiment plan) requires opting into data training\\n\"\n    )\n    model_list_markdown += \"* Requires phone number verification.\\n\\n\"\n    model_list_markdown += \"**Limits (per-model):** 1 request/second, 500,000 tokens/minute, 1,000,000,000 tokens/month\\n\\n\"\n    model_list_markdown += \"- [Open and Proprietary Mistral models](https://docs.mistral.ai/getting-started/models/models_overview/)\\n\"\n    model_list_markdown += \"\\n\"\n\n    # --- Mistral (Codestral) ---\n    model_list_markdown += (\n        \"### [Mistral (Codestral)](https://codestral.mistral.ai/)\\n\\n\"\n    )\n    model_list_markdown += \"* Currently free to use\\n\"\n    model_list_markdown += \"* Monthly subscription based\\n\"\n    model_list_markdown += \"* Requires phone number verification\\n\\n\"\n    model_list_markdown += \"**Limits:** 30 requests/minute, 2,000 requests/day\\n\\n\"\n    model_list_markdown += \"- Codestral\\n\"\n    model_list_markdown += \"\\n\"\n\n    # --- HuggingFace Serverless Inference ---\n    model_list_markdown += \"### [HuggingFace Inference Providers](https://huggingface.co/docs/inference-providers/en/index)\\n\\n\"\n    model_list_markdown += \"HuggingFace Serverless Inference limited to models smaller than 10GB. Some popular models are supported even if they exceed 10GB.\\n\\n\"\n    model_list_markdown += \"**Limits:** [$0.10/month in credits](https://huggingface.co/docs/inference-providers/en/pricing)\\n\\n\"\n    model_list_markdown += \"- Various open models across supported providers\\n\"\n    model_list_markdown += \"\\n\"\n\n    # --- Vercel AI Gateway ---\n    model_list_markdown += \"### [Vercel AI Gateway](https://vercel.com/docs/ai-gateway)\\n\\n\"\n    model_list_markdown += \"Routes to various supported providers.\\n\\n\"\n    model_list_markdown += \"**Limits:** [$5/month](https://vercel.com/docs/ai-gateway/pricing)\\n\\n\"\n    model_list_markdown += \"\\n\"\n\n    # --- OpenCode Zen ---\n    model_list_markdown += \"### [OpenCode Zen](https://opencode.ai/docs/zen/)\\n\\n\"\n    model_list_markdown += \"AI gateway with curated models.\\n\\n\"\n    model_list_markdown += \"Free models may use data for improvement.\\n\\n\"\n    model_list_markdown += \"- Big Pickle Stealth\\n\"\n    model_list_markdown += \"- MiniMax M2.5 Free\\n\"\n    model_list_markdown += \"- Arcee Large Preview Free\\n\"\n    model_list_markdown += \"\\n\"\n\n    # --- Cerebras ---\n    model_list_markdown += \"### [Cerebras](https://cloud.cerebras.ai/)\\n\\n\"\n    model_list_markdown += \"<table><thead><tr><th>Model Name</th><th>Model Limits</th></tr></thead><tbody>\\n\"\n    cerebras_models = [\n        {\n            \"name\": \"gpt-oss-120b\",\n            \"limits_text\": \"30 requests/minute<br>60,000 tokens/minute<br>900 requests/hour<br>1,000,000 tokens/hour<br>14,400 requests/day<br>1,000,000 tokens/day\"\n        },\n        {\n            \"name\": \"Llama 3.1 8B\",\n            \"limits_text\": \"30 requests/minute<br>60,000 tokens/minute<br>900 requests/hour<br>1,000,000 tokens/hour<br>14,400 requests/day<br>1,000,000 tokens/day\"\n        },\n    ]\n    for model in cerebras_models:\n        model_list_markdown += (\n            f\"<tr><td>{model['name']}</td><td>{model['limits_text']}</td></tr>\\n\"\n        )\n    model_list_markdown += \"</tbody></table>\\n\\n\"\n\n    # --- Groq ---\n    model_list_markdown += \"### [Groq](https://console.groq.com)\\n\\n\"\n    if groq_models:\n        model_list_markdown += \"<table><thead><tr><th>Model Name</th><th>Model Limits</th></tr></thead><tbody>\\n\"\n        for model in groq_models:\n            limits_str = get_human_limits(model)\n            model_list_markdown += (\n                f\"<tr><td>{model['name']}</td><td>{limits_str}</td></tr>\\n\"\n            )\n        model_list_markdown += \"</tbody></table>\\n\"\n    model_list_markdown += \"\\n\"\n\n    # --- Cohere ---\n    model_list_markdown += \"### [Cohere](https://cohere.com)\\n\\n\"\n    model_list_markdown += \"**Limits:**\\n\\n\"\n    model_list_markdown += \"[20 requests/minute<br>1,000 requests/month](https://docs.cohere.com/docs/rate-limits)\\n\\n\"\n    model_list_markdown += \"Models share a common monthly quota.\\n\\n\"\n    if cohere_models:\n        for model in cohere_models:\n            model_list_markdown += f\"- {model['name']}\\n\"\n    else:\n        model_list_markdown += \"- No chat models available right now.\\n\"\n    model_list_markdown += \"\\n\"\n\n    # --- GitHub Models ---\n    model_list_markdown += (\n        \"### [GitHub Models](https://github.com/marketplace/models)\\n\\n\"\n    )\n    model_list_markdown += \"Extremely restrictive input/output token limits.\\n\\n\"\n    model_list_markdown += \"**Limits:** [Dependent on Copilot subscription tier (Free/Pro/Pro+/Business/Enterprise)](https://docs.github.com/en/github-models/prototyping-with-ai-models#rate-limits)\\n\\n\"\n    if github_models:\n        for model in github_models:\n            model_list_markdown += f\"- {model['name']}\\n\"\n    model_list_markdown += \"\\n\"\n\n    # --- Cloudflare Workers AI ---\n    model_list_markdown += (\n        \"### [Cloudflare Workers AI](https://developers.cloudflare.com/workers-ai)\\n\\n\"\n    )\n    model_list_markdown += \"**Limits:** [10,000 neurons/day](https://developers.cloudflare.com/workers-ai/platform/pricing/#free-allocation)\\n\\n\"\n    if cloudflare_models:\n        for model in cloudflare_models:\n            model_list_markdown += f\"- {model['name']}\\n\"\n    model_list_markdown += \"\\n\"\n\n    # --- Google Cloud Vertex AI ---\n    vertex_llama_models = []\n    vertex_gemini_models = []\n    vertex_deepseek_models = []\n    if vertex_llama_models or vertex_gemini_models or vertex_deepseek_models:\n        model_list_markdown += \"### [Google Cloud Vertex AI](https://console.cloud.google.com/vertex-ai/model-garden)\\n\\n\"\n        model_list_markdown += \"Very stringent payment verification for Google Cloud.\\n\\n\"\n        model_list_markdown += \"<table><thead><tr><th>Model Name</th><th>Model Limits</th></tr></thead><tbody>\\n\"\n\n    # Write Gemini models to table\n    first_gemini = True\n    if vertex_gemini_models:\n        for model in vertex_gemini_models:\n            limits_str = get_human_limits(model)\n            model_list_markdown += f'<tr><td><a href=\"https://cloud.google.com/vertex-ai/generative-ai/docs/multimodal/gemini-experimental\" target=\"_blank\">{model['name']}</a></td>'\n            if first_gemini:\n                model_list_markdown += f'<td rowspan=\"{len(vertex_gemini_models)}\">{limits_str}<br>Shared Quota</td>'\n                first_gemini = False\n            model_list_markdown += \"</tr>\\n\"\n\n    # Write Llama models to table\n    if vertex_llama_models:\n        for model in vertex_llama_models:\n            limits_str = get_human_limits(model)\n            model_list_markdown += f'<tr><td><a href=\"https://console.cloud.google.com/vertex-ai/publishers/meta/model-garden/{model['urlId']}\" target=\"_blank\">{model['name']}</a></td><td>{limits_str}<br>Free during preview</td></tr>\\n'\n\n    # Write DeepSeek models to table\n    if vertex_deepseek_models:\n        for model in vertex_deepseek_models:\n            limits_str = get_human_limits(model)\n            model_list_markdown += f'<tr><td><a href=\"https://console.cloud.google.com/vertex-ai/publishers/deepseek-ai/model-garden/{model['urlId']}\" target=\"_blank\">{model['name']}</a></td><td>{limits_str}<br>Free during preview</td></tr>\\n'\n\n    model_list_markdown += \"</tbody></table>\\n\\n\"\n\n    # --- Trial Providers Section Generation ---\n    trial_list_markdown = \"\"\n\n    # --- Static Trial Providers (Markdown List/Simple Entry) ---\n    trial_providers_static = [\n        {\n            \"name\": \"Fireworks\",\n            \"url\": \"https://fireworks.ai/\",\n            \"credits\": \"$1\",\n            \"requirements\": \"\",\n            \"models_desc\": \"[Various open models](https://fireworks.ai/models)\",\n        },\n        {\n            \"name\": \"Baseten\",\n            \"url\": \"https://app.baseten.co/\",\n            \"credits\": \"$30\",\n            \"requirements\": \"\",\n            \"models_desc\": \"[Any supported model - pay by compute time](https://www.baseten.co/library/)\",\n        },\n        {\n            \"name\": \"Nebius\",\n            \"url\": \"https://tokenfactory.nebius.com/\",\n            \"credits\": \"$1\",\n            \"requirements\": \"\",\n            \"models_desc\": \"[Various open models](https://tokenfactory.nebius.com/models)\",\n        },\n        {\n            \"name\": \"Novita\",\n            \"url\": \"https://novita.ai/?ref=ytblmjc&utm_source=affiliate\",\n            \"credits\": \"$0.5 for 1 year\",\n            \"requirements\": \"\",\n            \"models_desc\": \"[Various open models](https://novita.ai/models)\",\n        },\n        {\n            \"name\": \"AI21\",\n            \"url\": \"https://studio.ai21.com/\",\n            \"credits\": \"$10 for 3 months\",\n            \"requirements\": \"\",\n            \"models_desc\": \"Jamba family of models\",\n        },\n        {\n            \"name\": \"Upstage\",\n            \"url\": \"https://console.upstage.ai/\",\n            \"credits\": \"$10 for 3 months\",\n            \"requirements\": \"\",\n            \"models_desc\": \"Solar Pro/Mini\",\n        },\n        {\n            \"name\": \"NLP Cloud\",\n            \"url\": \"https://nlpcloud.com/home\",\n            \"credits\": \"$15\",\n            \"requirements\": \"Phone number verification\",\n            \"models_desc\": \"Various open models\",\n        },\n        {\n            \"name\": \"Alibaba Cloud (International) Model Studio\",\n            \"url\": \"https://bailian.console.alibabacloud.com/\",\n            \"credits\": \"1 million tokens/model\",\n            \"requirements\": \"\",\n            \"models_desc\": \"[Various open and proprietary Qwen models](https://www.alibabacloud.com/en/product/modelstudio)\",\n        },\n        {\n            \"name\": \"Modal\",\n            \"url\": \"https://modal.com\",\n            \"credits\": \"$5/month upon sign up, $30/month with payment method added\",\n            \"requirements\": \"\",\n            \"models_desc\": \"Any supported model - pay by compute time\",\n        },\n        {\n            \"name\": \"Inference.net\",\n            \"url\": \"https://inference.net\",\n            \"credits\": \"$1, $25 on responding to email survey\",\n            \"requirements\": \"\",\n            \"models_desc\": \"Various open models\",\n        },\n    ]\n\n    for provider in trial_providers_static:\n        trial_list_markdown += f\"### [{provider['name']}]({provider['url']})\\n\\n\"\n        trial_list_markdown += f\"**Credits:** {provider['credits']}\\n\\n\"\n        if provider[\"requirements\"]:\n            trial_list_markdown += f\"**Requirements:** {provider['requirements']}\\n\\n\"\n        trial_list_markdown += f\"**Models:** {provider['models_desc']}\\n\\n\"\n\n    # --- Hyperbolic (Trial - Table) ---\n    if hyperbolic_models:\n        trial_list_markdown += \"### [Hyperbolic](https://app.hyperbolic.ai/)\\n\\n\"\n        trial_list_markdown += \"**Credits:** $1\\n\\n\"\n        trial_list_markdown += \"**Models:**\\n\"\n        for model in hyperbolic_models:\n            trial_list_markdown += f\"- {model['name']}\\n\"\n        trial_list_markdown += \"\\n\"\n\n    # --- SambaNova Cloud (Trial - Table) ---\n    if samba_models:\n        trial_list_markdown += \"### [SambaNova Cloud](https://cloud.sambanova.ai/)\\n\\n\"\n        trial_list_markdown += \"**Credits:** $5 for 3 months\\n\\n\"\n        trial_list_markdown += \"**Models:**\\n\"\n        for model in samba_models:\n            trial_list_markdown += f\"- {model['name']}\\n\"   \n        trial_list_markdown += \"\\n\"\n\n    # --- Scaleway Generative APIs (Trial - Table) ---\n    if scaleway_models:\n        trial_list_markdown += \"### [Scaleway Generative APIs](https://console.scaleway.com/generative-api/models)\\n\\n\"\n        trial_list_markdown += \"**Credits:** 1,000,000 free tokens\\n\\n\"\n        trial_list_markdown += \"**Models:**\\n\"\n        for model in scaleway_models:\n            trial_list_markdown += f\"- {model['name']}\\n\"\n        trial_list_markdown += \"\\n\"\n\n    if MISSING_MODELS:\n        logger.warning(\"Missing models:\")\n        logger.warning(\n            \"\\n\" + \"\\n\".join([f'\"{model}\": \"{model}\",' for model in MISSING_MODELS])\n        )\n\n    with open(os.path.join(script_dir, \"README_template.md\"), \"r\") as f:\n        readme = f.read()\n    warning = \"\"\"<!---\nWARNING: DO NOT EDIT THIS FILE DIRECTLY. IT IS GENERATED BY src/pull_available_models.py\n--->\n\"\"\"\n    initial_templated = (\n        (warning + readme)\n        .replace(\"{{MODEL_LIST}}\", model_list_markdown)\n        .replace(\"{{TRIAL_LIST_MARKDOWN}}\", trial_list_markdown)\n    )\n    toc_markdown = generate_toc(initial_templated)\n    with open(os.path.join(script_dir, \"..\", \"README.md\"), \"w\") as f:\n        f.write(initial_templated.replace(\"{{TOC}}\", toc_markdown))\n    logger.info(\"Wrote models to README.md\")\n\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "src/requirements.txt",
    "content": "requests\npython-dotenv\ngoogle-cloud-quotas\nmistralai\nbeautifulsoup4"
  }
]