diff --git a/.gitea/workflows/codex.yml b/.gitea/workflows/codex.yml new file mode 100644 index 0000000..b331591 --- /dev/null +++ b/.gitea/workflows/codex.yml @@ -0,0 +1,27 @@ +name: Codex Reply + +on: + issue_comment: + types: [created] + +jobs: + codex: + runs-on: linux_amd64 + if: | + contains(github.event.comment.body, '@codex') && + github.event.comment.user.login != 'anuti' + steps: + - uses: actions/checkout@v4 + + - uses: ./install-opencode + + - uses: ./codex-reply + with: + issue-number: ${{ github.event.issue.number }} + repository: ${{ github.repository }} + comment-body: ${{ github.event.comment.body }} + comment-author: ${{ github.event.comment.user.login }} + issue-title: ${{ github.event.issue.title }} + issue-body: ${{ github.event.issue.body }} + api-token: ${{ secrets.TOKEN }} + api-key: ${{ secrets.OPENCODE_API_KEY }} diff --git a/codex-reply/action.yml b/codex-reply/action.yml new file mode 100644 index 0000000..90b63d7 --- /dev/null +++ b/codex-reply/action.yml @@ -0,0 +1,136 @@ +name: Codex Reply +description: "Risponde a un commento @codex analizzando issue, cronologia e codice repository." + +inputs: + issue-number: + description: Numero dell'issue. + required: true + repository: + description: Repository in formato owner/repo. + required: true + comment-body: + description: Testo del commento che contiene @codex. + required: true + comment-author: + description: Login dell'autore del commento. + required: true + issue-title: + description: Titolo dell'issue. + required: true + issue-body: + description: Corpo dell'issue. + required: true + api-token: + description: Token API Gitea. + required: true + gitea-host: + description: URL del server Gitea. + required: false + default: "https://git.incloud.ovh" + api-key: + description: API key per opencode. + required: true + model: + description: Modello AI in formato provider/model. + required: false + default: "deepseek/deepseek-v4-flash" + +runs: + using: composite + steps: + - name: Configura autenticazione opencode + shell: bash + run: | + set -euo pipefail + if ! command -v jq &> /dev/null; then + echo "jq non trovato, tentativo di installazione..." + if command -v apt-get &> /dev/null; then + apt-get update -qq && apt-get install -y -qq jq 2>&1 || true + elif command -v apk &> /dev/null; then + apk add --no-cache jq 2>&1 || true + fi + command -v jq &> /dev/null || { echo "ERRORE: impossibile installare jq."; exit 1; } + fi + mkdir -p ~/.local/share/opencode + jq -n \ + --arg provider "opencode-go" \ + --arg key "${{ inputs.api-key }}" \ + '{($provider): {type: "api", key: $key}}' > ~/.local/share/opencode/auth.json + chmod 600 ~/.local/share/opencode/auth.json + + - name: Rispondi a @codex + id: codex + shell: bash + run: | + set -euo pipefail + + if ! command -v curl &> /dev/null; then + echo "curl non trovato, tentativo di installazione..." + if command -v apt-get &> /dev/null; then + apt-get update -qq && apt-get install -y -qq curl 2>&1 || true + elif command -v apk &> /dev/null; then + apk add --no-cache curl 2>&1 || true + fi + command -v curl &> /dev/null || { echo "ERRORE: impossibile installare curl."; exit 1; } + fi + + HOST="${{ inputs.gitea-host }}" + TOKEN="${{ inputs.api-token }}" + REPO="${{ inputs.repository }}" + ISSUE_NUM="${{ inputs.issue-number }}" + AUTHOR="${{ inputs.comment-author }}" + MODEL_ARG=() + [ -n "${{ inputs.model }}" ] && MODEL_ARG=(--model "${{ inputs.model }}") + + # Estrai la domanda dopo @codex + QUERY=$(echo "${{ inputs.comment-body }}" | sed -n 's/.*@codex[[:space:],:]*//p') + if [ -z "$QUERY" ]; then + QUERY="(nessuna domanda specifica dopo @codex)" + fi + + # Recupera cronologia commenti, escludendo quelli del bot + COMMENTS_JSON=$(curl -sS -f "$HOST/api/v1/repos/$REPO/issues/$ISSUE_NUM/comments" \ + -H "Authorization: token $TOKEN" \ + -H "Accept: application/json" 2>/dev/null || echo "[]") + COMMENTS_TEXT=$(echo "$COMMENTS_JSON" | jq -r ' + [.[] | select(.user.login != "anuti")] + | sort_by(.created_at) + | .[-20:] + | .[] + | "**\(.user.login)**: \(.body)\n" + ' 2>/dev/null || echo "") + + # Costruisci il prompt + BODY=$(echo "${{ inputs.issue-body }}" | head -c 10000) + [ "${#BODY}" -ge 10000 ] && BODY+=$'\n... (troncato)' + + printf -v PROMPT '%s\n\n%s\n%s\n\n## %s\n%s\n%s\n\n## %s\n%s\n\n## %s - @%s\n%s' \ + "Sei un assistente esperto per repository software." \ + "Rispondi alla domanda in modo diretto e tecnico, in italiano." \ + "Analizza il codice sorgente nel repository quando e' rilevante." \ + "Issue" \ + "**Titolo**: ${{ inputs.issue-title }}" \ + "**Corpo**: $BODY" \ + "Cronologia commenti" "$COMMENTS_TEXT" \ + "Domanda" "$AUTHOR" "$QUERY" + + # Esegui opencode + OUTFILE="$GITHUB_WORKSPACE/opencode-codex.txt" + opencode run "$PROMPT" "${MODEL_ARG[@]}" --dangerously-skip-permissions 2>&1 | tee "$OUTFILE" + + # Pulisci la risposta: togli ANSI codes, banner build, righe vuote iniziali + ESC=$(printf '\x1b') + sed "s/${ESC}\[[0-9;]*m//g" "$OUTFILE" > /tmp/codex-clean.txt + RESPONSE=$(sed '/^> build /d; /^[[:space:]]*$/d' /tmp/codex-clean.txt) + if [ -z "$RESPONSE" ]; then + RESPONSE="Mi dispiace, non ho generato una risposta. Riprova." + fi + + # Pubblica il commento + COMMENT_JSON=$(jq -n --arg body "$RESPONSE" '{body: $body}') + curl -sS -X POST "$HOST/api/v1/repos/$REPO/issues/$ISSUE_NUM/comments" \ + -H "Authorization: token $TOKEN" \ + -H "Content-Type: application/json" \ + -d "$COMMENT_JSON" 2>/dev/null && \ + echo "Risposta @codex pubblicata sull'issue #$ISSUE_NUM" || \ + echo "WARN: Impossibile pubblicare la risposta"