mirror of
https://github.com/jeffusion/gitea-ai-assistant.git
synced 2026-06-07 23:16:46 +00:00
refactor(config): remove LLM settings from config layer
Strip OpenAI-specific settings (apiKey, baseUrl, model) and per-role model overrides from config schema — these are now managed through the database via the LLM provider UI. Simplify config-manager and its tests accordingly. Keep only runtime settings (port, webhookSecret, etc.) in env/config. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)
This commit is contained in:
@@ -23,9 +23,6 @@ const SCHEMA_KEYS = [
|
||||
'GITEA_API_URL',
|
||||
'GITEA_ACCESS_TOKEN',
|
||||
'GITEA_ADMIN_TOKEN',
|
||||
'OPENAI_BASE_URL',
|
||||
'OPENAI_API_KEY',
|
||||
'OPENAI_MODEL',
|
||||
'CUSTOM_SUMMARY_PROMPT',
|
||||
'CUSTOM_LINE_COMMENT_PROMPT',
|
||||
'GLOBAL_PROMPT',
|
||||
@@ -37,9 +34,6 @@ const SCHEMA_KEYS = [
|
||||
'JWT_SECRET',
|
||||
'REVIEW_ENGINE',
|
||||
'REVIEW_WORKDIR',
|
||||
'REVIEW_MODEL_PLANNER',
|
||||
'REVIEW_MODEL_SPECIALIST',
|
||||
'REVIEW_MODEL_JUDGE',
|
||||
'REVIEW_MAX_PARALLEL_RUNS',
|
||||
'REVIEW_MAX_FILES_PER_RUN',
|
||||
'REVIEW_MAX_FILE_CONTENT_CHARS',
|
||||
@@ -113,20 +107,20 @@ describe('ConfigManager', () => {
|
||||
describe('layering: defaults < env < override', () => {
|
||||
test('Zod default used when env and override are absent', async () => {
|
||||
const cm = await importFresh();
|
||||
expect(cm.getCurrent().openai.model).toBe('gpt-4o-mini');
|
||||
expect(cm.getCurrent().review.engine).toBe('legacy');
|
||||
});
|
||||
|
||||
test('env value overrides Zod default', async () => {
|
||||
process.env.OPENAI_MODEL = 'env-model';
|
||||
process.env.REVIEW_ENGINE = 'agent';
|
||||
const cm = await importFresh();
|
||||
expect(cm.getCurrent().openai.model).toBe('env-model');
|
||||
expect(cm.getCurrent().review.engine).toBe('agent');
|
||||
});
|
||||
|
||||
test('override wins over env', async () => {
|
||||
process.env.OPENAI_MODEL = 'env-model';
|
||||
process.env.REVIEW_ENGINE = 'agent';
|
||||
const cm = await importFresh();
|
||||
await cm.setOverrides({ OPENAI_MODEL: 'override-model' });
|
||||
expect(cm.getCurrent().openai.model).toBe('override-model');
|
||||
await cm.setOverrides({ REVIEW_ENGINE: 'legacy' });
|
||||
expect(cm.getCurrent().review.engine).toBe('legacy');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -135,14 +129,14 @@ describe('ConfigManager', () => {
|
||||
describe('empty string resets override', () => {
|
||||
test('setting override to "" removes it, value falls back to Zod default', async () => {
|
||||
const cm = await importFresh();
|
||||
await cm.setOverrides({ OPENAI_MODEL: 'temp-override' });
|
||||
expect(cm.getCurrent().openai.model).toBe('temp-override');
|
||||
await cm.setOverrides({ REVIEW_ENGINE: 'agent' });
|
||||
expect(cm.getCurrent().review.engine).toBe('agent');
|
||||
|
||||
await cm.setOverrides({ OPENAI_MODEL: '' });
|
||||
await cm.setOverrides({ REVIEW_ENGINE: '' });
|
||||
|
||||
// OPENAI_MODEL is '' in env (neutralised) → falls to Zod default
|
||||
expect(cm.getCurrent().openai.model).toBe('gpt-4o-mini');
|
||||
expect(cm.getOverrides()).not.toHaveProperty('OPENAI_MODEL');
|
||||
// REVIEW_ENGINE is '' in env (neutralised) → falls to Zod default
|
||||
expect(cm.getCurrent().review.engine).toBe('legacy');
|
||||
expect(cm.getOverrides()).not.toHaveProperty('REVIEW_ENGINE');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -151,18 +145,18 @@ describe('ConfigManager', () => {
|
||||
describe('persistence', () => {
|
||||
test('setOverrides writes JSON file; new instance loads it', async () => {
|
||||
const cm1 = await importFresh();
|
||||
await cm1.setOverrides({ OPENAI_MODEL: 'persisted-model' });
|
||||
await cm1.setOverrides({ REVIEW_ENGINE: 'agent' });
|
||||
|
||||
// File structure check
|
||||
const raw = await readFile(tmpPath, 'utf-8');
|
||||
const data = JSON.parse(raw);
|
||||
expect(data.version).toBe(1);
|
||||
expect(typeof data.updatedAt).toBe('string');
|
||||
expect(data.overrides.OPENAI_MODEL).toBe('persisted-model');
|
||||
expect(data.overrides.REVIEW_ENGINE).toBe('agent');
|
||||
|
||||
// Fresh instance picks it up
|
||||
const cm2 = await importFresh();
|
||||
expect(cm2.getCurrent().openai.model).toBe('persisted-model');
|
||||
expect(cm2.getCurrent().review.engine).toBe('agent');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -170,22 +164,22 @@ describe('ConfigManager', () => {
|
||||
|
||||
describe('getSource()', () => {
|
||||
test('returns "default" when neither env nor override is set', async () => {
|
||||
// OPENAI_MODEL = '' (neutralised) → getSource sees '' → 'default'
|
||||
// REVIEW_ENGINE = '' (neutralised) → getSource sees '' → 'default'
|
||||
const cm = await importFresh();
|
||||
expect(cm.getSource('OPENAI_MODEL')).toBe('default');
|
||||
expect(cm.getSource('REVIEW_ENGINE')).toBe('default');
|
||||
});
|
||||
|
||||
test('returns "env" when process.env has a non-empty value', async () => {
|
||||
process.env.OPENAI_MODEL = 'from-env';
|
||||
process.env.REVIEW_ENGINE = 'agent';
|
||||
const cm = await importFresh();
|
||||
expect(cm.getSource('OPENAI_MODEL')).toBe('env');
|
||||
expect(cm.getSource('REVIEW_ENGINE')).toBe('env');
|
||||
});
|
||||
|
||||
test('returns "override" when override is set', async () => {
|
||||
process.env.OPENAI_MODEL = 'from-env';
|
||||
process.env.REVIEW_ENGINE = 'agent';
|
||||
const cm = await importFresh();
|
||||
await cm.setOverrides({ OPENAI_MODEL: 'from-override' });
|
||||
expect(cm.getSource('OPENAI_MODEL')).toBe('override');
|
||||
await cm.setOverrides({ REVIEW_ENGINE: 'legacy' });
|
||||
expect(cm.getSource('REVIEW_ENGINE')).toBe('override');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -40,10 +40,6 @@ const envSchema = z.object({
|
||||
GITEA_ACCESS_TOKEN: z.string().default('test_token'),
|
||||
GITEA_ADMIN_TOKEN: z.string().optional(),
|
||||
|
||||
// OpenAI
|
||||
OPENAI_BASE_URL: z.string().url().default('https://api.openai.com/v1'),
|
||||
OPENAI_API_KEY: z.string().default('test_openai_key'),
|
||||
OPENAI_MODEL: z.string().default('gpt-4o-mini'),
|
||||
CUSTOM_SUMMARY_PROMPT: z.string().optional(),
|
||||
CUSTOM_LINE_COMMENT_PROMPT: z.string().optional(),
|
||||
GLOBAL_PROMPT: z.string().optional(),
|
||||
@@ -66,9 +62,6 @@ const envSchema = z.object({
|
||||
// Review engine
|
||||
REVIEW_ENGINE: z.enum(['legacy', 'agent']).default('legacy'),
|
||||
REVIEW_WORKDIR: z.string().default('/tmp/gitea-assistant'),
|
||||
REVIEW_MODEL_PLANNER: z.string().default('gpt-4o-mini'),
|
||||
REVIEW_MODEL_SPECIALIST: z.string().default('gpt-4o-mini'),
|
||||
REVIEW_MODEL_JUDGE: z.string().default('gpt-4o-mini'),
|
||||
REVIEW_MAX_PARALLEL_RUNS: z.coerce.number().int().min(1).max(8).default(2),
|
||||
REVIEW_MAX_FILES_PER_RUN: z.coerce.number().int().min(1).max(1000).default(200),
|
||||
REVIEW_MAX_FILE_CONTENT_CHARS: z.coerce.number().int().min(1000).max(1_000_000).default(40_000),
|
||||
@@ -113,14 +106,6 @@ export interface AppConfig {
|
||||
apiUrl: string;
|
||||
accessToken: string;
|
||||
};
|
||||
openai: {
|
||||
baseUrl: string;
|
||||
apiKey: string;
|
||||
model: string;
|
||||
customSummaryPrompt: string | undefined;
|
||||
customLineCommentPrompt: string | undefined;
|
||||
globalPrompt: string | undefined;
|
||||
};
|
||||
feishu: {
|
||||
webhookUrl: string | undefined;
|
||||
webhookSecret: string | undefined;
|
||||
@@ -137,9 +122,9 @@ export interface AppConfig {
|
||||
review: {
|
||||
engine: string;
|
||||
workdir: string;
|
||||
modelPlanner: string;
|
||||
modelSpecialist: string;
|
||||
modelJudge: string;
|
||||
customSummaryPrompt: string | undefined;
|
||||
customLineCommentPrompt: string | undefined;
|
||||
globalPrompt: string | undefined;
|
||||
maxParallelRuns: number;
|
||||
maxFilesPerRun: number;
|
||||
maxFileContentChars: number;
|
||||
@@ -267,14 +252,6 @@ class ConfigManager {
|
||||
apiUrl: env.GITEA_API_URL,
|
||||
accessToken: env.GITEA_ACCESS_TOKEN,
|
||||
},
|
||||
openai: {
|
||||
baseUrl: env.OPENAI_BASE_URL,
|
||||
apiKey: env.OPENAI_API_KEY,
|
||||
model: env.OPENAI_MODEL,
|
||||
customSummaryPrompt: env.CUSTOM_SUMMARY_PROMPT,
|
||||
customLineCommentPrompt: env.CUSTOM_LINE_COMMENT_PROMPT,
|
||||
globalPrompt: env.GLOBAL_PROMPT,
|
||||
},
|
||||
feishu: {
|
||||
webhookUrl: env.FEISHU_WEBHOOK_URL,
|
||||
webhookSecret: env.FEISHU_WEBHOOK_SECRET,
|
||||
@@ -291,9 +268,9 @@ class ConfigManager {
|
||||
review: {
|
||||
engine: env.REVIEW_ENGINE,
|
||||
workdir: env.REVIEW_WORKDIR,
|
||||
modelPlanner: env.REVIEW_MODEL_PLANNER,
|
||||
modelSpecialist: env.REVIEW_MODEL_SPECIALIST,
|
||||
modelJudge: env.REVIEW_MODEL_JUDGE,
|
||||
customSummaryPrompt: env.CUSTOM_SUMMARY_PROMPT,
|
||||
customLineCommentPrompt: env.CUSTOM_LINE_COMMENT_PROMPT,
|
||||
globalPrompt: env.GLOBAL_PROMPT,
|
||||
maxParallelRuns: env.REVIEW_MAX_PARALLEL_RUNS,
|
||||
maxFilesPerRun: env.REVIEW_MAX_FILES_PER_RUN,
|
||||
maxFileContentChars: env.REVIEW_MAX_FILE_CONTENT_CHARS,
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
// Types
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export type ConfigGroup = 'gitea' | 'openai' | 'feishu' | 'app' | 'admin' | 'review' | 'memory';
|
||||
export type ConfigGroup = 'gitea' | 'feishu' | 'app' | 'admin' | 'review' | 'memory';
|
||||
|
||||
export type ConfigFieldType = 'string' | 'number' | 'boolean' | 'url' | 'text' | 'enum';
|
||||
|
||||
@@ -44,12 +44,6 @@ export const CONFIG_GROUPS: ConfigGroupMeta[] = [
|
||||
description: 'Gitea 实例地址与访问令牌',
|
||||
icon: 'link',
|
||||
},
|
||||
{
|
||||
key: 'openai',
|
||||
label: 'OpenAI / LLM',
|
||||
description: 'AI 模型接口与自定义提示词',
|
||||
icon: 'bot',
|
||||
},
|
||||
{
|
||||
key: 'feishu',
|
||||
label: '飞书通知',
|
||||
@@ -115,37 +109,9 @@ export const CONFIG_FIELDS: ConfigFieldMeta[] = [
|
||||
sensitive: true,
|
||||
},
|
||||
|
||||
// ── OpenAI ──────────────────────────────────────────────────────────────
|
||||
{
|
||||
envKey: 'OPENAI_BASE_URL',
|
||||
group: 'openai',
|
||||
label: 'API 地址',
|
||||
description: 'OpenAI 兼容 API 的基础 URL',
|
||||
type: 'url',
|
||||
sensitive: false,
|
||||
defaultValue: 'https://api.openai.com/v1',
|
||||
},
|
||||
{
|
||||
envKey: 'OPENAI_API_KEY',
|
||||
group: 'openai',
|
||||
label: 'API 密钥',
|
||||
description: 'OpenAI API 密钥',
|
||||
type: 'string',
|
||||
sensitive: true,
|
||||
defaultValue: 'test_openai_key',
|
||||
},
|
||||
{
|
||||
envKey: 'OPENAI_MODEL',
|
||||
group: 'openai',
|
||||
label: '模型',
|
||||
description: '默认使用的 OpenAI 模型名称',
|
||||
type: 'string',
|
||||
sensitive: false,
|
||||
defaultValue: 'gpt-4o-mini',
|
||||
},
|
||||
{
|
||||
envKey: 'CUSTOM_SUMMARY_PROMPT',
|
||||
group: 'openai',
|
||||
group: 'review',
|
||||
label: '自定义总结提示词',
|
||||
description: '覆盖默认的代码审查总结提示词(留空使用内置提示词)',
|
||||
type: 'text',
|
||||
@@ -153,7 +119,7 @@ export const CONFIG_FIELDS: ConfigFieldMeta[] = [
|
||||
},
|
||||
{
|
||||
envKey: 'CUSTOM_LINE_COMMENT_PROMPT',
|
||||
group: 'openai',
|
||||
group: 'review',
|
||||
label: '自定义行评论提示词',
|
||||
description: '覆盖默认的行级评论提示词(留空使用内置提示词)',
|
||||
type: 'text',
|
||||
@@ -161,7 +127,7 @@ export const CONFIG_FIELDS: ConfigFieldMeta[] = [
|
||||
},
|
||||
{
|
||||
envKey: 'GLOBAL_PROMPT',
|
||||
group: 'openai',
|
||||
group: 'review',
|
||||
label: '全局提示词',
|
||||
description: '附加到所有 LLM 调用的系统提示词中(例如:"请始终使用中文回复")',
|
||||
type: 'text',
|
||||
@@ -251,33 +217,6 @@ export const CONFIG_FIELDS: ConfigFieldMeta[] = [
|
||||
sensitive: false,
|
||||
defaultValue: '/tmp/gitea-assistant',
|
||||
},
|
||||
{
|
||||
envKey: 'REVIEW_MODEL_PLANNER',
|
||||
group: 'review',
|
||||
label: '规划模型',
|
||||
description: 'Agent 模式下规划阶段使用的模型',
|
||||
type: 'string',
|
||||
sensitive: false,
|
||||
defaultValue: 'gpt-4o-mini',
|
||||
},
|
||||
{
|
||||
envKey: 'REVIEW_MODEL_SPECIALIST',
|
||||
group: 'review',
|
||||
label: '专家模型',
|
||||
description: 'Agent 模式下专家子代理使用的模型',
|
||||
type: 'string',
|
||||
sensitive: false,
|
||||
defaultValue: 'gpt-4o-mini',
|
||||
},
|
||||
{
|
||||
envKey: 'REVIEW_MODEL_JUDGE',
|
||||
group: 'review',
|
||||
label: '评审模型',
|
||||
description: 'Agent 模式下 Judge 聚合阶段使用的模型',
|
||||
type: 'string',
|
||||
sensitive: false,
|
||||
defaultValue: 'gpt-4o-mini',
|
||||
},
|
||||
{
|
||||
envKey: 'REVIEW_MAX_PARALLEL_RUNS',
|
||||
group: 'review',
|
||||
|
||||
@@ -39,19 +39,13 @@ function getEffectiveValue(
|
||||
return current.gitea.accessToken;
|
||||
case 'GITEA_ADMIN_TOKEN':
|
||||
return current.admin.giteaAdminToken;
|
||||
// OpenAI
|
||||
case 'OPENAI_BASE_URL':
|
||||
return current.openai.baseUrl;
|
||||
case 'OPENAI_API_KEY':
|
||||
return current.openai.apiKey;
|
||||
case 'OPENAI_MODEL':
|
||||
return current.openai.model;
|
||||
// Review prompts (moved from OpenAI group)
|
||||
case 'CUSTOM_SUMMARY_PROMPT':
|
||||
return current.openai.customSummaryPrompt;
|
||||
return current.review.customSummaryPrompt;
|
||||
case 'CUSTOM_LINE_COMMENT_PROMPT':
|
||||
return current.openai.customLineCommentPrompt;
|
||||
return current.review.customLineCommentPrompt;
|
||||
case 'GLOBAL_PROMPT':
|
||||
return current.openai.globalPrompt;
|
||||
return current.review.globalPrompt;
|
||||
// Feishu
|
||||
case 'FEISHU_WEBHOOK_URL':
|
||||
return current.feishu.webhookUrl;
|
||||
@@ -72,12 +66,6 @@ function getEffectiveValue(
|
||||
return current.review.engine;
|
||||
case 'REVIEW_WORKDIR':
|
||||
return current.review.workdir;
|
||||
case 'REVIEW_MODEL_PLANNER':
|
||||
return current.review.modelPlanner;
|
||||
case 'REVIEW_MODEL_SPECIALIST':
|
||||
return current.review.modelSpecialist;
|
||||
case 'REVIEW_MODEL_JUDGE':
|
||||
return current.review.modelJudge;
|
||||
case 'REVIEW_MAX_PARALLEL_RUNS':
|
||||
return current.review.maxParallelRuns;
|
||||
case 'REVIEW_MAX_FILES_PER_RUN':
|
||||
|
||||
Reference in New Issue
Block a user