molecule-ai-workspace-templ.../scripts/derive-provider.sh
Hongming Wang ae3bf3196e fix(hermes): align env vars with upstream + add 12 missing providers
Three small but real cleanups against hermes-agent v0.12.0
(NousResearch/hermes-agent, 2026-04-30):

1. Rename HERMES_DEFAULT_MODEL -> HERMES_INFERENCE_MODEL (upstream's
   actual env name). Reads BOTH for one release cycle so workspace-server
   (which still writes the legacy name) doesn't break — drop the legacy
   fallback after workspace-server is updated in a follow-up PR.

2. Drop HERMES_API_KEY from start.sh's .env heredoc. That var only feeds
   hermes-agent's TUI gateway bridge, NOT any LLM provider. Provider
   credentials go through OPENROUTER_API_KEY / OPENAI_API_KEY / etc.

3. Add 12 missing provider prefixes to derive-provider.sh so model slugs
   like xai/grok-4, bedrock/anthropic.claude-sonnet-4, lmstudio/local,
   copilot/gpt-4o, etc., route to the correct provider instead of
   falling through to "auto".

New tests/test_derive_provider.sh — 26 sh-style assertions covering the
legacy fallback, the precedence rule, all 12 new providers, and a few
regression cases for adjacent prefixes (minimax vs minimax-oauth, qwen
vs qwen-oauth, alibaba vs alibaba-coding-plan).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 19:23:43 -07:00

133 lines
6.0 KiB
Bash
Executable File

