php-devops/.github/workflows/ai-worker.yml
Snider a78ef46133 feat: add AI worker workflows (Gemini + Jules)
Distributed AI compute using contributor's free tiers:

Gemini 2.0 Flash (ai-worker.yml):
- 1500 req/day free from Google
- Code review, security scan, bug detection
- Contributor sets GEMINI_API_KEY in fork secrets

Jules/Copilot (jules-dispatch.yml):
- Triggered by @jules or /jules comments
- Creates PRs to fix issues automatically
- Uses contributor's Copilot allowance (free for OSS)

Documentation (doc/free-tier-compute.md):
- Setup guide for all free tiers
- Compute distribution model diagram
- Donor fleet instructions

Innocent strategy: Jules commits fixes to contributor's fork 😇

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-31 22:30:38 +00:00

126 lines
4.6 KiB
YAML

name: AI Worker (Free Tier)
# Runs on contributor's fork - uses THEIR API allowances
# Gemini 2.0 Flash: 1500 req/day free
# Jules: Available to all GitHub users
on:
pull_request:
types: [opened, synchronize]
issues:
types: [labeled]
permissions:
contents: write
pull-requests: write
issues: write
env:
# Contributors set their own GEMINI_API_KEY in fork secrets
# Free tier: 1500 requests/day, 1M tokens/min
GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
jobs:
# Analyze PR with Gemini (contributor's free tier)
gemini-review:
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Get diff
id: diff
run: |
DIFF=$(git diff origin/${{ github.base_ref }}...HEAD | head -c 50000)
echo "diff<<EOFMARKER" >> $GITHUB_OUTPUT
echo "$DIFF" >> $GITHUB_OUTPUT
echo "EOFMARKER" >> $GITHUB_OUTPUT
- name: Gemini Analysis
if: env.GEMINI_API_KEY != ''
id: gemini
run: |
# Call Gemini 2.0 Flash (free tier)
RESPONSE=$(curl -s "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash-exp:generateContent?key=$GEMINI_API_KEY" \
-H 'Content-Type: application/json' \
-d @- << 'PAYLOAD'
{
"contents": [{
"parts": [{
"text": "Review this code diff. Be concise. List: 1) Security issues 2) Bugs 3) Improvements. If none, say 'LGTM'.\n\nDiff:\n${{ steps.diff.outputs.diff }}"
}]
}],
"generationConfig": {
"temperature": 0.2,
"maxOutputTokens": 1000
}
}
PAYLOAD
)
# Extract text from response
REVIEW=$(echo "$RESPONSE" | jq -r '.candidates[0].content.parts[0].text // "Analysis failed"')
echo "review<<EOFMARKER" >> $GITHUB_OUTPUT
echo "$REVIEW" >> $GITHUB_OUTPUT
echo "EOFMARKER" >> $GITHUB_OUTPUT
- name: Post Gemini review
if: steps.gemini.outputs.review != ''
uses: actions/github-script@v7
with:
script: |
const review = `${{ steps.gemini.outputs.review }}`;
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body: `## 🤖 Gemini Analysis (Free Tier)\n\n${review}\n\n---\n_Powered by contributor's Gemini API allowance (1500 req/day free)_`
});
# Trigger Jules to fix issues (contributor's Copilot allowance)
trigger-jules:
if: github.event_name == 'issues' && github.event.label.name == 'agent:ready'
runs-on: ubuntu-latest
steps:
- name: Check Jules eligibility
id: check
uses: actions/github-script@v7
with:
script: |
// Check if this repo has Copilot coding agent enabled
const issue = context.payload.issue;
console.log(`Issue #${issue.number}: ${issue.title}`);
console.log(`Labels: ${issue.labels.map(l => l.name).join(', ')}`);
// Jules can be assigned to issues with specific labels
const hasAgentic = issue.labels.some(l => l.name === 'agentic');
return { eligible: hasAgentic, issueNumber: issue.number };
- name: Assign to Jules
if: steps.check.outputs.result != ''
uses: actions/github-script@v7
with:
script: |
// Trigger Jules by adding the copilot label
// Jules will pick up the issue and create a PR
try {
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.payload.issue.number,
labels: ['copilot'] // This triggers Jules/Copilot coding agent
});
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.payload.issue.number,
body: `## 🤖 Jules Activated\n\nThis issue has been assigned to GitHub Copilot coding agent (Jules).\n\nJules will:\n1. Analyze the issue\n2. Create a solution\n3. Open a PR with the fix\n\n_Using contributor's Copilot allowance_ 🆓`
});
} catch (e) {
console.log('Could not assign to Jules:', e.message);
}