mirror of
https://github.com/jeffusion/gitea-ai-assistant.git
synced 2026-03-27 10:05:50 +00:00
feat(config): add Codex engine configuration fields
Add CODEX_API_URL, CODEX_API_KEY, CODEX_MODEL, CODEX_TIMEOUT_MS, and CODEX_REVIEW_PROMPT to config schema and manager. Wire Codex engine dispatch in review controller alongside agent/legacy engines. Register MCP Streamable HTTP endpoint at /mcp/gitea-review in app entry point. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)
This commit is contained in:
@@ -41,6 +41,13 @@ export interface AppConfig {
|
||||
llmRetryMaxAttempts: number;
|
||||
llmRetryBaseDelayMs: number;
|
||||
enableTriage: boolean;
|
||||
// Codex engine
|
||||
codexApiUrl: string;
|
||||
codexApiKey: string | undefined;
|
||||
codexModel: string;
|
||||
codexTimeoutMs: number;
|
||||
codexReviewPrompt: string | undefined;
|
||||
// Memory (shared)
|
||||
qdrantUrl: string | undefined;
|
||||
enableMemory: boolean;
|
||||
fewShotExamplesCount: number;
|
||||
@@ -161,6 +168,13 @@ class ConfigManager {
|
||||
llmRetryMaxAttempts: toNumber('LLM_RETRY_MAX_ATTEMPTS', 3),
|
||||
llmRetryBaseDelayMs: toNumber('LLM_RETRY_BASE_DELAY_MS', 1000),
|
||||
enableTriage: toBoolean('ENABLE_TRIAGE', true),
|
||||
// Codex engine
|
||||
codexApiUrl: values.CODEX_API_URL ?? 'https://api.openai.com/v1',
|
||||
codexApiKey: values.CODEX_API_KEY,
|
||||
codexModel: values.CODEX_MODEL ?? 'o3',
|
||||
codexTimeoutMs: toNumber('CODEX_TIMEOUT_MS', 300000),
|
||||
codexReviewPrompt: values.CODEX_REVIEW_PROMPT,
|
||||
// Memory
|
||||
qdrantUrl: values.QDRANT_URL,
|
||||
enableMemory: toBoolean('ENABLE_MEMORY', false),
|
||||
fewShotExamplesCount: toNumber('FEW_SHOT_EXAMPLES_COUNT', 10),
|
||||
|
||||
@@ -178,10 +178,10 @@ export const CONFIG_FIELDS: ConfigFieldMeta[] = [
|
||||
envKey: 'REVIEW_ENGINE',
|
||||
group: 'review',
|
||||
label: '审查引擎',
|
||||
description: '代码审查模式:legacy(传统)或 agent(多代理编排)',
|
||||
description: '代码审查模式:legacy(传统)、agent(多代理编排)或 codex(Codex CLI)',
|
||||
type: 'enum',
|
||||
sensitive: false,
|
||||
enumValues: ['legacy', 'agent'],
|
||||
enumValues: ['legacy', 'agent', 'codex'],
|
||||
defaultValue: 'legacy',
|
||||
},
|
||||
{
|
||||
@@ -309,6 +309,53 @@ export const CONFIG_FIELDS: ConfigFieldMeta[] = [
|
||||
defaultValue: true,
|
||||
},
|
||||
|
||||
// ── Codex 审查引擎 ──────────────────────────────────────────────────────
|
||||
{
|
||||
envKey: 'CODEX_API_URL',
|
||||
group: 'review',
|
||||
label: 'Codex API 地址',
|
||||
description: 'Codex CLI 使用的 LLM API 端点地址',
|
||||
type: 'url',
|
||||
sensitive: false,
|
||||
defaultValue: 'https://api.openai.com/v1',
|
||||
},
|
||||
{
|
||||
envKey: 'CODEX_API_KEY',
|
||||
group: 'review',
|
||||
label: 'Codex API 密钥',
|
||||
description: 'Codex CLI 调用 LLM 所需的 API 密钥',
|
||||
type: 'string',
|
||||
sensitive: true,
|
||||
},
|
||||
{
|
||||
envKey: 'CODEX_MODEL',
|
||||
group: 'review',
|
||||
label: 'Codex 模型',
|
||||
description: 'Codex CLI 使用的模型名称',
|
||||
type: 'string',
|
||||
sensitive: false,
|
||||
defaultValue: 'o3',
|
||||
},
|
||||
{
|
||||
envKey: 'CODEX_TIMEOUT_MS',
|
||||
group: 'review',
|
||||
label: 'Codex 超时(ms)',
|
||||
description: 'Codex CLI 单次审查的执行超时时间(毫秒)',
|
||||
type: 'number',
|
||||
sensitive: false,
|
||||
min: 30000,
|
||||
max: 600000,
|
||||
defaultValue: 300000,
|
||||
},
|
||||
{
|
||||
envKey: 'CODEX_REVIEW_PROMPT',
|
||||
group: 'review',
|
||||
label: 'Codex 审查提示词',
|
||||
description: '覆盖 Codex 引擎默认的代码审查提示词(留空使用内置提示词)',
|
||||
type: 'text',
|
||||
sensitive: false,
|
||||
},
|
||||
|
||||
// ── 记忆与学习 ──────────────────────────────────────────────────────────
|
||||
{
|
||||
envKey: 'QDRANT_URL',
|
||||
|
||||
@@ -2,6 +2,7 @@ import * as crypto from 'node:crypto';
|
||||
import { Context } from 'hono';
|
||||
import { map } from 'lodash-es';
|
||||
import config from '../config';
|
||||
import { codexEngine } from '../review/codex/codex-engine';
|
||||
import { reviewEngine } from '../review/engine';
|
||||
import { aiReviewService } from '../services/ai-review';
|
||||
import { feishuService } from '../services/feishu';
|
||||
@@ -150,13 +151,13 @@ async function handlePullRequestEvent(c: Context, body: any): Promise<Response>
|
||||
}
|
||||
}
|
||||
|
||||
if (config.review.engine === 'agent') {
|
||||
if (config.review.engine === 'agent' || config.review.engine === 'codex') {
|
||||
// Fork PR策略:始终clone base repo(保证有baseSha),headCloneUrl作为额外remote(保证有headSha)
|
||||
const baseCloneUrl = resolveCloneUrl(repo);
|
||||
const headSha = pullRequest.head?.sha;
|
||||
const baseSha = pullRequest.base?.sha;
|
||||
if (!baseCloneUrl || !headSha || !baseSha) {
|
||||
return c.json({ error: '缺少Agent审查所需字段(clone_url/base sha/head sha)' }, 400);
|
||||
return c.json({ error: '缺少审查所需字段(clone_url/base sha/head sha)' }, 400);
|
||||
}
|
||||
|
||||
// 检测fork PR:head.repo存在且与base repo不同
|
||||
@@ -167,7 +168,8 @@ async function handlePullRequestEvent(c: Context, body: any): Promise<Response>
|
||||
|
||||
// 包含baseSha以支持retarget场景:相同headSha但baseSha变化时需要重新审查
|
||||
const idempotencyKey = `${owner}/${repoName}#${prNumber}:${baseSha}...${headSha}`;
|
||||
const { run, reused } = await reviewEngine.enqueuePullRequest({
|
||||
const engineInstance = config.review.engine === 'codex' ? codexEngine : reviewEngine;
|
||||
const { run, reused } = await engineInstance.enqueuePullRequest({
|
||||
eventType: 'pull_request',
|
||||
idempotencyKey,
|
||||
owner,
|
||||
@@ -179,10 +181,11 @@ async function handlePullRequestEvent(c: Context, body: any): Promise<Response>
|
||||
headSha,
|
||||
});
|
||||
|
||||
const engineLabel = config.review.engine === 'codex' ? 'Codex' : 'Agent';
|
||||
return c.json(
|
||||
{
|
||||
status: reused ? 'deduplicated' : 'accepted',
|
||||
message: reused ? '审查任务已存在,已去重' : 'Agent代码审查任务已入队',
|
||||
message: reused ? '审查任务已存在,已去重' : `${engineLabel}代码审查任务已入队`,
|
||||
runId: run.id,
|
||||
},
|
||||
202
|
||||
@@ -261,15 +264,16 @@ async function handleCommitStatusEvent(c: Context, body: any): Promise<Response>
|
||||
removed: commitInfo.removed.length,
|
||||
});
|
||||
|
||||
// Agent模式优先处理:从本地仓库派生diff,不依赖webhook文件列表
|
||||
if (config.review.engine === 'agent') {
|
||||
// Agent/Codex模式优先处理:从本地仓库派生diff,不依赖webhook文件列表
|
||||
if (config.review.engine === 'agent' || config.review.engine === 'codex') {
|
||||
const cloneUrl = resolveCloneUrl(body.repository);
|
||||
if (!cloneUrl) {
|
||||
return c.json({ error: '缺少Agent审查所需字段(clone_url)' }, 400);
|
||||
return c.json({ error: '缺少审查所需字段(clone_url)' }, 400);
|
||||
}
|
||||
|
||||
const idempotencyKey = `${owner}/${repoName}@${commitSha}`;
|
||||
const { run, reused } = await reviewEngine.enqueueCommit({
|
||||
const engineInstance = config.review.engine === 'codex' ? codexEngine : reviewEngine;
|
||||
const { run, reused } = await engineInstance.enqueueCommit({
|
||||
eventType: 'commit_status',
|
||||
idempotencyKey,
|
||||
owner,
|
||||
@@ -280,10 +284,11 @@ async function handleCommitStatusEvent(c: Context, body: any): Promise<Response>
|
||||
relatedPrNumber: relatedPR?.number,
|
||||
});
|
||||
|
||||
const engineLabel = config.review.engine === 'codex' ? 'Codex' : 'Agent';
|
||||
return c.json(
|
||||
{
|
||||
status: reused ? 'deduplicated' : 'accepted',
|
||||
message: reused ? '审查任务已存在,已去重' : 'Agent提交审查任务已入队',
|
||||
message: reused ? '审查任务已存在,已去重' : `${engineLabel}提交审查任务已入队`,
|
||||
runId: run.id,
|
||||
},
|
||||
202
|
||||
|
||||
@@ -9,6 +9,8 @@ import { llmConfigRouter } from './controllers/llm-config';
|
||||
import { handleGiteaWebhook } from './controllers/review';
|
||||
import { initMasterKey } from './crypto/secrets';
|
||||
import { initDatabase } from './db/database';
|
||||
import { codexEngine } from './review/codex/codex-engine';
|
||||
import { mcpRouter } from './review/codex/mcp-handler';
|
||||
import { reviewEngine } from './review/engine';
|
||||
|
||||
initMasterKey();
|
||||
@@ -40,6 +42,9 @@ app.get('/', (c) => {
|
||||
});
|
||||
});
|
||||
|
||||
// MCP 端点(Codex 审查引擎使用,无需认证)
|
||||
app.route('/mcp/gitea-review', mcpRouter);
|
||||
|
||||
// 统一的Gitea webhook路由 - 处理所有事件类型
|
||||
app.post('/webhook/gitea', handleGiteaWebhook);
|
||||
|
||||
@@ -71,9 +76,13 @@ app.get('*', serveStatic({ path: './public/index.html' }));
|
||||
const port = config.app.port;
|
||||
console.log(`⚡️ 服务启动在 http://localhost:${port}`);
|
||||
|
||||
// 启动审查引擎(根据配置选择)
|
||||
reviewEngine.start().catch((error) => {
|
||||
console.error('❌ 启动Agent Review Engine失败', error);
|
||||
});
|
||||
codexEngine.start().catch((error) => {
|
||||
console.error('❌ 启动Codex Review Engine失败', error);
|
||||
});
|
||||
|
||||
// 初始化反馈系统(总是初始化,记忆系统可选)
|
||||
const reviewStore = reviewEngine.getStore();
|
||||
|
||||
Reference in New Issue
Block a user