mirror of
https://github.com/codewhale-hq/Codewhale.git
synced 2026-11-08 01:48:20 -10:00
fix(provider): switching provider lands on its current model; /model lists the route's roster
Founder report: `/provider stepfun` silently selected step-3.7-flash, far older than step-5-preview, and `/model` did not show step-5-preview even when searching for it. Root causes: - StepFun's built-in default was step-3.7-flash in the descriptor and the seed spec. `catalog_models_dev.py defaults audit` never flagged it because it treats `-preview` rows as non-replacements. Step 5 Preview is StepFun's current flagship and is fully wired (reasoning effort map, 1M context, pricing), so the descriptor, seed spec and rendered seed `default` flag move to step-5-preview. A saved [providers.stepfun].model is still honoured. The audit's other recorded holds (openai, google, xai, meta) stay as recorded; meta was briefly moved and reverted to respect its DEFAULT_AUDIT_REASONS entry. - The default /model view (#6533) showed only the current model, pins, recents and one default per provider; the current route's other models were behind search. The existing custom-relay roster rule now covers the current route of any provider: up to 20 rows of its own roster (bundled plus live), newest first, in a new trailing `Roster` section so the one-default-per-provider invariant holds. - In the picker search, Shift+P and Shift+F pinned or added the highlighted row to Fleet instead of typing, so `Step-5-Preview` became `Step-5-review` and matched nothing (#6500 made them act during search). While searching they are now query text; Ctrl+P / Ctrl+F act on the highlighted row at any time and the footer advertises them while searching. Search also matches ids with separators removed (`step5`). Known gap, not fixed here: live rows on a non-default official host (e.g. StepFun Step Plan `/step_plan/v1`) are not completed from bundled facts, because `CatalogOffering::complete_from` requires the live fingerprint to match the bundled row's published base; they show "reasoning unknown" and no context. Widening that needs a per-fact rule (pricing must not cross PAYG/plan hosts). Proof (dev build of this tree, founder-shaped CODEWHALE_HOME copy with the Step Plan route and its cached live roster, saved stepfun model removed): `/provider stepfun` -> status line `StepFun / StepFlash · step-5-preview`; `/model` lists step-5-preview, step-3.7-flash, step-3.5-flash-2603, step-3.5-flash; searches `step5` and `Step-5-Preview` both find step-5-preview; Ctrl+P during search pins it. Checks: codewhale-config 805 passed, 1 failed (the StepFun default assertion in catalog/tests.rs, updated; rerun 1 passed); route::export golden regenerated with WRITE_GOLDEN=1 (1 passed); codewhale-agent 70 passed; codewhale-tui tui::model_picker 33 passed; commands::groups::core:: provider 37 passed; scripts/catalog_models_dev_test.py 18 passed. Touched paths: CHANGELOG.md, crates/tui/CHANGELOG.md, crates/agent/src/lib.rs, crates/config/assets/models_dev.bundled.json, crates/config/assets/provider_descriptors.json, crates/config/src/catalog/tests.rs, crates/config/src/route/providers-export.golden.json, crates/tui/src/tui/model_picker.rs, docs/PROVIDERS.md, scripts/catalog/models_dev_seed.toml. No provider_lake/provider_catalog_live edits. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
3400ebb5c9
commit
7d2308ac81
10 files changed
+89
-55
No files matched your search
@@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
- **Switching provider lands on its current model:** `/provider stepfun` now selects Step 5 Preview instead of the older Step 3.7 Flash; a model you saved for a provider is still kept. `/model` now lists the current provider's own models (bundled and live, newest first, up to 20) instead of only the selected one. Search also matches ids typed without separators (`step5`). While you search, Shift+P and Shift+F type their letters rather than pinning or adding to Fleet, so `Step-5-Preview` can be typed in full; Ctrl+P and Ctrl+F pin or add the highlighted row at any time.
|
||||
- **Remote control works again:** `codewhale rc` (`/rc`) can attach to the hosted control plane for web Chat again. The control plane had only accepted the retired 0.9.13 runtime identity, so every 0.10.x Engine was refused with `runner_runtime_identity_unavailable`; it now accepts the exact released runtime identity (commit, image and digest) and fails closed when they are not configured.
|
||||
- **Modern MCP client:** MCP servers now connect through the official MCP SDK (2.4) behind Codewhale's Rust broker by default (`mcp_backend = "host"`; `"rust"` stays available for one release). Servers that speak the 2026-07-28 revision are discovered with `server/discover`; other stdio and HTTP servers fall back to the legacy `initialize` handshake, and configured SSE servers keep theirs. Rust keeps process, network, credential, catalog, approval and cancellation authority. (CW-6713)
|
||||
- **MCP compatibility fixes:** older servers that answer the discovery probe with an error or an unrelated result now connect through the legacy handshake instead of being refused after the probe. A server that rejects sign-in reports its real 401 and the `/mcp login` hint rather than the probe's "method not found". A stale OAuth token is refreshed once when the probe itself is rejected. A server whose command cannot start now says why (for example, command not found) instead of a generic broker refusal.
|
||||
|
||||
@@ -1131,7 +1131,7 @@ mod tests {
|
||||
|
||||
for (provider, id) in [
|
||||
(ProviderKind::Zai, "GLM-5.2"),
|
||||
(ProviderKind::Stepfun, "step-3.7-flash"),
|
||||
(ProviderKind::Stepfun, "step-5-preview"),
|
||||
(ProviderKind::Minimax, "MiniMax-M2.1"),
|
||||
(ProviderKind::MinimaxAnthropic, "MiniMax-M3"),
|
||||
(ProviderKind::Openmodel, "deepseek-v4-flash"),
|
||||
@@ -1307,7 +1307,7 @@ mod tests {
|
||||
|
||||
let stepfun = registry.resolve_ok(None, Some(ProviderKind::Stepfun));
|
||||
assert_eq!(stepfun.resolved.provider, ProviderKind::Stepfun);
|
||||
assert_eq!(stepfun.resolved.id, "step-3.7-flash");
|
||||
assert_eq!(stepfun.resolved.id, "step-5-preview");
|
||||
|
||||
for (alias, expected) in [
|
||||
("minimax", "MiniMax-M3"),
|
||||
|
||||
@@ -6102,7 +6102,6 @@
|
||||
"knowledge": "2026-03-01",
|
||||
"release_date": "2026-05-29",
|
||||
"last_updated": "2026-05-29",
|
||||
"default": true,
|
||||
"attachment": true,
|
||||
"reasoning": true,
|
||||
"reasoning_options": [
|
||||
@@ -6150,6 +6149,7 @@
|
||||
"description": "StepFun's next-generation flagship base model for coding and professional knowledge work, with native text, image, and video input and a 1M-token context window",
|
||||
"release_date": "2026-09-16",
|
||||
"last_updated": "2026-09-20",
|
||||
"default": true,
|
||||
"attachment": true,
|
||||
"reasoning": true,
|
||||
"reasoning_options": [
|
||||
|
||||
@@ -1133,7 +1133,7 @@
|
||||
"id": "stepfun",
|
||||
"label": "StepFun / StepFlash",
|
||||
"base_url": "https://api.stepfun.ai/v1",
|
||||
"default_model": "step-3.7-flash",
|
||||
"default_model": "step-5-preview",
|
||||
"env_vars": [
|
||||
"STEPFUN_API_KEY",
|
||||
"STEP_API_KEY"
|
||||
|
||||
@@ -1433,7 +1433,7 @@ fn stepfun_bundled_coding_models_preserve_default_and_plan_pricing_boundary() {
|
||||
.find(|row| row.default_for_provider)
|
||||
.unwrap()
|
||||
.wire_model_id,
|
||||
"step-3.7-flash"
|
||||
"step-5-preview"
|
||||
);
|
||||
for row in &rows {
|
||||
assert_eq!(row.reasoning, Some(true));
|
||||
|
||||
@@ -1249,7 +1249,7 @@
|
||||
"label": "StepFun / StepFlash",
|
||||
"endpoint": "https://api.stepfun.ai/v1",
|
||||
"wire": "chat-completions",
|
||||
"defaultModel": "step-3.7-flash",
|
||||
"defaultModel": "step-5-preview",
|
||||
"envVars": [
|
||||
"STEPFUN_API_KEY",
|
||||
"STEP_API_KEY"
|
||||
@@ -1271,12 +1271,12 @@
|
||||
},
|
||||
{
|
||||
"id": "step-3.7-flash",
|
||||
"canonical": "stepfun/step-3.7-flash",
|
||||
"default": true
|
||||
"canonical": "stepfun/step-3.7-flash"
|
||||
},
|
||||
{
|
||||
"id": "step-5-preview",
|
||||
"canonical": "stepfun/step-5-preview"
|
||||
"canonical": "stepfun/step-5-preview",
|
||||
"default": true
|
||||
}
|
||||
],
|
||||
"transport": "chat-completions"
|
||||
|
||||
@@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
- **Switching provider lands on its current model:** `/provider stepfun` now selects Step 5 Preview instead of the older Step 3.7 Flash; a model you saved for a provider is still kept. `/model` now lists the current provider's own models (bundled and live, newest first, up to 20) instead of only the selected one. Search also matches ids typed without separators (`step5`). While you search, Shift+P and Shift+F type their letters rather than pinning or adding to Fleet, so `Step-5-Preview` can be typed in full; Ctrl+P and Ctrl+F pin or add the highlighted row at any time.
|
||||
- **Remote control works again:** `codewhale rc` (`/rc`) can attach to the hosted control plane for web Chat again. The control plane had only accepted the retired 0.9.13 runtime identity, so every 0.10.x Engine was refused with `runner_runtime_identity_unavailable`; it now accepts the exact released runtime identity (commit, image and digest) and fails closed when they are not configured.
|
||||
- **Modern MCP client:** MCP servers now connect through the official MCP SDK (2.4) behind Codewhale's Rust broker by default (`mcp_backend = "host"`; `"rust"` stays available for one release). Servers that speak the 2026-07-28 revision are discovered with `server/discover`; other stdio and HTTP servers fall back to the legacy `initialize` handshake, and configured SSE servers keep theirs. Rust keeps process, network, credential, catalog, approval and cancellation authority. (CW-6713)
|
||||
- **MCP compatibility fixes:** older servers that answer the discovery probe with an error or an unrelated result now connect through the legacy handshake instead of being refused after the probe. A server that rejects sign-in reports its real 401 and the `/mcp login` hint rather than the probe's "method not found". A stale OAuth token is refreshed once when the probe itself is rejected. A server whose command cannot start now says why (for example, command not found) instead of a generic broker refusal.
|
||||
|
||||
@@ -358,19 +358,20 @@ enum DefaultSection {
|
||||
Pinned,
|
||||
Recent,
|
||||
ProviderDefault,
|
||||
Roster,
|
||||
}
|
||||
|
||||
/// Most rows the default view's recent section shows.
|
||||
const RECENT_ROW_LIMIT: usize = 8;
|
||||
|
||||
/// Most rows one custom relay's own roster contributes to the default view.
|
||||
/// Most rows the current route's roster, or one custom relay's roster,
|
||||
/// contributes to the default view.
|
||||
///
|
||||
/// A relay has no bundled snapshot to browse: its roster exists only in the
|
||||
/// listing that route itself answered. Hiding all of it but the configured
|
||||
/// default is what made `/model` show one row for a route the provider panel
|
||||
/// had just listed a dozen for. The bound keeps a gateway fronting a thousand
|
||||
/// models from taking the view over.
|
||||
const CUSTOM_ROSTER_ROWS_IN_DEFAULT_VIEW: usize = 20;
|
||||
/// Hiding all of a route's roster but the configured default is what made
|
||||
/// `/model` show one row for a route the provider panel had just listed a
|
||||
/// dozen for. The bound keeps a gateway fronting a thousand models from taking
|
||||
/// the view over.
|
||||
const ROUTE_ROSTER_ROWS_IN_DEFAULT_VIEW: usize = 20;
|
||||
|
||||
/// A fresh live roster for the row's exact route omits its id. Only a fresh
|
||||
/// roster may say so: a stale one lags the provider (and would condemn ids it
|
||||
@@ -2453,28 +2454,44 @@ fn assign_default_sections(
|
||||
row.default_rank = Some((DefaultSection::ProviderDefault, usize::MAX));
|
||||
}
|
||||
}
|
||||
// A custom relay's roster is the reason the route was configured, and the
|
||||
// default view used to hide every one of its rows but the provider default
|
||||
// -- the panel listed the route's dozen models and `/model` showed one.
|
||||
// Surface the route's own live rows, bounded per route so a large gateway
|
||||
// cannot take the view over.
|
||||
// The current route's roster and a custom relay's roster are what the
|
||||
// user came to choose from, and the default view used to hide every row
|
||||
// but the provider default -- the panel listed a dozen models and `/model`
|
||||
// showed one. Surface each such route's own rows, bounded per route so a
|
||||
// large gateway cannot take the view over: newest first for a built-in
|
||||
// provider, the relay's own listing order for a custom relay.
|
||||
let current_identity = current.map(|(identity, _)| identity);
|
||||
let mut roster: Vec<usize> = rows
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|(_, row)| {
|
||||
row.default_rank.is_none()
|
||||
&& row.not_listed.is_none()
|
||||
&& !model_is_non_language(row)
|
||||
&& row_provider_identity(row).is_some_and(|identity| {
|
||||
current_identity == Some(identity)
|
||||
|| (row.provider == Some(ProviderKind::Custom)
|
||||
&& matches!(row.metadata.source, Some(CatalogSource::Live { .. })))
|
||||
})
|
||||
})
|
||||
.map(|(index, _)| index)
|
||||
.collect();
|
||||
roster.sort_by_cached_key(|&index| {
|
||||
let row = &rows[index];
|
||||
(row.provider != Some(ProviderKind::Custom))
|
||||
.then(|| std::cmp::Reverse(model_version_key(&row.id)))
|
||||
});
|
||||
let mut shown_per_route: BTreeMap<String, usize> = BTreeMap::new();
|
||||
for row in rows.iter_mut() {
|
||||
if row.default_rank.is_some()
|
||||
|| row.provider != Some(ProviderKind::Custom)
|
||||
|| !matches!(row.metadata.source, Some(CatalogSource::Live { .. }))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
let Some(identity) = row_provider_identity(row).map(str::to_string) else {
|
||||
for index in roster {
|
||||
let Some(identity) = row_provider_identity(&rows[index]).map(str::to_string) else {
|
||||
continue;
|
||||
};
|
||||
let shown = shown_per_route.entry(identity).or_default();
|
||||
if *shown >= CUSTOM_ROSTER_ROWS_IN_DEFAULT_VIEW {
|
||||
if *shown >= ROUTE_ROSTER_ROWS_IN_DEFAULT_VIEW {
|
||||
continue;
|
||||
}
|
||||
*shown += 1;
|
||||
row.default_rank = Some((DefaultSection::ProviderDefault, usize::MAX));
|
||||
rows[index].default_rank = Some((DefaultSection::Roster, usize::MAX));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3035,10 +3052,17 @@ fn model_row_matches_query(
|
||||
return true;
|
||||
}
|
||||
let normalized_query = normalize_picker_search_text(&query);
|
||||
let compact = |text: &str| {
|
||||
text.chars()
|
||||
.filter(char::is_ascii_alphanumeric)
|
||||
.collect::<String>()
|
||||
};
|
||||
let compact_query = compact(&query);
|
||||
let matches = |candidate: &str| {
|
||||
let candidate = candidate.to_ascii_lowercase();
|
||||
candidate.contains(&query)
|
||||
|| normalize_picker_search_text(&candidate).contains(&normalized_query)
|
||||
|| (!compact_query.is_empty() && compact(&candidate).contains(&compact_query))
|
||||
};
|
||||
let provider_matches = row.provider.is_some_and(|provider| {
|
||||
row.provider_identity.as_deref().is_some_and(matches)
|
||||
@@ -4320,16 +4344,16 @@ impl ModalView for ModelPickerView {
|
||||
{
|
||||
self.explain_unselectable_selection()
|
||||
}
|
||||
// ⇧P / ⇧F act on the highlighted row, searching or not: a search
|
||||
// is how a model is found, and the verbs used to become query
|
||||
// text the moment one was typed (#6500). The filter ignores case,
|
||||
// so only a custom id being typed (no catalog row highlighted)
|
||||
// still receives the capital letter. Some terminals report the
|
||||
// ⇧P / ⇧F act on the highlighted row while no search is typed;
|
||||
// while searching they are query text, so `Step-5-Preview` types
|
||||
// its `P` instead of pinning a row. Ctrl+P / Ctrl+F act on the
|
||||
// highlighted row either way, so a model found by search can still
|
||||
// be pinned or added to Fleet (#6500). Some terminals report the
|
||||
// chord as lowercase + SHIFT, hence `eq_ignore_ascii_case`.
|
||||
KeyCode::Char(ch)
|
||||
if key.modifiers == KeyModifiers::SHIFT
|
||||
&& ch.eq_ignore_ascii_case(&'p')
|
||||
&& (self.query.is_empty() || self.highlighted_route().is_some()) =>
|
||||
if ch.eq_ignore_ascii_case(&'p')
|
||||
&& ((key.modifiers == KeyModifiers::SHIFT && self.query.is_empty())
|
||||
|| key.modifiers == KeyModifiers::CONTROL) =>
|
||||
{
|
||||
let Some((provider, provider_id, model)) = self.highlighted_route() else {
|
||||
return ViewAction::None;
|
||||
@@ -4341,10 +4365,10 @@ impl ModalView for ModelPickerView {
|
||||
})
|
||||
}
|
||||
KeyCode::Char(ch)
|
||||
if key.modifiers == KeyModifiers::SHIFT
|
||||
&& ch.eq_ignore_ascii_case(&'f')
|
||||
if ch.eq_ignore_ascii_case(&'f')
|
||||
&& self.purpose == ModelPickerPurpose::Session
|
||||
&& (self.query.is_empty() || self.highlighted_route().is_some()) =>
|
||||
&& ((key.modifiers == KeyModifiers::SHIFT && self.query.is_empty())
|
||||
|| key.modifiers == KeyModifiers::CONTROL) =>
|
||||
{
|
||||
let Some((provider, provider_id, model)) = self.highlighted_route() else {
|
||||
return ViewAction::None;
|
||||
@@ -4635,17 +4659,23 @@ impl ModelPickerView {
|
||||
}
|
||||
// Keep compact route modals focused on the core browse/apply actions;
|
||||
// wider shells have room to disclose the pin action too.
|
||||
// ⇧P / ⇧F act only on a highlighted catalog route; without one
|
||||
// (typing a custom id) they are query text, so they are not offered.
|
||||
// Pin and Fleet act only on a highlighted catalog route. While a
|
||||
// search is typed the shifted letters are query text, so the Ctrl
|
||||
// chords are advertised instead.
|
||||
if inner.width >= 72 && inner.height >= 16 && self.highlighted_route().is_some() {
|
||||
let (fleet_key, pin_key) = if searching {
|
||||
("Ctrl+F", "Ctrl+P")
|
||||
} else {
|
||||
("⇧F", "⇧P")
|
||||
};
|
||||
if self.purpose == ModelPickerPurpose::Session {
|
||||
footer_hints.push(ActionHint::new(
|
||||
"⇧F",
|
||||
fleet_key,
|
||||
tr(self.locale, MessageId::PickerActionFleet),
|
||||
));
|
||||
}
|
||||
footer_hints.push(ActionHint::new(
|
||||
"⇧P",
|
||||
pin_key,
|
||||
tr(self.locale, MessageId::PickerActionPin),
|
||||
));
|
||||
}
|
||||
@@ -6450,7 +6480,7 @@ model = "deepseek/deepseek-v4-flash"
|
||||
},
|
||||
..Default::default()
|
||||
};
|
||||
let roster: Vec<_> = (0..CUSTOM_ROSTER_ROWS_IN_DEFAULT_VIEW + 5)
|
||||
let roster: Vec<_> = (0..ROUTE_ROSTER_ROWS_IN_DEFAULT_VIEW + 5)
|
||||
.map(|index| offering(&format!("relay-{index}")))
|
||||
.collect();
|
||||
crate::provider_catalog_live::record_success_if_current(
|
||||
@@ -6478,7 +6508,7 @@ model = "deepseek/deepseek-v4-flash"
|
||||
.collect();
|
||||
assert_eq!(
|
||||
visible.len(),
|
||||
CUSTOM_ROSTER_ROWS_IN_DEFAULT_VIEW,
|
||||
ROUTE_ROSTER_ROWS_IN_DEFAULT_VIEW,
|
||||
"the relay's own roster shows in the default view, bounded per route: {visible:?}"
|
||||
);
|
||||
assert!(
|
||||
@@ -6799,10 +6829,12 @@ api_key = "fixture-xai-key"
|
||||
"at most one default per provider: {rows:#?}"
|
||||
);
|
||||
}
|
||||
// The active provider's roster stays one search away.
|
||||
assert!(
|
||||
!rows.iter().any(|(_, id, _)| id == "deepseek-v4-pro"),
|
||||
"{rows:#?}"
|
||||
rows.iter()
|
||||
.any(|(identity, id, section)| identity == "deepseek"
|
||||
&& id == "deepseek-v4-pro"
|
||||
&& *section == Some(DefaultSection::Roster)),
|
||||
"the active provider's roster is listed: {rows:#?}"
|
||||
);
|
||||
|
||||
picker.update_query("GLM-5.2".to_string());
|
||||
|
||||
+3
-3
@@ -901,8 +901,8 @@ overlay and lets DSH resolve its own keys.
|
||||
|
||||
StepFun's four coding models are available through both its standard API and
|
||||
[Step Plan](https://platform.stepfun.ai/docs/en/step-plan/integrations/reasoning-api).
|
||||
Choose the billing route in `/provider`, then the model in `/model`; existing
|
||||
Step 3.7 selections remain unchanged. [Step 5 Preview](https://platform.stepfun.ai/docs/en/guides/models/step-5-preview)
|
||||
Choose the billing route in `/provider`, then the model in `/model`; Step 5
|
||||
Preview is the default, and an existing Step 3.7 selection remains unchanged. [Step 5 Preview](https://platform.stepfun.ai/docs/en/guides/models/step-5-preview)
|
||||
has a 1M context window. Step 5 and Step 3.7 expose low/medium/high reasoning;
|
||||
Step 3.5 Flash 2603 exposes low/high; base Step 3.5 uses provider-default reasoning.
|
||||
[Published API prices](https://platform.stepfun.ai/docs/en/guides/pricing/details)
|
||||
@@ -1217,7 +1217,7 @@ See [Catalog refresh](CATALOG_REFRESH.md) for freshness and estimate boundaries.
|
||||
| `arcee` | `trinity-large-thinking`, `trinity-large-preview`; provider-hinted custom model IDs pass through | yes | yes for `trinity-large-thinking`; no for `trinity-large-preview` |
|
||||
| `moonshot` | `kimi-k2.7-code`, `kimi-k2.6` | yes | yes |
|
||||
| `zai` | `GLM-5.3`, `GLM-5.3-Flash`, `GLM-5.2`, `GLM-5.1`, `GLM-5-Turbo`; provider-hinted custom model IDs pass through | yes | yes |
|
||||
| `stepfun` | `step-3.7-flash` (default), `step-5-preview`, `step-3.5-flash`, `step-3.5-flash-2603` | yes | yes |
|
||||
| `stepfun` | `step-5-preview` (default), `step-3.7-flash`, `step-3.5-flash`, `step-3.5-flash-2603` | yes | yes |
|
||||
| `minimax` | `MiniMax-M3`, `MiniMax-M2.1`, `MiniMax-M2.1-highspeed`, `MiniMax-M2`; `MiniMax-M2.7`, `MiniMax-M2.7-highspeed`, `MiniMax-M2.5` and `MiniMax-M2.5-highspeed` stay accepted until their 2026-11-18 retirement | yes | yes |
|
||||
| `minimax-anthropic` | `MiniMax-M3`, `MiniMax-M2.7` | yes | yes |
|
||||
| `sglang` | `deepseek-ai/DeepSeek-V4-Pro`, `deepseek-ai/DeepSeek-V4-Flash` | yes | yes |
|
||||
|
||||
@@ -358,7 +358,7 @@ name = "StepFun / StepFlash"
|
||||
api = "https://api.stepfun.ai/v1"
|
||||
npm = "@ai-sdk/openai-compatible"
|
||||
env = ["STEPFUN_API_KEY", "STEP_API_KEY"]
|
||||
default = "step-3.7-flash"
|
||||
default = "step-5-preview"
|
||||
models = [
|
||||
"step-3.5-flash",
|
||||
"step-3.5-flash-2603",
|
||||
|
||||
Reference in new issue
Block a user