{ pkgs, lib, config, ... }: let cfg = config.mods.server.hermes; auth2apiCfg = config.mods.server.auth2api; # auth2api exposes an OpenAI-compatible endpoint; point Hermes at it. auth2apiUrl = "http://${auth2apiCfg.host}:${toString auth2apiCfg.port}/v1"; in with lib; { options.mods.server.hermes = { enable = mkEnableOption "Hermes AI agent (NousResearch)"; model = mkOption { type = types.str; default = "claude-sonnet-4-6"; description = '' Model identifier as exposed by auth2api. Available aliases: sonnet, haiku, opus, claude-sonnet-4-6, claude-haiku-4-5, claude-opus-4-6, etc. Run: curl http://127.0.0.1:8317/v1/models ''; }; container = { enable = mkOption { type = types.bool; default = true; description = "Run Hermes inside a persistent Ubuntu container."; }; hostUsers = mkOption { type = types.listOf types.str; default = [ config.mods.user.name ]; description = '' Users that get a ~/.hermes symlink to the service stateDir and are added to the hermes group for shared file access. ''; }; extraVolumes = mkOption { type = types.listOf types.str; default = [ ]; description = "Extra volume mounts (host:container:mode) for the container."; }; }; addToSystemPackages = mkOption { type = types.bool; default = true; description = '' Put the hermes CLI on the system PATH and set HERMES_HOME so the interactive CLI shares state with the gateway service. ''; }; extraPackages = mkOption { type = types.listOf types.package; default = [ ]; description = "Extra Nix packages available to the agent at runtime."; }; stateDir = mkOption { type = types.str; default = "/var/lib/hermes"; description = "State directory (HERMES_HOME parent)."; }; }; config = mkIf cfg.enable { # Hermes needs auth2api running to have a backend to call. assertions = [ { assertion = auth2apiCfg.enable; message = "mods.server.hermes requires mods.server.auth2api.enable = true"; } ]; services.hermes-agent = { enable = true; inherit (cfg) stateDir addToSystemPackages extraPackages; # auth2api runs unauthenticated on localhost (api-keys: []), but Hermes's # provider resolver requires a non-empty key env var for any provider. # AUTH2API_KEY is a dummy — auth2api ignores Bearer tokens when api-keys: []. environment.AUTH2API_KEY = "auth2api"; # ── Model ────────────────────────────────────────────────────────────── # Declare auth2api as a named custom provider in the providers: section. # Hermes resolves it via resolve_user_provider, which accepts arbitrary # base_url + key_env without needing a built-in provider slug. settings = { providers.auth2api = { name = "auth2api (Claude via OAuth)"; api = auth2apiUrl; key_env = "AUTH2API_KEY"; transport = "openai_chat"; }; model = { default = cfg.model; provider = "auth2api"; }; toolsets = [ "all" ]; max_turns = 100; terminal = { backend = "local"; timeout = 180; }; memory = { memory_enabled = true; user_profile_enabled = true; }; display = { compact = false; show_reasoning = true; }; }; # ── Container ────────────────────────────────────────────────────────── container = mkIf cfg.container.enable { enable = true; backend = "docker"; inherit (cfg.container) hostUsers extraVolumes; }; }; }; }