# NousResearch/hermes-agent · Add StepFun and Step Plan provider support

## Open now

- Nothing open on GitHub.

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.

## TL;DR

- 3 member(s): 0 open issue(s), 1 closed issue(s), 0 open PR(s), 1 merged PR(s), 1 PR(s) closed without merging.
- A fix was merged to main in PR #13893. Verify it addresses your case.
- **Recommended:** read PR #13893 (and any competing PRs below) before filing. Confirm the merged change covers your symptom.

## Maintainer resolution verdict

- Internal-tracker verdict over this cluster's issue members: `unresolved`. This is distinct from the fix status above. Weigh it yourself.

## How this cluster was grouped

- Link evidence (`STRUCTURAL_SPANNING`): every member is connected by structural evidence: shared changed files, or a closing/superseded reference.
- Pairwise link density `0.67`: the share of member pairs that carry a link, counting a structural link in full and a relevance-only link at 0.36. `1.00` means every pair is linked.

## Closed / duplicate issues

- #8853: \[Feature\]:  Add StepFun / Step Plan model provider support

## Prior & competing PRs

- **#13893: feat: add Step Plan provider support (salvage #6005)**. merged to main · fix tier `verified`
  - maintainer direction: category `merged`
  - files: `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:
  - `agent/auxiliary_client.py`

    ```diff
    @@ -134,6 +134,7 @@ def _fixed_temperature_for_model(
         "gemini": "gemini-3-flash-preview",
         "zai": "glm-4.5-flash",
         "kimi-coding": "kimi-k2-turbo-preview",
    +    "stepfun": "step-3.5-flash",
         "kimi-coding-cn": "kimi-k2-turbo-preview",
         "minimax": "MiniMax-M2.7",
         "minimax-cn": "MiniMax-M2.7",
    ```
  - `agent/model_metadata.py`

    ```diff
    @@ -25,7 +25,7 @@
     # are preserved so the full model name reaches cache lookups and server queries.
     _PROVIDER_PREFIXES: frozenset[str] = frozenset({
         "openrouter", "nous", "openai-codex", "copilot", "copilot-acp",
    -    "gemini", "ollama-cloud", "zai", "kimi-coding", "kimi-coding-cn", "minimax", "minimax-cn", "anthropic", "deepseek",
    +    "gemini", "ollama-cloud", "zai", "kimi-coding", "kimi-coding-cn", "stepfun", "minimax", "minimax-cn", "anthropic", "deepseek",
         "opencode-zen", "opencode-go", "ai-gateway", "kilocode", "alibaba",
         "qwen-oauth",
         "xiaomi",
    ```

    ```diff
    @@ -36,7 +36,7 @@
         "glm", "z-ai", "z.ai", "zhipu", "github", "github-copilot",
         "github-models", "kimi", "moonshot", "kimi-cn", "moonshot-cn", "claude", "deep-seek",
         "ollama",
    -    "opencode", "zen", "go", "vercel", "kilo", "dashscope", "aliyun", "qwen",
    +    "stepfun", "opencode", "zen", "go", "vercel", "kilo", "dashscope", "aliyun", "qwen",
         "mimo", "xiaomi-mimo",
         "arcee-ai", "arceeai",
         "xai", "x-ai", "x.ai", "grok",
    ```

    ```diff
    @@ -237,6 +237,8 @@ def _is_custom_endpoint(base_url: str) -> bool:
         "api.moonshot.ai": "kimi-coding",
         "api.moonshot.cn": "kimi-coding-cn",
         "api.kimi.com": "kimi-coding",
    +    "api.stepfun.ai": "stepfun",
    +    "api.stepfun.com": "stepfun",
         "api.arcee.ai": "arcee",
         "api.minimax": "minimax",
         "dashscope.aliyuncs.com": "alibaba",
    ```
  - `agent/models_dev.py`

    ```diff
    @@ -146,6 +146,7 @@ class ProviderInfo:
         "openai-codex": "openai",
         "zai": "zai",
         "kimi-coding": "kimi-for-coding",
    +    "stepfun": "stepfun",
         "kimi-coding-cn": "kimi-for-coding",
         "minimax": "minimax",
         "minimax-cn": "minimax-cn",
    ```
  - `hermes_cli/auth.py`

    ```diff
    @@ -72,6 +72,8 @@
     DEFAULT_GITHUB_MODELS_BASE_URL = "https://api.githubcopilot.com"
     DEFAULT_COPILOT_ACP_BASE_URL = "acp://copilot"
     DEFAULT_OLLAMA_CLOUD_BASE_URL = "https://ollama.com/v1"
    +STEPFUN_STEP_PLAN_INTL_BASE_URL = "https://api.stepfun.ai/step_plan/v1"
    +STEPFUN_STEP_PLAN_CN_BASE_URL = "https://api.stepfun.com/step_plan/v1"
     CODEX_OAUTH_CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann"
     CODEX_OAUTH_TOKEN_URL = "[redacted:secret-assignment]"
     CODEX_ACCESS_TOKEN_REFRESH_SKEW_SECONDS = 120
    ```

    ```diff
    @@ -182,6 +184,14 @@ class ProviderConfig:
             inference_base_url="https://api.moonshot.cn/v1",
             api_key_env_vars=("KIMI_CN_API_KEY",),
         ),
    +    "stepfun": ProviderConfig(
    +        id="stepfun",
    +        name="StepFun Step Plan",
    +        auth_type="api_key",
    +        inference_base_url=STEPFUN_STEP_PLAN_INTL_BASE_URL,
    +        api_key_env_vars=("STEPFUN_API_KEY",),
    +        base_url_env_var="STEPFUN_BASE_URL",
    +    ),
         "arcee": ProviderConfig(
             id="arcee",
             name="Arcee AI",
    ```

    ```diff
    @@ -992,6 +1002,7 @@ def resolve_provider(
             "x-ai": "xai", "x.ai": "xai", "grok": "xai",
             "kimi": "kimi-coding", "kimi-for-coding": "kimi-coding", "moonshot": "kimi-coding",
             "kimi-cn": "kimi-coding-cn", "moonshot-cn": "kimi-coding-cn",
    +        "step": "stepfun", "stepfun-coding-plan": "stepfun",
             "arcee-ai": "arcee", "arceeai": "arcee",
             "minimax-china": "minimax-cn", "minimax_cn": "minimax-cn",
             "claude": "anthropic", "claude-code": "anthropic",
    ```
  - `hermes_cli/config.py`

    ```diff
    @@ -1050,6 +1050,22 @@ def _ensure_hermes_home_managed(home: Path):
             "category": "provider",
             "advanced": True,
         },
    +    "STEPFUN_API_KEY": {
    +        "description": "StepFun Step Plan API key",
    +        "prompt": "StepFun Step Plan API key",
    +        "url": "https://platform.stepfun.com/",
    +        "password": True,
    +        "category": "provider",
    +        "advanced": True,
    +    },
    +    "STEPFUN_BASE_URL": {
    +        "description": "StepFun Step Plan base URL override",
    +        "prompt": "StepFun Step Plan base URL (leave empty for default)",
    +        "url": None,
    +        "password": [redacted:secret-assignment]
    +        "category": "provider",
    +        "advanced": True,
    +    },
         "ARCEEAI_API_KEY": {
             "description": "Arcee AI API key",
             "prompt": "Arcee AI API key",
    ```
  - `hermes_cli/doctor.py`

    ```diff
    @@ -912,6 +912,7 @@ def run_doctor(args):
         _apikey_providers = [
             ("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),
             ("Kimi / Moonshot",  ("KIMI_API_KEY",),                              "https://api.moonshot.ai/v1/models",   "KIMI_BASE_URL", True),
    +        ("StepFun Step Plan",   ("STEPFUN_API_KEY",),                           "https://api.stepfun.ai/step_plan/v1/models", "STEPFUN_BASE_URL", True),
             ("Kimi / Moonshot (China)", ("KIMI_CN_API_KEY",),                    "https://api.moonshot.cn/v1/models",   None, True),
             ("Arcee AI",         ("ARCEEAI_API_KEY",),                            "https://api.arcee.ai/api/v1/models",  "ARCEE_BASE_URL", True),
             ("DeepSeek",         ("DEEPSEEK_API_KEY",),                           "https://api.deepseek.com/v1/models",  "DEEPSEEK_BASE_URL", True),
    ```
  - `hermes_cli/main.py`

    ```diff
    @@ -1566,6 +1566,8 @@ def _named_custom_provider_map(cfg) -> dict[str, dict[str, str]]:
             _model_flow_anthropic(config, current_model)
         elif selected_provider == "kimi-coding":
             _model_flow_kimi(config, current_model)
    +    elif selected_provider == "stepfun":
    +        _model_flow_stepfun(config, current_model)
         elif selected_provider == "bedrock":
             _model_flow_bedrock(config, current_model)
         elif selected_provider in (
    ```

    ```diff
    @@ -3462,6 +3464,140 @@ def _model_flow_kimi(config, current_model=""):
             print("No change.")
     
     
    +def _infer_stepfun_region(base_url: str) -> str:
    +    """Infer the current StepFun region from the configured endpoint."""
    +    normalized = (base_url or "").strip().lower()
    +    if "api.stepfun.com" in normalized:
    +        return "china"
    +    return "international"
    +
    +
    +def _stepfun_base_url_for_region(region: str) -> str:
    +    from hermes_cli.auth import (
    +        STEPFUN_STEP_PLAN_CN_BASE_URL,
    +        STEPFUN_STEP_PLAN_INTL_BASE_URL,
    +    )
    +
    +    return (
    +        STEPFUN_STEP_PLAN_CN_BASE_URL
    +        if region == "china"
    +        else STEPFUN_STEP_PLAN_INTL_BASE_URL
    +    )
    +
    +
    +def _model_flow_stepfun(config, current_model=""):
    +    """StepFun Step Plan flow with region-specific endpoints."""
    +    from hermes_cli.auth import (
    +        PROVIDER_REGISTRY,
    +        _prompt_model_selection,
    +        _save_model_choice,
    +        deactivate_provider,
    +    )
    +    from hermes_cli.config import get_env_value, save_env_value, load_config, save_config
    +    from hermes_cli.models import fetch_api_models
    +
    +    provider_id = "stepfun"
    +    pconfig = PROVIDER_REGISTRY[provider_id]
    +    key_env = pconfig.api_key_env_vars[0] if pconfig.api_key_env_vars else ""
    +    base_url_env = pconfig.base_url_env_var or ""
    +
    +    existing_key = ""
    +    for ev in pconfig.api_key_env_vars:
    +        existing_key = get_env_value(ev) or os.getenv(ev, "")
    +        if existing_key:
    +            break
    +
    +    if not existing_key:
    +        print(f"No {pconfig.name} API key configured.")
    +        if key_env:
    +            try:
    +                import getpass
    +                new_key = getpass.getpass(f"{key_env} (or Enter to cancel): ").strip()
    +            except (KeyboardInterrupt, EOFError):
    +                print()
    +                return
    +            if not new_key:
    +                print("Cancelled.")
    +                return
    +            save_env_value(key_env, new_key)
    +            existing_key = new_key
    +            print("API key saved.")
    +            print()
    +    else:
    +        print(f"  {pconfig.name} API key: {existing_key[:8]}... ✓")
    +        print()
    +
    +    current_base = ""
    +    if base_url_env:
    +        current_base = get_env_value(base_url_env) or os.getenv(base_url_env, "")
    +    if not current_base:
    +        model_cfg = config.get("model")
    +        if isinstance(model_cfg, dict):
    +            current_base = str(model_cfg.get("base_url") or "").strip()
    +    current_region = _infer_stepfun_region(current_base or pconfig.inference_base_url)
    +
    +    region_choices = [
    +        ("international", f"International ({_stepfun_base_url_for_region('international')})"),
    +        ("china", f"China ({_stepfun_base_url_for_region('china')})"),
    +    ]
    +    ordered_regions = []
    +    for region_key, label in region_choices:
    +        if region_key == current_region:
    +            ordered_regions.insert(0, (region_key, f"{label}  ← currently active"))
    +        else:
    +            ordered_regions.append((region_key, label))
    +    ordered_regions.append(("cancel", "Cancel"))
    +
    +    region_idx = _prompt_provider_choice([label for _, label in ordered_regions])
    +    if region_idx is None or ordered_regions[region_idx][0] == "cancel":
    +        print("No change.")
    +        return
    +
    +    selected_region = ordered_regions[region_idx][0]
    +    effective_base = _stepfun_base_url_for_region(selected_region)
    +    if base_url_env:
    +        save_env_value(base_url_env, effective_base)
    +
    +    live_models = fetch_api_models(existing_key, effective_base)
    +    if live_models:
    +        model_list = live_models
    +        print(f"  Found {len(model_list)} model(s) from {pconfig.name} API")
    +    else:
    +        model_list = _PROVIDER_MODELS.get(provider_id, [])
    +        if model_list:
    +            print(
    +                f"  Could not auto-detect models from {pconfig.name} API — "
    +                "showing Step Plan fallback catalog."
    +            )
    +
    +    if model_list:
    +        selected = _prompt_model_selection(model_list, current_model=current_model)
    +    else:
    +        try:
    +            selected = input("Model name: ").strip()
    +        except (KeyboardInterrupt, EOFError):
    +            selected = None
    +
    +    if selected:
    +        _save_model_choice(selected)
    +
    +        cfg = load_config()
    +        model = cfg.get("model")
    +        if not isinstance(model, dict):
    +            model = {"default": model} if model else {}
    +            cfg["model"] = model
    +        model["provider"] = provider_id
    +        model["base_url"] = effective_base
    +        model.pop("api_mode", None)
    +        save_config(cfg)
    +        deactivate_provider()
    +
    +        config["model"] = dict(model)
    +        print(f"Default model set to: {selected} (via {pconfig.name})")
    +    else:
    +        print("No change.")
    +
    +
     def _model_flow_bedrock_api_key(config, region, current_model=""):
         """Bedrock API Key mode — uses the OpenAI-compatible bedrock-mantle endpoint.
     
    ```

    ```diff
    @@ -6530,6 +6666,7 @@ def main():
                 "zai",
                 "kimi-coding",
                 "kimi-coding-cn",
    +            "stepfun",
                 "minimax",
                 "minimax-cn",
                 "kilocode",
    ```
  - `hermes_cli/model_switch.py`

    ```diff
    @@ -143,7 +143,7 @@ class ModelIdentity(NamedTuple):
         # Z.AI / GLM
         "glm":       ModelIdentity("z-ai", "glm"),
     
    -    # StepFun
    +    # Step Plan (StepFun)
         "step":      ModelIdentity("stepfun", "step"),
     
         # Xiaomi
    ```
  - `hermes_cli/models.py`

    ```diff
    @@ -210,6 +210,10 @@ def _codex_curated_models() -> list[str]:
             "kimi-k2-turbo-preview",
             "kimi-k2-0905-preview",
         ],
    +    "stepfun": [
    +        "step-3.5-flash",
    +        "step-3.5-flash-2603",
    +    ],
         "moonshot": [
             "kimi-k2.6",
             "kimi-k2.5",
    ```

    ```diff
    @@ -699,6 +703,7 @@ class ProviderEntry(NamedTuple):
         ProviderEntry("zai",            "Z.AI / GLM",               "Z.AI / GLM (Zhipu AI direct API)"),
         ProviderEntry("kimi-coding",    "Kimi / Kimi Coding Plan",  "Kimi Coding Plan (api.kimi.com) & Moonshot API"),
         ProviderEntry("kimi-coding-cn", "Kimi / Moonshot (China)",  "Kimi / Moonshot China (Moonshot CN direct API)"),
    +    ProviderEntry("stepfun",        "StepFun Step Plan",       "StepFun Step Plan (agent/coding models via Step Plan API)"),
         ProviderEntry("minimax",        "MiniMax",                  "MiniMax (global direct API)"),
         ProviderEntry("minimax-cn",     "MiniMax (China)",          "MiniMax China (domestic direct API)"),
         ProviderEntry("alibaba",        "Alibaba Cloud (DashScope)","Alibaba Cloud / DashScope Coding (Qwen + multi-provider)"),
    ```

    ```diff
    @@ -733,6 +738,8 @@ class ProviderEntry(NamedTuple):
         "moonshot": "kimi-coding",
         "kimi-cn": "kimi-coding-cn",
         "moonshot-cn": "kimi-coding-cn",
    +    "step": "stepfun",
    +    "stepfun-coding-plan": "stepfun",
         "arcee-ai": "arcee",
         "arceeai": "arcee",
         "minimax-china": "minimax-cn",
    ```

    ```diff
    @@ -1613,6 +1620,19 @@ def provider_model_ids(provider: Optional[str], *, force_refresh: bool = False)
                         return live
             except Exception:
                 pass
    +    if normalized == "stepfun":
    +        try:
    +            from hermes_cli.auth import resolve_api_key_provider_credentials
    +
    +            creds = resolve_api_key_provider_credentials("stepfun")
    +            api_key = [redacted:secret-assignment]"api_key") or "").strip()
    +            base_url = str(creds.get("base_url") or "").strip()
    +            if api_key and base_url:
    +                live = fetch_api_models(api_key, base_url)
    +                if live:
    +                    return live
    +        except Exception:
    +            pass
         if normalized == "anthropic":
             live = _fetch_anthropic_models()
             if live:
    ```
  - `hermes_cli/providers.py`

    ```diff
    @@ -94,6 +94,12 @@ class HermesOverlay:
             transport="openai_chat",
             base_url_env_var="KIMI_BASE_URL",
         ),
    +    "stepfun": HermesOverlay(
    +        transport="openai_chat",
    +        extra_env_vars=("STEPFUN_API_KEY",),
    +        base_url_override="https://api.stepfun.ai/step_plan/v1",
    +        base_url_env_var="STEPFUN_BASE_URL",
    +    ),
         "minimax": HermesOverlay(
             transport="anthropic_messages",
             base_url_env_var="MINIMAX_BASE_URL",
    ```

    ```diff
    @@ -210,6 +216,10 @@ class ProviderDef:
         "kimi-coding-cn": "kimi-for-coding",
         "moonshot": "kimi-for-coding",
     
    +    # stepfun
    +    "step": "stepfun",
    +    "stepfun-coding-plan": "stepfun",
    +
         # minimax-cn
         "minimax-china": "minimax-cn",
         "minimax_cn": "minimax-cn",
    ```

    ```diff
    @@ -294,6 +304,7 @@ class ProviderDef:
         "nous": "Nous Portal",
         "openai-codex": "OpenAI Codex",
         "copilot-acp": "GitHub Copilot ACP",
    +    "stepfun": "StepFun Step Plan",
         "xiaomi": "Xiaomi MiMo",
         "local": "Local endpoint",
         "bedrock": "AWS Bedrock",
    ```
  - `hermes_cli/setup.py`

    ```diff
    @@ -96,6 +96,7 @@ def _supports_same_provider_pool_setup(provider: str) -> bool:
         "zai": ["glm-5.1", "glm-5", "glm-4.7", "glm-4.5", "glm-4.5-flash"],
         "kimi-coding": ["kimi-k2.6", "kimi-k2.5", "kimi-k2-thinking", "kimi-k2-turbo-preview"],
         "kimi-coding-cn": ["kimi-k2.6", "kimi-k2.5", "kimi-k2-thinking", "kimi-k2-turbo-preview"],
    +    "stepfun": ["step-3.5-flash", "step-3.5-flash-2603"],
         "arcee": ["trinity-large-thinking", "trinity-large-preview", "trinity-mini"],
         "minimax": ["MiniMax-M2.7", "MiniMax-M2.5", "MiniMax-M2.1", "MiniMax-M2"],
         "minimax-cn": ["MiniMax-M2.7", "MiniMax-M2.5", "MiniMax-M2.1", "MiniMax-M2"],
    ```

    ```diff
    @@ -804,6 +805,7 @@ def setup_model_provider(config: dict, *, quick: bool = False):
                 "zai": "Z.AI / GLM",
                 "kimi-coding": "Kimi / Moonshot",
                 "kimi-coding-cn": "Kimi / Moonshot (China)",
    +            "stepfun": "StepFun Step Plan",
                 "minimax": "MiniMax",
                 "minimax-cn": "MiniMax CN",
                 "anthropic": "Anthropic",
    ```
  - `hermes_cli/status.py`

    ```diff
    @@ -122,6 +122,7 @@ def show_status(args):
             "OpenAI": "OPENAI_API_KEY",
             "Z.AI/GLM": "GLM_API_KEY",
             "Kimi": "KIMI_API_KEY",
    +        "StepFun Step Plan": "STEPFUN_API_KEY",
             "MiniMax": "MINIMAX_API_KEY",
             "MiniMax-CN": "MINIMAX_CN_API_KEY",
             "Firecrawl": "FIRECRAWL_API_KEY",
    ```

    ```diff
    @@ -252,6 +253,7 @@ def show_status(args):
         apikey_providers = {
             "Z.AI / GLM":       ("GLM_API_KEY", "ZAI_API_KEY", "Z_AI_API_KEY"),
             "Kimi / Moonshot":  ("KIMI_API_KEY",),
    +        "StepFun Step Plan": ("STEPFUN_API_KEY",),
             "MiniMax":          ("MINIMAX_API_KEY",),
             "MiniMax (China)":  ("MINIMAX_CN_API_KEY",),
         }
    ```
  - `tests/agent/test_model_metadata.py`

    ```diff
    @@ -385,6 +385,7 @@ def test_known_provider_prefix_is_stripped(self):
             assert _strip_provider_prefix("local:my-model") == "my-model"
             assert _strip_provider_prefix("openrouter:anthropic/claude-sonnet-4") == "anthropic/claude-sonnet-4"
             assert _strip_provider_prefix("anthropic:claude-sonnet-4") == "claude-sonnet-4"
    +        assert _strip_provider_prefix("stepfun:step-3.5-flash") == "step-3.5-flash"
     
         def test_ollama_model_tag_preserved(self):
             """Ollama model:tag format must NOT be stripped."""
    ```
  - `tests/agent/test_models_dev.py`

    ```diff
    @@ -82,6 +82,7 @@ def test_all_mapped_providers_are_strings(self):
         def test_known_providers_mapped(self):
             assert PROVIDER_TO_MODELS_DEV["anthropic"] == "anthropic"
             assert PROVIDER_TO_MODELS_DEV["copilot"] == "github-copilot"
    +        assert PROVIDER_TO_MODELS_DEV["stepfun"] == "stepfun"
             assert PROVIDER_TO_MODELS_DEV["kilocode"] == "kilo"
             assert PROVIDER_TO_MODELS_DEV["ai-gateway"] == "vercel"
     
    ```
  - `tests/hermes_cli/test_api_key_providers.py`

    ```diff
    @@ -15,6 +15,8 @@
         get_auth_status,
         AuthError,
         KIMI_CODE_BASE_URL,
    +    STEPFUN_STEP_PLAN_INTL_BASE_URL,
    +    STEPFUN_STEP_PLAN_CN_BASE_URL,
         _resolve_kimi_base_url,
     )
     from hermes_cli.copilot_auth import _try_gh_cli_token
    ```

    ```diff
    @@ -35,6 +37,7 @@ class TestProviderRegistry:
             ("xai", "xAI", "api_key"),
             ("nvidia", "NVIDIA NIM", "api_key"),
             ("kimi-coding", "Kimi / Moonshot", "api_key"),
    +        ("stepfun", "StepFun Step Plan", "api_key"),
             ("minimax", "MiniMax", "api_key"),
             ("minimax-cn", "MiniMax (China)", "api_key"),
             ("ai-gateway", "Vercel AI Gateway", "api_key"),
    ```

    ```diff
    @@ -83,6 +86,11 @@ def test_minimax_env_vars(self):
             assert pconfig.api_key_env_vars == ("MINIMAX_API_KEY",)
             assert pconfig.base_url_env_var == "MINIMAX_BASE_URL"
     
    +    def test_stepfun_env_vars(self):
    +        pconfig = PROVIDER_REGISTRY["stepfun"]
    +        assert pconfig.api_key_env_vars == ("STEPFUN_API_KEY",)
    +        assert pconfig.base_url_env_var == "STEPFUN_BASE_URL"
    +
         def test_minimax_cn_env_vars(self):
             pconfig = PROVIDER_REGISTRY["minimax-cn"]
             assert pconfig.api_key_env_vars == ("MINIMAX_CN_API_KEY",)
    ```

    ```diff
    @@ -108,6 +116,7 @@ def test_base_urls(self):
             assert PROVIDER_REGISTRY["copilot-acp"].inference_base_url == "acp://copilot"
             assert PROVIDER_REGISTRY["zai"].inference_base_url == "https://api.z.ai/api/paas/v4"
             assert PROVIDER_REGISTRY["kimi-coding"].inference_base_url == "https://api.moonshot.ai/v1"
    +        assert PROVIDER_REGISTRY["stepfun"].inference_base_url == STEPFUN_STEP_PLAN_INTL_BASE_URL
             assert PROVIDER_REGISTRY["minimax"].inference_base_url == "https://api.minimax.io/anthropic"
             assert PROVIDER_REGISTRY["minimax-cn"].inference_base_url == "https://api.minimaxi.com/anthropic"
             assert PROVIDER_REGISTRY["ai-gateway"].inference_base_url == "https://ai-gateway.vercel.sh/v1"
    ```

    ```diff
    @@ -130,7 +139,8 @@ def test_oauth_providers_unchanged(self):
         "OPENROUTER_API_KEY", "OPENAI_API_KEY", "ANTHROPIC_API_KEY", "ANTHROPIC_TOKEN",
         "CLAUDE_CODE_OAUTH_TOKEN",
         "GLM_API_KEY", "ZAI_API_KEY", "Z_AI_API_KEY",
    -    "KIMI_API_KEY", "KIMI_BASE_URL", "MINIMAX_API_KEY", "MINIMAX_CN_API_KEY",
    +    "KIMI_API_KEY", "KIMI_BASE_URL", "STEPFUN_API_KEY", "STEPFUN_BASE_URL",
    +    "MINIMAX_API_KEY", "MINIMAX_CN_API_KEY",
         "AI_GATEWAY_API_KEY", "AI_GATEWAY_BASE_URL",
         "KILOCODE_API_KEY", "KILOCODE_BASE_URL",
         "DASHSCOPE_API_KEY", "OPENCODE_ZEN_API_KEY", "OPENCODE_GO_API_KEY",
    ```

    ```diff
    @@ -156,6 +166,9 @@ def test_explicit_zai(self):
         def test_explicit_kimi_coding(self):
             assert resolve_provider("kimi-coding") == "kimi-coding"
     
    +    def test_explicit_stepfun(self):
    +        assert resolve_provider("stepfun") == "stepfun"
    +
         def test_explicit_minimax(self):
             assert resolve_provider("minimax") == "minimax"
     
    ```

    ```diff
    @@ -180,6 +193,9 @@ def test_alias_kimi(self):
         def test_alias_moonshot(self):
             assert resolve_provider("moonshot") == "kimi-coding"
     
    +    def test_alias_step(self):
    +        assert resolve_provider("step") == "stepfun"
    +
         def test_alias_minimax_underscore(self):
             assert resolve_provider("minimax_cn") == "minimax-cn"
     
    ```

    ```diff
    @@ -248,6 +264,10 @@ def test_auto_detects_kimi_key(self, monkeypatch):
             monkeypatch.setenv("KIMI_API_KEY", "test-kimi-key")
             assert resolve_provider("auto") == "kimi-coding"
     
    +    def test_auto_detects_stepfun_key(self, monkeypatch):
    +        monkeypatch.setenv("STEPFUN_API_KEY", "test-stepfun-key")
    +        assert resolve_provider("auto") == "stepfun"
    +
         def test_auto_detects_minimax_key(self, monkeypatch):
             monkeypatch.setenv("MINIMAX_API_KEY", "test-mm-key")
             assert resolve_provider("auto") == "minimax"
    ```

    ```diff
    @@ -312,6 +332,13 @@ def test_custom_base_url(self, monkeypatch):
             status = get_api_key_provider_status("kimi-coding")
             assert status["base_url"] == "https://custom.kimi.example/v1"
     
    +    def test_stepfun_status_uses_configured_base_url(self, monkeypatch):
    +        monkeypatch.setenv("STEPFUN_API_KEY", "stepfun-key")
    +        monkeypatch.setenv("STEPFUN_BASE_URL", STEPFUN_STEP_PLAN_CN_BASE_URL)
    +        status = get_api_key_provider_status("stepfun")
    +        assert status["configured"] is True
    +        assert status["base_url"] == STEPFUN_STEP_PLAN_CN_BASE_URL
    +
         def test_copilot_status_uses_gh_cli_token(self, monkeypatch):
             monkeypatch.setattr("hermes_cli.copilot_auth._try_gh_cli_token", lambda: "gho_gh_cli_token")
             status = get_api_key_provider_status("copilot")
    ```

    ```diff
    @@ -429,6 +456,19 @@ def test_resolve_kimi_with_key(self, monkeypatch):
             assert creds["api_key"] == "kimi-secret-key"
             assert creds["base_url"] == "https://api.moonshot.ai/v1"
     
    +    def test_resolve_stepfun_with_key(self, monkeypatch):
    +        monkeypatch.setenv("STEPFUN_API_KEY", "stepfun-secret-key")
    +        creds = resolve_api_key_provider_credentials("stepfun")
    +        assert creds["provider"] == "stepfun"
    +        assert creds["api_key"] == "stepfun-secret-key"
    +        assert creds["base_url"] == STEPFUN_STEP_PLAN_INTL_BASE_URL
    +
    +    def test_resolve_stepfun_custom_base_url(self, monkeypatch):
    +        monkeypatch.setenv("STEPFUN_API_KEY", "stepfun-secret-key")
    +        monkeypatch.setenv("STEPFUN_BASE_URL", STEPFUN_STEP_PLAN_CN_BASE_URL)
    +        creds = resolve_api_key_provider_credentials("stepfun")
    +        assert creds["base_url"] == STEPFUN_STEP_PLAN_CN_BASE_URL
    +
         def test_resolve_minimax_with_key(self, monkeypatch):
             monkeypatch.setenv("MINIMAX_API_KEY", "mm-secret-key")
             creds = resolve_api_key_provider_credentials("minimax")
    ```

    ```diff
    @@ -519,6 +559,16 @@ def test_runtime_kimi(self, monkeypatch):
             assert result["api_mode"] == "chat_completions"
             assert result["api_key"] == "kimi-key"
     
    +    def test_runtime_stepfun(self, monkeypatch):
    +        monkeypatch.setenv("STEPFUN_API_KEY", "stepfun-key")
    +        monkeypatch.setenv("STEPFUN_BASE_URL", STEPFUN_STEP_PLAN_CN_BASE_URL)
    +        from hermes_cli.runtime_provider import resolve_runtime_provider
    +        result = resolve_runtime_provider(requested="stepfun")
    +        assert result["provider"] == "stepfun"
    +        assert result["api_mode"] == "chat_completions"
    +        assert result["api_key"] == "stepfun-key"
    +        assert result["base_url"] == STEPFUN_STEP_PLAN_CN_BASE_URL
    +
         def test_runtime_minimax(self, monkeypatch):
             monkeypatch.setenv("MINIMAX_API_KEY", "mm-key")
             from hermes_cli.runtime_provider import resolve_runtime_provider
    ```
  - `tests/hermes_cli/test_model_provider_persistence.py`

    ```diff
    @@ -32,6 +32,8 @@ def config_home(tmp_path, monkeypatch):
         monkeypatch.delenv("OPENAI_BASE_URL", raising=False)
         monkeypatch.delenv("OPENAI_API_KEY", raising=False)
         monkeypatch.delenv("OPENROUTER_API_KEY", raising=False)
    +    monkeypatch.delenv("STEPFUN_API_KEY", raising=False)
    +    monkeypatch.delenv("STEPFUN_BASE_URL", raising=False)
         return home
     
     
    ```

    ```diff
    @@ -330,3 +332,33 @@ def test_empty_base_url_keeps_default(self, config_home, monkeypatch):
     
             saved = get_env_value("GLM_BASE_URL") or ""
             assert saved == "", "Empty input should not save a base URL"
    +
    +    def test_stepfun_provider_saved_with_selected_region(self, config_home, monkeypatch):
    +        from hermes_cli.main import _model_flow_stepfun
    +        from hermes_cli.config import load_config, get_env_value
    +
    +        monkeypatch.setenv("STEPFUN_API_KEY", "stepfun-test-key")
    +
    +        with patch(
    +            "hermes_cli.main._prompt_provider_choice",
    +            return_value=1,
    +        ), patch(
    +            "hermes_cli.models.fetch_api_models",
    +            return_value=["step-3.5-flash", "step-3-agent-lite"],
    +        ), patch(
    +            "hermes_cli.auth._prompt_model_selection",
    +            return_value="step-3-agent-lite",
    +        ), patch(
    +            "hermes_cli.auth.deactivate_provider",
    +        ):
    +            _model_flow_stepfun(load_config(), "old-model")
    +
    +        import yaml
    +
    +        config = yaml.safe_load((config_home / "config.yaml").read_text()) or {}
    +        model = config.get("model")
    +        assert isinstance(model, dict)
    +        assert model.get("provider") == "stepfun"
    +        assert model.get("default") == "step-3-agent-lite"
    +        assert model.get("base_url") == "https://api.stepfun.com/step_plan/v1"
    +        assert get_env_value("STEPFUN_BASE_URL") == "https://api.stepfun.com/step_plan/v1"
    ```
  - `tests/hermes_cli/test_model_validation.py`

    ```diff
    @@ -63,6 +63,11 @@ def test_provider_alias_resolved(self):
             assert provider == "zai"
             assert model == "glm-5"
     
    +    def test_stepfun_alias_resolved(self):
    +        provider, model = parse_model_input("step:step-3.5-flash", "openrouter")
    +        assert provider == "stepfun"
    +        assert model == "step-3.5-flash"
    +
         def test_no_slash_no_colon_keeps_provider(self):
             provider, model = parse_model_input("gpt-5.4", "openrouter")
             assert provider == "openrouter"
    ```

    ```diff
    @@ -154,6 +159,7 @@ def test_known_aliases(self):
             assert normalize_provider("glm") == "zai"
             assert normalize_provider("kimi") == "kimi-coding"
             assert normalize_provider("moonshot") == "kimi-coding"
    +        assert normalize_provider("step") == "stepfun"
             assert normalize_provider("github-copilot") == "copilot"
     
         def test_case_insensitive(self):
    ```

    ```diff
    @@ -164,6 +170,7 @@ class TestProviderLabel:
         def test_known_labels_and_auto(self):
             assert provider_label("anthropic") == "Anthropic"
             assert provider_label("kimi") == "Kimi / Kimi Coding Plan"
    +        assert provider_label("stepfun") == "StepFun Step Plan"
             assert provider_label("copilot") == "GitHub Copilot"
             assert provider_label("copilot-acp") == "GitHub Copilot ACP"
             assert provider_label("auto") == "Auto"
    ```

    ```diff
    @@ -193,6 +200,16 @@ def test_unknown_provider_returns_empty(self):
         def test_zai_returns_glm_models(self):
             assert "glm-5" in provider_model_ids("zai")
     
    +    def test_stepfun_prefers_live_catalog(self):
    +        with patch(
    +            "hermes_cli.auth.resolve_api_key_provider_credentials",
    +            return_value={"api_key": "***", "base_url": "https://api.stepfun.com/step_plan/v1"},
    +        ), patch(
    +            "hermes_cli.models.fetch_api_models",
    +            return_value=["step-3.5-flash", "step-3-agent-lite"],
    +        ):
    +            assert provider_model_ids("stepfun") == ["step-3.5-flash", "step-3-agent-lite"]
    +
         def test_copilot_prefers_live_catalog(self):
             with patch("hermes_cli.auth.resolve_api_key_provider_credentials", return_value={"api_key": "[redacted:secret-assignment]"}), \
                  patch("hermes_cli.models._fetch_github_models", return_value=["gpt-5.4", "claude-sonnet-4.6"]):
    ```
- **#8879: feat: Add StepFun provider support**. closed without merge
  - maintainer direction: category `superseded_by_pr`; implication `superseded`; refs `#13893`

## Fix status

- Merged to main in PR #13893 (cited above). No release data is tracked. Verify downstream.
- Fix last verified (merged) `2026-04-22`. This confirmation is old and may be out of date (a revert/reopen would not have invalidated it here); re-verify.
