feat: multi-gateway profile support, provider management overhaul, and model settings tab
- Profile-aware proxy: inject API key from profile-specific .env, route requests via X-Hermes-Profile header - Remove auth.json dependency: built-in providers use .env, custom providers use config.yaml - Add allProviders field to available-models response with all hardcoded provider catalogs - Add Models tab in Settings for editing provider API keys (built-in → .env, custom → config.yaml) - Add PUT /api/config/providers/:poolKey for updating provider credentials - ProviderFormModal uses backend allProviders for preset dropdown - Gateway log format support: parse both agent and gateway log formats - Add webui server.log to log viewer with log rotation at 3MB - Fix provider delete loading state and OAuth provider cleanup - Setup script: require Node.js 23+, auto-upgrade if version too low Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -28,23 +28,26 @@ async function waitForGatewayReady(upstream: string, timeoutMs: number = 5000):
|
||||
return false
|
||||
}
|
||||
|
||||
/** Resolve profile name from request */
|
||||
function resolveProfile(ctx: Context): string {
|
||||
return ctx.get('x-hermes-profile') || (ctx.query.profile as string) || 'default'
|
||||
}
|
||||
|
||||
/** Resolve upstream URL for a request based on profile header/query */
|
||||
function resolveUpstream(ctx: Context): string {
|
||||
const mgr = getGatewayManager()
|
||||
if (mgr) {
|
||||
// Check X-Hermes-Profile header or ?profile= query param
|
||||
const profile = ctx.get('x-hermes-profile') || (ctx.query.profile as string)
|
||||
if (profile) {
|
||||
const profile = resolveProfile(ctx)
|
||||
if (profile && profile !== 'default') {
|
||||
return mgr.getUpstream(profile)
|
||||
}
|
||||
// Default to active profile's upstream
|
||||
return mgr.getUpstream()
|
||||
}
|
||||
// Fallback: static upstream from config
|
||||
return config.upstream.replace(/\/$/, '')
|
||||
}
|
||||
|
||||
export async function proxy(ctx: Context) {
|
||||
const profile = resolveProfile(ctx)
|
||||
const upstream = resolveUpstream(ctx)
|
||||
// Rewrite path for upstream gateway:
|
||||
// /api/hermes/v1/* -> /v1/* (upstream uses /v1/ prefix)
|
||||
@@ -59,7 +62,7 @@ export async function proxy(ctx: Context) {
|
||||
const lower = key.toLowerCase()
|
||||
if (lower === 'host') {
|
||||
headers['host'] = new URL(upstream).host
|
||||
} else if (lower === 'authorization' || lower === 'origin' || lower === 'referer' || lower === 'connection') {
|
||||
} else if (lower === 'origin' || lower === 'referer' || lower === 'connection') {
|
||||
continue
|
||||
} else {
|
||||
const v = Array.isArray(value) ? value[0] : value
|
||||
@@ -67,6 +70,15 @@ export async function proxy(ctx: Context) {
|
||||
}
|
||||
}
|
||||
|
||||
// Inject Hermes gateway API key from profile's .env
|
||||
const mgr = getGatewayManager()
|
||||
if (mgr) {
|
||||
const apiKey = mgr.getApiKey(profile)
|
||||
if (apiKey) {
|
||||
headers['authorization'] = `Bearer ${apiKey}`
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
// Build request body from raw body
|
||||
let body: string | undefined
|
||||
|
||||
Reference in New Issue
Block a user