Nothing open on GitHub. Fix merged in #13893 on 22 Apr 2026. Check it covers your case.
The short answer
- Known?
- Yes. 3 related items grouped here, 1 issue already closed.
- Fix event
- A fix was merged to main in PR #13893. Check it covers your case. No release data is tracked.
- Tracker
- The maintainers' tracker marks this
unresolved. That is separate from the fix event. - Still open
- 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.
Open issues0
Open PRs0
Fixes on record
-
#13893 feat: add Step Plan provider support (salvage #6005)
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.pyShow the diff
-
agent/auxiliary_client.py@@ -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@@ -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", @@ -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", @@ -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@@ -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@@ -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 @@ -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", @@ -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@@ -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@@ -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@@ -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 ( @@ -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. @@ -6530,6 +6666,7 @@ def main(): "zai", "kimi-coding", "kimi-coding-cn", + "stepfun", "minimax", "minimax-cn", "kilocode", -
hermes_cli/model_switch.py@@ -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@@ -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", @@ -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)"), @@ -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", @@ -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@@ -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", @@ -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", @@ -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@@ -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"], @@ -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@@ -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", @@ -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@@ -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@@ -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@@ -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 @@ -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"), @@ -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",) @@ -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" @@ -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", @@ -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" @@ -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" @@ -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" @@ -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") @@ -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") @@ -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@@ -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 @@ -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@@ -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" @@ -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): @@ -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" @@ -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"]):
-
1 PR closed without merging
From the close comments: superseded by another PR 1
-
#8879 feat: Add StepFun provider support · superseded by another PR →
#13893
Closed issues · 1
- Closed#8853[Feature]: Add StepFun / Step Plan model provider support
How this cluster was grouped
-
Link evidence (
structural links throughout): every member is connected by structural evidence: shared changed files, or a closing/superseded reference. -
Pairwise link density
0.67: the share of item pairs that carry a link, counting a structural link in full and a relevance-only link at 0.36.1.00means every pair is linked.
Pick up a Lab task
Check whether a fix covers the open issues here, with your own agent. Bring back evidence for maintainers.
Open the LabAt a glance
- Merged PRs
- #13893
- Closed issues
- 1
- Attempts
- 1 PR closed without merging
- Fix link checked
- 22 Apr 2026. This confirmation is old and may be out of date (a revert or reopen would not have invalidated it here); re-verify.