Skip to content
4 changes: 3 additions & 1 deletion apps/desktop/electron/main/ipc/provider-ipc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -338,7 +338,9 @@ export function registerProviderIpc({
// ModelInfo is catalog metadata. Keep its published reasoning fields
// intact; Composer and runtime resolve the exact user binding when
// they need effective per-provider capabilities.
...(modelsDevModel ? { catalogSource: "models.dev" as const } : {}),
...(modelsDevModel
? { catalogSource: modelsDevModel.metadataSource ?? "models.dev" as const }
: {}),
};
};

Expand Down
13 changes: 10 additions & 3 deletions apps/desktop/electron/main/models-dev-catalog.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { readFile } from "node:fs/promises";
import { stepfunModelSupplement } from "./stepfun-model-metadata.ts";
import { modelIdsMatch } from "@pi-desktop/shared";
import type {
ModelCost,
Expand Down Expand Up @@ -54,6 +55,8 @@ export type ModelsDevProvider = {
};

export type ModelsDevModel = {
/** Absent for models.dev; explicit for a reviewed first-party supplement. */
metadataSource?: "provider";
providerKey: string;
providerName: string;
providerApi?: string;
Expand Down Expand Up @@ -717,7 +720,7 @@ export function modelInfoFromModelsDev(
capabilities: capabilityList(model),
supportedThinkingLevels: [...model.thinkingLevels],
source: "discovered",
catalogSource: "models.dev",
catalogSource: model.metadataSource ?? "models.dev",
};
}

Expand All @@ -742,7 +745,7 @@ export function modelConfigFromModelsDev(
...(model.cost?.tiers ? { tiers: model.cost.tiers } : {}),
};
const config: ModelConfig = {
source: "models.dev",
source: model.metadataSource ?? "models.dev",
name: model.displayName,
baseUrl: baseUrl ?? model.providerApi ?? "",
reasoning,
Expand Down Expand Up @@ -1002,7 +1005,11 @@ export class ModelsDevCatalog {
candidates.sort((left, right) =>
right.score - left.score || left.model.modelId.length - right.model.modelId.length,
);
const result = candidates[0]?.model;
const supplement = stepfunModelSupplement(input);
const firstParty = supplement && preferred.find(({ model, provider }) =>
apiMatches(input.baseUrl, provider.api) && modelIdsMatch(model.modelId, requested),
)?.model;
const result = firstParty ?? supplement ?? candidates[0]?.model;
// Cache the result (a miss included) so a repeated miss is also O(1) and
// cannot grow the candidate index with query-dependent keys.
this.lookupMemo.set(memoKey, result);
Expand Down
46 changes: 46 additions & 0 deletions apps/desktop/electron/main/stepfun-model-metadata.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import type { ModelsDevModel } from "./models-dev-catalog";

/**
* First-party metadata while models.dev has no StepFun Step 5 record.
* https://platform.stepfun.com/docs/zh/guides/models/step-5-preview
* Verified 2026-09-21: /v1/models publishes max_input_tokens=1024000,
* enable_vision_input=true and reasoning_effort_support_list=low/medium/high.
* The documented 64k output ceiling is represented conservatively as 64000.
* Never infer gateway capabilities from a model name or vendor label alone.
*/
export function stepfunModelSupplement(input: {
baseUrl?: string;
modelId: string;
}): ModelsDevModel | undefined {
if (input.modelId !== "step-5-preview" || !input.baseUrl) return undefined;
let endpoint: URL;
try {
endpoint = new URL(input.baseUrl);
} catch {
return undefined;
}
if (
endpoint.origin !== "https://api.stepfun.com" ||
!["/v1", "/step_plan/v1"].includes(endpoint.pathname.replace(/\/+$/, "")) ||
endpoint.username || endpoint.password || endpoint.search || endpoint.hash
) return undefined;
return {
providerKey: "stepfun",
providerName: "StepFun",
providerApi: `${endpoint.origin}${endpoint.pathname.replace(/\/+$/, "")}`,
metadataSource: "provider",
modelId: "step-5-preview",
displayName: "Step 5 Preview",
reasoning: true,
reasoningPublished: true,
thinkingLevels: ["low", "medium", "high"],
reasoningOptions: [{ type: "effort", values: ["low", "medium", "high"] }],
modalities: { input: ["text", "image", "video"], output: ["text"] },
modalitiesPublished: true,
inputPublished: true,
outputPublished: true,
toolCall: true,
structuredOutput: true,
limit: { context: 1_024_000, input: 1_024_000, output: 64_000 },
};
}
6 changes: 4 additions & 2 deletions docs/adr/0134-models-dev-sole-model-metadata-source.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# ADR 0134: Use models.dev as the sole model metadata source with a local snapshot

- Status: Accepted
- Status: Accepted; amended by [StepFun first-party metadata](stepfun-first-party-model-metadata.md)
- Date: 2026-08-29
- Deciders: PI-Desktop core
- Amends: ADR 0027, ADR 0133, D136, D266
Expand All @@ -22,7 +22,9 @@ cost tiers. Those fields need one stable owner and a local offline snapshot.

## Decision

`https://models.dev/api.json` is the only model metadata/configuration source.
`https://models.dev/api.json` is the general model metadata/configuration source.
The StepFun amendment defines a source-labelled, exact-endpoint exception for
`step-5-preview` until its first-party record is available.

1. Electron main reads the bundled public document from
`resources/models.dev/api.json` in development and
Expand Down
2 changes: 2 additions & 0 deletions docs/adr/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -333,4 +333,6 @@ Each ADR includes:
| provider-display-order | [Provider display order](provider-display-order.md) | Accepted |
| registry-header-variable-spelling | [Remote header variables accept the registry's `{name}` spelling](registry-header-variable-spelling.md) | Proposed |
| provider-system-certificates | [Desktop sidecar uses OS-trusted certificates](provider-system-certificates.md) | Accepted |
| stepfun-first-party-model-metadata | [First-party StepFun metadata for a newly released model](stepfun-first-party-model-metadata.md) | Accepted (amends ADR 0134) |

| image-generation-capability | [Image generation as a configured Agent capability](image-generation-capability.md) | Accepted |
62 changes: 62 additions & 0 deletions docs/adr/stepfun-first-party-model-metadata.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# ADR: First-party StepFun metadata for a newly released model

- Status: Accepted
- Date: 2026-09-21
- Amends: ADR 0134
- Related: issue #738

## Context

StepFun serves `step-5-preview` through its authenticated model-list API, but
models.dev has no record for that model under the first-party StepFun provider.
Its aggregator records disagree on reasoning, tool support and output limits.
Falling across providers can therefore misconfigure a real, runnable model.
The StepFun API publishes a 1,024,000-token input window and low/medium/high
reasoning; its official model guide documents vision, tools and 64k output.

## Decision

Keep models.dev as the general catalog. Introduce one reviewed, source-labelled
supplement for exactly `step-5-preview` at `https://api.stepfun.com/v1`
and the official Step Plan endpoint `https://api.stepfun.com/step_plan/v1`.
The supplement lives in a separate pure module in Electron main. A first-party
models.dev record for the same endpoint/model takes precedence as soon as it
exists; otherwise the supplement precedes cross-provider matches. It is also
available when the bundled catalog cannot load. No network fetch is added to
runtime startup or model lookup.

Metadata carries `provider` provenance, never falsely `models.dev`. The existing
optional model-info catalog-source field and internal runtime source union
accept this additive value. Existing consumers and stored bindings need no
migration. Lookup still goes through the same settings/session/subagent path;
explicit binding overrides remain authoritative.

Only exact HTTPS origin and version path matches qualify. Custom gateways,
lookalike hosts and other model IDs retain their
existing behavior. Live discovery remains the authority on availability: the
supplement does not insert a model into an endpoint's response or claim that
an API key has access. Video capability metadata does not add video attachment
transport to the desktop app.

## Alternatives

- Wait for models.dev: leaves a publicly available model misconfigured.
- Copy a reseller record: repeats inaccurate limits and capabilities.
- Modify the bundled models.dev document: falsely attributes vendor data to
models.dev and loses the change on catalog refresh.
- Introduce a broad parallel model catalog: unnecessary scope and maintenance.

## Consequences

The exception is narrow and removable once first-party catalog coverage is
stable. Its source must be rechecked when StepFun changes the model. The 64k
output ceiling is conservatively represented as 64,000 tokens; no unverified
pricing is supplied. Existing source values, protocols, persistence and secret
ownership remain unchanged.

## References

- [Step 5 Preview guide](https://platform.stepfun.com/docs/zh/guides/models/step-5-preview)
- `GET https://api.stepfun.com/v1/models` (verified 2026-09-21; no credentials
or account data are retained in repository fixtures)
- `apps/desktop/electron/main/stepfun-model-metadata.ts`
4 changes: 4 additions & 0 deletions docs/guide/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,3 +60,7 @@ number; use the sidebar when you are exploring a domain.
See the [AI development workflow](/spec/06-delivery/03-ai-development-workflow)
and [change checklist](/spec/06-delivery/05-change-checklist) for the complete
repository rules.

## StepFun

See [StepFun Step 5 Preview setup](stepfun.md).
33 changes: 33 additions & 0 deletions docs/guide/stepfun.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# StepFun Step 5 Preview

## Unreleased change

Add StepFun Plan to the AI-service picker and support Step 5 Preview with its
first-party context, vision, tool and reasoning capabilities.

## Setup

1. Open **Settings → Model configuration → Add AI service**.
2. Select **StepFun Plan** (`https://api.stepfun.com/step_plan/v1`) and enter your
Step API key. The preset uses Chat Completions and subscription credits.
The ordinary API has no dedicated picker entry; existing saved custom
services keep their endpoint and model configuration.
3. Fetch the model list and select `step-5-preview`, then save.
4. Select the saved model in a conversation. Choose low, medium or high
thinking intensity, or retain the default medium.

If the service cannot list models, add `step-5-preview` as a custom model ID
on that same StepFun service. API entitlement is still required.

The official endpoint reports a 1,024,000-token input limit, used here as a
conservative context window. The output cap uses a conservative 64,000 tokens from the documented 64k limit.
Text, images and tool calls use the existing conversation pipeline. The model's
video capability does not enable video attachments in PI-Desktop.

Advanced per-model overrides remain available. Existing custom gateways keep
their own configuration; the official-endpoint supplement does not apply to
reseller URLs. This change does not install or save an API key.

See the [official model guide](https://platform.stepfun.com/docs/zh/guides/models/step-5-preview).

See the [official Step Plan guide](https://platform.stepfun.com/docs/zh/step-plan/overview).
6 changes: 6 additions & 0 deletions docs/spec/03-runtime/02-agent-runtime.md
Original file line number Diff line number Diff line change
Expand Up @@ -1512,3 +1512,9 @@ cause survives adapter message flattening, remains on the final error row,
and never triggers a provider transport rebuild. Protocol errors such as
`EPROTO` keep their existing retry behavior. See
[certificate trust ADR](../../adr/provider-system-certificates.md).

### Retry preference read/write compatibility

Settings reads normalize missing or disabled `infiniteProviderRetry` to the
boolean `false`. A loaded settings object remains valid when another preference
is changed and saved. Explicit invalid non-boolean writes remain rejected.
8 changes: 8 additions & 0 deletions docs/spec/03-runtime/11-provider-model-system.md
Original file line number Diff line number Diff line change
Expand Up @@ -762,3 +762,11 @@ fix.
- Automatic paid-plan discovery for every vendor portal
- Proprietary non-HTTP SDKs without pi-ai support
- Cloud-synced provider profiles

### First-party StepFun metadata exception

ADR `stepfun-first-party-model-metadata` narrowly amends the sole-source rule
above: official StepFun Step 5 Preview uses a source-labelled, reviewed
supplement until models.dev publishes its first-party record. The exception
is confined to the exact official HTTPS endpoint/model pair and preserves
user overrides. Other models retain the existing catalog behavior.
7 changes: 6 additions & 1 deletion docs/spec/03-runtime/12-provider-config-schema.md
Original file line number Diff line number Diff line change
Expand Up @@ -407,7 +407,7 @@ type ModelCatalogCacheRecord = {
contextWindow?: number
source: "bundled" | "discovered" | "user"
/** Renderer annotation for a row resolved from the bundled models.dev snapshot. */
catalogSource?: "models.dev"
catalogSource?: "models.dev" | "provider"
updatedAt: string
raw?: unknown
}
Expand Down Expand Up @@ -614,3 +614,8 @@ secret:provider:<providerId>:oauth
The two refs are independent, so one row may hold a key, a vendor account, or
both; see [14-secrets-storage](14-secrets-storage.md) §10. Future multi-secret
providers may add further suffixes (`:client_secret`, etc.).

The optional `catalogSource: "provider"` identifies the reviewed first-party
StepFun Step 5 Preview supplement (ADR `stepfun-first-party-model-metadata`).
It is additive metadata, not a new persisted model availability source;
`source: bundled | discovered | user` and credential ownership are unchanged.
18 changes: 18 additions & 0 deletions docs/spec/03-runtime/13-model-catalog-and-selection.md
Original file line number Diff line number Diff line change
Expand Up @@ -544,6 +544,24 @@ same model to the check mark, the toggle and the duplicate guard.
neighbouring 1M-line windows apart (`1M` / `1.05M` / `1.1M`), and never
renders a `K` mantissa at or above 1000

### StepFun Step 5 Preview

The service picker exposes only StepFun Plan for StepFun, selecting
`https://api.stepfun.com/step_plan/v1` and Chat Completions. Ordinary API
services remain usable as custom configurations; existing saved endpoints
are never rewritten or relabelled as subscription services.
Discovery still determines which models the key can use. For exactly
`step-5-preview` at the plan endpoint or a saved ordinary API endpoint, a reviewed first-party metadata supplement
provides a 1,024,000-token context/input window, a conservative 64,000-token
output cap, image and tool support, and low/medium/high reasoning when the
first-party models.dev record is absent. Third-party catalog records do not
override that supplement. A first-party record supersedes it after refresh.

The metadata source is `provider`, distinct from `models.dev`; user binding
overrides keep their existing precedence. Other endpoints and model IDs are
unchanged. The desktop does not add video attachment transport. See ADR
`stepfun-first-party-model-metadata` and the StepFun setup guide.

## Image model binding

The default conversation model has a separate **Image generation model** row below
Expand Down
6 changes: 6 additions & 0 deletions docs/zh-CN/spec/03-runtime/02-agent-runtime.md
Original file line number Diff line number Diff line change
Expand Up @@ -1099,3 +1099,9 @@ System/Direct/Custom 代理路由保持不变。
终态。结构化原因会穿过 adapter 的错误扁平化,保留在最终错误行中,也不会触发
provider transport 重建。`EPROTO` 等协议错误继续使用原有重试行为。详见
[证书信任 ADR](../../../adr/provider-system-certificates.md)。

### Retry preference read/write compatibility

Settings reads normalize missing or disabled `infiniteProviderRetry` to the
boolean `false`. A loaded settings object remains valid when another preference
is changed and saved. Explicit invalid non-boolean writes remain rejected.
8 changes: 8 additions & 0 deletions docs/zh-CN/spec/03-runtime/11-provider-model-system.md
Original file line number Diff line number Diff line change
Expand Up @@ -648,6 +648,14 @@ OpenAI Responses 适配器必须把 `response.completed`(以及
- 不支持 pi-ai 的专有非 HTTP SDK
- 云同步的提供商配置文件

### First-party StepFun metadata exception

ADR `stepfun-first-party-model-metadata` narrowly amends the sole-source rule
above: official StepFun Step 5 Preview uses a source-labelled, reviewed
supplement until models.dev publishes its first-party record. The exception
is confined to the exact official HTTPS endpoint/model pair and preserves
user overrides. Other models retain the existing catalog behavior.

## 托管搜索消息与预算契约

- 搜索内容和进度事件必须拥有正式适配器类型,不能伪装成客户端工具调用。
Expand Down
5 changes: 5 additions & 0 deletions docs/zh-CN/spec/03-runtime/12-provider-config-schema.md
Original file line number Diff line number Diff line change
Expand Up @@ -453,3 +453,8 @@ secret:provider:<providerId>:oauth
两个引用相互独立,因此一行可以只有密钥、只有厂商账户,或两者兼有;参见
[14-secrets-storage](14-secrets-storage.md) §10。未来的多重秘密提供商可能会
继续添加后缀(`:client_secret` 等)。

The optional `catalogSource: "provider"` identifies the reviewed first-party
StepFun Step 5 Preview supplement (ADR `stepfun-first-party-model-metadata`).
It is additive metadata, not a new persisted model availability source;
`source: bundled | discovered | user` and credential ownership are unchanged.
18 changes: 18 additions & 0 deletions docs/zh-CN/spec/03-runtime/13-model-catalog-and-selection.md
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,24 @@ Electron 使用本地 `models.dev` 记录装饰缓存和新发现的模型行。
- [ ] 紧凑上限文本不会高于已发布值,1M 附近的相邻窗口保持可区分
(`1M` / `1.05M` / `1.1M`),且永远不会渲染出大于等于 1000 的 `K` 尾数

### StepFun Step 5 Preview

The service picker exposes only StepFun Plan for StepFun, selecting
`https://api.stepfun.com/step_plan/v1` and Chat Completions. Ordinary API
services remain usable as custom configurations; existing saved endpoints
are never rewritten or relabelled as subscription services.
Discovery still determines which models the key can use. For exactly
`step-5-preview` at the plan endpoint or a saved ordinary API endpoint, a reviewed first-party metadata supplement
provides a 1,024,000-token context/input window, a conservative 64,000-token
output cap, image and tool support, and low/medium/high reasoning when the
first-party models.dev record is absent. Third-party catalog records do not
override that supplement. A first-party record supersedes it after refresh.

The metadata source is `provider`, distinct from `models.dev`; user binding
overrides keep their existing precedence. Other endpoints and model IDs are
unchanged. The desktop does not add video attachment transport. See ADR
`stepfun-first-party-model-metadata` and the StepFun setup guide.

## 生图模型绑定

默认对话模型下方有独立的生图模型行。模型高级设置可指定唯一绑定;保存服务商表单才生效,取消丢弃选择,替换不会改变对话默认值。工具和批量合约见[图片生成与编辑](/zh-CN/spec/03-runtime/21-image-generation)。
2 changes: 1 addition & 1 deletion packages/agent-runtime/src/model-capabilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ export function modelConfigWithBinding(
model.contextWindow;
const catalogContextWindow =
model.catalogContextWindow ??
(model.source === "models.dev" && model.contextWindow > 0
((model.source === "models.dev" || model.source === "provider") && model.contextWindow > 0
? model.contextWindow
: undefined);
return {
Expand Down
5 changes: 3 additions & 2 deletions packages/agent-runtime/src/thinking-level.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,13 @@ export type ThinkingCapabilitySet = {
};

/**
* Serializable model metadata resolved in Electron main from models.dev.
* Serializable model metadata resolved in Electron main from models.dev or a
* reviewed first-party supplement.
* pi-ai consumes this record through its selected transport adapter but does
* not provide model names, limits, modalities, thinking levels, or prices.
*/
export type ModelConfig = {
source: "models.dev" | "generic";
source: "models.dev" | "provider" | "generic";
name: string;
baseUrl: string;
description?: string;
Expand Down
1 change: 1 addition & 0 deletions packages/i18n/src/locales/de/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1184,6 +1184,7 @@ sklm: {
"presetMinimaxCn": "MiniMax",
"presetMinimaxCnOpenai": "MiniMax (OpenAI)",
"presetKimiCoding": "Kimi für Codierung",
presetStepfunPlan: "StepFun Plan",
"presetXiaomi": "Xiaomi",
"apiStyleResponses": "OpenAI-Antworten",
"apiStyleAnthropic": "Anthropic Messages",
Expand Down
1 change: 1 addition & 0 deletions packages/i18n/src/locales/en/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1199,6 +1199,7 @@ sklm: {
presetMinimaxCn: "MiniMax",
presetMinimaxCnOpenai: "MiniMax (OpenAI)",
presetKimiCoding: "Kimi For Coding",
presetStepfunPlan: "StepFun Plan",
presetXiaomi: "Xiaomi",
apiStyleResponses: "OpenAI Responses",
apiStyleAnthropic: "Anthropic Messages",
Expand Down
Loading
Loading