mirror of
https://github.com/Sea-Haven-Industries/open-swe.git
synced 2026-09-30 19:43:15 +00:00
Some checks are pending
CI / Lint (push) Waiting to run
CI / Format check (push) Waiting to run
CI / Typecheck (push) Waiting to run
CI / Unit tests (push) Waiting to run
CI / Playwright E2E (push) Waiting to run
CI / Docker build smoke (push) Waiting to run
CI / Triage ledger up to date (push) Waiting to run
CI / ui bun.lock in sync (push) Waiting to run
* feat(models): re-add Fable 5 with admin disable toggle (port of upstream #1677) * refactor(models): convert re-added Fable 5 to Bedrock model IDs * fix(open-swe): correct Fable copy to describe provider data sharing, not ZDR The ported admin toggle description and code comments described Fable 5 as incompatible with Zero Data Retention. That is backwards: Fable 5 requires the account to opt into Bedrock provider_data_share — prompts/completions are retained and shared with Anthropic (up to 30 days, incl. human review). The old UI copy would lead an admin to believe the opposite of what enabling the toggle does. Reword the toggle description and the gate_fable_model / team_settings comments accordingly. Still off by default. Refs #171.
225 lines
7.9 KiB
Python
225 lines
7.9 KiB
Python
"""Supported models and reasoning efforts surfaced in the profile editor."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import TypedDict
|
|
|
|
|
|
class ModelOption(TypedDict):
|
|
id: str
|
|
label: str
|
|
efforts: list[str]
|
|
default_effort: str
|
|
supports_images: bool
|
|
|
|
|
|
SUPPORTED_MODELS: list[ModelOption] = [
|
|
{
|
|
"id": "bedrock_converse:us.anthropic.claude-opus-4-8",
|
|
"label": "Opus 4.8 (Bedrock)",
|
|
"efforts": ["low", "medium", "high", "xhigh", "max"],
|
|
"default_effort": "high",
|
|
"supports_images": True,
|
|
},
|
|
{
|
|
"id": "bedrock_converse:us.anthropic.claude-sonnet-5",
|
|
"label": "Sonnet 5 (Bedrock)",
|
|
"efforts": ["low", "medium", "high", "xhigh", "max"],
|
|
"default_effort": "high",
|
|
"supports_images": True,
|
|
},
|
|
{
|
|
"id": "bedrock_converse:us.anthropic.claude-fable-5",
|
|
"label": "Fable 5",
|
|
"efforts": ["low", "medium", "high", "xhigh", "max"],
|
|
"default_effort": "high",
|
|
"supports_images": True,
|
|
},
|
|
{
|
|
"id": "fireworks:accounts/fireworks/models/kimi-k2p7-code",
|
|
"label": "Kimi K2.7",
|
|
"efforts": ["low", "medium", "high"],
|
|
"default_effort": "high",
|
|
"supports_images": False,
|
|
},
|
|
{
|
|
"id": "fireworks:accounts/fireworks/models/deepseek-v4-pro",
|
|
"label": "DeepSeek V4 Pro",
|
|
"efforts": ["none", "low", "medium", "high", "xhigh", "max"],
|
|
"default_effort": "high",
|
|
"supports_images": False,
|
|
},
|
|
{
|
|
"id": "fireworks:accounts/fireworks/models/glm-5p2",
|
|
"label": "GLM 5.2",
|
|
"efforts": ["none", "high", "max"],
|
|
"default_effort": "high",
|
|
"supports_images": False,
|
|
},
|
|
{
|
|
"id": "fireworks:accounts/fireworks/models/minimax-m3",
|
|
"label": "MiniMax M3",
|
|
"efforts": ["medium", "high"],
|
|
"default_effort": "high",
|
|
"supports_images": True,
|
|
},
|
|
{
|
|
"id": "fireworks:accounts/fireworks/models/gpt-oss-120b",
|
|
"label": "gpt-oss-120b",
|
|
"efforts": ["low", "medium", "high"],
|
|
"default_effort": "medium",
|
|
"supports_images": False,
|
|
},
|
|
{
|
|
"id": "fireworks:accounts/fireworks/models/deepseek-v4-flash",
|
|
"label": "DeepSeek V4 Flash",
|
|
"efforts": ["none", "medium", "high"],
|
|
"default_effort": "high",
|
|
"supports_images": False,
|
|
},
|
|
]
|
|
|
|
SUPPORTED_MODEL_IDS: frozenset[str] = frozenset(m["id"] for m in SUPPORTED_MODELS)
|
|
|
|
_FABLE_ID_PREFIX = "bedrock_converse:us.anthropic.claude-fable"
|
|
|
|
FABLE_MODEL_IDS: frozenset[str] = frozenset(
|
|
m["id"] for m in SUPPORTED_MODELS if m["id"].startswith(_FABLE_ID_PREFIX)
|
|
)
|
|
|
|
|
|
def fable_disabled_fallback(effort: object = None) -> tuple[str, str]:
|
|
"""Newest supported non-Fable Claude model (keeps the Claude family),
|
|
else the global default. Substitutes a Fable selection when Fable is
|
|
disabled workspace-wide, preserving ``effort`` when the fallback supports it."""
|
|
for m in SUPPORTED_MODELS:
|
|
if m["id"].startswith("bedrock_converse:us.anthropic.") and m["id"] not in FABLE_MODEL_IDS:
|
|
return m["id"], _fallback_effort_for(m, effort) or m["default_effort"]
|
|
return default_model_pair()
|
|
|
|
|
|
def gate_fable_model(
|
|
model_id: str, effort: str | None, *, fable_enabled: bool
|
|
) -> tuple[str, str | None]:
|
|
"""Provider-data-share gate: if Fable is disabled but a Fable id was
|
|
resolved, swap in a safe non-Fable model. Fable requires the account to opt
|
|
into Bedrock ``provider_data_share`` (prompts/completions shared with the
|
|
provider), so it is gated off by default. Non-Fable selections pass through
|
|
unchanged. Applied at every model-construction entrypoint so a disabled
|
|
Fable model can never reach ``make_model``, no matter which layer selected
|
|
it."""
|
|
if not fable_enabled and isinstance(model_id, str) and model_id in FABLE_MODEL_IDS:
|
|
return fable_disabled_fallback(effort)
|
|
return model_id, effort
|
|
|
|
|
|
DEFAULT_MODEL_ID: str = "bedrock_converse:us.anthropic.claude-opus-4-8"
|
|
DEFAULT_MODEL_EFFORT: str = "medium"
|
|
|
|
|
|
def model_supports_effort(model_id: str, effort: str) -> bool:
|
|
for m in SUPPORTED_MODELS:
|
|
if m["id"] == model_id:
|
|
return effort in m["efforts"]
|
|
return False
|
|
|
|
|
|
def model_supports_images(model_id: str) -> bool:
|
|
for m in SUPPORTED_MODELS:
|
|
if m["id"] == model_id:
|
|
return m["supports_images"]
|
|
return False
|
|
|
|
|
|
def _provider_of(model_id: str) -> str | None:
|
|
provider, _, rest = model_id.partition(":")
|
|
return provider if rest else None
|
|
|
|
|
|
def _claude_family_of(model_id: str) -> str | None:
|
|
provider, _, name = model_id.partition(":")
|
|
if provider == "anthropic":
|
|
claude = name
|
|
elif provider == "bedrock_converse":
|
|
# Bedrock ids embed the model as a region-prefixed path, e.g.
|
|
# "us.anthropic.claude-sonnet-5" — take the trailing "claude-*" segment so
|
|
# our Bedrock Claude models get the same family-aware fallback.
|
|
claude = name.rpartition(".")[2]
|
|
else:
|
|
return None
|
|
if not claude.startswith("claude-"):
|
|
return None
|
|
parts = claude.split("-")
|
|
if len(parts) < 2:
|
|
return None
|
|
return "-".join(parts[:2])
|
|
|
|
|
|
def _fallback_effort_for(model: ModelOption, effort: object) -> str | None:
|
|
if not isinstance(effort, str):
|
|
return None
|
|
if effort in model["efforts"]:
|
|
return effort
|
|
if (
|
|
model["id"].startswith("google_genai:")
|
|
and effort == "none"
|
|
and "minimal" in model["efforts"]
|
|
):
|
|
return "minimal"
|
|
return None
|
|
|
|
|
|
def provider_fallback_pair(model_id: object, effort: object = None) -> tuple[str, str] | None:
|
|
"""Newest supported ``(model_id, effort)`` for the same provider/family.
|
|
|
|
Keeps a stored selection on its original provider when its exact id has
|
|
dropped out of the supported set (e.g. an Opus minor-version bump), preferring
|
|
the same Claude family when available instead of falling through to the
|
|
cross-provider global default. Preserves ``effort`` when the fallback model
|
|
supports it, otherwise uses that model's default effort. Returns ``None`` when
|
|
no supported model shares the provider.
|
|
"""
|
|
if not isinstance(model_id, str):
|
|
return None
|
|
provider = _provider_of(model_id)
|
|
if provider is None:
|
|
return None
|
|
family = _claude_family_of(model_id)
|
|
if family is not None:
|
|
for m in SUPPORTED_MODELS:
|
|
if _provider_of(m["id"]) == provider and _claude_family_of(m["id"]) == family:
|
|
return m["id"], _fallback_effort_for(m, effort) or m["default_effort"]
|
|
for m in SUPPORTED_MODELS:
|
|
if _provider_of(m["id"]) == provider:
|
|
return m["id"], _fallback_effort_for(m, effort) or m["default_effort"]
|
|
return None
|
|
|
|
|
|
def default_model_pair() -> tuple[str, str]:
|
|
"""Hardcoded fallback (model_id, reasoning_effort) used when no team default is set."""
|
|
if DEFAULT_MODEL_ID in SUPPORTED_MODEL_IDS and model_supports_effort(
|
|
DEFAULT_MODEL_ID, DEFAULT_MODEL_EFFORT
|
|
):
|
|
return DEFAULT_MODEL_ID, DEFAULT_MODEL_EFFORT
|
|
first = SUPPORTED_MODELS[0]
|
|
return first["id"], first["default_effort"]
|
|
|
|
|
|
def default_vision_model_pair() -> tuple[str, str]:
|
|
"""Default (model_id, reasoning_effort) to use when image input is required.
|
|
|
|
Prefers the configured default model when it is vision-capable (Opus 4.8),
|
|
otherwise the first vision-capable supported model. The fork ships only
|
|
Bedrock/Fireworks models, so this selects on ``supports_images`` rather than
|
|
upstream's ``openai:``/``anthropic:`` provider filter.
|
|
"""
|
|
if (
|
|
DEFAULT_MODEL_ID in SUPPORTED_MODEL_IDS
|
|
and model_supports_images(DEFAULT_MODEL_ID)
|
|
and model_supports_effort(DEFAULT_MODEL_ID, DEFAULT_MODEL_EFFORT)
|
|
):
|
|
return DEFAULT_MODEL_ID, DEFAULT_MODEL_EFFORT
|
|
for model in SUPPORTED_MODELS:
|
|
if model["supports_images"]:
|
|
return model["id"], model["default_effort"]
|
|
return default_model_pair()
|