Skip to content
This repository was archived by the owner on Mar 30, 2026. It is now read-only.

Feature Request: Support Antigravity AI Credit Overages for seamless quota fallback #1459

Feature Request: Support Antigravity AI Credit Overages for seamless quota fallback

Feature Request: Support Antigravity AI Credit Overages for seamless quota fallback #1459

Workflow file for this run

name: '🏷️ Issue Triage'
on:
issues:
types:
- 'opened'
- 'reopened'
issue_comment:
types:
- 'created'
workflow_dispatch:
inputs:
issue_number:
description: 'Issue number to triage'
required: true
type: 'number'
concurrency:
group: '${{ github.workflow }}-${{ github.event.issue.number || github.event.inputs.issue_number }}'
cancel-in-progress: true
permissions:
contents: 'read'
issues: 'write'
jobs:
triage-issue:
if: |
github.event_name == 'workflow_dispatch' ||
github.event_name == 'issues' ||
(
github.event_name == 'issue_comment' &&
contains(github.event.comment.body, '/triage') &&
(
github.event.comment.author_association == 'OWNER' ||
github.event.comment.author_association == 'MEMBER' ||
github.event.comment.author_association == 'COLLABORATOR'
)
)
timeout-minutes: 30
runs-on: self-hosted
steps:
- name: 'Check if already triaged'
id: 'check_labels'
if: github.event_name != 'workflow_dispatch'
uses: 'actions/github-script@v7'
with:
script: |
const labels = context.payload.issue?.labels?.map(l => l.name) || [];
const triageLabels = ['bug', 'enhancement', 'documentation', 'question', 'invalid'];
const areaLabels = labels.filter(l => l.startsWith('area/'));
const hasTriageLabel = labels.some(l => triageLabels.includes(l)) || areaLabels.length > 0;
const isRetriage = context.payload.comment?.body?.includes('/triage');
if (hasTriageLabel && !labels.includes('needs-triage') && !isRetriage) {
core.info(`Issue already triaged: ${labels.join(', ')}. Skipping.`);
core.setOutput('skip', 'true');
} else {
core.setOutput('skip', 'false');
}
- name: 'Get issue data'
id: 'get_issue'
if: steps.check_labels.outputs.skip != 'true'
uses: 'actions/github-script@v7'
with:
script: |
const issueNumber = context.payload.inputs?.issue_number || context.issue?.number;
core.setOutput('number', issueNumber);
- name: 'Run Opencode Triage'
id: 'opencode_triage'
if: steps.check_labels.outputs.skip != 'true'
run: |
echo "Running Opencode Triage on issue ${{ steps.get_issue.outputs.number }}..."
# Move to the repository
cd /home/admin/coding/opencode-antigravity-auth
# Run CLI with the agent config
OUTPUT=$(opencode run --agent triage-bot "Triage issue ${{ steps.get_issue.outputs.number }}")
echo "Opencode Output: $OUTPUT"
echo "result<<EOF" >> $GITHUB_OUTPUT
echo "$OUTPUT" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
- name: 'Apply Labels and Respond'
if: steps.check_labels.outputs.skip != 'true'
uses: 'actions/github-script@v7'
env:
ISSUE_NUMBER: '${{ steps.get_issue.outputs.number }}'
OPENCODE_OUTPUT: '${{ steps.opencode_triage.outputs.result }}'
with:
script: |
const rawOutput = process.env.OPENCODE_OUTPUT;
if (!rawOutput) {
core.warning('No triage output available');
return;
}
core.info(`Triage output: ${rawOutput}`);
let parsed;
try {
parsed = JSON.parse(rawOutput.trim());
} catch (e) {
// Try to extract JSON from code blocks first
const codeBlockMatch = rawOutput.match(/```(?:json)?\s*([\s\S]*?)\s*```/);
// Find the LAST JSON object containing triage fields (avoid matching code snippets)
// Match JSON objects that contain "type_label" to ensure we get the triage output
const triageJsonMatch = rawOutput.match(/\{"type_label"[\s\S]*?\}(?=\s*$|\s*```|\s*\n\n)/);
// Fallback: find all potential JSON objects and try parsing from the end
let jsonStr = codeBlockMatch?.[1] || triageJsonMatch?.[0];
if (!jsonStr) {
// Last resort: find all { } pairs and try parsing from the last one
const allMatches = [...rawOutput.matchAll(/\{[^{}]*(?:\{[^{}]*\}[^{}]*)*\}/g)];
for (let i = allMatches.length - 1; i >= 0; i--) {
try {
const candidate = JSON.parse(allMatches[i][0]);
if (candidate.type_label && candidate.area_label) {
jsonStr = allMatches[i][0];
break;
}
} catch {}
}
}
if (jsonStr) {
try {
parsed = JSON.parse(jsonStr.trim());
} catch (e2) {
core.setFailed(`Failed to parse output: ${rawOutput}`);
return;
}
} else {
core.setFailed(`Failed to parse output: ${rawOutput}`);
return;
}
}
const typeLabel = parsed.type_label;
const areaLabel = parsed.area_label;
const duplicateOf = parsed.duplicate_of;
const suggestedResponse = parsed.suggested_response;
// Validate required fields
if (!typeLabel || !areaLabel || suggestedResponse === undefined) {
core.setFailed(`Invalid JSON structure: missing required fields. Parsed: ${JSON.stringify(parsed)}`);
return;
}
const validTypeLabels = ['bug', 'enhancement', 'documentation', 'question', 'invalid'];
const validAreaLabels = ['area/auth', 'area/models', 'area/config', 'area/compat'];
if (!validTypeLabels.includes(typeLabel)) {
core.warning(`Invalid type_label: ${typeLabel}`);
return;
}
if (!validAreaLabels.includes(areaLabel)) {
core.warning(`Invalid area_label: ${areaLabel}`);
return;
}
let labelsToAdd = [];
let labelsToRemove = ['needs-triage'];
// Remove existing triage labels to prevent conflicts
validTypeLabels.forEach(label => labelsToRemove.push(label));
validAreaLabels.forEach(label => labelsToRemove.push(label));
// Only add one type label and one area label
if (typeLabel && validTypeLabels.includes(typeLabel)) {
labelsToAdd.push(typeLabel);
}
if (areaLabel && validAreaLabels.includes(areaLabel)) {
labelsToAdd.push(areaLabel);
}
if (duplicateOf) {
labelsToAdd.push('duplicate');
}
// Validate single label selection and deduplicate
const typeLabels = labelsToAdd.filter(label => validTypeLabels.includes(label));
const areaLabels = labelsToAdd.filter(label => validAreaLabels.includes(label));
const otherLabels = labelsToAdd.filter(label => !validTypeLabels.includes(label) && !validAreaLabels.includes(label));
if (typeLabels.length > 1) {
core.warning(`Multiple type labels detected: ${typeLabels.join(', ')}. Using only: ${typeLabel}`);
}
if (areaLabels.length > 1) {
core.warning(`Multiple area labels detected: ${areaLabels.join(', ')}. Using only: ${areaLabel}`);
}
// Rebuild labelsToAdd with single type and area labels
labelsToAdd = [];
if (typeLabel && validTypeLabels.includes(typeLabel)) {
labelsToAdd.push(typeLabel);
}
if (areaLabel && validAreaLabels.includes(areaLabel)) {
labelsToAdd.push(areaLabel);
}
labelsToAdd.push(...otherLabels);
// Avoid removing labels that are being added
labelsToRemove = labelsToRemove.filter(label => !labelsToAdd.includes(label));
const issueNumber = parseInt(process.env.ISSUE_NUMBER);
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issueNumber,
labels: labelsToAdd
});
core.info(`Added labels: ${labelsToAdd.join(', ')}`);
for (const label of labelsToRemove) {
try {
await github.rest.issues.removeLabel({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issueNumber,
name: label
});
core.info(`Removed label: ${label}`);
} catch (e) {
core.info(`Label ${label} not present, skipping removal`);
}
}
if (suggestedResponse && suggestedResponse.trim()) {
let body = `👋 Thanks for opening this issue!\n\n${suggestedResponse}`;
if (duplicateOf) {
body += `\n\n🔗 This appears to be related to #${duplicateOf}. Please check that issue for updates.`;
}
body += `\n\n---\n*This is an automated response. A maintainer will review your issue soon.*`;
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issueNumber,
body: body
});
core.info('Posted response comment');
}
if (duplicateOf) {
await github.rest.issues.update({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issueNumber,
state: 'closed',
state_reason: 'not_planned'
});
core.info(`Closed as duplicate of #${duplicateOf}`);
}
- name: 'Comment on failure'
if: failure()
uses: 'actions/github-script@v7'
env:
ISSUE_NUMBER: '${{ steps.get_issue.outputs.number || github.event.issue.number }}'
RUN_URL: '${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}'
with:
script: |
if (!process.env.ISSUE_NUMBER) return;
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: parseInt(process.env.ISSUE_NUMBER),
body: `⚠️ Automated triage failed. [View logs](${process.env.RUN_URL})`
});