#!/usr/bin/env bash
# derive-provider.sh — map a hermes-agent model slug to its provider
# name. Sourced by both install.sh (SaaS bare-host path) and start.sh
# (Docker path) so the two entry-points stay consistent.
#
# Contract:
# Reads: $HERMES_INFERENCE_PROVIDER (if already set, we respect it)
# $HERMES_INFERENCE_MODEL (preferred — matches upstream env name)
# $HERMES_DEFAULT_MODEL (legacy fallback — name we invented before
# 2026-05; workspace-server still writes
# it during the migration window)
# $HERMES_API_KEY / $NOUS_API_KEY (affect the nousresearch/* branch)
# Writes: $PROVIDER — the derived provider name, or "auto" if unknown.
#
# Upstream's actual env var is $HERMES_INFERENCE_MODEL (see
# website/docs/reference/environment-variables.md in NousResearch/hermes-agent).
# We accept both for one release cycle so workspaces booting under the legacy
# control-plane don't break — drop $HERMES_DEFAULT_MODEL once workspace-server
# is updated to write the upstream name.
#
# Why the per-template sub-script (vs doing this in CP): every runtime
# has its own provider taxonomy. Keeping the logic inside the template
# repo means CP stays runtime-agnostic and adding a new runtime with
# different provider semantics doesn't require a CP edit.
#
# Hermes-specific quirks encoded here:
# - `openai/...` routes through `openrouter` (hermes has no direct
# openai provider; openai-codex is OAuth-only for Codex models)
# - `nousresearch/...` prefers direct `nous` if HERMES_API_KEY is
# set, else falls back to `openrouter` (which also serves Hermes 3)
# - chinese-region variants (minimax-cn, kimi-coding-cn) keep their
# full prefix as the provider name
#
# See molecule-controlplane/docs/canary-tenants.md and the hermes-agent
# providers.md docs for the full taxonomy.
# Honour an explicit override.
if [ -n "${HERMES_INFERENCE_PROVIDER:-}" ]; then
PROVIDER="${HERMES_INFERENCE_PROVIDER}"
return 0 2>/dev/null || exit 0
fi
# Resolve the model slug — prefer the upstream env name, fall back to legacy.
_HERMES_MODEL="${HERMES_INFERENCE_MODEL:-${HERMES_DEFAULT_MODEL:-}}"
if [ -z "${_HERMES_MODEL}" ]; then
PROVIDER="auto"
return 0 2>/dev/null || exit 0
fi
case "${_HERMES_MODEL}" in
# Keep full CN-suffix as provider so chinese-region keys route right
minimax-cn/*) PROVIDER="minimax-cn" ;;
kimi-coding-cn/*) PROVIDER="kimi-coding-cn" ;;
# Direct-SDK providers (clean 1:1 prefix→provider mapping)
minimax/*) PROVIDER="minimax" ;;
anthropic/*) PROVIDER="anthropic" ;;
gemini/*) PROVIDER="gemini" ;;
deepseek/*) PROVIDER="deepseek" ;;
zai/*) PROVIDER="zai" ;;
kimi-coding/*) PROVIDER="kimi-coding" ;;
alibaba/*|dashscope/*|qwen/*) PROVIDER="alibaba" ;;
xiaomi/*|mimo/*) PROVIDER="xiaomi" ;;
arcee/*|arcee-ai/*) PROVIDER="arcee" ;;
nvidia/*|nim/*) PROVIDER="nvidia" ;;
ollama-cloud/*) PROVIDER="ollama-cloud" ;;
huggingface/*|hf/*) PROVIDER="huggingface" ;;
ai-gateway/*|aigateway/*) PROVIDER="ai-gateway" ;;
kilocode/*) PROVIDER="kilocode" ;;
opencode-zen/*) PROVIDER="opencode-zen" ;;
opencode-go/*) PROVIDER="opencode-go" ;;
# Hermes-specific routing quirks. `openai/*` has two valid targets:
# 1. hermes's "custom" provider pointed at api.openai.com — requires
# OPENAI_API_KEY. install.sh sees this case and auto-populates
# HERMES_CUSTOM_{BASE_URL,API_KEY} so the direct-OpenAI path works
# without the user having to set HERMES_CUSTOM_* explicitly.
# 2. OpenRouter (hermes's built-in path — requires OPENROUTER_API_KEY).
#
# Priority: prefer **custom** (direct OpenAI) when OPENAI_API_KEY is set.
# The operator supplying OPENAI_API_KEY for an openai/* model is an
# explicit intent signal to hit OpenAI directly. The previous "prefer
# OR if any OR key exists" rule silently hijacked that intent whenever
# a tenant-global OPENROUTER_API_KEY was present (even if stale/empty
# enough to 401), which is exactly what bit the 2026-04-23 E2E (surfaced
# as OpenRouter's `401 Missing Authentication header` in the agent reply).
#
# To explicitly route openai/* through OR, set HERMES_INFERENCE_PROVIDER=openrouter
# (handled at the top of this file) or use an openrouter/* model slug.
openai/*)
if [ -n "${OPENAI_API_KEY:-}" ]; then
PROVIDER="custom"
elif [ -n "${OPENROUTER_API_KEY:-}" ]; then
PROVIDER="openrouter"
else
PROVIDER="openrouter" # no-key fallback — hermes will error clearly
fi
;;
nousresearch/*)
# Prefer direct Nous Portal if Nous credentials present, else OR.
if [ -n "${HERMES_API_KEY:-}" ] || [ -n "${NOUS_API_KEY:-}" ]; then
PROVIDER="nous"
else
PROVIDER="openrouter"
fi
;;
# Explicit catch-alls
openrouter/*) PROVIDER="openrouter" ;;
custom/*) PROVIDER="custom" ;;
# Additional 1:1 prefix→provider mappings — kept aligned with upstream's
# HERMES_INFERENCE_PROVIDER list (website/docs/reference/environment-variables.md
# in NousResearch/hermes-agent, v0.12.0 / 2026-04-30). Place these BEFORE the
# catch-all so they win.
xai/*|grok/*) PROVIDER="xai" ;;
bedrock/*|aws/*) PROVIDER="bedrock" ;;
tencent/*|tencent-tokenhub/*) PROVIDER="tencent-tokenhub" ;;
gmi/*) PROVIDER="gmi" ;;
qwen-oauth/*) PROVIDER="qwen-oauth" ;;
lmstudio/*|lm-studio/*) PROVIDER="lmstudio" ;;
minimax-oauth/*) PROVIDER="minimax-oauth" ;;
alibaba-coding-plan/*) PROVIDER="alibaba-coding-plan" ;;
google-gemini-cli/*) PROVIDER="google-gemini-cli" ;;
openai-codex/*) PROVIDER="openai-codex" ;;
copilot-acp/*) PROVIDER="copilot-acp" ;;
copilot/*) PROVIDER="copilot" ;;
# Unknown prefix → let hermes auto-detect
*) PROVIDER="auto" ;;
esac