Add workspace chat tabs #353
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Issue Auto Response | |
| on: | |
| issues: | |
| types: [opened, labeled] | |
| issue_comment: | |
| types: [created] | |
| concurrency: | |
| group: issue-auto-response-${{ github.event.issue.number }} | |
| cancel-in-progress: true | |
| permissions: | |
| contents: read | |
| jobs: | |
| auto-response: | |
| if: | | |
| !github.event.issue.pull_request && | |
| !endsWith(github.actor, '[bot]') && | |
| !contains(github.event.issue.labels.*.name, 'duplicate') && | |
| !contains(github.event.issue.labels.*.name, 'spam') && | |
| !contains(github.event.issue.labels.*.name, 'bot-skip') && | |
| vars.CODEX_BOT_ENABLED == 'true' | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| issues: write | |
| steps: | |
| - name: Check bot response state | |
| id: check_bot | |
| uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7 | |
| with: | |
| script: | | |
| const marker = "*Open-CoDesign Bot*"; | |
| const allowedLogins = (process.env.BOT_LOGINS || "github-actions[bot]") | |
| .split(",").map((v) => v.trim()).filter(Boolean); | |
| const comments = await github.paginate( | |
| github.rest.issues.listComments, | |
| { | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: context.issue.number, | |
| per_page: 100 | |
| } | |
| ); | |
| const botComments = comments | |
| .filter((c) => { | |
| if (!(c?.body || "").includes(marker)) return false; | |
| const u = c.user; | |
| if (!u || u.type !== "Bot") return false; | |
| return allowedLogins.includes(u.login); | |
| }) | |
| .sort((a, b) => new Date(b.created_at || 0) - new Date(a.created_at || 0)); | |
| const latestBot = botComments[0] || null; | |
| let shouldRespond = false; | |
| let reason = "unhandled event"; | |
| if (context.eventName === "issues") { | |
| const body = (context.payload.issue?.body || "").trim(); | |
| const labelName = context.payload.label?.name || ""; | |
| const forceRerun = labelName === "bot-rerun"; | |
| shouldRespond = Boolean(body) && (!latestBot || forceRerun); | |
| reason = !body | |
| ? "skip empty issue body" | |
| : shouldRespond | |
| ? forceRerun | |
| ? "forced issue bot rerun" | |
| : "new issue without bot response" | |
| : "issue already has bot response"; | |
| } else if (context.eventName === "issue_comment") { | |
| const comment = context.payload.comment; | |
| const author = comment?.user; | |
| const isBotComment = | |
| author?.type === "Bot" || | |
| (author?.login || "").endsWith("[bot]"); | |
| const body = (comment?.body || "").trim(); | |
| if (isBotComment) { | |
| reason = "skip bot comment"; | |
| } else if (!body) { | |
| reason = "skip empty comment"; | |
| } else if (!latestBot) { | |
| shouldRespond = true; | |
| reason = "human comment without prior bot response"; | |
| } else { | |
| const commentTime = new Date(comment.created_at || 0).getTime(); | |
| const botTime = new Date(latestBot.created_at || 0).getTime(); | |
| shouldRespond = commentTime > botTime; | |
| reason = shouldRespond | |
| ? "human follow-up after bot response" | |
| : "comment is not newer than latest bot response"; | |
| } | |
| } | |
| core.info(`Issue bot response decision: ${shouldRespond ? "run" : "skip"} (${reason})`); | |
| core.setOutput("should_respond", shouldRespond ? "true" : "false"); | |
| core.setOutput("reason", reason); | |
| core.setOutput("latest_bot_comment_id", latestBot ? String(latestBot.id) : ""); | |
| env: | |
| BOT_LOGINS: ${{ vars.BOT_LOGINS }} | |
| - name: Checkout repository | |
| if: steps.check_bot.outputs.should_respond == 'true' | |
| uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Resolve issue provider config | |
| if: steps.check_bot.outputs.should_respond == 'true' | |
| id: issue_config | |
| shell: bash | |
| env: | |
| DEFAULT_API_KEY: ${{ secrets.OPENAI_API_KEY }} | |
| DEFAULT_BASE_URL: ${{ secrets.OPENAI_BASE_URL }} | |
| DEFAULT_MODEL: ${{ vars.OPENAI_MODEL }} | |
| DEFAULT_EFFORT: ${{ vars.OPENAI_EFFORT }} | |
| REVIEW_API_KEY: ${{ secrets.REVIEW_OPENAI_API_KEY }} | |
| REVIEW_BASE_URL: ${{ secrets.REVIEW_OPENAI_BASE_URL }} | |
| REVIEW_MODEL: ${{ vars.REVIEW_OPENAI_MODEL }} | |
| REVIEW_EFFORT: ${{ vars.REVIEW_OPENAI_EFFORT }} | |
| run: | | |
| api_key="$REVIEW_API_KEY" | |
| [ -n "$api_key" ] || api_key="$DEFAULT_API_KEY" | |
| base_url="$REVIEW_BASE_URL" | |
| [ -n "$base_url" ] || base_url="$DEFAULT_BASE_URL" | |
| model="$REVIEW_MODEL" | |
| [ -n "$model" ] || model="$DEFAULT_MODEL" | |
| effort="$REVIEW_EFFORT" | |
| [ -n "$effort" ] || effort="$DEFAULT_EFFORT" | |
| [ -n "$model" ] || model='gpt-5.4' | |
| [ -n "$effort" ] || effort='high' | |
| is_deepseek='false' | |
| case "$base_url" in | |
| *api.deepseek.com*) | |
| is_deepseek='true' | |
| ;; | |
| esac | |
| { | |
| echo "api_key=$api_key" | |
| echo "base_url=$base_url" | |
| echo "model=$model" | |
| echo "effort=$effort" | |
| echo "is_deepseek=$is_deepseek" | |
| } >> "$GITHUB_OUTPUT" | |
| - name: Set up Node.js for DeepSeek issue response | |
| if: steps.check_bot.outputs.should_respond == 'true' && steps.issue_config.outputs.is_deepseek == 'true' | |
| uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 | |
| with: | |
| node-version: "20" | |
| - name: Run DeepSeek for Issue Auto Response | |
| if: steps.check_bot.outputs.should_respond == 'true' && steps.issue_config.outputs.is_deepseek == 'true' | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| GITHUB_TOKEN: ${{ github.token }} | |
| DEEPSEEK_API_KEY: ${{ steps.issue_config.outputs.api_key }} | |
| DEEPSEEK_BASE_URL: ${{ steps.issue_config.outputs.base_url }} | |
| DEEPSEEK_MODEL: ${{ steps.issue_config.outputs.model }} | |
| DEEPSEEK_EFFORT: ${{ steps.issue_config.outputs.effort }} | |
| run: node .github/scripts/deepseek-issue-response.mjs | |
| - name: Run Codex for Issue Auto Response | |
| if: steps.check_bot.outputs.should_respond == 'true' && steps.issue_config.outputs.is_deepseek != 'true' | |
| uses: openai/codex-action@e0fdf01220eb9a88167c4898839d273e3f2609d1 # v1 | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| GITHUB_TOKEN: ${{ github.token }} | |
| with: | |
| openai-api-key: ${{ steps.issue_config.outputs.api_key }} | |
| responses-api-endpoint: ${{ steps.issue_config.outputs.base_url }} | |
| model: ${{ steps.issue_config.outputs.model }} | |
| effort: ${{ steps.issue_config.outputs.effort }} | |
| sandbox: danger-full-access | |
| safety-strategy: drop-sudo | |
| prompt-file: .github/prompts/issue-auto-response.md | |
| allow-bots: true | |
| allow-users: '*' |