diff --git a/.github/labeler.yml b/.github/labeler.yml index 733e8579afd1..8a254ac93f8d 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -246,6 +246,10 @@ - changed-files: - any-glob-to-any-file: - "extensions/deepseek/**" +"extensions: stepfun": + - changed-files: + - any-glob-to-any-file: + - "extensions/stepfun/**" "extensions: anthropic": - changed-files: - any-glob-to-any-file: diff --git a/CHANGELOG.md b/CHANGELOG.md index 8953f2eb201d..7d3293f4c3f2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ Docs: https://docs.openclaw.ai - Channels/context visibility: add configurable `contextVisibility` per channel (`all`, `allowlist`, `allowlist_quote`) so supplemental quote, thread, and fetched history context can be filtered by sender allowlists instead of always passing through as received. - Matrix/exec approvals: add Matrix-native exec approval prompts with account-scoped approvers, channel-or-DM delivery, and room-thread aware resolution handling. (#58635) Thanks @gumadeiras. +- Providers/StepFun: add the bundled StepFun provider plugin with standard and Step Plan endpoints, China/global onboarding choices, `step-3.5-flash` on both catalogs, and `step-3.5-flash-2603` currently exposed on Step Plan. (#60032) Thanks @hengm3467. ### Fixes diff --git a/docs/concepts/model-providers.md b/docs/concepts/model-providers.md index bc48a971f9db..84e44d4a9590 100644 --- a/docs/concepts/model-providers.md +++ b/docs/concepts/model-providers.md @@ -106,7 +106,7 @@ Current bundled examples: policy, binary-thinking/live-model policy, and usage auth + quota fetching - `mistral`, `opencode`, and `opencode-go`: plugin-owned capability metadata - `byteplus`, `cloudflare-ai-gateway`, `huggingface`, `kimi-coding`, - `modelstudio`, `nvidia`, `qianfan`, `synthetic`, `together`, `venice`, + `modelstudio`, `nvidia`, `qianfan`, `stepfun`, `synthetic`, `together`, `venice`, `vercel-ai-gateway`, and `volcengine`: plugin-owned catalogs only - `minimax` and `xiaomi`: plugin-owned catalogs plus usage auth/snapshot logic @@ -265,6 +265,8 @@ See [/providers/kilocode](/providers/kilocode) for setup details. - Qianfan: `qianfan` (`QIANFAN_API_KEY`) - Model Studio: `modelstudio` (`MODELSTUDIO_API_KEY`) - NVIDIA: `nvidia` (`NVIDIA_API_KEY`) +- StepFun: `stepfun` / `stepfun-plan` (`STEPFUN_API_KEY`) +- Example models: `stepfun/step-3.5-flash`, `stepfun-plan/step-3.5-flash-2603` - Together: `together` (`TOGETHER_API_KEY`) - Venice: `venice` (`VENICE_API_KEY`) - Xiaomi: `xiaomi` (`XIAOMI_API_KEY`) diff --git a/docs/docs.json b/docs/docs.json index 1f6b3a82b0a6..23fc9065998d 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -1249,6 +1249,7 @@ "providers/qwen_modelstudio", "providers/qwen", "providers/sglang", + "providers/stepfun", "providers/synthetic", "providers/together", "providers/venice", diff --git a/docs/providers/index.md b/docs/providers/index.md index caf2ea90393b..c2888b74f62d 100644 --- a/docs/providers/index.md +++ b/docs/providers/index.md @@ -50,6 +50,7 @@ Looking for chat channel docs (WhatsApp/Telegram/Discord/Slack/Mattermost (plugi - [Qianfan](/providers/qianfan) - [Qwen / Model Studio (Alibaba Cloud)](/providers/qwen_modelstudio) - [SGLang (local models)](/providers/sglang) +- [StepFun](/providers/stepfun) - [Synthetic](/providers/synthetic) - [Together AI](/providers/together) - [Venice (Venice AI, privacy-focused)](/providers/venice) diff --git a/docs/providers/models.md b/docs/providers/models.md index 7c8c8c758f6a..d633eeeae864 100644 --- a/docs/providers/models.md +++ b/docs/providers/models.md @@ -24,22 +24,23 @@ model as `provider/model`. ## Supported providers (starter set) -- [OpenAI (API + Codex)](/providers/openai) - [Anthropic (API + Claude Code CLI)](/providers/anthropic) -- [OpenRouter](/providers/openrouter) -- [Vercel AI Gateway](/providers/vercel-ai-gateway) +- [Amazon Bedrock](/providers/bedrock) - [Cloudflare AI Gateway](/providers/cloudflare-ai-gateway) -- [Moonshot AI (Kimi + Kimi Coding)](/providers/moonshot) -- [Mistral](/providers/mistral) -- [Synthetic](/providers/synthetic) -- [OpenCode (Zen + Go)](/providers/opencode) -- [Z.AI](/providers/zai) - [GLM models](/providers/glm) - [MiniMax](/providers/minimax) -- [Venice (Venice AI)](/providers/venice) -- [Amazon Bedrock](/providers/bedrock) +- [Mistral](/providers/mistral) +- [Moonshot AI (Kimi + Kimi Coding)](/providers/moonshot) +- [OpenAI (API + Codex)](/providers/openai) +- [OpenCode (Zen + Go)](/providers/opencode) +- [OpenRouter](/providers/openrouter) - [Qianfan](/providers/qianfan) +- [StepFun](/providers/stepfun) +- [Synthetic](/providers/synthetic) +- [Vercel AI Gateway](/providers/vercel-ai-gateway) +- [Venice (Venice AI)](/providers/venice) - [xAI](/providers/xai) +- [Z.AI](/providers/zai) For the full provider catalog (xAI, Groq, Mistral, etc.) and advanced configuration, see [Model providers](/concepts/model-providers). diff --git a/docs/providers/stepfun.md b/docs/providers/stepfun.md new file mode 100644 index 000000000000..95f0d0e7345e --- /dev/null +++ b/docs/providers/stepfun.md @@ -0,0 +1,137 @@ +--- +summary: "Use StepFun models with OpenClaw" +read_when: + - You want StepFun models in OpenClaw + - You need StepFun setup guidance +title: "StepFun" +--- + +# StepFun + +OpenClaw includes a bundled StepFun provider plugin with two provider ids: + +- `stepfun` for the standard endpoint +- `stepfun-plan` for the Step Plan endpoint + +The built-in catalogs currently differ by surface: + +- Standard: `step-3.5-flash` +- Step Plan: `step-3.5-flash`, `step-3.5-flash-2603` + +## Region and endpoint overview + +- China standard endpoint: `https://api.stepfun.com/v1` +- Global standard endpoint: `https://api.stepfun.ai/v1` +- China Step Plan endpoint: `https://api.stepfun.com/step_plan/v1` +- Global Step Plan endpoint: `https://api.stepfun.ai/step_plan/v1` +- Auth env var: `STEPFUN_API_KEY` + +Use a China key with the `.com` endpoints and a global key with the `.ai` +endpoints. + +## CLI setup + +Interactive setup: + +```bash +openclaw onboard +``` + +Choose one of these auth choices: + +- `stepfun-standard-api-key-cn` +- `stepfun-standard-api-key-intl` +- `stepfun-plan-api-key-cn` +- `stepfun-plan-api-key-intl` + +Non-interactive examples: + +```bash +openclaw onboard --auth-choice stepfun-standard-api-key-intl --stepfun-api-key "$STEPFUN_API_KEY" +openclaw onboard --auth-choice stepfun-plan-api-key-intl --stepfun-api-key "$STEPFUN_API_KEY" +``` + +## Model refs + +- Standard default model: `stepfun/step-3.5-flash` +- Step Plan default model: `stepfun-plan/step-3.5-flash` +- Step Plan alternate model: `stepfun-plan/step-3.5-flash-2603` + +## Config snippets + +Standard provider: + +```json5 +{ + env: { STEPFUN_API_KEY: "your-key" }, + agents: { defaults: { model: { primary: "stepfun/step-3.5-flash" } } }, + models: { + mode: "merge", + providers: { + stepfun: { + baseUrl: "https://api.stepfun.ai/v1", + api: "openai-completions", + apiKey: "${STEPFUN_API_KEY}", + models: [ + { + id: "step-3.5-flash", + name: "Step 3.5 Flash", + reasoning: true, + input: ["text"], + cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, + contextWindow: 262144, + maxTokens: 65536, + }, + ], + }, + }, + }, +} +``` + +Step Plan provider: + +```json5 +{ + env: { STEPFUN_API_KEY: "your-key" }, + agents: { defaults: { model: { primary: "stepfun-plan/step-3.5-flash" } } }, + models: { + mode: "merge", + providers: { + "stepfun-plan": { + baseUrl: "https://api.stepfun.ai/step_plan/v1", + api: "openai-completions", + apiKey: "${STEPFUN_API_KEY}", + models: [ + { + id: "step-3.5-flash", + name: "Step 3.5 Flash", + reasoning: true, + input: ["text"], + cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, + contextWindow: 262144, + maxTokens: 65536, + }, + { + id: "step-3.5-flash-2603", + name: "Step 3.5 Flash 2603", + reasoning: true, + input: ["text"], + cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, + contextWindow: 262144, + maxTokens: 65536, + }, + ], + }, + }, + }, +} +``` + +## Notes + +- The provider is bundled with OpenClaw, so there is no separate plugin install step. +- `step-3.5-flash-2603` is currently exposed only on `stepfun-plan`. +- A single auth flow writes region-matched profiles for both `stepfun` and `stepfun-plan`, so both surfaces can be discovered together. +- Use `openclaw models list` and `openclaw models set ` to inspect or switch models. +- For the broader provider overview, see [Model providers](/concepts/model-providers). diff --git a/docs/reference/wizard.md b/docs/reference/wizard.md index 56958324cdf3..5ee3b1b68aae 100644 --- a/docs/reference/wizard.md +++ b/docs/reference/wizard.md @@ -48,6 +48,9 @@ For a high-level overview, see [Onboarding (CLI)](/start/wizard). - More detail: [Cloudflare AI Gateway](/providers/cloudflare-ai-gateway) - **MiniMax**: config is auto-written; hosted default is `MiniMax-M2.7`. - More detail: [MiniMax](/providers/minimax) + - **StepFun**: config is auto-written for StepFun standard or Step Plan on China or global endpoints. + - Standard currently includes `step-3.5-flash`, and Step Plan also includes `step-3.5-flash-2603`. + - More detail: [StepFun](/providers/stepfun) - **Synthetic (Anthropic-compatible)**: prompts for `SYNTHETIC_API_KEY`. - More detail: [Synthetic](/providers/synthetic) - **Moonshot (Kimi K2)**: config is auto-written. diff --git a/docs/start/wizard-cli-reference.md b/docs/start/wizard-cli-reference.md index 0c52146019a4..6f92c31ad980 100644 --- a/docs/start/wizard-cli-reference.md +++ b/docs/start/wizard-cli-reference.md @@ -16,7 +16,7 @@ For the short guide, see [Onboarding (CLI)](/start/wizard). Local mode (default) walks you through: -- Model and auth setup (OpenAI Code subscription OAuth, Anthropic API key or setup token, plus MiniMax, GLM, Ollama, Moonshot, and AI Gateway options) +- Model and auth setup (OpenAI Code subscription OAuth, Anthropic API key or setup token, plus MiniMax, GLM, Ollama, Moonshot, StepFun, and AI Gateway options) - Workspace location and bootstrap files - Gateway settings (port, bind, auth, tailscale) - Channels and providers (Telegram, WhatsApp, Discord, Google Chat, Mattermost plugin, Signal) @@ -177,6 +177,11 @@ What you set: Config is auto-written. Hosted default is `MiniMax-M2.7`. More detail: [MiniMax](/providers/minimax). + + Config is auto-written for StepFun standard or Step Plan on China or global endpoints. + Standard currently includes `step-3.5-flash`, and Step Plan also includes `step-3.5-flash-2603`. + More detail: [StepFun](/providers/stepfun). + Prompts for `SYNTHETIC_API_KEY`. More detail: [Synthetic](/providers/synthetic). diff --git a/extensions/stepfun/index.ts b/extensions/stepfun/index.ts new file mode 100644 index 000000000000..af1145d12ce6 --- /dev/null +++ b/extensions/stepfun/index.ts @@ -0,0 +1,251 @@ +import { + definePluginEntry, + type OpenClawConfig, + type ProviderCatalogContext, +} from "openclaw/plugin-sdk/plugin-entry"; +import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth-api-key"; +import { + applyStepFunPlanConfig, + applyStepFunPlanConfigCn, + applyStepFunStandardConfig, + applyStepFunStandardConfigCn, +} from "./onboard.js"; +import { + buildStepFunPlanProvider, + buildStepFunProvider, + STEPFUN_DEFAULT_MODEL_REF, + STEPFUN_PLAN_CN_BASE_URL, + STEPFUN_PLAN_DEFAULT_MODEL_REF, + STEPFUN_PLAN_INTL_BASE_URL, + STEPFUN_PLAN_PROVIDER_ID, + STEPFUN_PROVIDER_ID, + STEPFUN_STANDARD_CN_BASE_URL, + STEPFUN_STANDARD_INTL_BASE_URL, +} from "./provider-catalog.js"; + +type StepFunRegion = "cn" | "intl"; +type StepFunSurface = "standard" | "plan"; + +function trimExplicitBaseUrl(ctx: ProviderCatalogContext, providerId: string): string | undefined { + const explicitProvider = ctx.config.models?.providers?.[providerId]; + const baseUrl = + typeof explicitProvider?.baseUrl === "string" ? explicitProvider.baseUrl.trim() : ""; + return baseUrl || undefined; +} + +function inferRegionFromBaseUrl(baseUrl: string | undefined): StepFunRegion | undefined { + if (!baseUrl) { + return undefined; + } + try { + const host = new URL(baseUrl).hostname.toLowerCase(); + if (host === "api.stepfun.com") { + return "cn"; + } + if (host === "api.stepfun.ai") { + return "intl"; + } + } catch { + return undefined; + } + return undefined; +} + +function inferRegionFromProfileId(profileId: string | undefined): StepFunRegion | undefined { + if (!profileId) { + return undefined; + } + if (profileId.includes(":cn")) { + return "cn"; + } + if (profileId.includes(":intl")) { + return "intl"; + } + return undefined; +} + +function inferRegionFromEnv(env: NodeJS.ProcessEnv): StepFunRegion | undefined { + // Shared env-only setup needs one stable fallback region. + if (env.STEPFUN_API_KEY?.trim()) { + return "intl"; + } + return undefined; +} + +function inferRegionFromExplicitBaseUrls(ctx: ProviderCatalogContext): StepFunRegion | undefined { + return ( + inferRegionFromBaseUrl(trimExplicitBaseUrl(ctx, STEPFUN_PROVIDER_ID)) ?? + inferRegionFromBaseUrl(trimExplicitBaseUrl(ctx, STEPFUN_PLAN_PROVIDER_ID)) + ); +} + +function resolveDefaultBaseUrl(surface: StepFunSurface, region: StepFunRegion): string { + if (surface === "plan") { + return region === "cn" ? STEPFUN_PLAN_CN_BASE_URL : STEPFUN_PLAN_INTL_BASE_URL; + } + return region === "cn" ? STEPFUN_STANDARD_CN_BASE_URL : STEPFUN_STANDARD_INTL_BASE_URL; +} + +function resolveStepFunCatalog( + ctx: ProviderCatalogContext, + params: { providerId: string; surface: StepFunSurface }, +) { + const auth = ctx.resolveProviderAuth(params.providerId); + const apiKey = auth.apiKey ?? ctx.resolveProviderApiKey(params.providerId).apiKey; + if (!apiKey) { + return null; + } + + const explicitBaseUrl = trimExplicitBaseUrl(ctx, params.providerId); + const region = + inferRegionFromBaseUrl(explicitBaseUrl) ?? + inferRegionFromExplicitBaseUrls(ctx) ?? + inferRegionFromProfileId(auth.profileId) ?? + inferRegionFromEnv(ctx.env); + // Keep discovery working for legacy/manual auth profiles that resolved a + // key but do not encode region in the profile id. + const baseUrl = explicitBaseUrl ?? resolveDefaultBaseUrl(params.surface, region ?? "intl"); + return { + provider: + params.surface === "plan" + ? { ...buildStepFunPlanProvider(baseUrl), apiKey } + : { ...buildStepFunProvider(baseUrl), apiKey }, + }; +} + +function resolveProfileIds(region: StepFunRegion): [string, string] { + return region === "cn" + ? ["stepfun:cn", "stepfun-plan:cn"] + : ["stepfun:intl", "stepfun-plan:intl"]; +} + +function createStepFunApiKeyMethod(params: { + providerId: string; + methodId: string; + label: string; + hint: string; + region: StepFunRegion; + promptMessage: string; + defaultModel: string; + choiceId: string; + choiceLabel: string; + choiceHint: string; + applyConfig: (cfg: OpenClawConfig) => OpenClawConfig; +}) { + return createProviderApiKeyAuthMethod({ + providerId: params.providerId, + methodId: params.methodId, + label: params.label, + hint: params.hint, + optionKey: "stepfunApiKey", + flagName: "--stepfun-api-key", + envVar: "STEPFUN_API_KEY", + promptMessage: params.promptMessage, + profileIds: resolveProfileIds(params.region), + allowProfile: false, + defaultModel: params.defaultModel, + expectedProviders: [STEPFUN_PROVIDER_ID, STEPFUN_PLAN_PROVIDER_ID], + applyConfig: params.applyConfig, + wizard: { + choiceId: params.choiceId, + choiceLabel: params.choiceLabel, + choiceHint: params.choiceHint, + groupId: "stepfun", + groupLabel: "StepFun", + groupHint: "Standard / Step Plan (China / Global)", + }, + }); +} + +export default definePluginEntry({ + id: STEPFUN_PROVIDER_ID, + name: "StepFun", + description: "Bundled StepFun standard and Step Plan provider plugin", + register(api) { + api.registerProvider({ + id: STEPFUN_PROVIDER_ID, + label: "StepFun", + docsPath: "/providers/stepfun", + envVars: ["STEPFUN_API_KEY"], + auth: [ + createStepFunApiKeyMethod({ + providerId: STEPFUN_PROVIDER_ID, + methodId: "standard-api-key-cn", + label: "StepFun Standard API key (China)", + hint: "Endpoint: api.stepfun.com/v1", + region: "cn", + promptMessage: "Enter StepFun API key for China endpoints", + defaultModel: STEPFUN_DEFAULT_MODEL_REF, + choiceId: "stepfun-standard-api-key-cn", + choiceLabel: "StepFun Standard API key (China)", + choiceHint: "Endpoint: api.stepfun.com/v1", + applyConfig: applyStepFunStandardConfigCn, + }), + createStepFunApiKeyMethod({ + providerId: STEPFUN_PROVIDER_ID, + methodId: "standard-api-key-intl", + label: "StepFun Standard API key (Global/Intl)", + hint: "Endpoint: api.stepfun.ai/v1", + region: "intl", + promptMessage: "Enter StepFun API key for global endpoints", + defaultModel: STEPFUN_DEFAULT_MODEL_REF, + choiceId: "stepfun-standard-api-key-intl", + choiceLabel: "StepFun Standard API key (Global/Intl)", + choiceHint: "Endpoint: api.stepfun.ai/v1", + applyConfig: applyStepFunStandardConfig, + }), + ], + catalog: { + order: "paired", + run: async (ctx) => + resolveStepFunCatalog(ctx, { + providerId: STEPFUN_PROVIDER_ID, + surface: "standard", + }), + }, + }); + + api.registerProvider({ + id: STEPFUN_PLAN_PROVIDER_ID, + label: "StepFun Step Plan", + docsPath: "/providers/stepfun", + envVars: ["STEPFUN_API_KEY"], + auth: [ + createStepFunApiKeyMethod({ + providerId: STEPFUN_PLAN_PROVIDER_ID, + methodId: "plan-api-key-cn", + label: "StepFun Step Plan API key (China)", + hint: "Endpoint: api.stepfun.com/step_plan/v1", + region: "cn", + promptMessage: "Enter StepFun API key for China endpoints", + defaultModel: STEPFUN_PLAN_DEFAULT_MODEL_REF, + choiceId: "stepfun-plan-api-key-cn", + choiceLabel: "StepFun Step Plan API key (China)", + choiceHint: "Endpoint: api.stepfun.com/step_plan/v1", + applyConfig: applyStepFunPlanConfigCn, + }), + createStepFunApiKeyMethod({ + providerId: STEPFUN_PLAN_PROVIDER_ID, + methodId: "plan-api-key-intl", + label: "StepFun Step Plan API key (Global/Intl)", + hint: "Endpoint: api.stepfun.ai/step_plan/v1", + region: "intl", + promptMessage: "Enter StepFun API key for global endpoints", + defaultModel: STEPFUN_PLAN_DEFAULT_MODEL_REF, + choiceId: "stepfun-plan-api-key-intl", + choiceLabel: "StepFun Step Plan API key (Global/Intl)", + choiceHint: "Endpoint: api.stepfun.ai/step_plan/v1", + applyConfig: applyStepFunPlanConfig, + }), + ], + catalog: { + order: "paired", + run: async (ctx) => + resolveStepFunCatalog(ctx, { + providerId: STEPFUN_PLAN_PROVIDER_ID, + surface: "plan", + }), + }, + }); + }, +}); diff --git a/extensions/stepfun/onboard.ts b/extensions/stepfun/onboard.ts new file mode 100644 index 000000000000..383052dfe0d4 --- /dev/null +++ b/extensions/stepfun/onboard.ts @@ -0,0 +1,82 @@ +import { + createModelCatalogPresetAppliers, + type ModelProviderConfig, + type OpenClawConfig, + type ProviderOnboardPresetAppliers, +} from "openclaw/plugin-sdk/provider-onboard"; +import { + buildStepFunPlanProvider, + buildStepFunProvider, + STEPFUN_DEFAULT_MODEL_REF, + STEPFUN_PLAN_CN_BASE_URL, + STEPFUN_PLAN_DEFAULT_MODEL_REF, + STEPFUN_PLAN_INTL_BASE_URL, + STEPFUN_PLAN_PROVIDER_ID, + STEPFUN_PROVIDER_ID, + STEPFUN_STANDARD_CN_BASE_URL, + STEPFUN_STANDARD_INTL_BASE_URL, +} from "./provider-catalog.js"; + +export { + STEPFUN_DEFAULT_MODEL_REF, + STEPFUN_PLAN_CN_BASE_URL, + STEPFUN_PLAN_DEFAULT_MODEL_REF, + STEPFUN_PLAN_INTL_BASE_URL, + STEPFUN_STANDARD_CN_BASE_URL, + STEPFUN_STANDARD_INTL_BASE_URL, +}; + +function createStepFunPresetAppliers(params: { + providerId: string; + primaryModelRef: string; + alias: string; + buildProvider: (baseUrl: string) => ModelProviderConfig; +}): ProviderOnboardPresetAppliers<[string]> { + return createModelCatalogPresetAppliers<[string]>({ + primaryModelRef: params.primaryModelRef, + resolveParams: (_cfg: OpenClawConfig, baseUrl: string) => { + const provider = params.buildProvider(baseUrl); + const models = provider.models ?? []; + return { + providerId: params.providerId, + api: provider.api ?? "openai-completions", + baseUrl, + catalogModels: models, + aliases: [ + ...models.map((model) => `${params.providerId}/${model.id}`), + { modelRef: params.primaryModelRef, alias: params.alias }, + ], + }; + }, + }); +} + +const stepFunPresetAppliers = createStepFunPresetAppliers({ + providerId: STEPFUN_PROVIDER_ID, + primaryModelRef: STEPFUN_DEFAULT_MODEL_REF, + alias: "StepFun", + buildProvider: buildStepFunProvider, +}); + +const stepFunPlanPresetAppliers = createStepFunPresetAppliers({ + providerId: STEPFUN_PLAN_PROVIDER_ID, + primaryModelRef: STEPFUN_PLAN_DEFAULT_MODEL_REF, + alias: "StepFun Plan", + buildProvider: buildStepFunPlanProvider, +}); + +export function applyStepFunStandardConfigCn(cfg: OpenClawConfig): OpenClawConfig { + return stepFunPresetAppliers.applyConfig(cfg, STEPFUN_STANDARD_CN_BASE_URL); +} + +export function applyStepFunStandardConfig(cfg: OpenClawConfig): OpenClawConfig { + return stepFunPresetAppliers.applyConfig(cfg, STEPFUN_STANDARD_INTL_BASE_URL); +} + +export function applyStepFunPlanConfigCn(cfg: OpenClawConfig): OpenClawConfig { + return stepFunPlanPresetAppliers.applyConfig(cfg, STEPFUN_PLAN_CN_BASE_URL); +} + +export function applyStepFunPlanConfig(cfg: OpenClawConfig): OpenClawConfig { + return stepFunPlanPresetAppliers.applyConfig(cfg, STEPFUN_PLAN_INTL_BASE_URL); +} diff --git a/extensions/stepfun/openclaw.plugin.json b/extensions/stepfun/openclaw.plugin.json new file mode 100644 index 000000000000..07851b818195 --- /dev/null +++ b/extensions/stepfun/openclaw.plugin.json @@ -0,0 +1,73 @@ +{ + "id": "stepfun", + "enabledByDefault": true, + "providers": ["stepfun", "stepfun-plan"], + "autoEnableWhenConfiguredProviders": ["stepfun", "stepfun-plan"], + "providerAuthEnvVars": { + "stepfun": ["STEPFUN_API_KEY"], + "stepfun-plan": ["STEPFUN_API_KEY"] + }, + "providerAuthChoices": [ + { + "provider": "stepfun", + "method": "standard-api-key-cn", + "choiceId": "stepfun-standard-api-key-cn", + "choiceLabel": "StepFun Standard API key (China)", + "choiceHint": "Endpoint: api.stepfun.com/v1", + "groupId": "stepfun", + "groupLabel": "StepFun", + "groupHint": "Standard / Step Plan (China / Global)", + "optionKey": "stepfunApiKey", + "cliFlag": "--stepfun-api-key", + "cliOption": "--stepfun-api-key ", + "cliDescription": "StepFun API key" + }, + { + "provider": "stepfun", + "method": "standard-api-key-intl", + "choiceId": "stepfun-standard-api-key-intl", + "choiceLabel": "StepFun Standard API key (Global/Intl)", + "choiceHint": "Endpoint: api.stepfun.ai/v1", + "groupId": "stepfun", + "groupLabel": "StepFun", + "groupHint": "Standard / Step Plan (China / Global)", + "optionKey": "stepfunApiKey", + "cliFlag": "--stepfun-api-key", + "cliOption": "--stepfun-api-key ", + "cliDescription": "StepFun API key" + }, + { + "provider": "stepfun-plan", + "method": "plan-api-key-cn", + "choiceId": "stepfun-plan-api-key-cn", + "choiceLabel": "StepFun Step Plan API key (China)", + "choiceHint": "Endpoint: api.stepfun.com/step_plan/v1", + "groupId": "stepfun", + "groupLabel": "StepFun", + "groupHint": "Standard / Step Plan (China / Global)", + "optionKey": "stepfunApiKey", + "cliFlag": "--stepfun-api-key", + "cliOption": "--stepfun-api-key ", + "cliDescription": "StepFun API key" + }, + { + "provider": "stepfun-plan", + "method": "plan-api-key-intl", + "choiceId": "stepfun-plan-api-key-intl", + "choiceLabel": "StepFun Step Plan API key (Global/Intl)", + "choiceHint": "Endpoint: api.stepfun.ai/step_plan/v1", + "groupId": "stepfun", + "groupLabel": "StepFun", + "groupHint": "Standard / Step Plan (China / Global)", + "optionKey": "stepfunApiKey", + "cliFlag": "--stepfun-api-key", + "cliOption": "--stepfun-api-key ", + "cliDescription": "StepFun API key" + } + ], + "configSchema": { + "type": "object", + "additionalProperties": false, + "properties": {} + } +} diff --git a/extensions/stepfun/package.json b/extensions/stepfun/package.json new file mode 100644 index 000000000000..f5598e981ecb --- /dev/null +++ b/extensions/stepfun/package.json @@ -0,0 +1,12 @@ +{ + "name": "@openclaw/stepfun-provider", + "version": "2026.4.1-beta.1", + "private": true, + "description": "OpenClaw StepFun provider plugin", + "type": "module", + "openclaw": { + "extensions": [ + "./index.ts" + ] + } +} diff --git a/extensions/stepfun/provider-catalog.ts b/extensions/stepfun/provider-catalog.ts new file mode 100644 index 000000000000..245e4a3bee31 --- /dev/null +++ b/extensions/stepfun/provider-catalog.ts @@ -0,0 +1,69 @@ +import type { + ModelDefinitionConfig, + ModelProviderConfig, +} from "openclaw/plugin-sdk/provider-model-shared"; + +export const STEPFUN_PROVIDER_ID = "stepfun"; +export const STEPFUN_PLAN_PROVIDER_ID = "stepfun-plan"; + +export const STEPFUN_STANDARD_CN_BASE_URL = "https://api.stepfun.com/v1"; +export const STEPFUN_STANDARD_INTL_BASE_URL = "https://api.stepfun.ai/v1"; +export const STEPFUN_PLAN_CN_BASE_URL = "https://api.stepfun.com/step_plan/v1"; +export const STEPFUN_PLAN_INTL_BASE_URL = "https://api.stepfun.ai/step_plan/v1"; + +export const STEPFUN_DEFAULT_MODEL_ID = "step-3.5-flash"; +export const STEPFUN_FLASH_2603_MODEL_ID = "step-3.5-flash-2603"; +export const STEPFUN_DEFAULT_MODEL_REF = `${STEPFUN_PROVIDER_ID}/${STEPFUN_DEFAULT_MODEL_ID}`; +export const STEPFUN_PLAN_DEFAULT_MODEL_REF = `${STEPFUN_PLAN_PROVIDER_ID}/${STEPFUN_DEFAULT_MODEL_ID}`; + +const STEPFUN_DEFAULT_COST = { + input: 0, + output: 0, + cacheRead: 0, + cacheWrite: 0, +} as const; + +function buildStepFunModel(id: string, name: string): ModelDefinitionConfig { + return { + id, + name, + reasoning: true, + input: ["text"], + cost: STEPFUN_DEFAULT_COST, + contextWindow: 262_144, + maxTokens: 65_536, + }; +} + +const STEPFUN_STANDARD_MODEL_CATALOG: ReadonlyArray = [ + buildStepFunModel(STEPFUN_DEFAULT_MODEL_ID, "Step 3.5 Flash"), +]; + +const STEPFUN_PLAN_MODEL_CATALOG: ReadonlyArray = [ + buildStepFunModel(STEPFUN_DEFAULT_MODEL_ID, "Step 3.5 Flash"), + buildStepFunModel(STEPFUN_FLASH_2603_MODEL_ID, "Step 3.5 Flash 2603"), +]; + +function cloneCatalog(models: ReadonlyArray): ModelDefinitionConfig[] { + return models.map((model) => ({ ...model })); +} + +export function buildStepFunProvider( + baseUrl: string = STEPFUN_STANDARD_INTL_BASE_URL, +): ModelProviderConfig { + return { + baseUrl, + api: "openai-completions", + models: cloneCatalog(STEPFUN_STANDARD_MODEL_CATALOG), + }; +} + +export function buildStepFunPlanProvider( + baseUrl: string = STEPFUN_PLAN_INTL_BASE_URL, +): ModelProviderConfig { + return { + baseUrl, + api: "openai-completions", + models: cloneCatalog(STEPFUN_PLAN_MODEL_CATALOG), + }; +} diff --git a/src/agents/models-config.e2e-harness.ts b/src/agents/models-config.e2e-harness.ts index a54d06057e62..ddfbda697877 100644 --- a/src/agents/models-config.e2e-harness.ts +++ b/src/agents/models-config.e2e-harness.ts @@ -105,6 +105,7 @@ export const MODELS_CONFIG_IMPLICIT_ENV_VARS = [ "QIANFAN_API_KEY", "MODELSTUDIO_API_KEY", "SYNTHETIC_API_KEY", + "STEPFUN_API_KEY", "TOGETHER_API_KEY", "VOLCANO_ENGINE_API_KEY", "BYTEPLUS_API_KEY", diff --git a/src/agents/models-config.providers.stepfun.test.ts b/src/agents/models-config.providers.stepfun.test.ts new file mode 100644 index 000000000000..b9ed525319ac --- /dev/null +++ b/src/agents/models-config.providers.stepfun.test.ts @@ -0,0 +1,119 @@ +import { mkdtempSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { describe, expect, it } from "vitest"; +import { upsertAuthProfile } from "./auth-profiles.js"; +import { resolveImplicitProvidersForTest } from "./models-config.e2e-harness.js"; + +const EXPECTED_STANDARD_MODELS = ["step-3.5-flash"]; +const EXPECTED_PLAN_MODELS = ["step-3.5-flash", "step-3.5-flash-2603"]; + +describe("StepFun provider catalog", () => { + it("includes standard and Step Plan providers when STEPFUN_API_KEY is configured", async () => { + const agentDir = mkdtempSync(join(tmpdir(), "openclaw-test-")); + const providers = await resolveImplicitProvidersForTest({ + agentDir, + env: { ...process.env, STEPFUN_API_KEY: "test-stepfun-key" }, + }); + + expect(providers?.stepfun).toMatchObject({ + baseUrl: "https://api.stepfun.ai/v1", + api: "openai-completions", + apiKey: "STEPFUN_API_KEY", + }); + expect(providers?.["stepfun-plan"]).toMatchObject({ + baseUrl: "https://api.stepfun.ai/step_plan/v1", + api: "openai-completions", + apiKey: "STEPFUN_API_KEY", + }); + expect(providers?.stepfun?.models?.map((model) => model.id)).toEqual(EXPECTED_STANDARD_MODELS); + expect(providers?.["stepfun-plan"]?.models?.map((model) => model.id)).toEqual( + EXPECTED_PLAN_MODELS, + ); + }); + + it("falls back to global endpoints for untagged StepFun auth profiles", async () => { + const agentDir = mkdtempSync(join(tmpdir(), "openclaw-test-")); + + upsertAuthProfile({ + profileId: "stepfun:default", + credential: { + type: "api_key", + provider: "stepfun", + key: "sk-stepfun-default", // pragma: allowlist secret + }, + agentDir, + }); + upsertAuthProfile({ + profileId: "stepfun-plan:default", + credential: { + type: "api_key", + provider: "stepfun-plan", + key: "sk-stepfun-default", // pragma: allowlist secret + }, + agentDir, + }); + + const providers = await resolveImplicitProvidersForTest({ agentDir, env: {} }); + + expect(providers?.stepfun?.baseUrl).toBe("https://api.stepfun.ai/v1"); + expect(providers?.["stepfun-plan"]?.baseUrl).toBe("https://api.stepfun.ai/step_plan/v1"); + expect(providers?.stepfun?.models?.map((model) => model.id)).toEqual(EXPECTED_STANDARD_MODELS); + expect(providers?.["stepfun-plan"]?.models?.map((model) => model.id)).toEqual( + EXPECTED_PLAN_MODELS, + ); + }); + + it("uses China endpoints when explicit config points the paired surface at the China host", async () => { + const agentDir = mkdtempSync(join(tmpdir(), "openclaw-test-")); + const providers = await resolveImplicitProvidersForTest({ + agentDir, + env: { ...process.env, STEPFUN_API_KEY: "test-stepfun-key" }, + config: { + models: { + providers: { + "stepfun-plan": { + baseUrl: "https://api.stepfun.com/step_plan/v1", + models: [], + }, + }, + }, + }, + }); + + expect(providers?.stepfun?.baseUrl).toBe("https://api.stepfun.com/v1"); + expect(providers?.["stepfun-plan"]?.baseUrl).toBe("https://api.stepfun.com/step_plan/v1"); + }); + + it("discovers both providers from shared regional auth profiles", async () => { + const agentDir = mkdtempSync(join(tmpdir(), "openclaw-test-")); + + upsertAuthProfile({ + profileId: "stepfun:cn", + credential: { + type: "api_key", + provider: "stepfun", + key: "sk-stepfun-cn", // pragma: allowlist secret + }, + agentDir, + }); + upsertAuthProfile({ + profileId: "stepfun-plan:cn", + credential: { + type: "api_key", + provider: "stepfun-plan", + key: "sk-stepfun-cn", // pragma: allowlist secret + }, + agentDir, + }); + + const providers = await resolveImplicitProvidersForTest({ agentDir, env: {} }); + + expect(providers?.stepfun?.baseUrl).toBe("https://api.stepfun.com/v1"); + expect(providers?.["stepfun-plan"]?.baseUrl).toBe("https://api.stepfun.com/step_plan/v1"); + expect(providers?.stepfun?.models?.map((model) => model.id)).toEqual(EXPECTED_STANDARD_MODELS); + expect(providers?.["stepfun-plan"]?.models?.map((model) => model.id)).toEqual( + EXPECTED_PLAN_MODELS, + ); + }); +}); diff --git a/src/plugins/bundled-provider-auth-env-vars.generated.ts b/src/plugins/bundled-provider-auth-env-vars.generated.ts index 041de85b61e7..e6eca7d9091a 100644 --- a/src/plugins/bundled-provider-auth-env-vars.generated.ts +++ b/src/plugins/bundled-provider-auth-env-vars.generated.ts @@ -32,6 +32,8 @@ export const BUNDLED_PROVIDER_AUTH_ENV_VAR_CANDIDATES = { perplexity: ["PERPLEXITY_API_KEY", "OPENROUTER_API_KEY"], qianfan: ["QIANFAN_API_KEY"], sglang: ["SGLANG_API_KEY"], + stepfun: ["STEPFUN_API_KEY"], + "stepfun-plan": ["STEPFUN_API_KEY"], synthetic: ["SYNTHETIC_API_KEY"], tavily: ["TAVILY_API_KEY"], together: ["TOGETHER_API_KEY"],