[eric] models: default is opus-5 via one DEFAULT_MODEL constant; per-provider picks upgraded to gpt-5.6 + latest gemini

This commit is contained in:
ciregenz
2026-07-27 18:29:10 -07:00
parent 10a36dc90c
commit ff38bed50e
16 changed files with 35 additions and 22 deletions
+5 -1
View File
@@ -31,10 +31,14 @@ DEFAULT_SYSTEM_PROMPT = (
)
# Fresh-install / unset-fallback model. "opus-5" is the plain row: cc/ sub lane for subscription users, API key otherwise.
DEFAULT_MODEL = "opus-5"
class AppSettings(BaseModel):
default_system_prompt: Optional[str] = DEFAULT_SYSTEM_PROMPT
default_folder: Optional[str] = None
default_model: str = "sonnet"
default_model: str = DEFAULT_MODEL
default_mode: str = "agent"
default_max_turns: Optional[int] = None
default_thinking_level: Literal["off", "low", "medium", "high", "auto"] = "auto"
+2 -1
View File
@@ -136,7 +136,8 @@ async def clear_free_trial(settings_obj) -> None:
if getattr(settings_obj, "default_model", None) == "haiku" and (
has_own_model(settings_obj) or await p_has_connected_subscription()
):
settings_obj.default_model = "sonnet"
from backend.apps.settings.models import DEFAULT_MODEL
settings_obj.default_model = DEFAULT_MODEL
settings_obj.free_trial_token = None
await save_settings_async(settings_obj)
await p_sync_routing(settings_obj)
@@ -14,6 +14,7 @@ from uuid import uuid4
from backend.apps.swarm.exportable import DepRef, ExportContext, RemapTable
from backend.apps.swarm.models import EntityType, Requirement, RequirementKind
from backend.apps.settings.models import DEFAULT_MODEL
P_BUILTIN_MODES = {"agent", "ask", "plan", "view-builder", "skill-builder"}
# Transcript fields ride along so the shared agent keeps its history; ids inside (message ids, branch ids, their parent/fork refs) are self-consistent within the one session file, so they carry verbatim with no remap.
@@ -95,7 +96,7 @@ class SessionExportable:
"name": payload.get("name") or "Agent",
"status": "completed",
"provider": payload.get("provider") or "anthropic",
"model": payload.get("model") or "sonnet",
"model": payload.get("model") or DEFAULT_MODEL,
"mode": payload.get("mode") or "agent",
"system_prompt": payload.get("system_prompt"),
"allowed_tools": payload.get("allowed_tools") or [],
+2 -1
View File
@@ -14,6 +14,7 @@ from typing import Optional
from backend.apps.agents.core.models import AgentConfig
from backend.apps.workflows.models import Workflow, WorkflowRun
from backend.apps.workflows import storage
from backend.apps.settings.models import DEFAULT_MODEL
logger = logging.getLogger(__name__)
@@ -268,7 +269,7 @@ async def execute(
resolved_allowed_tools = _resolve_allowed_tools(wf)
config = AgentConfig(
name=wf.title or "Workflow",
model=wf.model or "sonnet",
model=wf.model or DEFAULT_MODEL,
mode=wf.mode or "agent",
provider=wf.provider or "anthropic",
system_prompt=_resolve_system_prompt(wf),
+2 -1
View File
@@ -2,6 +2,7 @@ from pydantic import BaseModel, ConfigDict, Field, field_validator, model_valida
from typing import Optional, Literal, Any
from datetime import datetime
from uuid import uuid4
from backend.apps.settings.models import DEFAULT_MODEL
# Each "tier" in the permission chain: notify in app, fall through to text after N minutes if no response, then to call after a further N minutes/hours. Matches images 17 to 19 (Schedule edit). Order in the list = escalation order.
@@ -97,7 +98,7 @@ class Workflow(BaseModel):
# Tool names observed in the source chat when this workflow was generated. This preserves conversion context without pretending those calls map to generated workflow step ids. Explicit approval decisions still live in remembered_approvals and are the only values reused as permissions.
source_tools: list[str] = Field(default_factory=list)
dashboard_id: Optional[str] = None
model: str = "sonnet"
model: str = DEFAULT_MODEL
mode: str = "agent"
provider: str = "anthropic"
created_at: datetime = Field(default_factory=datetime.now)
+6 -5
View File
@@ -20,6 +20,7 @@ from backend.apps.workflows.models import (
GenerateMetadataResponse,
)
from backend.apps.workflows import storage, scheduler, executor, audit, escalation
from backend.apps.settings.models import DEFAULT_MODEL
logger = logging.getLogger(__name__)
@@ -265,7 +266,7 @@ async def create_workflow(body: WorkflowCreate):
permissions=body.permissions or [],
source_session_id=body.source_session_id,
dashboard_id=body.dashboard_id,
model=body.model or "sonnet",
model=body.model or DEFAULT_MODEL,
mode=body.mode or "agent",
provider=body.provider or "anthropic",
cost_cap_usd_monthly=body.cost_cap_usd_monthly,
@@ -313,7 +314,7 @@ async def create_workflow(body: WorkflowCreate):
@workflows.router.post("/generate-metadata")
async def generate_workflow_metadata(body: GenerateMetadataRequest) -> GenerateMetadataResponse:
# Preview-time naming for the convert-to-workflow draft. Generates without persisting so the card can show a real title before the user saves.
wf = Workflow(steps=body.steps, model=body.model or "sonnet")
wf = Workflow(steps=body.steps, model=body.model or DEFAULT_MODEL)
title, description, labels = await _generate_workflow_metadata(wf)
return GenerateMetadataResponse(title=title, description=description, step_labels=labels)
@@ -1017,7 +1018,7 @@ async def edit_agent_session(workflow_id: str):
edit_dashboard_id = p_wsm.active_dashboard_id or executor.resolve_workflow_dashboard_id(wf)
config = AgentConfig(
name=f"Edit Agent: {wf.title}",
model=wf.model or "sonnet",
model=wf.model or DEFAULT_MODEL,
mode=wf.mode or "agent",
provider=wf.provider or "anthropic",
system_prompt=system_prompt,
@@ -1256,7 +1257,7 @@ async def test_run_workflow(workflow_id: str, body: dict):
resolved_allowed_tools = executor._resolve_allowed_tools(wf)
config = AgentConfig(
name=f"{wf.title or 'Workflow'} (test)",
model=wf.model or "sonnet",
model=wf.model or DEFAULT_MODEL,
mode=wf.mode or "agent",
provider=wf.provider or "anthropic",
system_prompt=executor._resolve_system_prompt(wf),
@@ -1395,7 +1396,7 @@ async def schedule_agent_session(workflow_id: str):
)
config = AgentConfig(
name=f"Scheduling: {wf.title}",
model=wf.model or "sonnet",
model=wf.model or DEFAULT_MODEL,
mode=wf.mode or "agent",
provider=wf.provider or "anthropic",
system_prompt=system_prompt,
+2 -1
View File
@@ -503,7 +503,8 @@ async def browser_agent_run(request: Request):
body = await request.json()
tasks = body.get("tasks", [])
model = body.get("model", "sonnet")
from backend.apps.settings.models import DEFAULT_MODEL
model = body.get("model", DEFAULT_MODEL)
dashboard_id = body.get("dashboard_id", "")
pre_selected_browser_ids = body.get("pre_selected_browser_ids", [])
parent_session_id = body.get("parent_session_id", "")
+2 -2
View File
@@ -168,13 +168,13 @@ async def test_clear_keeps_haiku_as_trial_face_unless_a_real_model_is_connected(
s2 = AppSettings(connection_mode="free-trial", free_trial_token="ftk",
default_model="haiku", anthropic_api_key="sk-ant-real")
await clear_free_trial(s2)
assert s2.default_model == "sonnet"
assert s2.default_model == "opus-5"
# A connected 9Router subscription (invisible in settings) also frees haiku -> sonnet.
monkeypatch.setattr(ft, "p_has_connected_subscription", _true)
s3 = AppSettings(connection_mode="free-trial", free_trial_token="ftk", default_model="haiku")
await clear_free_trial(s3)
assert s3.default_model == "sonnet"
assert s3.default_model == "opus-5"
# A user who deliberately picked haiku OUTSIDE free-trial mode is left alone.
s4 = AppSettings(connection_mode="own_key", default_model="haiku")
+1 -1
View File
@@ -63,7 +63,7 @@ def test_minimal_old_file_fills_missing_with_defaults(settings_file):
p_write(settings_file, {"theme": "light"})
s = store.load_settings()
assert s.theme == "light"
assert s.default_model == "sonnet" # filled from default
assert s.default_model == "opus-5" # filled from default
assert s.auto_reveal_sub_agents is True
+1
View File
@@ -7,6 +7,7 @@
"": {
"name": "open-swarm",
"version": "1.0.0",
"license": "AGPL-3.0-only",
"dependencies": {
"@codemirror/lang-html": "^6.4.11",
"@codemirror/lang-json": "^6.0.2",
+3 -3
View File
@@ -289,9 +289,9 @@ const DEFAULT_MODEL_PRIORITY: string[] = [
];
const DEFAULT_MODEL_PICKS: Record<string, string[]> = {
Anthropic: ['sonnet-cc', 'sonnet'],
OpenAI: ['gpt-5.4-mini', 'gpt-5.4'],
Google: ['gemini-2.5-flash', 'gemini-3-flash', 'gemini-2.5-pro'],
Anthropic: ['opus-5-cc', 'opus-5', 'opus-5-api', 'sonnet-5-cc', 'sonnet-5'],
OpenAI: ['gpt-5.6', 'gpt-5.6-api', 'gpt-5.5', 'gpt-5.5-api'],
Google: ['gemini-3.6-flash-api', 'gemini-3.5-flash-api', 'gemini-3.1-flash-lite'],
'OpenSwarm Pro': ['sonnet', 'opus'],
OpenSwarm: ['gpt-5-mini', 'claude-haiku-4.5', 'gpt-4.1'],
};
@@ -337,7 +337,7 @@ const AgentChat: React.FC<AgentChatProps> = ({ sessionId: sessionIdProp, onClose
// Holds the last non-empty suggestions so the docked banner's exit fade renders them instead of going blank the instant the array is cleared.
const mcpSnapshotRef = useRef<Array<{ id: string; title: string; description: string; reason?: string }>>([]);
const [mode, setMode] = useState('agent');
const [model, setModel] = useState('sonnet');
const [model, setModel] = useState('opus-5');
// Workflow build chat only: brief "this model now runs the workflow" notice when the user switches models, so the run-model change isn't silent.
const [workflowModelNotice, setWorkflowModelNotice] = useState<string | null>(null);
const workflowModelNoticeTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
@@ -3,6 +3,7 @@ import { useAppSelector } from '@/shared/hooks';
import { sortModelsForPicker } from '../model-picker/modelPicker';
const FALLBACK_MODELS = [
{ value: 'opus-5', label: 'Claude Opus 5', context_window: 1_000_000, reasoning: true },
{ value: 'sonnet', label: 'Claude Sonnet 4.6', context_window: 1_000_000, reasoning: true },
{ value: 'opus', label: 'Claude Opus 4.6', context_window: 1_000_000, reasoning: true },
{ value: 'haiku', label: 'Claude Haiku 4.5', context_window: 200_000, reasoning: true },
@@ -84,14 +84,14 @@ const DashboardToolbar = React.forwardRef<HTMLDivElement, Props>(
const defaultThinkingLevel = useAppSelector((s) => s.settings.data.default_thinking_level);
const settingsLoaded = useAppSelector((s) => s.settings.loaded);
const [mode, setMode] = useState(defaultMode || 'agent');
const [model, setModel] = useState(defaultModel || 'sonnet');
const [model, setModel] = useState(defaultModel || 'opus-5');
const [thinkingLevel, setThinkingLevel] = useState<'off' | 'low' | 'medium' | 'high' | 'auto'>(defaultThinkingLevel || 'auto');
// Snap to the persisted Settings defaults as soon as they arrive from the backend. Without the settingsLoaded guard, the effect fires against the Redux initialState ('sonnet') before the real default has loaded, and the settingsApplied flag then locks out the real default for the rest of the session, so new chats spawn under the stale value.
const settingsApplied = useRef(false);
useEffect(() => {
if (settingsLoaded && !settingsApplied.current) {
setMode(defaultMode || 'agent');
setModel(defaultModel || 'sonnet');
setModel(defaultModel || 'opus-5');
setThinkingLevel(defaultThinkingLevel || 'auto');
settingsApplied.current = true;
}
@@ -126,7 +126,7 @@ const DashboardToolbar = React.forwardRef<HTMLDivElement, Props>(
useEffect(() => {
if (settingsLoaded && inputOpen && !prevInputOpen.current) {
setMode(defaultMode || 'agent');
setModel(defaultModel || 'sonnet');
setModel(defaultModel || 'opus-5');
setThinkingLevel(defaultThinkingLevel || 'auto');
}
prevInputOpen.current = inputOpen;
@@ -44,6 +44,7 @@ let lastOpenTab: string | null = null;
// Shown only in the brief window before the live model list loads from the backend. Keep the flagship current so the default-model dropdown isn't stale.
const DEFAULT_MODEL_FALLBACK = [
{ value: 'opus-5', label: 'Claude Opus 5' },
{ value: 'opus-4-8', label: 'Claude Opus 4.8' },
{ value: 'sonnet', label: 'Claude Sonnet 4.6' },
{ value: 'opus', label: 'Claude Opus 4.6' },
+1 -1
View File
@@ -156,7 +156,7 @@ interface SettingsState {
export const DEFAULT_SETTINGS: AppSettings = {
default_system_prompt: DEFAULT_SYSTEM_PROMPT,
default_folder: null,
default_model: 'sonnet',
default_model: 'opus-5',
default_mode: 'agent',
default_max_turns: null,
default_thinking_level: 'auto',