Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions docs/features/stepfun-token-plan-provider/plan.md
Original file line number Diff line number Diff line change
@@ -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.
89 changes: 89 additions & 0 deletions docs/features/stepfun-token-plan-provider/spec.md
Original file line number Diff line number Diff line change
@@ -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: <https://platform.stepfun.com/docs/zh/step-plan/quick-start>
- Step Plan subscription: <https://platform.stepfun.com/step-plan>
- Step API keys: <https://platform.stepfun.com/interface-key>
26 changes: 26 additions & 0 deletions docs/features/stepfun-token-plan-provider/tasks.md
Original file line number Diff line number Diff line change
@@ -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`.
15 changes: 15 additions & 0 deletions src/main/provider/defaults.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
15 changes: 15 additions & 0 deletions src/main/provider/providerRegistry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -503,6 +503,21 @@ const PROVIDER_ID_REGISTRY = new Map<string, AiSdkProviderDefinition>([
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({
Expand Down
1 change: 1 addition & 0 deletions src/shared/providerDbCatalog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const PROVIDER_DB_BACKED_PROVIDER_IDS = new Set([
'nvidia',
'o3fan',
'stepfun',
'stepfun-step-plan',
'upstage',
'kimi-for-coding',
'openai-codex'
Expand Down
82 changes: 82 additions & 0 deletions test/main/provider/basicApiKeyProviders.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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']
Expand Down Expand Up @@ -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({
Expand Down
12 changes: 12 additions & 0 deletions test/main/provider/defaultProviders.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
3 changes: 3 additions & 0 deletions test/main/shared/providerDbCatalog.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
})
})
Loading