diff --git a/.github/workflows/update-model-catalog.yml b/.github/workflows/update-model-catalog.yml new file mode 100644 index 0000000000..e74212825d --- /dev/null +++ b/.github/workflows/update-model-catalog.yml @@ -0,0 +1,106 @@ +name: Update model catalog + +on: + schedule: + # Daily at 05:17 UTC; off the top of the hour to dodge the cron rush. + - cron: '17 5 * * *' + workflow_dispatch: + +permissions: + contents: write + pull-requests: write + +concurrency: + group: update-model-catalog + cancel-in-progress: false + +env: + UPDATE_BRANCH: bot/update-model-catalog + CATALOG_FILE: packages/ai/src/models.generated.ts + +jobs: + regenerate: + name: Regenerate and open PR + runs-on: ubuntu-latest + timeout-minutes: 20 + steps: + - name: Checkout + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + ref: ${{ github.event.repository.default_branch }} + + - name: Setup Node.js + uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 + with: + node-version: 22 + cache: npm + + # npm ci runs every workspace's install scripts, and some need the same + # native libraries CI installs (node-canvas builds against cairo/pango). + - name: Install system dependencies + run: | + sudo apt-get update + sudo apt-get install -y libcairo2-dev libpango1.0-dev libjpeg-dev libgif-dev librsvg2-dev + + - name: Install dependencies + run: npm ci + + - name: Regenerate model catalog + working-directory: packages/ai + run: npm run generate-models + + - name: Guard against gutted providers + # generate-models exits 0 even when a source fetch fails, and most + # failed sources fall back to an empty list rather than the committed + # snapshot. A large drop in total models means a source silently + # failed, so refuse to open a PR from that state. + run: | + old=$(git show "HEAD:$CATALOG_FILE" | grep -c 'satisfies Model') + new=$(grep -c 'satisfies Model' "$CATALOG_FILE") + echo "Committed snapshot: $old models; regenerated: $new models" + if [ "$new" -lt $(( old * 9 / 10 )) ]; then + echo "::error::Regenerated catalog dropped from $old to $new models; a provider fetch likely failed." + exit 1 + fi + + - name: Detect changes + id: diff + run: | + if git diff --quiet -- "$CATALOG_FILE"; then + echo "Catalog unchanged; nothing to do." + echo "changed=false" >> "$GITHUB_OUTPUT" + else + git diff --stat -- "$CATALOG_FILE" + echo "changed=true" >> "$GITHUB_OUTPUT" + fi + + - name: Create or update pull request + if: steps.diff.outputs.changed == 'true' + env: + GH_TOKEN: ${{ github.token }} + # GITHUB_TOKEN-created PRs need a maintainer to close/reopen them + # before merge so pull_request CI runs. + run: | + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + + old=$(git show "HEAD:$CATALOG_FILE" | grep -c 'satisfies Model') + new=$(grep -c 'satisfies Model' "$CATALOG_FILE") + body=$(printf 'Automated daily refresh of `%s` from the live provider catalogs (models.dev, OpenRouter, Vercel AI Gateway, Prime Inference).\n\nModel count: %s -> %s. Review the diff for pricing changes, renames, and removals before merging.\n\nCI does not run automatically on PRs opened by the workflow token. Close and reopen this PR to trigger it.' "$CATALOG_FILE" "$old" "$new") + + git switch -c "$UPDATE_BRANCH" + git add "$CATALOG_FILE" + git commit -m "chore(ai): refresh the generated model catalog from live provider catalogs" + git push -f origin "$UPDATE_BRANCH" + + open_pr=$(gh pr list --head "$UPDATE_BRANCH" --base main --state open --json number --jq '.[0].number // empty') + if [ -z "$open_pr" ]; then + gh pr create \ + --base main \ + --head "$UPDATE_BRANCH" \ + --title "chore(ai): refresh the generated model catalog from live provider catalogs" \ + --body "$body" + else + gh pr edit "$open_pr" --body "$body" + echo "Updated pull request #$open_pr with the refreshed catalog." + fi