{"repo":"NousResearch/hermes-agent","public_id":"8853","display_label":"Add StepFun and Step Plan provider support","summary":"These issues and pull requests concern adding StepFun and Step Plan as model providers. The work appears across multiple related submissions, including at least one salvage attempt of a prior PR.","subsystem":null,"counts":{"members":3,"open_issues":0,"open_issues_distinct":0,"open_prs":0,"closed_issues":1,"merged_prs":1,"attempt_prs":1},"landed_fix":{"status":"merged_to_main","basis":"structural","pr":13893,"merged_at":"2026-04-22T09:59:59+00:00"},"resolution":{"state":"unresolved","by":null},"freshness":{"state":"stale","last_verified_at":"2026-04-22T09:59:59+00:00","is_stale":true},"grouping":{"backbone":"STRUCTURAL_SPANNING","link_density":0.6666666666666666},"fix_edges":[{"issue_number":8853,"pr_number":13893,"tier":"verified","basis":"superseded_ref","confidence":0.9,"recoverable_from_timeline":false,"pr_merged_at":"2026-04-22T09:59:59+00:00"}],"members":[{"kind":"issue","number":8853,"title":"[Feature]:  Add StepFun / Step Plan model provider support","url":null,"state":"closed"},{"kind":"pr","number":8879,"title":"feat: Add StepFun provider support","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#13893","number":13893}],"category":"superseded_by_pr","implication":"superseded"},"merged":false},{"kind":"pr","number":13893,"title":"feat: add Step Plan provider support (salvage #6005)","url":null,"state":"merged","close_reason":{"category":"merged"},"merged":true,"is_fix_pr":true,"fix_tier":"verified","files_changed":["agent/auxiliary_client.py","agent/model_metadata.py","agent/models_dev.py","hermes_cli/auth.py","hermes_cli/config.py","hermes_cli/doctor.py","hermes_cli/main.py","hermes_cli/model_switch.py","hermes_cli/models.py","hermes_cli/providers.py","hermes_cli/setup.py","hermes_cli/status.py","tests/agent/test_model_metadata.py","tests/agent/test_models_dev.py","tests/hermes_cli/test_api_key_providers.py","tests/hermes_cli/test_model_provider_persistence.py","tests/hermes_cli/test_model_validation.py"],"fix_hunks":[{"file":"agent/auxiliary_client.py","hunks":["@@ -134,6 +134,7 @@ def _fixed_temperature_for_model(\n     \"gemini\": \"gemini-3-flash-preview\",\n     \"zai\": \"glm-4.5-flash\",\n     \"kimi-coding\": \"kimi-k2-turbo-preview\",\n+    \"stepfun\": \"step-3.5-flash\",\n     \"kimi-coding-cn\": \"kimi-k2-turbo-preview\",\n     \"minimax\": \"MiniMax-M2.7\",\n     \"minimax-cn\": \"MiniMax-M2.7\","]},{"file":"agent/model_metadata.py","hunks":["@@ -25,7 +25,7 @@\n # are preserved so the full model name reaches cache lookups and server queries.\n _PROVIDER_PREFIXES: frozenset[str] = frozenset({\n     \"openrouter\", \"nous\", \"openai-codex\", \"copilot\", \"copilot-acp\",\n-    \"gemini\", \"ollama-cloud\", \"zai\", \"kimi-coding\", \"kimi-coding-cn\", \"minimax\", \"minimax-cn\", \"anthropic\", \"deepseek\",\n+    \"gemini\", \"ollama-cloud\", \"zai\", \"kimi-coding\", \"kimi-coding-cn\", \"stepfun\", \"minimax\", \"minimax-cn\", \"anthropic\", \"deepseek\",\n     \"opencode-zen\", \"opencode-go\", \"ai-gateway\", \"kilocode\", \"alibaba\",\n     \"qwen-oauth\",\n     \"xiaomi\",","@@ -36,7 +36,7 @@\n     \"glm\", \"z-ai\", \"z.ai\", \"zhipu\", \"github\", \"github-copilot\",\n     \"github-models\", \"kimi\", \"moonshot\", \"kimi-cn\", \"moonshot-cn\", \"claude\", \"deep-seek\",\n     \"ollama\",\n-    \"opencode\", \"zen\", \"go\", \"vercel\", \"kilo\", \"dashscope\", \"aliyun\", \"qwen\",\n+    \"stepfun\", \"opencode\", \"zen\", \"go\", \"vercel\", \"kilo\", \"dashscope\", \"aliyun\", \"qwen\",\n     \"mimo\", \"xiaomi-mimo\",\n     \"arcee-ai\", \"arceeai\",\n     \"xai\", \"x-ai\", \"x.ai\", \"grok\",","@@ -237,6 +237,8 @@ def _is_custom_endpoint(base_url: str) -> bool:\n     \"api.moonshot.ai\": \"kimi-coding\",\n     \"api.moonshot.cn\": \"kimi-coding-cn\",\n     \"api.kimi.com\": \"kimi-coding\",\n+    \"api.stepfun.ai\": \"stepfun\",\n+    \"api.stepfun.com\": \"stepfun\",\n     \"api.arcee.ai\": \"arcee\",\n     \"api.minimax\": \"minimax\",\n     \"dashscope.aliyuncs.com\": \"alibaba\","]},{"file":"agent/models_dev.py","hunks":["@@ -146,6 +146,7 @@ class ProviderInfo:\n     \"openai-codex\": \"openai\",\n     \"zai\": \"zai\",\n     \"kimi-coding\": \"kimi-for-coding\",\n+    \"stepfun\": \"stepfun\",\n     \"kimi-coding-cn\": \"kimi-for-coding\",\n     \"minimax\": \"minimax\",\n     \"minimax-cn\": \"minimax-cn\","]},{"file":"hermes_cli/auth.py","hunks":["@@ -72,6 +72,8 @@\n DEFAULT_GITHUB_MODELS_BASE_URL = \"https://api.githubcopilot.com\"\n DEFAULT_COPILOT_ACP_BASE_URL = \"acp://copilot\"\n DEFAULT_OLLAMA_CLOUD_BASE_URL = \"https://ollama.com/v1\"\n+STEPFUN_STEP_PLAN_INTL_BASE_URL = \"https://api.stepfun.ai/step_plan/v1\"\n+STEPFUN_STEP_PLAN_CN_BASE_URL = \"https://api.stepfun.com/step_plan/v1\"\n CODEX_OAUTH_CLIENT_ID = \"app_EMoamEEZ73f0CkXaXp7hrann\"\n CODEX_OAUTH_TOKEN_URL = \"[redacted:secret-assignment]\"\n CODEX_ACCESS_TOKEN_REFRESH_SKEW_SECONDS = 120","@@ -182,6 +184,14 @@ class ProviderConfig:\n         inference_base_url=\"https://api.moonshot.cn/v1\",\n         api_key_env_vars=(\"KIMI_CN_API_KEY\",),\n     ),\n+    \"stepfun\": ProviderConfig(\n+        id=\"stepfun\",\n+        name=\"StepFun Step Plan\",\n+        auth_type=\"api_key\",\n+        inference_base_url=STEPFUN_STEP_PLAN_INTL_BASE_URL,\n+        api_key_env_vars=(\"STEPFUN_API_KEY\",),\n+        base_url_env_var=\"STEPFUN_BASE_URL\",\n+    ),\n     \"arcee\": ProviderConfig(\n         id=\"arcee\",\n         name=\"Arcee AI\",","@@ -992,6 +1002,7 @@ def resolve_provider(\n         \"x-ai\": \"xai\", \"x.ai\": \"xai\", \"grok\": \"xai\",\n         \"kimi\": \"kimi-coding\", \"kimi-for-coding\": \"kimi-coding\", \"moonshot\": \"kimi-coding\",\n         \"kimi-cn\": \"kimi-coding-cn\", \"moonshot-cn\": \"kimi-coding-cn\",\n+        \"step\": \"stepfun\", \"stepfun-coding-plan\": \"stepfun\",\n         \"arcee-ai\": \"arcee\", \"arceeai\": \"arcee\",\n         \"minimax-china\": \"minimax-cn\", \"minimax_cn\": \"minimax-cn\",\n         \"claude\": \"anthropic\", \"claude-code\": \"anthropic\","]},{"file":"hermes_cli/config.py","hunks":["@@ -1050,6 +1050,22 @@ def _ensure_hermes_home_managed(home: Path):\n         \"category\": \"provider\",\n         \"advanced\": True,\n     },\n+    \"STEPFUN_API_KEY\": {\n+        \"description\": \"StepFun Step Plan API key\",\n+        \"prompt\": \"StepFun Step Plan API key\",\n+        \"url\": \"https://platform.stepfun.com/\",\n+        \"password\": True,\n+        \"category\": \"provider\",\n+        \"advanced\": True,\n+    },\n+    \"STEPFUN_BASE_URL\": {\n+        \"description\": \"StepFun Step Plan base URL override\",\n+        \"prompt\": \"StepFun Step Plan base URL (leave empty for default)\",\n+        \"url\": None,\n+        \"password\": [redacted:secret-assignment]\n+        \"category\": \"provider\",\n+        \"advanced\": True,\n+    },\n     \"ARCEEAI_API_KEY\": {\n         \"description\": \"Arcee AI API key\",\n         \"prompt\": \"Arcee AI API key\","]},{"file":"hermes_cli/doctor.py","hunks":["@@ -912,6 +912,7 @@ def run_doctor(args):\n     _apikey_providers = [\n         (\"Z.AI / GLM\",      (\"GLM_API_KEY\", \"ZAI_API_KEY\", \"Z_AI_API_KEY\"), \"https://api.z.ai/api/paas/v4/models\", \"GLM_BASE_URL\", True),\n         (\"Kimi / Moonshot\",  (\"KIMI_API_KEY\",),                              \"https://api.moonshot.ai/v1/models\",   \"KIMI_BASE_URL\", True),\n+        (\"StepFun Step Plan\",   (\"STEPFUN_API_KEY\",),                           \"https://api.stepfun.ai/step_plan/v1/models\", \"STEPFUN_BASE_URL\", True),\n         (\"Kimi / Moonshot (China)\", (\"KIMI_CN_API_KEY\",),                    \"https://api.moonshot.cn/v1/models\",   None, True),\n         (\"Arcee AI\",         (\"ARCEEAI_API_KEY\",),                            \"https://api.arcee.ai/api/v1/models\",  \"ARCEE_BASE_URL\", True),\n         (\"DeepSeek\",         (\"DEEPSEEK_API_KEY\",),                           \"https://api.deepseek.com/v1/models\",  \"DEEPSEEK_BASE_URL\", True),"]},{"file":"hermes_cli/main.py","hunks":["@@ -1566,6 +1566,8 @@ def _named_custom_provider_map(cfg) -> dict[str, dict[str, str]]:\n         _model_flow_anthropic(config, current_model)\n     elif selected_provider == \"kimi-coding\":\n         _model_flow_kimi(config, current_model)\n+    elif selected_provider == \"stepfun\":\n+        _model_flow_stepfun(config, current_model)\n     elif selected_provider == \"bedrock\":\n         _model_flow_bedrock(config, current_model)\n     elif selected_provider in (","@@ -3462,6 +3464,140 @@ def _model_flow_kimi(config, current_model=\"\"):\n         print(\"No change.\")\n \n \n+def _infer_stepfun_region(base_url: str) -> str:\n+    \"\"\"Infer the current StepFun region from the configured endpoint.\"\"\"\n+    normalized = (base_url or \"\").strip().lower()\n+    if \"api.stepfun.com\" in normalized:\n+        return \"china\"\n+    return \"international\"\n+\n+\n+def _stepfun_base_url_for_region(region: str) -> str:\n+    from hermes_cli.auth import (\n+        STEPFUN_STEP_PLAN_CN_BASE_URL,\n+        STEPFUN_STEP_PLAN_INTL_BASE_URL,\n+    )\n+\n+    return (\n+        STEPFUN_STEP_PLAN_CN_BASE_URL\n+        if region == \"china\"\n+        else STEPFUN_STEP_PLAN_INTL_BASE_URL\n+    )\n+\n+\n+def _model_flow_stepfun(config, current_model=\"\"):\n+    \"\"\"StepFun Step Plan flow with region-specific endpoints.\"\"\"\n+    from hermes_cli.auth import (\n+        PROVIDER_REGISTRY,\n+        _prompt_model_selection,\n+        _save_model_choice,\n+        deactivate_provider,\n+    )\n+    from hermes_cli.config import get_env_value, save_env_value, load_config, save_config\n+    from hermes_cli.models import fetch_api_models\n+\n+    provider_id = \"stepfun\"\n+    pconfig = PROVIDER_REGISTRY[provider_id]\n+    key_env = pconfig.api_key_env_vars[0] if pconfig.api_key_env_vars else \"\"\n+    base_url_env = pconfig.base_url_env_var or \"\"\n+\n+    existing_key = \"\"\n+    for ev in pconfig.api_key_env_vars:\n+        existing_key = get_env_value(ev) or os.getenv(ev, \"\")\n+        if existing_key:\n+            break\n+\n+    if not existing_key:\n+        print(f\"No {pconfig.name} API key configured.\")\n+        if key_env:\n+            try:\n+                import getpass\n+                new_key = getpass.getpass(f\"{key_env} (or Enter to cancel): \").strip()\n+            except (KeyboardInterrupt, EOFError):\n+                print()\n+                return\n+            if not new_key:\n+                print(\"Cancelled.\")\n+                return\n+            save_env_value(key_env, new_key)\n+            existing_key = new_key\n+            print(\"API key saved.\")\n+            print()\n+    else:\n+        print(f\"  {pconfig.name} API key: {existing_key[:8]}... ✓\")\n+        print()\n+\n+    current_base = \"\"\n+    if base_url_env:\n+        current_base = get_env_value(base_url_env) or os.getenv(base_url_env, \"\")\n+    if not current_base:\n+        model_cfg = config.get(\"model\")\n+        if isinstance(model_cfg, dict):\n+            current_base = str(model_cfg.get(\"base_url\") or \"\").strip()\n+    current_region = _infer_stepfun_region(current_base or pconfig.inference_base_url)\n+\n+    region_choices = [\n+        (\"international\", f\"International ({_stepfun_base_url_for_region('international')})\"),\n+        (\"china\", f\"China ({_stepfun_base_url_for_region('china')})\"),\n+    ]\n+    ordered_regions = []\n+    for region_key, label in region_choices:\n+        if region_key == current_region:\n+            ordered_regions.insert(0, (region_key, f\"{label}  ← currently active\"))\n+        else:\n+            ordered_regions.append((region_key, label))\n+    ordered_regions.append((\"cancel\", \"Cancel\"))\n+\n+    region_idx = _prompt_provider_choice([label for _, label in ordered_regions])\n+    if region_idx is None or ordered_regions[region_idx][0] == \"cancel\":\n+        print(\"No change.\")\n+        return\n+\n+    selected_region = ordered_regions[region_idx][0]\n+    effective_base = _stepfun_base_url_for_region(selected_region)\n+    if base_url_env:\n+        save_env_value(base_url_env, effective_base)\n+\n+    live_models = fetch_api_models(existing_key, effective_base)\n+    if live_models:\n+        model_list = live_models\n+        print(f\"  Found {len(model_list)} model(s) from {pconfig.name} API\")\n+    else:\n+        model_list = _PROVIDER_MODELS.get(provider_id, [])\n+        if model_list:\n+            print(\n+                f\"  Could not auto-detect models from {pconfig.name} API — \"\n+                \"showing Step Plan fallback catalog.\"\n+            )\n+\n+    if model_list:\n+        selected = _prompt_model_selection(model_list, current_model=current_model)\n+    else:\n+        try:\n+            selected = input(\"Model name: \").strip()\n+        except (KeyboardInterrupt, EOFError):\n+            selected = None\n+\n+    if selected:\n+        _save_model_choice(selected)\n+\n+        cfg = load_config()\n+        model = cfg.get(\"model\")\n+        if not isinstance(model, dict):\n+            model = {\"default\": model} if model else {}\n+            cfg[\"model\"] = model\n+        model[\"provider\"] = provider_id\n+        model[\"base_url\"] = effective_base\n+        model.pop(\"api_mode\", None)\n+        save_config(cfg)\n+        deactivate_provider()\n+\n+        config[\"model\"] = dict(model)\n+        print(f\"Default model set to: {selected} (via {pconfig.name})\")\n+    else:\n+        print(\"No change.\")\n+\n+\n def _model_flow_bedrock_api_key(config, region, current_model=\"\"):\n     \"\"\"Bedrock API Key mode — uses the OpenAI-compatible bedrock-mantle endpoint.\n ","@@ -6530,6 +6666,7 @@ def main():\n             \"zai\",\n             \"kimi-coding\",\n             \"kimi-coding-cn\",\n+            \"stepfun\",\n             \"minimax\",\n             \"minimax-cn\",\n             \"kilocode\","]},{"file":"hermes_cli/model_switch.py","hunks":["@@ -143,7 +143,7 @@ class ModelIdentity(NamedTuple):\n     # Z.AI / GLM\n     \"glm\":       ModelIdentity(\"z-ai\", \"glm\"),\n \n-    # StepFun\n+    # Step Plan (StepFun)\n     \"step\":      ModelIdentity(\"stepfun\", \"step\"),\n \n     # Xiaomi"]},{"file":"hermes_cli/models.py","hunks":["@@ -210,6 +210,10 @@ def _codex_curated_models() -> list[str]:\n         \"kimi-k2-turbo-preview\",\n         \"kimi-k2-0905-preview\",\n     ],\n+    \"stepfun\": [\n+        \"step-3.5-flash\",\n+        \"step-3.5-flash-2603\",\n+    ],\n     \"moonshot\": [\n         \"kimi-k2.6\",\n         \"kimi-k2.5\",","@@ -699,6 +703,7 @@ class ProviderEntry(NamedTuple):\n     ProviderEntry(\"zai\",            \"Z.AI / GLM\",               \"Z.AI / GLM (Zhipu AI direct API)\"),\n     ProviderEntry(\"kimi-coding\",    \"Kimi / Kimi Coding Plan\",  \"Kimi Coding Plan (api.kimi.com) & Moonshot API\"),\n     ProviderEntry(\"kimi-coding-cn\", \"Kimi / Moonshot (China)\",  \"Kimi / Moonshot China (Moonshot CN direct API)\"),\n+    ProviderEntry(\"stepfun\",        \"StepFun Step Plan\",       \"StepFun Step Plan (agent/coding models via Step Plan API)\"),\n     ProviderEntry(\"minimax\",        \"MiniMax\",                  \"MiniMax (global direct API)\"),\n     ProviderEntry(\"minimax-cn\",     \"MiniMax (China)\",          \"MiniMax China (domestic direct API)\"),\n     ProviderEntry(\"alibaba\",        \"Alibaba Cloud (DashScope)\",\"Alibaba Cloud / DashScope Coding (Qwen + multi-provider)\"),","@@ -733,6 +738,8 @@ class ProviderEntry(NamedTuple):\n     \"moonshot\": \"kimi-coding\",\n     \"kimi-cn\": \"kimi-coding-cn\",\n     \"moonshot-cn\": \"kimi-coding-cn\",\n+    \"step\": \"stepfun\",\n+    \"stepfun-coding-plan\": \"stepfun\",\n     \"arcee-ai\": \"arcee\",\n     \"arceeai\": \"arcee\",\n     \"minimax-china\": \"minimax-cn\",","@@ -1613,6 +1620,19 @@ def provider_model_ids(provider: Optional[str], *, force_refresh: bool = False)\n                     return live\n         except Exception:\n             pass\n+    if normalized == \"stepfun\":\n+        try:\n+            from hermes_cli.auth import resolve_api_key_provider_credentials\n+\n+            creds = resolve_api_key_provider_credentials(\"stepfun\")\n+            api_key = [redacted:secret-assignment]\"api_key\") or \"\").strip()\n+            base_url = str(creds.get(\"base_url\") or \"\").strip()\n+            if api_key and base_url:\n+                live = fetch_api_models(api_key, base_url)\n+                if live:\n+                    return live\n+        except Exception:\n+            pass\n     if normalized == \"anthropic\":\n         live = _fetch_anthropic_models()\n         if live:"]},{"file":"hermes_cli/providers.py","hunks":["@@ -94,6 +94,12 @@ class HermesOverlay:\n         transport=\"openai_chat\",\n         base_url_env_var=\"KIMI_BASE_URL\",\n     ),\n+    \"stepfun\": HermesOverlay(\n+        transport=\"openai_chat\",\n+        extra_env_vars=(\"STEPFUN_API_KEY\",),\n+        base_url_override=\"https://api.stepfun.ai/step_plan/v1\",\n+        base_url_env_var=\"STEPFUN_BASE_URL\",\n+    ),\n     \"minimax\": HermesOverlay(\n         transport=\"anthropic_messages\",\n         base_url_env_var=\"MINIMAX_BASE_URL\",","@@ -210,6 +216,10 @@ class ProviderDef:\n     \"kimi-coding-cn\": \"kimi-for-coding\",\n     \"moonshot\": \"kimi-for-coding\",\n \n+    # stepfun\n+    \"step\": \"stepfun\",\n+    \"stepfun-coding-plan\": \"stepfun\",\n+\n     # minimax-cn\n     \"minimax-china\": \"minimax-cn\",\n     \"minimax_cn\": \"minimax-cn\",","@@ -294,6 +304,7 @@ class ProviderDef:\n     \"nous\": \"Nous Portal\",\n     \"openai-codex\": \"OpenAI Codex\",\n     \"copilot-acp\": \"GitHub Copilot ACP\",\n+    \"stepfun\": \"StepFun Step Plan\",\n     \"xiaomi\": \"Xiaomi MiMo\",\n     \"local\": \"Local endpoint\",\n     \"bedrock\": \"AWS Bedrock\","]},{"file":"hermes_cli/setup.py","hunks":["@@ -96,6 +96,7 @@ def _supports_same_provider_pool_setup(provider: str) -> bool:\n     \"zai\": [\"glm-5.1\", \"glm-5\", \"glm-4.7\", \"glm-4.5\", \"glm-4.5-flash\"],\n     \"kimi-coding\": [\"kimi-k2.6\", \"kimi-k2.5\", \"kimi-k2-thinking\", \"kimi-k2-turbo-preview\"],\n     \"kimi-coding-cn\": [\"kimi-k2.6\", \"kimi-k2.5\", \"kimi-k2-thinking\", \"kimi-k2-turbo-preview\"],\n+    \"stepfun\": [\"step-3.5-flash\", \"step-3.5-flash-2603\"],\n     \"arcee\": [\"trinity-large-thinking\", \"trinity-large-preview\", \"trinity-mini\"],\n     \"minimax\": [\"MiniMax-M2.7\", \"MiniMax-M2.5\", \"MiniMax-M2.1\", \"MiniMax-M2\"],\n     \"minimax-cn\": [\"MiniMax-M2.7\", \"MiniMax-M2.5\", \"MiniMax-M2.1\", \"MiniMax-M2\"],","@@ -804,6 +805,7 @@ def setup_model_provider(config: dict, *, quick: bool = False):\n             \"zai\": \"Z.AI / GLM\",\n             \"kimi-coding\": \"Kimi / Moonshot\",\n             \"kimi-coding-cn\": \"Kimi / Moonshot (China)\",\n+            \"stepfun\": \"StepFun Step Plan\",\n             \"minimax\": \"MiniMax\",\n             \"minimax-cn\": \"MiniMax CN\",\n             \"anthropic\": \"Anthropic\","]},{"file":"hermes_cli/status.py","hunks":["@@ -122,6 +122,7 @@ def show_status(args):\n         \"OpenAI\": \"OPENAI_API_KEY\",\n         \"Z.AI/GLM\": \"GLM_API_KEY\",\n         \"Kimi\": \"KIMI_API_KEY\",\n+        \"StepFun Step Plan\": \"STEPFUN_API_KEY\",\n         \"MiniMax\": \"MINIMAX_API_KEY\",\n         \"MiniMax-CN\": \"MINIMAX_CN_API_KEY\",\n         \"Firecrawl\": \"FIRECRAWL_API_KEY\",","@@ -252,6 +253,7 @@ def show_status(args):\n     apikey_providers = {\n         \"Z.AI / GLM\":       (\"GLM_API_KEY\", \"ZAI_API_KEY\", \"Z_AI_API_KEY\"),\n         \"Kimi / Moonshot\":  (\"KIMI_API_KEY\",),\n+        \"StepFun Step Plan\": (\"STEPFUN_API_KEY\",),\n         \"MiniMax\":          (\"MINIMAX_API_KEY\",),\n         \"MiniMax (China)\":  (\"MINIMAX_CN_API_KEY\",),\n     }"]},{"file":"tests/agent/test_model_metadata.py","hunks":["@@ -385,6 +385,7 @@ def test_known_provider_prefix_is_stripped(self):\n         assert _strip_provider_prefix(\"local:my-model\") == \"my-model\"\n         assert _strip_provider_prefix(\"openrouter:anthropic/claude-sonnet-4\") == \"anthropic/claude-sonnet-4\"\n         assert _strip_provider_prefix(\"anthropic:claude-sonnet-4\") == \"claude-sonnet-4\"\n+        assert _strip_provider_prefix(\"stepfun:step-3.5-flash\") == \"step-3.5-flash\"\n \n     def test_ollama_model_tag_preserved(self):\n         \"\"\"Ollama model:tag format must NOT be stripped.\"\"\""]},{"file":"tests/agent/test_models_dev.py","hunks":["@@ -82,6 +82,7 @@ def test_all_mapped_providers_are_strings(self):\n     def test_known_providers_mapped(self):\n         assert PROVIDER_TO_MODELS_DEV[\"anthropic\"] == \"anthropic\"\n         assert PROVIDER_TO_MODELS_DEV[\"copilot\"] == \"github-copilot\"\n+        assert PROVIDER_TO_MODELS_DEV[\"stepfun\"] == \"stepfun\"\n         assert PROVIDER_TO_MODELS_DEV[\"kilocode\"] == \"kilo\"\n         assert PROVIDER_TO_MODELS_DEV[\"ai-gateway\"] == \"vercel\"\n "]},{"file":"tests/hermes_cli/test_api_key_providers.py","hunks":["@@ -15,6 +15,8 @@\n     get_auth_status,\n     AuthError,\n     KIMI_CODE_BASE_URL,\n+    STEPFUN_STEP_PLAN_INTL_BASE_URL,\n+    STEPFUN_STEP_PLAN_CN_BASE_URL,\n     _resolve_kimi_base_url,\n )\n from hermes_cli.copilot_auth import _try_gh_cli_token","@@ -35,6 +37,7 @@ class TestProviderRegistry:\n         (\"xai\", \"xAI\", \"api_key\"),\n         (\"nvidia\", \"NVIDIA NIM\", \"api_key\"),\n         (\"kimi-coding\", \"Kimi / Moonshot\", \"api_key\"),\n+        (\"stepfun\", \"StepFun Step Plan\", \"api_key\"),\n         (\"minimax\", \"MiniMax\", \"api_key\"),\n         (\"minimax-cn\", \"MiniMax (China)\", \"api_key\"),\n         (\"ai-gateway\", \"Vercel AI Gateway\", \"api_key\"),","@@ -83,6 +86,11 @@ def test_minimax_env_vars(self):\n         assert pconfig.api_key_env_vars == (\"MINIMAX_API_KEY\",)\n         assert pconfig.base_url_env_var == \"MINIMAX_BASE_URL\"\n \n+    def test_stepfun_env_vars(self):\n+        pconfig = PROVIDER_REGISTRY[\"stepfun\"]\n+        assert pconfig.api_key_env_vars == (\"STEPFUN_API_KEY\",)\n+        assert pconfig.base_url_env_var == \"STEPFUN_BASE_URL\"\n+\n     def test_minimax_cn_env_vars(self):\n         pconfig = PROVIDER_REGISTRY[\"minimax-cn\"]\n         assert pconfig.api_key_env_vars == (\"MINIMAX_CN_API_KEY\",)","@@ -108,6 +116,7 @@ def test_base_urls(self):\n         assert PROVIDER_REGISTRY[\"copilot-acp\"].inference_base_url == \"acp://copilot\"\n         assert PROVIDER_REGISTRY[\"zai\"].inference_base_url == \"https://api.z.ai/api/paas/v4\"\n         assert PROVIDER_REGISTRY[\"kimi-coding\"].inference_base_url == \"https://api.moonshot.ai/v1\"\n+        assert PROVIDER_REGISTRY[\"stepfun\"].inference_base_url == STEPFUN_STEP_PLAN_INTL_BASE_URL\n         assert PROVIDER_REGISTRY[\"minimax\"].inference_base_url == \"https://api.minimax.io/anthropic\"\n         assert PROVIDER_REGISTRY[\"minimax-cn\"].inference_base_url == \"https://api.minimaxi.com/anthropic\"\n         assert PROVIDER_REGISTRY[\"ai-gateway\"].inference_base_url == \"https://ai-gateway.vercel.sh/v1\"","@@ -130,7 +139,8 @@ def test_oauth_providers_unchanged(self):\n     \"OPENROUTER_API_KEY\", \"OPENAI_API_KEY\", \"ANTHROPIC_API_KEY\", \"ANTHROPIC_TOKEN\",\n     \"CLAUDE_CODE_OAUTH_TOKEN\",\n     \"GLM_API_KEY\", \"ZAI_API_KEY\", \"Z_AI_API_KEY\",\n-    \"KIMI_API_KEY\", \"KIMI_BASE_URL\", \"MINIMAX_API_KEY\", \"MINIMAX_CN_API_KEY\",\n+    \"KIMI_API_KEY\", \"KIMI_BASE_URL\", \"STEPFUN_API_KEY\", \"STEPFUN_BASE_URL\",\n+    \"MINIMAX_API_KEY\", \"MINIMAX_CN_API_KEY\",\n     \"AI_GATEWAY_API_KEY\", \"AI_GATEWAY_BASE_URL\",\n     \"KILOCODE_API_KEY\", \"KILOCODE_BASE_URL\",\n     \"DASHSCOPE_API_KEY\", \"OPENCODE_ZEN_API_KEY\", \"OPENCODE_GO_API_KEY\",","@@ -156,6 +166,9 @@ def test_explicit_zai(self):\n     def test_explicit_kimi_coding(self):\n         assert resolve_provider(\"kimi-coding\") == \"kimi-coding\"\n \n+    def test_explicit_stepfun(self):\n+        assert resolve_provider(\"stepfun\") == \"stepfun\"\n+\n     def test_explicit_minimax(self):\n         assert resolve_provider(\"minimax\") == \"minimax\"\n ","@@ -180,6 +193,9 @@ def test_alias_kimi(self):\n     def test_alias_moonshot(self):\n         assert resolve_provider(\"moonshot\") == \"kimi-coding\"\n \n+    def test_alias_step(self):\n+        assert resolve_provider(\"step\") == \"stepfun\"\n+\n     def test_alias_minimax_underscore(self):\n         assert resolve_provider(\"minimax_cn\") == \"minimax-cn\"\n ","@@ -248,6 +264,10 @@ def test_auto_detects_kimi_key(self, monkeypatch):\n         monkeypatch.setenv(\"KIMI_API_KEY\", \"test-kimi-key\")\n         assert resolve_provider(\"auto\") == \"kimi-coding\"\n \n+    def test_auto_detects_stepfun_key(self, monkeypatch):\n+        monkeypatch.setenv(\"STEPFUN_API_KEY\", \"test-stepfun-key\")\n+        assert resolve_provider(\"auto\") == \"stepfun\"\n+\n     def test_auto_detects_minimax_key(self, monkeypatch):\n         monkeypatch.setenv(\"MINIMAX_API_KEY\", \"test-mm-key\")\n         assert resolve_provider(\"auto\") == \"minimax\"","@@ -312,6 +332,13 @@ def test_custom_base_url(self, monkeypatch):\n         status = get_api_key_provider_status(\"kimi-coding\")\n         assert status[\"base_url\"] == \"https://custom.kimi.example/v1\"\n \n+    def test_stepfun_status_uses_configured_base_url(self, monkeypatch):\n+        monkeypatch.setenv(\"STEPFUN_API_KEY\", \"stepfun-key\")\n+        monkeypatch.setenv(\"STEPFUN_BASE_URL\", STEPFUN_STEP_PLAN_CN_BASE_URL)\n+        status = get_api_key_provider_status(\"stepfun\")\n+        assert status[\"configured\"] is True\n+        assert status[\"base_url\"] == STEPFUN_STEP_PLAN_CN_BASE_URL\n+\n     def test_copilot_status_uses_gh_cli_token(self, monkeypatch):\n         monkeypatch.setattr(\"hermes_cli.copilot_auth._try_gh_cli_token\", lambda: \"gho_gh_cli_token\")\n         status = get_api_key_provider_status(\"copilot\")","@@ -429,6 +456,19 @@ def test_resolve_kimi_with_key(self, monkeypatch):\n         assert creds[\"api_key\"] == \"kimi-secret-key\"\n         assert creds[\"base_url\"] == \"https://api.moonshot.ai/v1\"\n \n+    def test_resolve_stepfun_with_key(self, monkeypatch):\n+        monkeypatch.setenv(\"STEPFUN_API_KEY\", \"stepfun-secret-key\")\n+        creds = resolve_api_key_provider_credentials(\"stepfun\")\n+        assert creds[\"provider\"] == \"stepfun\"\n+        assert creds[\"api_key\"] == \"stepfun-secret-key\"\n+        assert creds[\"base_url\"] == STEPFUN_STEP_PLAN_INTL_BASE_URL\n+\n+    def test_resolve_stepfun_custom_base_url(self, monkeypatch):\n+        monkeypatch.setenv(\"STEPFUN_API_KEY\", \"stepfun-secret-key\")\n+        monkeypatch.setenv(\"STEPFUN_BASE_URL\", STEPFUN_STEP_PLAN_CN_BASE_URL)\n+        creds = resolve_api_key_provider_credentials(\"stepfun\")\n+        assert creds[\"base_url\"] == STEPFUN_STEP_PLAN_CN_BASE_URL\n+\n     def test_resolve_minimax_with_key(self, monkeypatch):\n         monkeypatch.setenv(\"MINIMAX_API_KEY\", \"mm-secret-key\")\n         creds = resolve_api_key_provider_credentials(\"minimax\")","@@ -519,6 +559,16 @@ def test_runtime_kimi(self, monkeypatch):\n         assert result[\"api_mode\"] == \"chat_completions\"\n         assert result[\"api_key\"] == \"kimi-key\"\n \n+    def test_runtime_stepfun(self, monkeypatch):\n+        monkeypatch.setenv(\"STEPFUN_API_KEY\", \"stepfun-key\")\n+        monkeypatch.setenv(\"STEPFUN_BASE_URL\", STEPFUN_STEP_PLAN_CN_BASE_URL)\n+        from hermes_cli.runtime_provider import resolve_runtime_provider\n+        result = resolve_runtime_provider(requested=\"stepfun\")\n+        assert result[\"provider\"] == \"stepfun\"\n+        assert result[\"api_mode\"] == \"chat_completions\"\n+        assert result[\"api_key\"] == \"stepfun-key\"\n+        assert result[\"base_url\"] == STEPFUN_STEP_PLAN_CN_BASE_URL\n+\n     def test_runtime_minimax(self, monkeypatch):\n         monkeypatch.setenv(\"MINIMAX_API_KEY\", \"mm-key\")\n         from hermes_cli.runtime_provider import resolve_runtime_provider"]},{"file":"tests/hermes_cli/test_model_provider_persistence.py","hunks":["@@ -32,6 +32,8 @@ def config_home(tmp_path, monkeypatch):\n     monkeypatch.delenv(\"OPENAI_BASE_URL\", raising=False)\n     monkeypatch.delenv(\"OPENAI_API_KEY\", raising=False)\n     monkeypatch.delenv(\"OPENROUTER_API_KEY\", raising=False)\n+    monkeypatch.delenv(\"STEPFUN_API_KEY\", raising=False)\n+    monkeypatch.delenv(\"STEPFUN_BASE_URL\", raising=False)\n     return home\n \n ","@@ -330,3 +332,33 @@ def test_empty_base_url_keeps_default(self, config_home, monkeypatch):\n \n         saved = get_env_value(\"GLM_BASE_URL\") or \"\"\n         assert saved == \"\", \"Empty input should not save a base URL\"\n+\n+    def test_stepfun_provider_saved_with_selected_region(self, config_home, monkeypatch):\n+        from hermes_cli.main import _model_flow_stepfun\n+        from hermes_cli.config import load_config, get_env_value\n+\n+        monkeypatch.setenv(\"STEPFUN_API_KEY\", \"stepfun-test-key\")\n+\n+        with patch(\n+            \"hermes_cli.main._prompt_provider_choice\",\n+            return_value=1,\n+        ), patch(\n+            \"hermes_cli.models.fetch_api_models\",\n+            return_value=[\"step-3.5-flash\", \"step-3-agent-lite\"],\n+        ), patch(\n+            \"hermes_cli.auth._prompt_model_selection\",\n+            return_value=\"step-3-agent-lite\",\n+        ), patch(\n+            \"hermes_cli.auth.deactivate_provider\",\n+        ):\n+            _model_flow_stepfun(load_config(), \"old-model\")\n+\n+        import yaml\n+\n+        config = yaml.safe_load((config_home / \"config.yaml\").read_text()) or {}\n+        model = config.get(\"model\")\n+        assert isinstance(model, dict)\n+        assert model.get(\"provider\") == \"stepfun\"\n+        assert model.get(\"default\") == \"step-3-agent-lite\"\n+        assert model.get(\"base_url\") == \"https://api.stepfun.com/step_plan/v1\"\n+        assert get_env_value(\"STEPFUN_BASE_URL\") == \"https://api.stepfun.com/step_plan/v1\""]},{"file":"tests/hermes_cli/test_model_validation.py","hunks":["@@ -63,6 +63,11 @@ def test_provider_alias_resolved(self):\n         assert provider == \"zai\"\n         assert model == \"glm-5\"\n \n+    def test_stepfun_alias_resolved(self):\n+        provider, model = parse_model_input(\"step:step-3.5-flash\", \"openrouter\")\n+        assert provider == \"stepfun\"\n+        assert model == \"step-3.5-flash\"\n+\n     def test_no_slash_no_colon_keeps_provider(self):\n         provider, model = parse_model_input(\"gpt-5.4\", \"openrouter\")\n         assert provider == \"openrouter\"","@@ -154,6 +159,7 @@ def test_known_aliases(self):\n         assert normalize_provider(\"glm\") == \"zai\"\n         assert normalize_provider(\"kimi\") == \"kimi-coding\"\n         assert normalize_provider(\"moonshot\") == \"kimi-coding\"\n+        assert normalize_provider(\"step\") == \"stepfun\"\n         assert normalize_provider(\"github-copilot\") == \"copilot\"\n \n     def test_case_insensitive(self):","@@ -164,6 +170,7 @@ class TestProviderLabel:\n     def test_known_labels_and_auto(self):\n         assert provider_label(\"anthropic\") == \"Anthropic\"\n         assert provider_label(\"kimi\") == \"Kimi / Kimi Coding Plan\"\n+        assert provider_label(\"stepfun\") == \"StepFun Step Plan\"\n         assert provider_label(\"copilot\") == \"GitHub Copilot\"\n         assert provider_label(\"copilot-acp\") == \"GitHub Copilot ACP\"\n         assert provider_label(\"auto\") == \"Auto\"","@@ -193,6 +200,16 @@ def test_unknown_provider_returns_empty(self):\n     def test_zai_returns_glm_models(self):\n         assert \"glm-5\" in provider_model_ids(\"zai\")\n \n+    def test_stepfun_prefers_live_catalog(self):\n+        with patch(\n+            \"hermes_cli.auth.resolve_api_key_provider_credentials\",\n+            return_value={\"api_key\": \"***\", \"base_url\": \"https://api.stepfun.com/step_plan/v1\"},\n+        ), patch(\n+            \"hermes_cli.models.fetch_api_models\",\n+            return_value=[\"step-3.5-flash\", \"step-3-agent-lite\"],\n+        ):\n+            assert provider_model_ids(\"stepfun\") == [\"step-3.5-flash\", \"step-3-agent-lite\"]\n+\n     def test_copilot_prefers_live_catalog(self):\n         with patch(\"hermes_cli.auth.resolve_api_key_provider_credentials\", return_value={\"api_key\": \"[redacted:secret-assignment]\"}), \\\n              patch(\"hermes_cli.models._fetch_github_models\", return_value=[\"gpt-5.4\", \"claude-sonnet-4.6\"]):\n"]}]}],"trust":{"default_tier":"verified","includes_suggested":false}}