diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 62d50cd0c1e..2535c1e4502 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -24,7 +24,7 @@ jobs: PORT: "3100" PAPERCLIP_API_URL: http://127.0.0.1:3100 PAPERCLIP_AUTH_HEADER: Bearer local-trusted-hermes-smoke - OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }} + OPENROUTER_API_KEY: ${{ secrets.PAP11985_OPENROUTER_API_KEY }} OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }} @@ -39,6 +39,9 @@ jobs: PAPERCLIP_API_URL_FOR_HERMES: http://127.0.0.1:3100 HERMES_SMOKE_DIAG_DIR: /tmp/hermes-gateway-diag-${{ github.run_id }}-${{ github.run_attempt }} HERMES_SMOKE_STATE_DIR: /tmp/hermes-gateway-state-${{ github.run_id }}-${{ github.run_attempt }} + HERMES_SMOKE_MODEL_PROVIDER: openrouter + HERMES_SMOKE_MODEL_DEFAULT: z-ai/glm-5.2 + HERMES_SMOKE_MODEL_BASE_URL: https://openrouter.ai/api/v1 RUN_TIMEOUT_SEC: "420" CASE_TIMEOUT_SEC: "420" diff --git a/doc/HERMES_GATEWAY_SMOKE.md b/doc/HERMES_GATEWAY_SMOKE.md index 09bf875b0a4..ba00cab9c64 100644 --- a/doc/HERMES_GATEWAY_SMOKE.md +++ b/doc/HERMES_GATEWAY_SMOKE.md @@ -26,6 +26,20 @@ and the non-loopback HTTP guard. `OPENROUTER_API_KEY`, `OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, `GEMINI_API_KEY`, `GOOGLE_API_KEY`, and `MISTRAL_API_KEY`, and logs only the provider env var names. +- To pin the fresh Hermes container to a known non-secret model config, set + `HERMES_SMOKE_MODEL_PROVIDER`, `HERMES_SMOKE_MODEL_DEFAULT`, and optionally + `HERMES_SMOKE_MODEL_BASE_URL`. For example, OpenRouter GLM: + + ```sh + HERMES_SMOKE_MODEL_PROVIDER=openrouter \ + HERMES_SMOKE_MODEL_DEFAULT=z-ai/glm-5.2 \ + HERMES_SMOKE_MODEL_BASE_URL=https://openrouter.ai/api/v1 \ + pnpm smoke:hermes-gateway-e2e + ``` + + This writes only `model` and `providers: {}` into the temporary Hermes home; + provider keys still come from environment variables and are redacted from + diagnostics. - Board/operator auth is required through `PAPERCLIP_AUTH_HEADER`, `PAPERCLIP_COOKIE`, or a board-capable `PAPERCLIP_API_KEY`. - Diagnostic files are redacted before they are written, except the join output diff --git a/scripts/smoke/hermes-gateway-e2e.sh b/scripts/smoke/hermes-gateway-e2e.sh index 1087b82ee74..0702bd4273c 100755 --- a/scripts/smoke/hermes-gateway-e2e.sh +++ b/scripts/smoke/hermes-gateway-e2e.sh @@ -48,6 +48,9 @@ HERMES_SMOKE_NETWORK="${HERMES_SMOKE_NETWORK:-}" HERMES_DOCKER_ADD_HOST="${HERMES_DOCKER_ADD_HOST:-1}" HERMES_SMOKE_STATE_DIR="${HERMES_SMOKE_STATE_DIR:-${TMPDIR:-/tmp}/paperclip-hermes-gateway-smoke-${RUN_SUFFIX}}" HERMES_SMOKE_DIAG_DIR="${HERMES_SMOKE_DIAG_DIR:-${TMPDIR:-/tmp}/paperclip-hermes-gateway-e2e-diag-${RUN_SUFFIX}}" +HERMES_SMOKE_MODEL_PROVIDER="${HERMES_SMOKE_MODEL_PROVIDER:-}" +HERMES_SMOKE_MODEL_DEFAULT="${HERMES_SMOKE_MODEL_DEFAULT:-}" +HERMES_SMOKE_MODEL_BASE_URL="${HERMES_SMOKE_MODEL_BASE_URL:-}" HERMES_AGENT_NAME="${HERMES_AGENT_NAME:-Hermes Gateway Smoke Agent ${RUN_SUFFIX}}" PAPERCLIP_API_URL_FOR_HERMES="${PAPERCLIP_API_URL_FOR_HERMES:-http://host.docker.internal:3100}" RUN_TIMEOUT_SEC="${RUN_TIMEOUT_SEC:-420}" @@ -89,6 +92,9 @@ Common flags: HERMES_DOCKER_ADD_HOST=0|1 HERMES_SMOKE_KEEP=1 # keep diagnostics/container HERMES_SMOKE_DIAG_DIR=/tmp/hermes-gateway-diag + HERMES_SMOKE_MODEL_PROVIDER=openrouter + HERMES_SMOKE_MODEL_DEFAULT=z-ai/glm-5.2 + HERMES_SMOKE_MODEL_BASE_URL=https://openrouter.ai/api/v1 Mode notes: HERMES_GATEWAY_API_BASE_URL is the URL stored on the Paperclip adapter and @@ -487,6 +493,38 @@ prepare_fresh_state() { fi } +yaml_single_quote() { + local value="$1" + value="${value//\'/\'\'}" + printf "'%s'" "$value" +} + +write_hermes_model_config() { + if [[ -z "$HERMES_SMOKE_MODEL_PROVIDER" && -z "$HERMES_SMOKE_MODEL_DEFAULT" && -z "$HERMES_SMOKE_MODEL_BASE_URL" ]]; then + return + fi + if [[ -z "$HERMES_SMOKE_MODEL_PROVIDER" || -z "$HERMES_SMOKE_MODEL_DEFAULT" ]]; then + fail "HERMES_SMOKE_MODEL_PROVIDER and HERMES_SMOKE_MODEL_DEFAULT must be set together" + fi + + local config_file="${HERMES_SMOKE_STATE_DIR}/hermes-home/config.yaml" + if [[ -e "$config_file" ]]; then + fail "Hermes model config already exists in fresh state: ${config_file}" + fi + + { + echo "model:" + printf " default: %s\n" "$(yaml_single_quote "$HERMES_SMOKE_MODEL_DEFAULT")" + printf " provider: %s\n" "$(yaml_single_quote "$HERMES_SMOKE_MODEL_PROVIDER")" + if [[ -n "$HERMES_SMOKE_MODEL_BASE_URL" ]]; then + printf " base_url: %s\n" "$(yaml_single_quote "$HERMES_SMOKE_MODEL_BASE_URL")" + fi + echo "providers: {}" + } > "$config_file" + chmod 644 "$config_file" + log "seeded Hermes model config provider=${HERMES_SMOKE_MODEL_PROVIDER} model=${HERMES_SMOKE_MODEL_DEFAULT}" +} + start_container() { docker rm -f "$HERMES_CONTAINER_NAME" >/dev/null 2>&1 || true @@ -914,6 +952,7 @@ main() { resolve_company_id prepare_fresh_state + write_hermes_model_config build_image start_container assert_fresh_container_state diff --git a/scripts/smoke/hermes-gateway-smoke.test.mjs b/scripts/smoke/hermes-gateway-smoke.test.mjs index 04cc44144e8..cb36e94e620 100644 --- a/scripts/smoke/hermes-gateway-smoke.test.mjs +++ b/scripts/smoke/hermes-gateway-smoke.test.mjs @@ -65,9 +65,33 @@ test("Hermes gateway smoke help documents operator safety flags", () => { const e2eHelp = run("bash", [e2eScript, "--help"]).stdout; assert.match(e2eHelp, /HERMES_SMOKE_KEEP/); assert.match(e2eHelp, /HERMES_SMOKE_NETWORK/); + assert.match(e2eHelp, /HERMES_SMOKE_MODEL_DEFAULT/); assert.match(e2eHelp, /Docker/); }); +test("E2E helper can seed a minimal Hermes model config without secrets", () => { + const result = runBashFunctions( + e2eScript, + ["log", "fail", "yaml_single_quote", "write_hermes_model_config"], + ` +tmp="$(mktemp -d)" +trap 'rm -rf "$tmp"' EXIT +HERMES_SMOKE_STATE_DIR="$tmp" +HERMES_SMOKE_MODEL_PROVIDER="openrouter" +HERMES_SMOKE_MODEL_DEFAULT="z-ai/glm-5.2" +HERMES_SMOKE_MODEL_BASE_URL="https://openrouter.ai/api/v1" +mkdir -p "$HERMES_SMOKE_STATE_DIR/hermes-home" +write_hermes_model_config +config="$HERMES_SMOKE_STATE_DIR/hermes-home/config.yaml" +grep -Fq "default: 'z-ai/glm-5.2'" "$config" +grep -Fq "provider: 'openrouter'" "$config" +grep -Fq "base_url: 'https://openrouter.ai/api/v1'" "$config" +! grep -Eiq "api[_-]?key|token|secret" "$config" +`, + ); + assertSuccess(result, "write_hermes_model_config"); +}); + test("join helper redacts known secrets without exposing raw key material", () => { const result = runBashFunctions( joinScript,