[codex] HTTP-first Codex upstream websocket settings (#280)
* http-first codex upstream websocket settings * address websocket runtime review comments --------- Co-authored-by: apple <apple@appledeMacBook-Pro.local>
This commit is contained in:
@@ -52,6 +52,9 @@ describe('settings and auth events', () => {
|
||||
config.logCleanupUsageLogsEnabled = false;
|
||||
config.logCleanupProgramLogsEnabled = false;
|
||||
config.logCleanupRetentionDays = 30;
|
||||
config.codexUpstreamWebsocketEnabled = false;
|
||||
config.proxySessionChannelConcurrencyLimit = 2;
|
||||
config.proxySessionChannelQueueWaitMs = 1500;
|
||||
config.routingFallbackUnitCost = 1;
|
||||
(config as any).telegramEnabled = false;
|
||||
(config as any).telegramApiBaseUrl = 'https://api.telegram.org';
|
||||
@@ -111,6 +114,38 @@ describe('settings and auth events', () => {
|
||||
expect(savedInterval?.value).toBe(JSON.stringify(8));
|
||||
});
|
||||
|
||||
it('persists codex upstream websocket and session lease settings from runtime settings', async () => {
|
||||
const updateResponse = await app.inject({
|
||||
method: 'PUT',
|
||||
url: '/api/settings/runtime',
|
||||
payload: {
|
||||
codexUpstreamWebsocketEnabled: true,
|
||||
proxySessionChannelConcurrencyLimit: 6,
|
||||
proxySessionChannelQueueWaitMs: 4200,
|
||||
},
|
||||
});
|
||||
|
||||
expect(updateResponse.statusCode).toBe(200);
|
||||
const updated = updateResponse.json() as {
|
||||
codexUpstreamWebsocketEnabled?: boolean;
|
||||
proxySessionChannelConcurrencyLimit?: number;
|
||||
proxySessionChannelQueueWaitMs?: number;
|
||||
};
|
||||
expect(updated.codexUpstreamWebsocketEnabled).toBe(true);
|
||||
expect(updated.proxySessionChannelConcurrencyLimit).toBe(6);
|
||||
expect(updated.proxySessionChannelQueueWaitMs).toBe(4200);
|
||||
expect(config.codexUpstreamWebsocketEnabled).toBe(true);
|
||||
expect(config.proxySessionChannelConcurrencyLimit).toBe(6);
|
||||
expect(config.proxySessionChannelQueueWaitMs).toBe(4200);
|
||||
|
||||
const savedWebsocket = await db.select().from(schema.settings).where(eq(schema.settings.key, 'codex_upstream_websocket_enabled')).get();
|
||||
const savedConcurrency = await db.select().from(schema.settings).where(eq(schema.settings.key, 'proxy_session_channel_concurrency_limit')).get();
|
||||
const savedQueueWait = await db.select().from(schema.settings).where(eq(schema.settings.key, 'proxy_session_channel_queue_wait_ms')).get();
|
||||
expect(savedWebsocket?.value).toBe(JSON.stringify(true));
|
||||
expect(savedConcurrency?.value).toBe(JSON.stringify(6));
|
||||
expect(savedQueueWait?.value).toBe(JSON.stringify(4200));
|
||||
});
|
||||
|
||||
it('returns current recognized admin IP in runtime settings response', async () => {
|
||||
const response = await app.inject({
|
||||
method: 'GET',
|
||||
|
||||
@@ -38,6 +38,9 @@ type RoutingWeights = typeof config.routingWeights;
|
||||
interface RuntimeSettingsBody {
|
||||
proxyToken?: string;
|
||||
systemProxyUrl?: string;
|
||||
codexUpstreamWebsocketEnabled?: boolean;
|
||||
proxySessionChannelConcurrencyLimit?: number;
|
||||
proxySessionChannelQueueWaitMs?: number;
|
||||
checkinCron?: string;
|
||||
checkinScheduleMode?: 'cron' | 'interval';
|
||||
checkinIntervalHours?: number;
|
||||
@@ -381,6 +384,11 @@ function applyImportedSettingToRuntime(key: string, value: unknown) {
|
||||
config.systemProxyUrl = normalizeSiteProxyUrl(value) || '';
|
||||
return;
|
||||
}
|
||||
case 'codex_upstream_websocket_enabled': {
|
||||
if (typeof value !== 'boolean') return;
|
||||
config.codexUpstreamWebsocketEnabled = value;
|
||||
return;
|
||||
}
|
||||
case 'proxy_error_keywords': {
|
||||
try {
|
||||
config.proxyErrorKeywords = parseProxyErrorKeywords(value);
|
||||
@@ -389,6 +397,18 @@ function applyImportedSettingToRuntime(key: string, value: unknown) {
|
||||
}
|
||||
return;
|
||||
}
|
||||
case 'proxy_session_channel_concurrency_limit': {
|
||||
const limit = Number(value);
|
||||
if (!Number.isFinite(limit) || limit < 0) return;
|
||||
config.proxySessionChannelConcurrencyLimit = Math.trunc(limit);
|
||||
return;
|
||||
}
|
||||
case 'proxy_session_channel_queue_wait_ms': {
|
||||
const queueWaitMs = Number(value);
|
||||
if (!Number.isFinite(queueWaitMs) || queueWaitMs < 0) return;
|
||||
config.proxySessionChannelQueueWaitMs = Math.trunc(queueWaitMs);
|
||||
return;
|
||||
}
|
||||
case 'proxy_empty_content_fail_enabled': {
|
||||
try {
|
||||
config.proxyEmptyContentFailEnabled = parseBooleanFlag(value, '空内容判定失败开关');
|
||||
@@ -557,6 +577,9 @@ function getRuntimeSettingsResponse(currentAdminIp = '') {
|
||||
logCleanupUsageLogsEnabled: config.logCleanupUsageLogsEnabled,
|
||||
logCleanupProgramLogsEnabled: config.logCleanupProgramLogsEnabled,
|
||||
logCleanupRetentionDays: config.logCleanupRetentionDays,
|
||||
codexUpstreamWebsocketEnabled: config.codexUpstreamWebsocketEnabled,
|
||||
proxySessionChannelConcurrencyLimit: config.proxySessionChannelConcurrencyLimit,
|
||||
proxySessionChannelQueueWaitMs: config.proxySessionChannelQueueWaitMs,
|
||||
routingFallbackUnitCost: config.routingFallbackUnitCost,
|
||||
routingWeights: config.routingWeights,
|
||||
webhookUrl: config.webhookUrl,
|
||||
@@ -940,6 +963,50 @@ export async function settingsRoutes(app: FastifyInstance) {
|
||||
invalidateSiteProxyCache();
|
||||
}
|
||||
|
||||
if (body.codexUpstreamWebsocketEnabled !== undefined) {
|
||||
let nextValue = false;
|
||||
try {
|
||||
nextValue = parseBooleanFlag(body.codexUpstreamWebsocketEnabled, 'Codex 上游 WebSocket 开关');
|
||||
} catch (err: any) {
|
||||
return reply.code(400).send({
|
||||
success: false,
|
||||
message: err?.message || 'Codex 上游 WebSocket 开关格式无效',
|
||||
});
|
||||
}
|
||||
|
||||
if (nextValue !== config.codexUpstreamWebsocketEnabled) {
|
||||
changedLabels.push('Codex 上游 WebSocket 默认策略');
|
||||
}
|
||||
config.codexUpstreamWebsocketEnabled = nextValue;
|
||||
upsertSetting('codex_upstream_websocket_enabled', config.codexUpstreamWebsocketEnabled);
|
||||
}
|
||||
|
||||
if (body.proxySessionChannelConcurrencyLimit !== undefined) {
|
||||
const limit = Number(body.proxySessionChannelConcurrencyLimit);
|
||||
if (!Number.isFinite(limit) || limit < 0) {
|
||||
return reply.code(400).send({ success: false, message: '会话通道并发上限必须是大于等于 0 的整数' });
|
||||
}
|
||||
const nextLimit = Math.trunc(limit);
|
||||
if (nextLimit !== config.proxySessionChannelConcurrencyLimit) {
|
||||
changedLabels.push(`会话通道并发上限(${config.proxySessionChannelConcurrencyLimit} -> ${nextLimit})`);
|
||||
}
|
||||
config.proxySessionChannelConcurrencyLimit = nextLimit;
|
||||
upsertSetting('proxy_session_channel_concurrency_limit', config.proxySessionChannelConcurrencyLimit);
|
||||
}
|
||||
|
||||
if (body.proxySessionChannelQueueWaitMs !== undefined) {
|
||||
const rawQueueWaitMs = Number(body.proxySessionChannelQueueWaitMs);
|
||||
if (!Number.isFinite(rawQueueWaitMs) || rawQueueWaitMs < 0) {
|
||||
return reply.code(400).send({ success: false, message: '会话通道排队等待时间必须是大于等于 0 的整数毫秒' });
|
||||
}
|
||||
const nextQueueWaitMs = Math.trunc(rawQueueWaitMs);
|
||||
if (nextQueueWaitMs !== config.proxySessionChannelQueueWaitMs) {
|
||||
changedLabels.push(`会话通道排队等待(${config.proxySessionChannelQueueWaitMs}ms -> ${nextQueueWaitMs}ms)`);
|
||||
}
|
||||
config.proxySessionChannelQueueWaitMs = nextQueueWaitMs;
|
||||
upsertSetting('proxy_session_channel_queue_wait_ms', config.proxySessionChannelQueueWaitMs);
|
||||
}
|
||||
|
||||
if (body.proxyErrorKeywords !== undefined) {
|
||||
let nextKeywords: string[] = [];
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user