mirror of
https://github.com/Sea-Haven-Industries/open-swe.git
synced 2026-09-30 09:13:14 +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.
122 lines
4.3 KiB
Python
122 lines
4.3 KiB
Python
from __future__ import annotations
|
|
|
|
from unittest.mock import AsyncMock, patch
|
|
|
|
import pytest
|
|
|
|
from agent.dashboard.options import FABLE_MODEL_IDS
|
|
from agent.dashboard.team_settings import TeamSettingsUpdate, get_team_fable_enabled
|
|
|
|
_FABLE = "bedrock_converse:us.anthropic.claude-fable-5"
|
|
|
|
# --- accessor: get_team_fable_enabled (async, patched store) ---
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_fable_enabled_defaults_false_when_absent() -> None:
|
|
# Legacy record with no fable_enabled key -> off.
|
|
with patch(
|
|
"agent.dashboard.team_settings.get_team_settings",
|
|
new_callable=AsyncMock,
|
|
return_value={},
|
|
):
|
|
assert await get_team_fable_enabled() is False
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_fable_enabled_true_when_set() -> None:
|
|
with patch(
|
|
"agent.dashboard.team_settings.get_team_settings",
|
|
new_callable=AsyncMock,
|
|
return_value={"fable_enabled": True},
|
|
):
|
|
assert await get_team_fable_enabled() is True
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_fable_enabled_false_for_non_bool_value() -> None:
|
|
# Fail-closed: any non-bool (e.g. a stray string) resolves to False.
|
|
with patch(
|
|
"agent.dashboard.team_settings.get_team_settings",
|
|
new_callable=AsyncMock,
|
|
return_value={"fable_enabled": "true"},
|
|
):
|
|
assert await get_team_fable_enabled() is False
|
|
|
|
|
|
# --- validation: TeamSettingsUpdate (sync) ---
|
|
|
|
|
|
def test_update_defaults_fable_disabled() -> None:
|
|
assert TeamSettingsUpdate().fable_enabled is False
|
|
|
|
|
|
def test_update_coerces_fable_model_when_disabled() -> None:
|
|
# Disabling Fable must never fail: a lingering Fable default is swapped for a
|
|
# safe non-Fable fallback (effort preserved) rather than rejected.
|
|
update = TeamSettingsUpdate(
|
|
default_agent_model=_FABLE,
|
|
default_agent_reasoning_effort="high",
|
|
)
|
|
assert update.default_agent_model not in FABLE_MODEL_IDS
|
|
assert update.default_agent_reasoning_effort == "high"
|
|
|
|
|
|
def test_update_coerces_fable_in_any_role_when_disabled() -> None:
|
|
# Same coercion applies to every model field, e.g. review chat.
|
|
update = TeamSettingsUpdate(
|
|
default_chat_model=_FABLE,
|
|
default_chat_reasoning_effort="high",
|
|
)
|
|
assert update.default_chat_model not in FABLE_MODEL_IDS
|
|
assert update.default_chat_reasoning_effort == "high"
|
|
|
|
|
|
def test_disable_transition_coerces_all_fable_defaults() -> None:
|
|
# The kill-switch flow: an admin who had picked Fable everywhere flips the
|
|
# toggle off, and the UI re-sends the whole settings blob with the Fable
|
|
# defaults still attached. The update must succeed and strip every Fable id.
|
|
update = TeamSettingsUpdate(
|
|
fable_enabled=False,
|
|
default_agent_model=_FABLE,
|
|
default_agent_reasoning_effort="high",
|
|
default_agent_subagent_model=_FABLE,
|
|
default_agent_subagent_reasoning_effort="high",
|
|
default_reviewer_model=_FABLE,
|
|
default_reviewer_reasoning_effort="high",
|
|
default_reviewer_subagent_model=_FABLE,
|
|
default_reviewer_subagent_reasoning_effort="high",
|
|
default_grouping_model=_FABLE,
|
|
default_grouping_reasoning_effort="high",
|
|
default_chat_model=_FABLE,
|
|
default_chat_reasoning_effort="high",
|
|
)
|
|
for field in (
|
|
"default_agent_model",
|
|
"default_agent_subagent_model",
|
|
"default_reviewer_model",
|
|
"default_reviewer_subagent_model",
|
|
"default_grouping_model",
|
|
"default_chat_model",
|
|
):
|
|
assert getattr(update, field) not in FABLE_MODEL_IDS, field
|
|
|
|
|
|
def test_update_leaves_non_fable_defaults_untouched_when_disabled() -> None:
|
|
# Coercion only rewrites Fable ids; other selections pass through unchanged.
|
|
update = TeamSettingsUpdate(
|
|
default_agent_model="bedrock_converse:us.anthropic.claude-sonnet-5",
|
|
default_agent_reasoning_effort="medium",
|
|
)
|
|
assert update.default_agent_model == "bedrock_converse:us.anthropic.claude-sonnet-5"
|
|
assert update.default_agent_reasoning_effort == "medium"
|
|
|
|
|
|
def test_update_accepts_fable_model_when_enabled() -> None:
|
|
update = TeamSettingsUpdate(
|
|
fable_enabled=True,
|
|
default_agent_model=_FABLE,
|
|
default_agent_reasoning_effort="high",
|
|
)
|
|
assert update.fable_enabled is True
|
|
assert update.default_agent_model == _FABLE
|