diff --git a/AGENTS.md b/AGENTS.md index 1e356bc4b..77f6ff68e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -3,6 +3,22 @@ This file defines the default working protocol for coding agents in this repository. Scope: entire repository. +## 0) Session Default Target (Mandatory) + +- When operator intent does not explicitly specify another repository/path, treat the active coding target as this repository (`/home/ubuntu/zeroclaw`). +- Do not switch to or implement in other repositories unless the operator explicitly requests that scope in the current conversation. +- Ambiguous wording (for example "这个仓库", "当前项目", "the repo") is resolved to `/home/ubuntu/zeroclaw` by default. +- Context mentioning external repositories does not authorize cross-repo edits; explicit current-turn override is required. +- Before any repo-affecting action, verify target lock (`pwd` + git root) to prevent accidental execution in sibling repositories. + +## 0.1) Clean Worktree First Gate (Mandatory) + +- Before handling any repository content (analysis, debugging, coding, tests, docs, CI), create a **new clean dedicated git worktree** for the active task. +- Do not perform substantive task work in a dirty workspace. +- Do not reuse a previously dirty worktree for a new task track. +- If the current location is dirty, stop and bootstrap a clean worktree/branch first. +- If worktree bootstrap fails, stop and report the blocker; do not continue in-place. + ## 1) Project Snapshot (Read First) ZeroClaw is a Rust-first autonomous agent runtime optimized for: diff --git a/docs/i18n/fr/providers-reference.md b/docs/i18n/fr/providers-reference.md index 7f3a4f8ef..6eaa7252b 100644 --- a/docs/i18n/fr/providers-reference.md +++ b/docs/i18n/fr/providers-reference.md @@ -20,3 +20,21 @@ Source anglaise: ## Notes de mise à jour - Ajout d'un réglage `provider.reasoning_level` pour le niveau de raisonnement OpenAI Codex. Voir la source anglaise pour les détails. +- 2026-03-01: ajout de la prise en charge du provider StepFun (`stepfun`, alias `step`, `step-ai`, `step_ai`). + +## StepFun (Résumé) + +- Provider ID: `stepfun` +- Aliases: `step`, `step-ai`, `step_ai` +- Base API URL: `https://api.stepfun.com/v1` +- Endpoints: `POST /v1/chat/completions`, `GET /v1/models` +- Auth env var: `STEP_API_KEY` (fallback: `STEPFUN_API_KEY`) +- Modèle par défaut: `step-3.5-flash` + +Validation rapide: + +```bash +export STEP_API_KEY="your-stepfun-api-key" +zeroclaw models refresh --provider stepfun +zeroclaw agent --provider stepfun --model step-3.5-flash -m "ping" +``` diff --git a/docs/i18n/ja/providers-reference.md b/docs/i18n/ja/providers-reference.md index 78af95755..7fc2db3b9 100644 --- a/docs/i18n/ja/providers-reference.md +++ b/docs/i18n/ja/providers-reference.md @@ -16,3 +16,24 @@ - Provider ID と環境変数名は英語のまま保持します。 - 正式な仕様は英語版原文を優先します。 + +## 更新ノート + +- 2026-03-01: StepFun provider 対応を追加(`stepfun`、alias: `step` / `step-ai` / `step_ai`)。 + +## StepFun クイックガイド + +- Provider ID: `stepfun` +- Aliases: `step`, `step-ai`, `step_ai` +- Base API URL: `https://api.stepfun.com/v1` +- Endpoints: `POST /v1/chat/completions`, `GET /v1/models` +- 認証 env var: `STEP_API_KEY`(fallback: `STEPFUN_API_KEY`) +- 既定モデル: `step-3.5-flash` + +クイック検証: + +```bash +export STEP_API_KEY="your-stepfun-api-key" +zeroclaw models refresh --provider stepfun +zeroclaw agent --provider stepfun --model step-3.5-flash -m "ping" +``` diff --git a/docs/i18n/ru/providers-reference.md b/docs/i18n/ru/providers-reference.md index ec5b48c9c..fec23b11f 100644 --- a/docs/i18n/ru/providers-reference.md +++ b/docs/i18n/ru/providers-reference.md @@ -16,3 +16,24 @@ - Provider ID и имена env переменных не переводятся. - Нормативное описание поведения — в английском оригинале. + +## Обновления + +- 2026-03-01: добавлена поддержка провайдера StepFun (`stepfun`, алиасы `step`, `step-ai`, `step_ai`). + +## StepFun (Кратко) + +- Provider ID: `stepfun` +- Алиасы: `step`, `step-ai`, `step_ai` +- Base API URL: `https://api.stepfun.com/v1` +- Эндпоинты: `POST /v1/chat/completions`, `GET /v1/models` +- Переменная авторизации: `STEP_API_KEY` (fallback: `STEPFUN_API_KEY`) +- Модель по умолчанию: `step-3.5-flash` + +Быстрая проверка: + +```bash +export STEP_API_KEY="your-stepfun-api-key" +zeroclaw models refresh --provider stepfun +zeroclaw agent --provider stepfun --model step-3.5-flash -m "ping" +``` diff --git a/docs/i18n/vi/providers-reference.md b/docs/i18n/vi/providers-reference.md index 32b347644..f000768a6 100644 --- a/docs/i18n/vi/providers-reference.md +++ b/docs/i18n/vi/providers-reference.md @@ -2,7 +2,7 @@ Tài liệu này liệt kê các provider ID, alias và biến môi trường chứa thông tin xác thực. -Cập nhật lần cuối: **2026-02-28**. +Cập nhật lần cuối: **2026-03-01**. ## Cách liệt kê các Provider @@ -33,6 +33,7 @@ Với chuỗi provider dự phòng (`reliability.fallback_providers`), mỗi pro | `vercel` | `vercel-ai` | Không | `VERCEL_API_KEY` | | `cloudflare` | `cloudflare-ai` | Không | `CLOUDFLARE_API_KEY` | | `moonshot` | `kimi` | Không | `MOONSHOT_API_KEY` | +| `stepfun` | `step`, `step-ai`, `step_ai` | Không | `STEP_API_KEY`, `STEPFUN_API_KEY` | | `kimi-code` | `kimi_coding`, `kimi_for_coding` | Không | `KIMI_CODE_API_KEY`, `MOONSHOT_API_KEY` | | `synthetic` | — | Không | `SYNTHETIC_API_KEY` | | `opencode` | `opencode-zen` | Không | `OPENCODE_API_KEY` | @@ -87,6 +88,29 @@ zeroclaw models refresh --provider volcengine zeroclaw agent --provider volcengine --model doubao-1-5-pro-32k-250115 -m "ping" ``` +### Ghi chú về StepFun + +- Provider ID: `stepfun` (alias: `step`, `step-ai`, `step_ai`) +- Base API URL: `https://api.stepfun.com/v1` +- Chat endpoint: `/chat/completions` +- Model discovery endpoint: `/models` +- Xác thực: `STEP_API_KEY` (fallback: `STEPFUN_API_KEY`) +- Model mặc định: `step-3.5-flash` + +Ví dụ thiết lập nhanh: + +```bash +export STEP_API_KEY="your-stepfun-api-key" +zeroclaw onboard --provider stepfun --api-key "$STEP_API_KEY" --model step-3.5-flash --force +``` + +Kiểm tra nhanh: + +```bash +zeroclaw models refresh --provider stepfun +zeroclaw agent --provider stepfun --model step-3.5-flash -m "ping" +``` + ### Ghi chú về SiliconFlow - Provider ID: `siliconflow` (alias: `silicon-cloud`, `siliconcloud`) diff --git a/docs/i18n/zh-CN/providers-reference.md b/docs/i18n/zh-CN/providers-reference.md index bb6268b00..326be0866 100644 --- a/docs/i18n/zh-CN/providers-reference.md +++ b/docs/i18n/zh-CN/providers-reference.md @@ -16,3 +16,25 @@ - Provider ID 与环境变量名称保持英文。 - 规范与行为说明以英文原文为准。 + +## 更新记录 + +- 2026-03-01:新增 StepFun provider 对齐信息(`stepfun` / `step` / `step-ai` / `step_ai`)。 + +## StepFun 快速说明 + +- Provider ID:`stepfun` +- 别名:`step`、`step-ai`、`step_ai` +- Base API URL:`https://api.stepfun.com/v1` +- 模型列表端点:`GET /v1/models` +- 对话端点:`POST /v1/chat/completions` +- 鉴权变量:`STEP_API_KEY`(回退:`STEPFUN_API_KEY`) +- 默认模型:`step-3.5-flash` + +快速验证: + +```bash +export STEP_API_KEY="your-stepfun-api-key" +zeroclaw models refresh --provider stepfun +zeroclaw agent --provider stepfun --model step-3.5-flash -m "ping" +``` diff --git a/docs/providers-reference.md b/docs/providers-reference.md index 1a490422e..ab41d6352 100644 --- a/docs/providers-reference.md +++ b/docs/providers-reference.md @@ -2,7 +2,7 @@ This document maps provider IDs, aliases, and credential environment variables. -Last verified: **February 28, 2026**. +Last verified: **March 1, 2026**. ## How to List Providers @@ -35,6 +35,7 @@ credential is not reused for fallback providers. | `vercel` | `vercel-ai` | No | `VERCEL_API_KEY` | | `cloudflare` | `cloudflare-ai` | No | `CLOUDFLARE_API_KEY` | | `moonshot` | `kimi` | No | `MOONSHOT_API_KEY` | +| `stepfun` | `step`, `step-ai`, `step_ai` | No | `STEP_API_KEY`, `STEPFUN_API_KEY` | | `kimi-code` | `kimi_coding`, `kimi_for_coding` | No | `KIMI_CODE_API_KEY`, `MOONSHOT_API_KEY` | | `synthetic` | — | No | `SYNTHETIC_API_KEY` | | `opencode` | `opencode-zen` | No | `OPENCODE_API_KEY` | @@ -137,6 +138,33 @@ zeroclaw models refresh --provider volcengine zeroclaw agent --provider volcengine --model doubao-1-5-pro-32k-250115 -m "ping" ``` +### StepFun Notes + +- Provider ID: `stepfun` (aliases: `step`, `step-ai`, `step_ai`) +- Base API URL: `https://api.stepfun.com/v1` +- Chat endpoint: `/chat/completions` +- Model discovery endpoint: `/models` +- Authentication: `STEP_API_KEY` (fallback: `STEPFUN_API_KEY`) +- Default model preset: `step-3.5-flash` +- Official docs: + - Chat Completions: + - Models List: + - OpenAI migration guide: + +Minimal setup example: + +```bash +export STEP_API_KEY="your-stepfun-api-key" +zeroclaw onboard --provider stepfun --api-key "$STEP_API_KEY" --model step-3.5-flash --force +``` + +Quick validation: + +```bash +zeroclaw models refresh --provider stepfun +zeroclaw agent --provider stepfun --model step-3.5-flash -m "ping" +``` + ### SiliconFlow Notes - Provider ID: `siliconflow` (aliases: `silicon-cloud`, `siliconcloud`) diff --git a/src/config/schema.rs b/src/config/schema.rs index c99c31779..0a9c6ed25 100644 --- a/src/config/schema.rs +++ b/src/config/schema.rs @@ -77,6 +77,7 @@ pub fn default_model_fallback_for_provider(provider_name: Option<&str>) -> &'sta "together-ai" => "meta-llama/Llama-3.3-70B-Instruct-Turbo", "cohere" => "command-a-03-2025", "moonshot" => "kimi-k2.5", + "stepfun" => "step-3.5-flash", "hunyuan" => "hunyuan-t1-latest", "glm" | "zai" => "glm-5", "minimax" => "MiniMax-M2.5", @@ -11817,6 +11818,9 @@ provider_api = "not-a-real-mode" let openai = resolve_default_model_id(None, Some("openai")); assert_eq!(openai, "gpt-5.2"); + let stepfun = resolve_default_model_id(None, Some("stepfun")); + assert_eq!(stepfun, "step-3.5-flash"); + let bedrock = resolve_default_model_id(None, Some("aws-bedrock")); assert_eq!(bedrock, "anthropic.claude-sonnet-4-5-20250929-v1:0"); } @@ -11828,6 +11832,12 @@ provider_api = "not-a-real-mode" let google_alias = resolve_default_model_id(None, Some("google-gemini")); assert_eq!(google_alias, "gemini-2.5-pro"); + + let step_alias = resolve_default_model_id(None, Some("step")); + assert_eq!(step_alias, "step-3.5-flash"); + + let step_ai_alias = resolve_default_model_id(None, Some("step-ai")); + assert_eq!(step_ai_alias, "step-3.5-flash"); } #[test] diff --git a/src/integrations/registry.rs b/src/integrations/registry.rs index 23dd2857b..455e62fdb 100644 --- a/src/integrations/registry.rs +++ b/src/integrations/registry.rs @@ -1,7 +1,7 @@ use super::{IntegrationCategory, IntegrationEntry, IntegrationStatus}; use crate::providers::{ is_doubao_alias, is_glm_alias, is_minimax_alias, is_moonshot_alias, is_qianfan_alias, - is_qwen_alias, is_siliconflow_alias, is_zai_alias, + is_qwen_alias, is_siliconflow_alias, is_stepfun_alias, is_zai_alias, }; /// Returns the full catalog of integrations @@ -352,6 +352,18 @@ pub fn all_integrations() -> Vec { } }, }, + IntegrationEntry { + name: "StepFun", + description: "Step 3, Step 3.5 Flash, and vision models", + category: IntegrationCategory::AiModel, + status_fn: |c| { + if c.default_provider.as_deref().is_some_and(is_stepfun_alias) { + IntegrationStatus::Active + } else { + IntegrationStatus::Available + } + }, + }, IntegrationEntry { name: "Synthetic", description: "Synthetic-1 and synthetic family models", @@ -1020,6 +1032,13 @@ mod tests { IntegrationStatus::Active )); + config.default_provider = Some("step-ai".to_string()); + let stepfun = entries.iter().find(|e| e.name == "StepFun").unwrap(); + assert!(matches!( + (stepfun.status_fn)(&config), + IntegrationStatus::Active + )); + config.default_provider = Some("qwen-intl".to_string()); let qwen = entries.iter().find(|e| e.name == "Qwen").unwrap(); assert!(matches!( diff --git a/src/onboard/wizard.rs b/src/onboard/wizard.rs index a5668a59a..d3f311b7a 100644 --- a/src/onboard/wizard.rs +++ b/src/onboard/wizard.rs @@ -25,7 +25,7 @@ use crate::migration::{ use crate::providers::{ canonical_china_provider_name, is_doubao_alias, is_glm_alias, is_glm_cn_alias, is_minimax_alias, is_moonshot_alias, is_qianfan_alias, is_qwen_alias, is_qwen_oauth_alias, - is_siliconflow_alias, is_zai_alias, is_zai_cn_alias, + is_siliconflow_alias, is_stepfun_alias, is_zai_alias, is_zai_cn_alias, }; use anyhow::{bail, Context, Result}; use console::style; @@ -966,6 +966,7 @@ fn default_model_for_provider(provider: &str) -> String { "together-ai" => "meta-llama/Llama-3.3-70B-Instruct-Turbo".into(), "cohere" => "command-a-03-2025".into(), "moonshot" => "kimi-k2.5".into(), + "stepfun" => "step-3.5-flash".into(), "hunyuan" => "hunyuan-t1-latest".into(), "glm" | "zai" => "glm-5".into(), "minimax" => "MiniMax-M2.5".into(), @@ -1246,6 +1247,24 @@ fn curated_models_for_provider(provider_name: &str) -> Vec<(String, String)> { "Kimi K2 0905 Preview (strong coding)".to_string(), ), ], + "stepfun" => vec![ + ( + "step-3.5-flash".to_string(), + "Step 3.5 Flash (recommended default)".to_string(), + ), + ( + "step-3".to_string(), + "Step 3 (flagship reasoning)".to_string(), + ), + ( + "step-2-mini".to_string(), + "Step 2 Mini (balanced and fast)".to_string(), + ), + ( + "step-1o-turbo-vision".to_string(), + "Step 1o Turbo Vision (multimodal)".to_string(), + ), + ], "glm" | "zai" => vec![ ("glm-5".to_string(), "GLM-5 (high reasoning)".to_string()), ( @@ -1483,6 +1502,7 @@ fn supports_live_model_fetch(provider_name: &str) -> bool { | "novita" | "cohere" | "moonshot" + | "stepfun" | "glm" | "zai" | "qwen" @@ -1515,6 +1535,7 @@ fn models_endpoint_for_provider(provider_name: &str) -> Option<&'static str> { "novita" => Some("https://api.novita.ai/openai/v1/models"), "cohere" => Some("https://api.cohere.com/compatibility/v1/models"), "moonshot" => Some("https://api.moonshot.ai/v1/models"), + "stepfun" => Some("https://api.stepfun.com/v1/models"), "glm" => Some("https://api.z.ai/api/paas/v4/models"), "zai" => Some("https://api.z.ai/api/coding/paas/v4/models"), "qwen" => Some("https://dashscope.aliyuncs.com/compatible-mode/v1/models"), @@ -2515,6 +2536,7 @@ async fn setup_provider(workspace_dir: &Path) -> Result<(String, String, String, "moonshot-intl", "Moonshot — Kimi API (international endpoint)", ), + ("stepfun", "StepFun — Step AI OpenAI-compatible endpoint"), ("glm", "GLM — ChatGLM / Zhipu (international endpoint)"), ("glm-cn", "GLM — ChatGLM / Zhipu (China endpoint)"), ( @@ -2934,6 +2956,8 @@ async fn setup_provider(workspace_dir: &Path) -> Result<(String, String, String, "https://console.volcengine.com/ark/region:ark+cn-beijing/apiKey" } else if is_siliconflow_alias(provider_name) { "https://cloud.siliconflow.cn/account/ak" + } else if is_stepfun_alias(provider_name) { + "https://platform.stepfun.com/interface-key" } else { match provider_name { "openrouter" => "https://openrouter.ai/keys", @@ -3239,6 +3263,7 @@ fn provider_env_var(name: &str) -> &'static str { "cohere" => "COHERE_API_KEY", "kimi-code" => "KIMI_CODE_API_KEY", "moonshot" => "MOONSHOT_API_KEY", + "stepfun" => "STEP_API_KEY", "glm" => "GLM_API_KEY", "minimax" => "MINIMAX_API_KEY", "qwen" => "DASHSCOPE_API_KEY", @@ -7817,6 +7842,7 @@ mod tests { ); assert_eq!(default_model_for_provider("venice"), "zai-org-glm-5"); assert_eq!(default_model_for_provider("moonshot"), "kimi-k2.5"); + assert_eq!(default_model_for_provider("stepfun"), "step-3.5-flash"); assert_eq!(default_model_for_provider("hunyuan"), "hunyuan-t1-latest"); assert_eq!(default_model_for_provider("tencent"), "hunyuan-t1-latest"); assert_eq!( @@ -7858,6 +7884,9 @@ mod tests { assert_eq!(canonical_provider_name("openai_codex"), "openai-codex"); assert_eq!(canonical_provider_name("moonshot-intl"), "moonshot"); assert_eq!(canonical_provider_name("kimi-cn"), "moonshot"); + assert_eq!(canonical_provider_name("step"), "stepfun"); + assert_eq!(canonical_provider_name("step-ai"), "stepfun"); + assert_eq!(canonical_provider_name("step_ai"), "stepfun"); assert_eq!(canonical_provider_name("kimi_coding"), "kimi-code"); assert_eq!(canonical_provider_name("kimi_for_coding"), "kimi-code"); assert_eq!(canonical_provider_name("glm-cn"), "glm"); @@ -7959,6 +7988,19 @@ mod tests { assert!(!ids.contains(&"kimi-thinking-preview".to_string())); } + #[test] + fn curated_models_for_stepfun_include_expected_defaults() { + let ids: Vec = curated_models_for_provider("stepfun") + .into_iter() + .map(|(id, _)| id) + .collect(); + + assert!(ids.contains(&"step-3.5-flash".to_string())); + assert!(ids.contains(&"step-3".to_string())); + assert!(ids.contains(&"step-2-mini".to_string())); + assert!(ids.contains(&"step-1o-turbo-vision".to_string())); + } + #[test] fn allows_unauthenticated_model_fetch_for_public_catalogs() { assert!(allows_unauthenticated_model_fetch("openrouter")); @@ -8046,6 +8088,9 @@ mod tests { assert!(supports_live_model_fetch("vllm")); assert!(supports_live_model_fetch("astrai")); assert!(supports_live_model_fetch("venice")); + assert!(supports_live_model_fetch("stepfun")); + assert!(supports_live_model_fetch("step")); + assert!(supports_live_model_fetch("step-ai")); assert!(supports_live_model_fetch("glm-cn")); assert!(supports_live_model_fetch("qwen-intl")); assert!(supports_live_model_fetch("qwen-coding-plan")); @@ -8120,6 +8165,14 @@ mod tests { curated_models_for_provider("volcengine"), curated_models_for_provider("ark") ); + assert_eq!( + curated_models_for_provider("stepfun"), + curated_models_for_provider("step") + ); + assert_eq!( + curated_models_for_provider("stepfun"), + curated_models_for_provider("step-ai") + ); assert_eq!( curated_models_for_provider("siliconflow"), curated_models_for_provider("silicon-cloud") @@ -8192,6 +8245,18 @@ mod tests { models_endpoint_for_provider("moonshot"), Some("https://api.moonshot.ai/v1/models") ); + assert_eq!( + models_endpoint_for_provider("stepfun"), + Some("https://api.stepfun.com/v1/models") + ); + assert_eq!( + models_endpoint_for_provider("step"), + Some("https://api.stepfun.com/v1/models") + ); + assert_eq!( + models_endpoint_for_provider("step-ai"), + Some("https://api.stepfun.com/v1/models") + ); assert_eq!( models_endpoint_for_provider("siliconflow"), Some("https://api.siliconflow.cn/v1/models") @@ -8497,6 +8562,9 @@ mod tests { assert_eq!(provider_env_var("minimax-oauth"), "MINIMAX_API_KEY"); assert_eq!(provider_env_var("minimax-oauth-cn"), "MINIMAX_API_KEY"); assert_eq!(provider_env_var("moonshot-intl"), "MOONSHOT_API_KEY"); + assert_eq!(provider_env_var("stepfun"), "STEP_API_KEY"); + assert_eq!(provider_env_var("step"), "STEP_API_KEY"); + assert_eq!(provider_env_var("step-ai"), "STEP_API_KEY"); assert_eq!(provider_env_var("zai-cn"), "ZAI_API_KEY"); assert_eq!(provider_env_var("doubao"), "ARK_API_KEY"); assert_eq!(provider_env_var("volcengine"), "ARK_API_KEY"); diff --git a/src/providers/mod.rs b/src/providers/mod.rs index d4a0cf431..1d51305be 100644 --- a/src/providers/mod.rs +++ b/src/providers/mod.rs @@ -83,6 +83,7 @@ const QWEN_OAUTH_CREDENTIAL_FILE: &str = ".qwen/oauth_creds.json"; const ZAI_GLOBAL_BASE_URL: &str = "https://api.z.ai/api/coding/paas/v4"; const ZAI_CN_BASE_URL: &str = "https://open.bigmodel.cn/api/coding/paas/v4"; const SILICONFLOW_BASE_URL: &str = "https://api.siliconflow.cn/v1"; +const STEPFUN_BASE_URL: &str = "https://api.stepfun.com/v1"; const VERCEL_AI_GATEWAY_BASE_URL: &str = "https://ai-gateway.vercel.sh/v1"; pub(crate) fn is_minimax_intl_alias(name: &str) -> bool { @@ -192,6 +193,10 @@ pub(crate) fn is_siliconflow_alias(name: &str) -> bool { matches!(name, "siliconflow" | "silicon-cloud" | "siliconcloud") } +pub(crate) fn is_stepfun_alias(name: &str) -> bool { + matches!(name, "stepfun" | "step" | "step-ai" | "step_ai") +} + #[derive(Clone, Copy, Debug)] enum MinimaxOauthRegion { Global, @@ -633,6 +638,8 @@ pub(crate) fn canonical_china_provider_name(name: &str) -> Option<&'static str> Some("doubao") } else if is_siliconflow_alias(name) { Some("siliconflow") + } else if is_stepfun_alias(name) { + Some("stepfun") } else if matches!(name, "hunyuan" | "tencent") { Some("hunyuan") } else { @@ -694,6 +701,14 @@ fn zai_base_url(name: &str) -> Option<&'static str> { } } +fn stepfun_base_url(name: &str) -> Option<&'static str> { + if is_stepfun_alias(name) { + Some(STEPFUN_BASE_URL) + } else { + None + } +} + #[derive(Debug, Clone)] pub struct ProviderRuntimeOptions { pub auth_profile_override: Option, @@ -943,6 +958,7 @@ fn resolve_provider_credential(name: &str, credential_override: Option<&str>) -> name if is_siliconflow_alias(name) => vec!["SILICONFLOW_API_KEY"], name if is_qwen_alias(name) => vec!["DASHSCOPE_API_KEY"], name if is_zai_alias(name) => vec!["ZAI_API_KEY"], + name if is_stepfun_alias(name) => vec!["STEP_API_KEY", "STEPFUN_API_KEY"], "nvidia" | "nvidia-nim" | "build.nvidia.com" => vec!["NVIDIA_API_KEY"], "synthetic" => vec!["SYNTHETIC_API_KEY"], "opencode" | "opencode-zen" => vec!["OPENCODE_API_KEY"], @@ -1274,6 +1290,12 @@ fn create_provider_with_url_and_options( true, ))) } + name if stepfun_base_url(name).is_some() => Ok(Box::new(OpenAiCompatibleProvider::new( + "StepFun", + stepfun_base_url(name).expect("checked in guard"), + key, + AuthStyle::Bearer, + ))), name if qwen_base_url(name).is_some() => { Ok(Box::new(OpenAiCompatibleProvider::new_with_vision( "Qwen", @@ -1831,6 +1853,12 @@ pub fn list_providers() -> Vec { aliases: &["kimi"], local: false, }, + ProviderInfo { + name: "stepfun", + display_name: "StepFun", + aliases: &["step", "step-ai", "step_ai"], + local: false, + }, ProviderInfo { name: "kimi-code", display_name: "Kimi Code", @@ -2273,6 +2301,10 @@ mod tests { assert!(is_siliconflow_alias("siliconflow")); assert!(is_siliconflow_alias("silicon-cloud")); assert!(is_siliconflow_alias("siliconcloud")); + assert!(is_stepfun_alias("stepfun")); + assert!(is_stepfun_alias("step")); + assert!(is_stepfun_alias("step-ai")); + assert!(is_stepfun_alias("step_ai")); assert!(!is_moonshot_alias("openrouter")); assert!(!is_glm_alias("openai")); @@ -2281,6 +2313,7 @@ mod tests { assert!(!is_qianfan_alias("cohere")); assert!(!is_doubao_alias("deepseek")); assert!(!is_siliconflow_alias("volcengine")); + assert!(!is_stepfun_alias("moonshot")); } #[test] @@ -2312,6 +2345,9 @@ mod tests { canonical_china_provider_name("silicon-cloud"), Some("siliconflow") ); + assert_eq!(canonical_china_provider_name("stepfun"), Some("stepfun")); + assert_eq!(canonical_china_provider_name("step"), Some("stepfun")); + assert_eq!(canonical_china_provider_name("step-ai"), Some("stepfun")); assert_eq!(canonical_china_provider_name("hunyuan"), Some("hunyuan")); assert_eq!(canonical_china_provider_name("tencent"), Some("hunyuan")); assert_eq!(canonical_china_provider_name("openai"), None); @@ -2352,6 +2388,10 @@ mod tests { assert_eq!(zai_base_url("z.ai-global"), Some(ZAI_GLOBAL_BASE_URL)); assert_eq!(zai_base_url("zai-cn"), Some(ZAI_CN_BASE_URL)); assert_eq!(zai_base_url("z.ai-cn"), Some(ZAI_CN_BASE_URL)); + + assert_eq!(stepfun_base_url("stepfun"), Some(STEPFUN_BASE_URL)); + assert_eq!(stepfun_base_url("step"), Some(STEPFUN_BASE_URL)); + assert_eq!(stepfun_base_url("step-ai"), Some(STEPFUN_BASE_URL)); } // ── Primary providers ──────────────────────────────────── @@ -2438,6 +2478,13 @@ mod tests { assert!(create_provider("kimi-cn", Some("key")).is_ok()); } + #[test] + fn factory_stepfun() { + assert!(create_provider("stepfun", Some("key")).is_ok()); + assert!(create_provider("step", Some("key")).is_ok()); + assert!(create_provider("step-ai", Some("key")).is_ok()); + } + #[test] fn factory_kimi_code() { assert!(create_provider("kimi-code", Some("key")).is_ok()); @@ -2990,6 +3037,9 @@ mod tests { "kimi-code", "moonshot-cn", "kimi-code", + "stepfun", + "step", + "step-ai", "synthetic", "opencode", "zai",