Compare commits

..

16 Commits

Author SHA1 Message Date
jiantw83 c7a2a3cfc7 fix: update AI Code Review step to use OpenRouter API instead of OpenAI 2026-05-12 03:28:17 +00:00
jiantw83 cf8dd629b2 fix: update AI Code Review step to use OpenAI API instead of Gemini 2026-05-12 03:25:39 +00:00
jiantw83 2c59ce1bc1 fix: enhance error handling in filterFalsePositivesWithAI to check for empty array response 2026-05-12 03:23:44 +00:00
jiantw83 878d8a5bb4 fix: update OLLAMA_BASE_URL to a specific endpoint in README.md 2026-05-12 03:23:44 +00:00
AI Review Bot e9874e61fe chore: update ai-review findings [skip ci] 2026-05-12 03:19:47 +00:00
jiantw83 940d03bc6e fix: add suggestion for necessary permissions in README.md for Action functionality 2026-05-12 03:18:04 +00:00
jiantw83 23ceb84073 fix: add OPENAI_MODEL variable to OpenAI and OpenRouter job configurations 2026-05-12 03:17:06 +00:00
AI Review Bot c16e07bddd chore: update ai-review findings [skip ci] 2026-05-12 03:11:29 +00:00
jiantw83 66a75f135f refactor: remove unused API keys and configurations from action and config files 2026-05-12 03:08:08 +00:00
AI Review Bot d73d360051 chore: update ai-review findings [skip ci] 2026-05-12 03:07:13 +00:00
jiantw83 4b382b4183 fix: update README for OpenRouter API compatibility and add tests for LLM configuration 2026-05-12 03:04:12 +00:00
AI Review Bot f8e24844e8 chore: update ai-review findings [skip ci] 2026-05-12 03:01:32 +00:00
jiantw83 c2e56e4bb2 fix: update GEMINI model version in configuration and workflows 2026-05-12 02:59:22 +00:00
jiantw83 774b78d84e fix: update AI Code Review to use GEMINI API and base URL 2026-05-12 02:56:34 +00:00
jiantw83 fd91ed4e5a fix: update OPENAI_BASE_URL to use the correct API endpoint 2026-05-12 02:54:27 +00:00
jiantw83 43e990cb30 fix: update workflow section in README for clarity and correct numbering 2026-05-12 02:53:19 +00:00
7 changed files with 143 additions and 98 deletions
+5
View File
@@ -7,5 +7,10 @@
{
"location": "app/git.js",
"suggestion": "GITEA_TOKEN 直接嵌入 URL 中"
},
{
"role": "Rex",
"location": "README.md",
"suggestion": "contents: write、pull-requests: write、issues: write 為此 Action 正常運作所必要的權限"
}
]
+2 -1
View File
@@ -33,8 +33,9 @@ jobs:
- name: AI Code Review
uses: https://gitea.jsc.idv.tw/jiantw83/code-review@v${{ needs.version.outputs.version }}
with:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
OPENAI_API_KEY: ${{ secrets.OPENROUTER_API_KEY }} # OpenRouter 使用 OpenAI 相容介面,以 OPENAI_API_KEY 傳入
OPENAI_BASE_URL: https://openrouter.ai/api/v1
OPENAI_MODEL: ${{ vars.OPENROUTER_MODEL }}
permissions:
contents: write
pull-requests: write
+31 -30
View File
@@ -28,7 +28,7 @@
2.`.gitea/workflows` 資料夾中建立 `ai-review.yaml'
3.`ai-review.yaml` 中填入以下內容(選擇一個使用)
### 1. OpenAIOpenRouter
### 1. OpenAI
```yaml
name: AI
on:
@@ -42,17 +42,39 @@ jobs:
- name: AI Code Review
uses: https://gitea.jsc.idv.tw/jiantw83/code-review@${{ vars.ACTION_CODE_REVIEW_VERSION }}
with:
# Github (h3285@evertrust.com.tw)
# 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
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
OPENAI_BASE_URL: https://openrouter.ai/api/v1
OPENAI_BASE_URL: https://api.openai.com/v1
OPENAI_MODEL: ${{ vars.OPENAI_MODEL }}
permissions:
contents: write
pull-requests: write
issues: write
```
### 2. Anthropic Claude
### 2. OpenRouter
```yaml
name: AI
on:
pull_request:
types: [opened, synchronize]
jobs:
code-review:
name: 'Code Review'
runs-on: ubuntu
steps:
- name: AI Code Review
uses: https://gitea.jsc.idv.tw/jiantw83/code-review@${{ vars.ACTION_CODE_REVIEW_VERSION }}
with:
OPENAI_API_KEY: ${{ secrets.OPENROUTER_API_KEY }} # OpenRouter 使用 OpenAI 相容介面,以 OPENAI_API_KEY 傳入
OPENAI_BASE_URL: https://openrouter.ai/api/v1
OPENAI_MODEL: ${{ vars.OPENROUTER_MODEL }}
permissions:
contents: write
pull-requests: write
issues: write
```
### 3. Anthropic Claude
```yaml
name: AI
on:
@@ -74,7 +96,7 @@ jobs:
issues: write
```
### 3. Google Gemini
### 4. Google Gemini
```yaml
name: AI
on:
@@ -90,13 +112,14 @@ jobs:
with:
GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
GEMINI_BASE_URL: https://generativelanguage.googleapis.com/v1beta
GEMINI_MODEL: ${{ vars.GEMINI_MODEL }}
permissions:
contents: write
pull-requests: write
issues: write
```
### 4. Amazon Q
### 5. Amazon Q
```yaml
name: AI
on:
@@ -118,28 +141,6 @@ jobs:
issues: write
```
### 5. SonarQube
```yaml
name: AI
on:
pull_request:
types: [opened, synchronize]
jobs:
code-review:
name: 'Code Review'
runs-on: ubuntu
steps:
- name: AI Code Review
uses: https://gitea.jsc.idv.tw/jiantw83/code-review@${{ vars.ACTION_CODE_REVIEW_VERSION }}
with:
SONARQUBE_TOKEN: ${{ secrets.SONARQUBE_TOKEN }}
SONARQUBE_URL: https://sonarqube.example.com
permissions:
contents: write
pull-requests: write
issues: write
```
### - Ollama
```yaml
@@ -155,7 +156,7 @@ jobs:
- name: AI Code Review
uses: https://gitea.jsc.idv.tw/jiantw83/code-review@${{ vars.ACTION_CODE_REVIEW_VERSION }}
with:
OLLAMA_BASE_URL: ${{ vars.OLLAMA_BASE_URL }}
OLLAMA_BASE_URL: https://ollama.jsc.idv.me/v1
OLLAMA_MODEL: ${{ vars.OLLAMA_MODEL }}
permissions:
contents: write
-58
View File
@@ -72,53 +72,7 @@ inputs:
description: 'Amazon Q Base URL'
required: false
# SonarQube
SONARQUBE_TOKEN:
description: 'SonarQube Token'
required: false
SONARQUBE_URL:
description: 'SonarQube URL'
required: false
# Kilo Code
KILO_API_KEY:
description: 'Kilo Code API Key'
required: false
KILO_BASE_URL:
description: 'Kilo Code Base URL'
required: false
# Roo Code
ROO_API_KEY:
description: 'Roo Code API Key'
required: false
ROO_BASE_URL:
description: 'Roo Code Base URL'
required: false
# Cline
CLINE_API_KEY:
description: 'Cline API Key'
required: false
CLINE_BASE_URL:
description: 'Cline Base URL'
required: false
# Continue
CONTINUE_API_KEY:
description: 'Continue API Key'
required: false
CONTINUE_BASE_URL:
description: 'Continue Base URL'
required: false
# Kade
KADE_API_KEY:
description: 'Kade API Key'
required: false
KADE_BASE_URL:
description: 'Kade Base URL'
required: false
runs:
using: 'docker'
@@ -145,15 +99,3 @@ runs:
OLLAMA_MODEL: ${{ inputs.OLLAMA_MODEL }}
AMAZONQ_API_KEY: ${{ inputs.AMAZONQ_API_KEY }}
AMAZONQ_BASE_URL: ${{ inputs.AMAZONQ_BASE_URL }}
SONARQUBE_TOKEN: ${{ inputs.SONARQUBE_TOKEN }}
SONARQUBE_URL: ${{ inputs.SONARQUBE_URL }}
KILO_API_KEY: ${{ inputs.KILO_API_KEY }}
KILO_BASE_URL: ${{ inputs.KILO_BASE_URL }}
ROO_API_KEY: ${{ inputs.ROO_API_KEY }}
ROO_BASE_URL: ${{ inputs.ROO_BASE_URL }}
CLINE_API_KEY: ${{ inputs.CLINE_API_KEY }}
CLINE_BASE_URL: ${{ inputs.CLINE_BASE_URL }}
CONTINUE_API_KEY: ${{ inputs.CONTINUE_API_KEY }}
CONTINUE_BASE_URL: ${{ inputs.CONTINUE_BASE_URL }}
KADE_API_KEY: ${{ inputs.KADE_API_KEY }}
KADE_BASE_URL: ${{ inputs.KADE_BASE_URL }}
+2 -7
View File
@@ -12,14 +12,9 @@ export function getLLMConfig() {
const checks = [
['openai', process.env.OPENAI_API_KEY, process.env.OPENAI_BASE_URL || 'https://api.openai.com/v1', process.env.OPENAI_MODEL || 'gpt-4o-mini'],
['claude', process.env.CLAUDE_API_KEY, process.env.CLAUDE_BASE_URL || 'https://api.anthropic.com/v1', process.env.CLAUDE_MODEL || 'claude-3-haiku-20240307'],
['gemini', process.env.GEMINI_API_KEY, process.env.GEMINI_BASE_URL || 'https://generativelanguage.googleapis.com/v1beta', process.env.GEMINI_MODEL || 'gemini-1.5-flash'],
['gemini', process.env.GEMINI_API_KEY, process.env.GEMINI_BASE_URL || 'https://generativelanguage.googleapis.com/v1beta', process.env.GEMINI_MODEL || 'gemini-2.5-flash'],
['ollama', 'ollama', process.env.OLLAMA_BASE_URL, process.env.OLLAMA_MODEL],
['amazonq', process.env.AMAZONQ_API_KEY, process.env.AMAZONQ_BASE_URL || 'https://q.api.aws', process.env.OPENAI_MODEL || 'amazon-q'],
['kilo', process.env.KILO_API_KEY, process.env.KILO_BASE_URL || 'https://api.kilocode.com/v1', process.env.OPENAI_MODEL || 'kilo-default'],
['roo', process.env.ROO_API_KEY, process.env.ROO_BASE_URL || 'https://api.roocode.com/v1', process.env.OPENAI_MODEL || 'roo-default'],
['cline', process.env.CLINE_API_KEY, process.env.CLINE_BASE_URL || 'https://api.cline.dev/v1', process.env.OPENAI_MODEL || 'cline-default'],
['continue', process.env.CONTINUE_API_KEY, process.env.CONTINUE_BASE_URL || 'https://api.continue.dev/v1', process.env.OPENAI_MODEL || 'continue-default'],
['kade', process.env.KADE_API_KEY, process.env.KADE_BASE_URL || 'https://api.kade.dev/v1', process.env.OPENAI_MODEL || 'kade-default'],
['amazonq', process.env.AMAZONQ_API_KEY, process.env.AMAZONQ_BASE_URL || 'https://q.api.aws', process.env.AMAZONQ_MODEL || 'amazon-q'],
];
for (const [provider, key, baseURL, model] of checks) {
if (key && baseURL) return { provider, apiKey: key, baseURL, model };
+101
View File
@@ -0,0 +1,101 @@
import { describe, it, beforeEach, afterEach } from 'node:test';
import assert from 'node:assert/strict';
import { getLLMConfig } from './config.js';
const ENV_KEYS = [
'OPENAI_API_KEY', 'OPENAI_BASE_URL', 'OPENAI_MODEL',
'CLAUDE_API_KEY', 'CLAUDE_BASE_URL', 'CLAUDE_MODEL',
'GEMINI_API_KEY', 'GEMINI_BASE_URL', 'GEMINI_MODEL',
'OLLAMA_BASE_URL', 'OLLAMA_MODEL',
'AMAZONQ_API_KEY', 'AMAZONQ_BASE_URL', 'AMAZONQ_MODEL',
];
let saved = {};
beforeEach(() => {
saved = {};
for (const k of ENV_KEYS) { saved[k] = process.env[k]; delete process.env[k]; }
});
afterEach(() => {
for (const k of ENV_KEYS) {
if (saved[k] === undefined) delete process.env[k];
else process.env[k] = saved[k];
}
});
describe('getLLMConfig', () => {
it('returns null provider when no env vars set', () => {
const cfg = getLLMConfig();
assert.equal(cfg.provider, null);
assert.equal(cfg.apiKey, null);
});
it('detects openai with defaults', () => {
process.env.OPENAI_API_KEY = 'sk-test';
const cfg = getLLMConfig();
assert.equal(cfg.provider, 'openai');
assert.equal(cfg.apiKey, 'sk-test');
assert.equal(cfg.baseURL, 'https://api.openai.com/v1');
assert.equal(cfg.model, 'gpt-4o-mini');
});
it('detects openai with custom base url and model', () => {
process.env.OPENAI_API_KEY = 'sk-test';
process.env.OPENAI_BASE_URL = 'https://openrouter.ai/api/v1';
process.env.OPENAI_MODEL = 'gpt-4o';
const cfg = getLLMConfig();
assert.equal(cfg.provider, 'openai');
assert.equal(cfg.baseURL, 'https://openrouter.ai/api/v1');
assert.equal(cfg.model, 'gpt-4o');
});
it('detects gemini with defaults', () => {
process.env.GEMINI_API_KEY = 'gemini-key';
const cfg = getLLMConfig();
assert.equal(cfg.provider, 'gemini');
assert.equal(cfg.model, 'gemini-2.5-flash');
});
it('detects gemini with custom model', () => {
process.env.GEMINI_API_KEY = 'gemini-key';
process.env.GEMINI_MODEL = 'gemini-2.0-flash';
const cfg = getLLMConfig();
assert.equal(cfg.model, 'gemini-2.0-flash');
});
it('detects claude with defaults', () => {
process.env.CLAUDE_API_KEY = 'claude-key';
const cfg = getLLMConfig();
assert.equal(cfg.provider, 'claude');
assert.equal(cfg.model, 'claude-3-haiku-20240307');
});
it('detects amazonq with its own model env', () => {
process.env.AMAZONQ_API_KEY = 'aq-key';
process.env.AMAZONQ_MODEL = 'my-amazon-model';
const cfg = getLLMConfig();
assert.equal(cfg.provider, 'amazonq');
assert.equal(cfg.model, 'my-amazon-model');
});
it('openai takes priority over gemini when both set', () => {
process.env.OPENAI_API_KEY = 'sk-test';
process.env.GEMINI_API_KEY = 'gemini-key';
const cfg = getLLMConfig();
assert.equal(cfg.provider, 'openai');
});
it('empty string api key is treated as not set', () => {
process.env.OPENAI_API_KEY = '';
process.env.GEMINI_API_KEY = 'gemini-key';
const cfg = getLLMConfig();
assert.equal(cfg.provider, 'gemini');
});
it('detects ollama without api key', () => {
process.env.OLLAMA_BASE_URL = 'http://localhost:11434';
process.env.OLLAMA_MODEL = 'llama3';
const cfg = getLLMConfig();
assert.equal(cfg.provider, 'ollama');
assert.equal(cfg.model, 'llama3');
});
});
+2 -2
View File
@@ -149,11 +149,11 @@ export async function filterFalsePositivesWithAI(findings) {
try {
const result = await chatJSON(systemPrompt, userContent);
if (Array.isArray(result)) {
if (Array.isArray(result) && result.length > 0) {
console.log(` AI 誤報過濾: ${findings.length} -> ${result.length}`);
return result;
}
throw new Error('AI 回傳非陣列');
throw new Error('AI 回傳空陣列或非陣列');
} catch (e) {
const status = e.response?.status;
if (status === 402 || status === 429) {