openai-client-base 0.13.0

Auto-generated Rust client for the OpenAI API
name: Generate Client

on:
  schedule:
    # Run every night at 2 AM UTC
    - cron: '0 2 * * *'
  workflow_dispatch: # Allow manual triggering

concurrency:
  group: generate-client
  cancel-in-progress: true

permissions:
  contents: write
  pull-requests: write

jobs:
  generate:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout repository
        uses: actions/checkout@v6
        with:
          token: ${{ secrets.GITHUB_TOKEN }}

      - name: Install Rust toolchain
        uses: dtolnay/rust-toolchain@stable
        with:
          components: rustfmt, clippy

      # Mint short-lived GitHub App token (ensures PR events trigger CI)
      - name: Create GitHub App token
        id: app-token
        uses: actions/create-github-app-token@v3
        with:
          client-id: ${{ secrets.BOT_APP_ID }}
          private-key: ${{ secrets.BOT_PRIVATE_KEY }}

      - name: Install uv
        uses: astral-sh/setup-uv@v8.1.0
        with:
          enable-cache: false
          python-version: '3.12.4'

      - name: Install system dependencies
        run: |
          sudo apt-get update
          sudo apt-get install -y curl ripgrep

      - name: Generate client
        env:
          OPENAPI_GENERATOR_LOG_LEVEL: error
        run: |
          # The script handles everything internally:
          # - Downloads latest Stainless OpenAPI spec
          # - Runs 2-layer patching pipeline
          # - Generates using Docker
          # - Applies post-generation patches
          chmod +x ./scripts/generate.sh
          ./scripts/generate.sh

      - name: Verify generation succeeded
        run: |
          # Ensure critical files exist
          test -f src/lib.rs || (echo "Error: src/lib.rs not found after generation" && exit 1)
          test -f Cargo.toml || (echo "Error: Cargo.toml not found after generation" && exit 1)
          
          # Verify the code compiles and passes checks
          echo "๐Ÿ”จ Building..."
          cargo build --all-features
          
          echo "๐Ÿ“‹ Running clippy..."
          cargo clippy --all-features --all-targets -- -D warnings
          
          echo "๐Ÿงช Running tests..."
          cargo test --all-features

      - name: Cleanup transient files
        run: |
          rm -rf scripts/__pycache__ || true

      - name: Check for changes
        id: changes
        run: |
          # Only consider generated outputs from the pipeline
          ALLOWED_PATHS=(
            src
            docs
            README.md
            Cargo.toml
            Cargo.lock
            stainless.yaml
            .openapi-generator
            .openapi-generator-ignore
          )
          if [ -n "$(git status --porcelain -- "${ALLOWED_PATHS[@]}")" ]; then
            echo "changes=true" >> $GITHUB_OUTPUT
          else
            echo "changes=false" >> $GITHUB_OUTPUT
          fi

      - name: Create Pull Request
        id: cpr
        if: steps.changes.outputs.changes == 'true'
        uses: peter-evans/create-pull-request@v8
        with:
          token: ${{ steps.app-token.outputs.token }}
          commit-message: "chore: update generated client from latest OpenAPI spec"
          title: "chore: update generated client"
          body: |
            ## ๐Ÿค– Automated Client Update
            
            This PR updates the generated client based on the latest Stainless OpenAPI specification.
            
            ### Changes
            - Downloaded latest spec from Stainless API
            - Applied Layer 1 patches (model field fixes)
            - Applied Layer 2 patches (Rust compatibility)
            - Generated new client code
            - Applied post-generation patches
            
            ### Verification
            โœ… Code compiles successfully
            โœ… All tests pass
            โœ… Clippy checks pass
            
            Please review the changes and merge if everything looks correct.
          branch: auto-update/client-generation
          delete-branch: true
          labels: |
            automated
            dependencies
            automerge
          add-paths: |
            src/**
            docs/**
            README.md
            Cargo.toml
            Cargo.lock
            stainless.yaml
            .openapi-generator-ignore

      - name: Enable auto-merge for generated PR
        if: steps.changes.outputs.changes == 'true' && steps.cpr.outputs.pull-request-number != ''
        env:
          GH_TOKEN: ${{ steps.app-token.outputs.token }}
        run: |
          gh pr merge "${{ steps.cpr.outputs.pull-request-number }}" --auto --rebase --delete-branch

      # No PAT fallback; fail early if token is missing
      - name: Assert App token present
        run: |
          if [ -z "${{ steps.app-token.outputs.token }}" ]; then
            echo "GitHub App token missing. Ensure BOT_APP_ID and BOT_PRIVATE_KEY are configured." >&2
            exit 1
          fi

  keepalive:
    runs-on: ubuntu-latest
    steps:
      - name: Keepalive Workflow
        uses: actions/github-script@v9
        with:
          github-token: ${{ secrets.GITHUB_TOKEN }}
          script: |
            const owner = context.repo.owner;
            const repo = context.repo.repo;
            const workflow_id = 'generate-client.yml';
            const workflow = await github.rest.actions.getWorkflow({ owner, repo, workflow_id });
            console.log(`Workflow ${workflow.data.name} is ${workflow.data.state}`);
            if (workflow.data.state === 'disabled_inactivity') {
              await github.rest.actions.enableWorkflow({ owner, repo, workflow_id });
              console.log('Workflow re-enabled');
            }
            const runs = await github.rest.actions.listWorkflowRuns({ owner, repo, workflow_id, per_page: 1 });
            if (runs.data.workflow_runs.length > 0) {
              const lastRun = runs.data.workflow_runs[0];
              console.log(`Last run: ${lastRun.created_at} - Status: ${lastRun.status}`);
            }