diff --git a/docs/providers/stepfun.md b/docs/providers/stepfun.md index d0423214647..2e341f07463 100644 --- a/docs/providers/stepfun.md +++ b/docs/providers/stepfun.md @@ -35,16 +35,18 @@ Auth env var: `STEPFUN_API_KEY` Standard (`stepfun`): -| Model ref | Context | Max output | Notes | -| ------------------------ | ------- | ---------- | ---------------------- | -| `stepfun/step-3.5-flash` | 262,144 | 65,536 | Default standard model | +| Model ref | Context | Max output | Notes | +| ------------------------ | ------- | ---------- | ------------------------------ | +| `stepfun/step-3.5-flash` | 262,144 | 65,536 | Default standard model | +| `stepfun/step-3.7-flash` | 262,144 | 262,144 | Multimodal image input support | Step Plan (`stepfun-plan`): -| Model ref | Context | Max output | Notes | -| ---------------------------------- | ------- | ---------- | -------------------------- | -| `stepfun-plan/step-3.5-flash` | 262,144 | 65,536 | Default Step Plan model | -| `stepfun-plan/step-3.5-flash-2603` | 262,144 | 65,536 | Additional Step Plan model | +| Model ref | Context | Max output | Notes | +| ---------------------------------- | ------- | ---------- | ------------------------------ | +| `stepfun-plan/step-3.5-flash` | 262,144 | 65,536 | Default Step Plan model | +| `stepfun-plan/step-3.7-flash` | 262,144 | 262,144 | Multimodal image input support | +| `stepfun-plan/step-3.5-flash-2603` | 262,144 | 65,536 | Additional Step Plan model | ## Getting started @@ -84,6 +86,7 @@ Step Plan (`stepfun-plan`): Default model: `stepfun/step-3.5-flash` + Alternate model: `stepfun/step-3.7-flash` @@ -122,7 +125,7 @@ Step Plan (`stepfun-plan`): Default model: `stepfun-plan/step-3.5-flash` - Alternate model: `stepfun-plan/step-3.5-flash-2603` + Alternate models: `stepfun-plan/step-3.7-flash`, `stepfun-plan/step-3.5-flash-2603` @@ -145,6 +148,36 @@ A single auth flow writes region-matched profiles for both `stepfun` and `stepfu api: "openai-completions", apiKey: "${STEPFUN_API_KEY}", models: [ + { + id: "step-3.7-flash", + name: "Step 3.7 Flash", + reasoning: true, + input: ["text", "image"], + thinkingLevelMap: { off: "low", minimal: "low", xhigh: "high", max: "high" }, + cost: { input: 0.2, output: 1.15, cacheRead: 0.04, cacheWrite: 0 }, + contextWindow: 262144, + maxTokens: 262144, + compat: { + supportsStore: false, + supportsDeveloperRole: false, + supportsUsageInStreaming: false, + supportsReasoningEffort: true, + supportsStrictMode: false, + supportedReasoningEfforts: ["low", "medium", "high"], + maxTokensField: "max_tokens", + reasoningEffortMap: { + off: "low", + none: "low", + minimal: "low", + low: "low", + medium: "medium", + high: "high", + xhigh: "high", + adaptive: "high", + max: "high", + }, + }, + }, { id: "step-3.5-flash", name: "Step 3.5 Flash", @@ -175,6 +208,36 @@ A single auth flow writes region-matched profiles for both `stepfun` and `stepfu api: "openai-completions", apiKey: "${STEPFUN_API_KEY}", models: [ + { + id: "step-3.7-flash", + name: "Step 3.7 Flash", + reasoning: true, + input: ["text", "image"], + thinkingLevelMap: { off: "low", minimal: "low", xhigh: "high", max: "high" }, + cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, + contextWindow: 262144, + maxTokens: 262144, + compat: { + supportsStore: false, + supportsDeveloperRole: false, + supportsUsageInStreaming: false, + supportsReasoningEffort: true, + supportsStrictMode: false, + supportedReasoningEfforts: ["low", "medium", "high"], + maxTokensField: "max_tokens", + reasoningEffortMap: { + off: "low", + none: "low", + minimal: "low", + low: "low", + medium: "medium", + high: "high", + xhigh: "high", + adaptive: "high", + max: "high", + }, + }, + }, { id: "step-3.5-flash", name: "Step 3.5 Flash", @@ -202,6 +265,8 @@ A single auth flow writes region-matched profiles for both `stepfun` and `stepfu + - `step-3.7-flash` accepts text and image input through OpenClaw. StepFun's API also supports video, which is not yet a model input modality in OpenClaw. + - Step 3.7 supports `low`, `medium`, and `high` reasoning effort. Because the model has no non-reasoning mode, `/think off` maps to `low`. - `step-3.5-flash-2603` is currently exposed only on `stepfun-plan`. - Use `openclaw models list` and `openclaw models set ` to inspect or switch models. diff --git a/extensions/stepfun/index.test.ts b/extensions/stepfun/index.test.ts index 27b37c46001..9e743ef881e 100644 --- a/extensions/stepfun/index.test.ts +++ b/extensions/stepfun/index.test.ts @@ -1,12 +1,20 @@ // Stepfun tests cover index plugin behavior. import { readFileSync } from "node:fs"; import { resolve } from "node:path"; +import type { Context, Model } from "openclaw/plugin-sdk/llm"; import { registerProviderPlugin, requireRegisteredProvider, } from "openclaw/plugin-sdk/plugin-test-runtime"; +import { buildOpenAICompletionsParams } from "openclaw/plugin-sdk/provider-transport-runtime"; import { describe, expect, it } from "vitest"; import stepfunPlugin from "./index.js"; +import { + STEPFUN_DEFAULT_MODEL_REF, + STEPFUN_PLAN_DEFAULT_MODEL_REF, + buildStepFunPlanProvider, + buildStepFunProvider, +} from "./provider-catalog.js"; type StepFunManifest = { setup?: { @@ -28,6 +36,91 @@ function readManifest(): StepFunManifest { } describe("stepfun provider registration", () => { + it("adds Step 3.7 Flash without changing existing defaults", () => { + const standard = buildStepFunProvider(); + const plan = buildStepFunPlanProvider(); + const standardModel = standard.models?.find((model) => model.id === "step-3.7-flash"); + const planModel = plan.models?.find((model) => model.id === "step-3.7-flash"); + if (!standardModel) { + throw new Error("StepFun Standard catalog did not provide Step 3.7 Flash"); + } + + expect(STEPFUN_DEFAULT_MODEL_REF).toBe("stepfun/step-3.5-flash"); + expect(STEPFUN_PLAN_DEFAULT_MODEL_REF).toBe("stepfun-plan/step-3.5-flash"); + expect( + standard.models?.find((model) => model.id === "step-3.5-flash")?.compat + ?.supportsReasoningEffort, + ).not.toBe(true); + expect(standardModel).toMatchObject({ + reasoning: true, + input: ["text", "image"], + thinkingLevelMap: { off: "low", minimal: "low", xhigh: "high", max: "high" }, + contextWindow: 262144, + maxTokens: 262144, + cost: { input: 0.2, output: 1.15, cacheRead: 0.04, cacheWrite: 0 }, + compat: { + supportsStore: false, + supportsDeveloperRole: false, + supportsUsageInStreaming: false, + supportsReasoningEffort: true, + supportsStrictMode: false, + supportedReasoningEfforts: ["low", "medium", "high"], + maxTokensField: "max_tokens", + reasoningEffortMap: expect.objectContaining({ off: "low", medium: "medium", max: "high" }), + }, + }); + expect(planModel).toMatchObject({ + reasoning: true, + input: ["text", "image"], + thinkingLevelMap: { off: "low", minimal: "low", xhigh: "high", max: "high" }, + contextWindow: 262144, + maxTokens: 262144, + cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, + compat: { + supportsStore: false, + supportsDeveloperRole: false, + supportsUsageInStreaming: false, + supportsReasoningEffort: true, + supportsStrictMode: false, + supportedReasoningEfforts: ["low", "medium", "high"], + maxTokensField: "max_tokens", + reasoningEffortMap: expect.objectContaining({ off: "low", medium: "medium", max: "high" }), + }, + }); + + const transportModel = { + ...standardModel, + provider: "stepfun", + api: "openai-completions", + baseUrl: standard.baseUrl, + } as Model<"openai-completions">; + const context = { + systemPrompt: "system", + messages: [{ role: "user", content: "hi", timestamp: 1 }], + } as Context; + const offReasoning = standardModel.thinkingLevelMap?.off; + expect(offReasoning).toBe("low"); + const lowPayload = buildOpenAICompletionsParams(transportModel, context, { + reasoning: offReasoning, + maxTokens: 128, + } as never); + expect(lowPayload.reasoning_effort).toBe("low"); + expect(lowPayload.max_tokens).toBe(128); + expect(lowPayload).not.toHaveProperty("max_completion_tokens"); + expect(lowPayload).not.toHaveProperty("store"); + expect(lowPayload).not.toHaveProperty("stream_options"); + expect(lowPayload.messages).toEqual( + expect.arrayContaining([expect.objectContaining({ role: "system", content: "system" })]), + ); + expect(lowPayload.messages).not.toEqual( + expect.arrayContaining([expect.objectContaining({ role: "developer" })]), + ); + expect( + buildOpenAICompletionsParams(transportModel, context, { reasoning: "high" } as never) + .reasoning_effort, + ).toBe("high"); + }); + it("keeps manifest auth choices aligned with runtime provider methods", async () => { const { providers } = await registerProviderPlugin({ plugin: stepfunPlugin, diff --git a/extensions/stepfun/openclaw.plugin.json b/extensions/stepfun/openclaw.plugin.json index 1eed475ed6b..f83fb6f5917 100644 --- a/extensions/stepfun/openclaw.plugin.json +++ b/extensions/stepfun/openclaw.plugin.json @@ -25,6 +25,46 @@ "baseUrl": "https://api.stepfun.ai/v1", "api": "openai-completions", "models": [ + { + "id": "step-3.7-flash", + "name": "Step 3.7 Flash", + "reasoning": true, + "input": ["text", "image"], + "thinkingLevelMap": { + "off": "low", + "minimal": "low", + "xhigh": "high", + "max": "high" + }, + "contextWindow": 262144, + "maxTokens": 262144, + "cost": { + "input": 0.2, + "output": 1.15, + "cacheRead": 0.04, + "cacheWrite": 0 + }, + "compat": { + "supportsStore": false, + "supportsDeveloperRole": false, + "supportsUsageInStreaming": false, + "supportsReasoningEffort": true, + "supportsStrictMode": false, + "supportedReasoningEfforts": ["low", "medium", "high"], + "maxTokensField": "max_tokens", + "reasoningEffortMap": { + "off": "low", + "none": "low", + "minimal": "low", + "low": "low", + "medium": "medium", + "high": "high", + "xhigh": "high", + "adaptive": "high", + "max": "high" + } + } + }, { "id": "step-3.5-flash", "name": "Step 3.5 Flash", @@ -45,6 +85,46 @@ "baseUrl": "https://api.stepfun.ai/step_plan/v1", "api": "openai-completions", "models": [ + { + "id": "step-3.7-flash", + "name": "Step 3.7 Flash", + "reasoning": true, + "input": ["text", "image"], + "thinkingLevelMap": { + "off": "low", + "minimal": "low", + "xhigh": "high", + "max": "high" + }, + "contextWindow": 262144, + "maxTokens": 262144, + "cost": { + "input": 0, + "output": 0, + "cacheRead": 0, + "cacheWrite": 0 + }, + "compat": { + "supportsStore": false, + "supportsDeveloperRole": false, + "supportsUsageInStreaming": false, + "supportsReasoningEffort": true, + "supportsStrictMode": false, + "supportedReasoningEfforts": ["low", "medium", "high"], + "maxTokensField": "max_tokens", + "reasoningEffortMap": { + "off": "low", + "none": "low", + "minimal": "low", + "low": "low", + "medium": "medium", + "high": "high", + "xhigh": "high", + "adaptive": "high", + "max": "high" + } + } + }, { "id": "step-3.5-flash", "name": "Step 3.5 Flash", diff --git a/src/agents/models-config.providers.stepfun.test.ts b/src/agents/models-config.providers.stepfun.test.ts index 3e8f0970323..ec118d039ea 100644 --- a/src/agents/models-config.providers.stepfun.test.ts +++ b/src/agents/models-config.providers.stepfun.test.ts @@ -7,8 +7,8 @@ import type { ModelDefinitionConfig, ModelProviderConfig } from "../config/types import { upsertAuthProfile } from "./auth-profiles.js"; import { installModelsConfigTestHooks } 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"]; +const EXPECTED_STANDARD_MODELS = ["step-3.7-flash", "step-3.5-flash"]; +const EXPECTED_PLAN_MODELS = ["step-3.7-flash", "step-3.5-flash", "step-3.5-flash-2603"]; const STEPFUN_STANDARD_CN_BASE_URL = "https://api.stepfun.com/v1"; const STEPFUN_STANDARD_INTL_BASE_URL = "https://api.stepfun.ai/v1"; const STEPFUN_PLAN_CN_BASE_URL = "https://api.stepfun.com/step_plan/v1";