From 7996c14795ef139e8d731e2a368dfe747eb01809 Mon Sep 17 00:00:00 2001 From: vominh1919 Date: Tue, 28 Apr 2026 09:36:19 +0700 Subject: [PATCH] fix: resolve model aliases during claw migrate (#16745) `hermes claw migrate` copied OpenClaw's model setting verbatim, which could be a display alias (e.g. "Claude Opus 4.6") instead of the actual API ID (e.g. "claude-opus-4-6"). Hermes then sent the alias to the API, causing HTTP 404 model not found. Fix: look up the model string in agents.defaults.models (plural) alias catalog. If found, use the resolved "id" field, prepending the provider prefix if needed. If not found (already an API ID), pass through unchanged. Fixes NousResearch/hermes-agent#16745 --- .../scripts/openclaw_to_hermes.py | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/optional-skills/migration/openclaw-migration/scripts/openclaw_to_hermes.py b/optional-skills/migration/openclaw-migration/scripts/openclaw_to_hermes.py index 39769d31f1..6882c00577 100644 --- a/optional-skills/migration/openclaw-migration/scripts/openclaw_to_hermes.py +++ b/optional-skills/migration/openclaw-migration/scripts/openclaw_to_hermes.py @@ -1671,6 +1671,29 @@ class Migrator: model_str = model_str.strip() + # Resolve a model alias against the OpenClaw model catalog. + # OpenClaw stores agents.defaults.model as either a bare string or + # {"primary": ""}, and that value can be either: + # - a full provider/model API ID (e.g. "anthropic/claude-opus-4-6"), or + # - a display alias (e.g. "Claude Opus 4.6") that maps to one. + # The catalog at agents.defaults.models is keyed by the full + # provider/model API ID with an "alias" field on the value, e.g.: + # {"anthropic/claude-opus-4-6": {"alias": "Claude Opus 4.6"}} + # If model_str matches an alias in the catalog, rewrite it to the + # catalog key (the real API ID). If it's already an API ID or has + # no catalog match, leave it alone and let downstream pass it through. + model_catalog = config.get("agents", {}).get("defaults", {}).get("models", {}) + if isinstance(model_catalog, dict) and model_str not in model_catalog: + for api_id, entry in model_catalog.items(): + if not isinstance(api_id, str): + continue + if isinstance(entry, dict) and entry.get("alias") == model_str: + model_str = api_id + break + if isinstance(entry, str) and entry == model_str: + model_str = api_id + break + if yaml is None: self.record("model-config", source_path, destination, "error", "PyYAML is not available") return