diff --git a/apps/desktop/src/app/settings/custom-endpoints-settings.tsx b/apps/desktop/src/app/settings/custom-endpoints-settings.tsx index b74b30a2909..b6f7514c513 100644 --- a/apps/desktop/src/app/settings/custom-endpoints-settings.tsx +++ b/apps/desktop/src/app/settings/custom-endpoints-settings.tsx @@ -32,6 +32,10 @@ interface EndpointForm { makeDefault: boolean model: string name: string + sshHost: string + sshKeyPath: string + sshPort: string + sshUser: string } const EMPTY_FORM: EndpointForm = { @@ -42,7 +46,11 @@ const EMPTY_FORM: EndpointForm = { id: '', makeDefault: true, model: '', - name: '' + name: '', + sshHost: '', + sshKeyPath: '', + sshPort: '', + sshUser: '' } function formFromEndpoint(endpoint: CustomEndpoint): EndpointForm { @@ -54,7 +62,11 @@ function formFromEndpoint(endpoint: CustomEndpoint): EndpointForm { id: endpoint.id, makeDefault: Boolean(endpoint.is_current), model: endpoint.model, - name: endpoint.name + name: endpoint.name, + sshHost: endpoint.ssh_tunnel?.host ?? '', + sshKeyPath: endpoint.ssh_tunnel?.key_path ?? '', + sshPort: endpoint.ssh_tunnel?.port ? String(endpoint.ssh_tunnel.port) : '', + sshUser: endpoint.ssh_tunnel?.user ?? '' } } @@ -70,7 +82,15 @@ function toPayload(form: EndpointForm, models?: string[]): CustomEndpointUpdate context_length: Number.isFinite(contextLength) && contextLength > 0 ? contextLength : undefined, discover_models: form.discoverModels, make_default: form.makeDefault, - models: models?.length ? models : undefined + models: models?.length ? models : undefined, + ssh_tunnel: form.sshHost.trim() + ? { + host: form.sshHost.trim(), + key_path: form.sshKeyPath.trim() || undefined, + port: Number.parseInt(form.sshPort, 10) || undefined, + user: form.sshUser.trim() || undefined + } + : {} } } @@ -320,6 +340,46 @@ export function CustomEndpointsSettings({ onConfigSaved, onMainModelChanged }: C value={form.baseUrl} /> +
+
+ SSH tunnel (optional) — the endpoint URL is resolved from the remote host; Hermes chooses a private local port. +
+
+ + + + +
+