diff --git a/docs/features/stepfun-token-plan-provider/plan.md b/docs/features/stepfun-token-plan-provider/plan.md new file mode 100644 index 0000000000..fed9867e8a --- /dev/null +++ b/docs/features/stepfun-token-plan-provider/plan.md @@ -0,0 +1,48 @@ +# StepFun Token Plan Provider Plan + +## Approach + +Add `stepfun-step-plan` as a second explicit built-in profile. Keep the provider on the existing +OpenAI-compatible runtime because the official Step Plan quick start documents the Chat +Completions base URL and standard Step API Key authentication. + +## Source Changes + +1. Add the disabled profile to `src/main/provider/defaults.ts` next to `stepfun`, including Step + Plan subscription, API-key, quick-start, model, and default-base-URL links. +2. Register `stepfun-step-plan` in `src/main/provider/providerRegistry.ts` with: + - the Chinese summary behavior preset; + - provider-db model source `stepfun-step-plan`; + - `Token Plan` model group; + - generate-text connection check using `step-3.7-flash`; + - API-key credential validation. +3. Add the provider ID to `src/shared/providerDbCatalog.ts` so main and renderer refresh flows treat + it as provider-db-backed. +4. Reuse the existing StepFun icon. `resolveModelIconKey('stepfun-step-plan')` already resolves the + `stepfun` key, so no renderer asset or component change is required. + +## Compatibility + +The existing `stepfun` ID and defaults remain unchanged. Provider settings already append newly +introduced default IDs without overwriting stored profiles, so upgrades receive a disabled Step +Plan profile while retaining all existing StepFun data. + +No data migration is required. Each provider ID owns its own encrypted API-key setting and model +selection state. + +## Test Strategy + +- Assert both built-in profiles retain their distinct base URLs and Step Plan documentation. +- Assert `stepfun-step-plan` resolves to the OpenAI-compatible runtime, matching provider-db source, + API-key validation, and `step-3.7-flash` check model. +- Assert provider-db model discovery requests the `stepfun-step-plan` catalog and assigns returned + models to the new provider ID. +- Assert the connection check invokes the generic runtime with the Step Plan base URL and official + validation model. +- Assert provider-db refresh classification includes the new ID and remains case/whitespace + tolerant. + +## Validation + +Run the focused provider tests, followed by repository-required formatting, i18n validation, lint, +and node/web type checks. diff --git a/docs/features/stepfun-token-plan-provider/spec.md b/docs/features/stepfun-token-plan-provider/spec.md new file mode 100644 index 0000000000..fb9730d586 --- /dev/null +++ b/docs/features/stepfun-token-plan-provider/spec.md @@ -0,0 +1,89 @@ +# StepFun Token Plan Provider Spec + +## Status + +Implemented and validated on 2026-07-20. + +## Problem + +DeepChat currently exposes one built-in StepFun provider profile. It uses +`https://api.stepfun.com/v1`, which is the standard pay-as-you-go endpoint. StepFun also exposes a +separate Step Plan endpoint at `https://api.stepfun.com/step_plan/v1`. Users with a Step Plan +subscription cannot select that billing route without manually changing the existing profile URL, +which makes the two billing modes mutually exclusive and obscures which quota a conversation uses. + +## Goal + +Expose the standard StepFun endpoint and Step Plan endpoint as two independent built-in provider +profiles while preserving existing StepFun settings. + +## Provider Contract + +| Billing mode | Provider ID | Display name | Base URL | Model source | Check model | +| --- | --- | --- | --- | --- | --- | +| Standard pay-as-you-go | `stepfun` | StepFun | `https://api.stepfun.com/v1` | `stepfun` | `step-3.5-flash` | +| Step Plan | `stepfun-step-plan` | StepFun Token Plan | `https://api.stepfun.com/step_plan/v1` | `stepfun-step-plan` | `step-3.7-flash` | + +Both profiles use the existing OpenAI-compatible runtime and API-key credential strategy. API keys +remain stored in main-process provider settings under each provider ID. A user may enter the same +Step API Key in both profiles, but the configurations remain independent. + +The built-in provider database already contains separate `stepfun` and `stepfun-step-plan` model +catalogs. DeepChat must bind each profile to its matching catalog so Step Plan-only models such as +`step-router-v1` are not inferred from the standard billing profile. + +## User Experience + +Before: + +```text +Providers + StepFun https://api.stepfun.com/v1 +``` + +After: + +```text +Providers + StepFun https://api.stepfun.com/v1 + StepFun Token Plan https://api.stepfun.com/step_plan/v1 +``` + +The existing StepFun profile keeps its ID, name, URL, API key, enabled state, and selected models. +The new StepFun Token Plan profile is added disabled, following the normal built-in-provider merge +behavior. The existing StepFun icon is reused through the model icon registry's provider-ID match. + +## Acceptance Criteria + +- Existing `stepfun` configurations continue using `https://api.stepfun.com/v1` without migration. +- A disabled `stepfun-step-plan` built-in profile is available at + `https://api.stepfun.com/step_plan/v1`. +- The Step Plan profile uses OpenAI-compatible Chat Completions with Bearer API-key authentication. +- The Step Plan profile reads models from the `stepfun-step-plan` provider database entry. +- Connection checks use `step-3.7-flash`, as recommended by the official Step Plan quick start. +- Refreshing a Step Plan profile refreshes provider-db metadata before materializing its models. +- Default configuration, runtime mapping, model discovery, and connection-check behavior have + focused tests. + +## Constraints + +- Do not replace or mutate the existing `stepfun` profile. +- Do not introduce a StepFun-specific provider class or SDK; the documented API is OpenAI + compatible. +- Do not share stored credentials implicitly between the two profiles. +- Keep renderer credential handling unchanged; raw API keys remain main-process-owned. +- Use the current generated provider database entry instead of duplicating model metadata in + source. + +## Non-Goals + +- Adding StepFun Global (`stepfun.ai`) profiles. +- Adding the Anthropic-compatible Step Plan endpoint used by Claude Code. +- Changing StepFun pricing, quota display, or account subscription management. +- Migrating custom providers that happen to use a StepFun URL. + +## References + +- Step Plan quick start: +- Step Plan subscription: +- Step API keys: diff --git a/docs/features/stepfun-token-plan-provider/tasks.md b/docs/features/stepfun-token-plan-provider/tasks.md new file mode 100644 index 0000000000..6118aa8240 --- /dev/null +++ b/docs/features/stepfun-token-plan-provider/tasks.md @@ -0,0 +1,26 @@ +# StepFun Token Plan Provider Tasks + +## Specification + +- [x] Confirm the official OpenAI-compatible Step Plan base URL. +- [x] Confirm API-key authentication and the recommended validation model. +- [x] Verify separate standard and Step Plan entries exist in the provider database. +- [x] Define compatibility and non-goals. + +## Implementation + +- [x] Add the `stepfun-step-plan` default provider profile. +- [x] Add the explicit runtime and provider-db registry definition. +- [x] Include the provider in provider-db refresh classification. +- [x] Keep the existing `stepfun` profile unchanged. + +## Tests And Validation + +- [x] Cover default profile metadata and distinct URLs. +- [x] Cover registry mapping and credential/check behavior. +- [x] Cover Step Plan provider-db model discovery. +- [x] Run focused provider tests. +- [x] Run `pnpm run format`. +- [x] Run `pnpm run i18n`. +- [x] Run `pnpm run lint`. +- [x] Run `pnpm run typecheck`. diff --git a/src/main/provider/defaults.ts b/src/main/provider/defaults.ts index a33e46f6fc..3fa9e55d2a 100644 --- a/src/main/provider/defaults.ts +++ b/src/main/provider/defaults.ts @@ -730,6 +730,21 @@ export const DEFAULT_PROVIDERS: LLM_PROVIDER_BASE[] = [ defaultBaseUrl: 'https://api.stepfun.com/v1' } }, + { + id: 'stepfun-step-plan', + name: 'StepFun Token Plan', + apiType: 'openai-completions', + apiKey: '', + baseUrl: 'https://api.stepfun.com/step_plan/v1', + enable: false, + websites: { + official: 'https://platform.stepfun.com/step-plan', + apiKey: 'https://platform.stepfun.com/interface-key', + docs: 'https://platform.stepfun.com/docs/zh/step-plan/quick-start', + models: 'https://platform.stepfun.com/docs/zh/step-plan/integrations/reasoning-api', + defaultBaseUrl: 'https://api.stepfun.com/step_plan/v1' + } + }, { id: 'groq', diff --git a/src/main/provider/providerRegistry.ts b/src/main/provider/providerRegistry.ts index 5753fdb3b5..1c24124e65 100644 --- a/src/main/provider/providerRegistry.ts +++ b/src/main/provider/providerRegistry.ts @@ -503,6 +503,21 @@ const PROVIDER_ID_REGISTRY = new Map([ checkMaxTokens: 16 }) ], + [ + 'stepfun-step-plan', + createDefinition({ + ...CHINESE_SUMMARY_OPENAI, + modelSource: 'provider-db', + providerDbSourceId: 'stepfun-step-plan', + providerDbGroup: 'Token Plan', + checkStrategy: 'generate-text', + credentialStrategy: 'api-key', + checkModelId: 'step-3.7-flash', + checkPrompt: 'Hello', + checkTemperature: 0.2, + checkMaxTokens: 16 + }) + ], [ 'together', createDefinition({ diff --git a/src/shared/providerDbCatalog.ts b/src/shared/providerDbCatalog.ts index 81ce45a3a9..a2004290a3 100644 --- a/src/shared/providerDbCatalog.ts +++ b/src/shared/providerDbCatalog.ts @@ -11,6 +11,7 @@ const PROVIDER_DB_BACKED_PROVIDER_IDS = new Set([ 'nvidia', 'o3fan', 'stepfun', + 'stepfun-step-plan', 'upstage', 'kimi-for-coding', 'openai-codex' diff --git a/test/main/provider/basicApiKeyProviders.test.ts b/test/main/provider/basicApiKeyProviders.test.ts index 6cde71a192..4fcc0b2c37 100644 --- a/test/main/provider/basicApiKeyProviders.test.ts +++ b/test/main/provider/basicApiKeyProviders.test.ts @@ -94,6 +94,7 @@ describe('basic API-key provider registrations', () => { ['huggingface', 'huggingface', 'Qwen/Qwen3-Coder-Next'], ['moonshot-ai', 'moonshot-ai', 'kimi-k2-0905-preview'], ['stepfun', 'stepfun', 'step-3.5-flash'], + ['stepfun-step-plan', 'stepfun-step-plan', 'step-3.7-flash'], ['upstage', 'upstage', 'solar-mini'], ['alibaba-token-plan', 'alibaba-token-plan', 'deepseek-v4-flash'], ['alibaba-token-plan-cn', 'alibaba-token-plan-cn', 'deepseek-v4-flash'] @@ -280,6 +281,87 @@ describe('basic API-key provider registrations', () => { ]) }) + it('maps StepFun Token Plan models from its dedicated provider DB catalog', async () => { + mockGetProvider.mockReturnValue({ + id: 'stepfun-step-plan', + name: 'StepFun Step Plan (China)', + models: [ + { + id: 'step-router-v1', + display_name: 'Step Router v1', + tool_call: true, + reasoning: { + supported: false + }, + modalities: { + input: ['text'], + output: ['text'] + }, + limit: { + context: 256000, + output: 256000 + } + } + ] + }) + + const provider = new AiSdkProvider( + createProvider({ + id: 'stepfun-step-plan', + name: 'StepFun Token Plan', + baseUrl: 'https://api.stepfun.com/step_plan/v1' + }), + createProviderSettings() + ) + const models = await provider.fetchModels() + + expect(mockGetProvider).toHaveBeenCalledWith('stepfun-step-plan') + expect(models).toEqual([ + expect.objectContaining({ + id: 'step-router-v1', + name: 'Step Router v1', + group: 'Token Plan', + providerId: 'stepfun-step-plan', + functionCall: true, + reasoning: false, + contextLength: 256000, + maxTokens: 32000 + }) + ]) + }) + + it('checks StepFun Token Plan with the recommended model and endpoint', async () => { + const provider = new AiSdkProvider( + createProvider({ + id: 'stepfun-step-plan', + name: 'StepFun Token Plan', + baseUrl: 'https://api.stepfun.com/step_plan/v1' + }), + createProviderSettings() + ) + ;(provider as any).isInitialized = true + + await expect(provider.check()).resolves.toEqual({ + isOk: true, + errorMsg: null + }) + expect(mockRunAiSdkGenerateText).toHaveBeenCalledWith( + expect.objectContaining({ + providerKind: 'openai-compatible', + provider: expect.objectContaining({ + id: 'stepfun-step-plan', + apiKey: 'test-key', + baseUrl: 'https://api.stepfun.com/step_plan/v1' + }) + }), + [{ role: 'user', content: 'Hello' }], + 'step-3.7-flash', + expect.any(Object), + 0.2, + 16 + ) + }) + it('routes OpenCode Go chat completions models through OpenAI-compatible runtime', async () => { const provider = new AiSdkProvider( createProvider({ diff --git a/test/main/provider/defaultProviders.test.ts b/test/main/provider/defaultProviders.test.ts index f8cb047fa9..fbde293aa3 100644 --- a/test/main/provider/defaultProviders.test.ts +++ b/test/main/provider/defaultProviders.test.ts @@ -54,10 +54,22 @@ describe('DEFAULT_PROVIDERS', () => { enable: false }) expect(providersById.get('stepfun')).toMatchObject({ + name: 'StepFun', apiType: 'openai-completions', baseUrl: 'https://api.stepfun.com/v1', enable: false }) + expect(providersById.get('stepfun-step-plan')).toMatchObject({ + name: 'StepFun Token Plan', + apiType: 'openai-completions', + baseUrl: 'https://api.stepfun.com/step_plan/v1', + enable: false, + websites: expect.objectContaining({ + official: 'https://platform.stepfun.com/step-plan', + docs: 'https://platform.stepfun.com/docs/zh/step-plan/quick-start', + defaultBaseUrl: 'https://api.stepfun.com/step_plan/v1' + }) + }) expect(providersById.get('upstage')).toMatchObject({ apiType: 'openai-completions', baseUrl: 'https://api.upstage.ai/v1/solar', diff --git a/test/main/shared/providerDbCatalog.test.ts b/test/main/shared/providerDbCatalog.test.ts index 2943660706..55662d672b 100644 --- a/test/main/shared/providerDbCatalog.test.ts +++ b/test/main/shared/providerDbCatalog.test.ts @@ -25,9 +25,12 @@ describe('provider DB catalog', () => { 'moonshot-ai', 'nvidia', 'stepfun', + 'stepfun-step-plan', 'upstage' ]) { expect(isProviderDbBackedProvider(providerId)).toBe(true) } + + expect(isProviderDbBackedProvider(' STEPFUN-STEP-PLAN ')).toBe(true) }) })