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:
jeffusion
2026-03-05 00:32:16 +08:00
committed by 路遥知码力
parent 0bb6cf7849
commit 984cf734fe
4 changed files with 36 additions and 138 deletions

View File

@@ -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');
});
});

View File

@@ -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,

View File

@@ -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',

View File

@@ -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':