tunacode-cli 0.0.35__py3-none-any.whl → 0.0.37__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of tunacode-cli might be problematic. Click here for more details.

Files changed (32) hide show
  1. tunacode/cli/commands/__init__.py +62 -0
  2. tunacode/cli/commands/base.py +99 -0
  3. tunacode/cli/commands/implementations/__init__.py +38 -0
  4. tunacode/cli/commands/implementations/conversation.py +115 -0
  5. tunacode/cli/commands/implementations/debug.py +189 -0
  6. tunacode/cli/commands/implementations/development.py +77 -0
  7. tunacode/cli/commands/implementations/model.py +61 -0
  8. tunacode/cli/commands/implementations/system.py +216 -0
  9. tunacode/cli/commands/registry.py +236 -0
  10. tunacode/cli/repl.py +91 -30
  11. tunacode/configuration/settings.py +9 -2
  12. tunacode/constants.py +1 -1
  13. tunacode/core/agents/main.py +53 -3
  14. tunacode/core/agents/utils.py +304 -0
  15. tunacode/core/setup/config_setup.py +0 -1
  16. tunacode/core/state.py +13 -2
  17. tunacode/setup.py +7 -2
  18. tunacode/tools/read_file.py +8 -2
  19. tunacode/tools/read_file_async_poc.py +18 -10
  20. tunacode/tools/run_command.py +11 -4
  21. tunacode/ui/console.py +31 -4
  22. tunacode/ui/output.py +7 -2
  23. tunacode/ui/panels.py +98 -5
  24. tunacode/ui/utils.py +3 -0
  25. tunacode/utils/text_utils.py +6 -2
  26. {tunacode_cli-0.0.35.dist-info → tunacode_cli-0.0.37.dist-info}/METADATA +17 -17
  27. {tunacode_cli-0.0.35.dist-info → tunacode_cli-0.0.37.dist-info}/RECORD +31 -21
  28. tunacode/cli/commands.py +0 -893
  29. {tunacode_cli-0.0.35.dist-info → tunacode_cli-0.0.37.dist-info}/WHEEL +0 -0
  30. {tunacode_cli-0.0.35.dist-info → tunacode_cli-0.0.37.dist-info}/entry_points.txt +0 -0
  31. {tunacode_cli-0.0.35.dist-info → tunacode_cli-0.0.37.dist-info}/licenses/LICENSE +0 -0
  32. {tunacode_cli-0.0.35.dist-info → tunacode_cli-0.0.37.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,62 @@
1
+ """Command system for TunaCode CLI.
2
+
3
+ This package provides a modular command system with:
4
+ - Base classes and infrastructure in `base.py`
5
+ - Command registry and factory in `registry.py`
6
+ - Command implementations organized by category in `implementations/`
7
+
8
+ The main public API provides backward compatibility with the original
9
+ commands.py module while enabling better organization and maintainability.
10
+ """
11
+
12
+ # Import base classes and infrastructure
13
+ from .base import Command, CommandCategory, CommandSpec, SimpleCommand
14
+
15
+ # Import all command implementations for backward compatibility
16
+ from .implementations import (
17
+ BranchCommand,
18
+ ClearCommand,
19
+ CompactCommand,
20
+ DumpCommand,
21
+ FixCommand,
22
+ HelpCommand,
23
+ InitCommand,
24
+ IterationsCommand,
25
+ ModelCommand,
26
+ ParseToolsCommand,
27
+ RefreshConfigCommand,
28
+ ThoughtsCommand,
29
+ UpdateCommand,
30
+ YoloCommand,
31
+ )
32
+
33
+ # Import registry and factory
34
+ from .registry import CommandDependencies, CommandFactory, CommandRegistry
35
+
36
+ # Maintain backward compatibility by exposing the same public API
37
+ __all__ = [
38
+ # Base infrastructure
39
+ "Command",
40
+ "SimpleCommand",
41
+ "CommandSpec",
42
+ "CommandCategory",
43
+ # Registry and factory
44
+ "CommandRegistry",
45
+ "CommandFactory",
46
+ "CommandDependencies",
47
+ # All command classes (imported from implementations)
48
+ "YoloCommand",
49
+ "DumpCommand",
50
+ "ThoughtsCommand",
51
+ "IterationsCommand",
52
+ "ClearCommand",
53
+ "FixCommand",
54
+ "ParseToolsCommand",
55
+ "RefreshConfigCommand",
56
+ "HelpCommand",
57
+ "BranchCommand",
58
+ "CompactCommand",
59
+ "UpdateCommand",
60
+ "ModelCommand",
61
+ "InitCommand",
62
+ ]
@@ -0,0 +1,99 @@
1
+ """Base classes and infrastructure for TunaCode CLI commands."""
2
+
3
+ from abc import ABC, abstractmethod
4
+ from dataclasses import dataclass
5
+ from enum import Enum
6
+ from typing import List
7
+
8
+ from ...types import CommandArgs, CommandContext, CommandResult
9
+
10
+
11
+ class CommandCategory(Enum):
12
+ """Categories for organizing commands."""
13
+
14
+ SYSTEM = "system"
15
+ NAVIGATION = "navigation"
16
+ DEVELOPMENT = "development"
17
+ MODEL = "model"
18
+ DEBUG = "debug"
19
+
20
+
21
+ class Command(ABC):
22
+ """Base class for all commands."""
23
+
24
+ @property
25
+ @abstractmethod
26
+ def name(self) -> str:
27
+ """The primary name of the command."""
28
+ pass
29
+
30
+ @property
31
+ @abstractmethod
32
+ def aliases(self) -> CommandArgs:
33
+ """Alternative names/aliases for the command."""
34
+ pass
35
+
36
+ @property
37
+ def description(self) -> str:
38
+ """Description of what the command does."""
39
+ return ""
40
+
41
+ @property
42
+ def category(self) -> CommandCategory:
43
+ """Category this command belongs to."""
44
+ return CommandCategory.SYSTEM
45
+
46
+ @abstractmethod
47
+ async def execute(self, args: CommandArgs, context: CommandContext) -> CommandResult:
48
+ """
49
+ Execute the command.
50
+
51
+ Args:
52
+ args: Command arguments (excluding the command name)
53
+ context: Execution context with state and config
54
+
55
+ Returns:
56
+ Command-specific return value
57
+ """
58
+ pass
59
+
60
+
61
+ @dataclass
62
+ class CommandSpec:
63
+ """Specification for a command's metadata."""
64
+
65
+ name: str
66
+ aliases: List[str]
67
+ description: str
68
+ category: CommandCategory = CommandCategory.SYSTEM
69
+
70
+
71
+ class SimpleCommand(Command):
72
+ """Base class for simple commands without complex logic.
73
+
74
+ This class provides a standard implementation for commands that don't
75
+ require special initialization or complex behavior. It reads all
76
+ properties from a class-level CommandSpec attribute.
77
+ """
78
+
79
+ spec: CommandSpec
80
+
81
+ @property
82
+ def name(self) -> str:
83
+ """The primary name of the command."""
84
+ return self.__class__.spec.name
85
+
86
+ @property
87
+ def aliases(self) -> CommandArgs:
88
+ """Alternative names/aliases for the command."""
89
+ return self.__class__.spec.aliases
90
+
91
+ @property
92
+ def description(self) -> str:
93
+ """Description of what the command does."""
94
+ return self.__class__.spec.description
95
+
96
+ @property
97
+ def category(self) -> CommandCategory:
98
+ """Category this command belongs to."""
99
+ return self.__class__.spec.category
@@ -0,0 +1,38 @@
1
+ """Command implementations for TunaCode CLI."""
2
+
3
+ # Import all command classes for easy access
4
+ from .conversation import CompactCommand
5
+ from .debug import (
6
+ DumpCommand,
7
+ FixCommand,
8
+ IterationsCommand,
9
+ ParseToolsCommand,
10
+ ThoughtsCommand,
11
+ YoloCommand,
12
+ )
13
+ from .development import BranchCommand, InitCommand
14
+ from .model import ModelCommand
15
+ from .system import ClearCommand, HelpCommand, RefreshConfigCommand, StreamingCommand, UpdateCommand
16
+
17
+ __all__ = [
18
+ # System commands
19
+ "HelpCommand",
20
+ "ClearCommand",
21
+ "RefreshConfigCommand",
22
+ "StreamingCommand",
23
+ "UpdateCommand",
24
+ # Debug commands
25
+ "YoloCommand",
26
+ "DumpCommand",
27
+ "ThoughtsCommand",
28
+ "IterationsCommand",
29
+ "FixCommand",
30
+ "ParseToolsCommand",
31
+ # Development commands
32
+ "BranchCommand",
33
+ "InitCommand",
34
+ # Model commands
35
+ "ModelCommand",
36
+ # Conversation commands
37
+ "CompactCommand",
38
+ ]
@@ -0,0 +1,115 @@
1
+ """Conversation management commands for TunaCode CLI."""
2
+
3
+ from typing import List, Optional
4
+
5
+ from ....types import CommandContext, ProcessRequestCallback
6
+ from ....ui import console as ui
7
+ from ..base import CommandCategory, CommandSpec, SimpleCommand
8
+
9
+
10
+ class CompactCommand(SimpleCommand):
11
+ """Compact conversation context."""
12
+
13
+ spec = CommandSpec(
14
+ name="compact",
15
+ aliases=["/compact"],
16
+ description="Summarize and compact the conversation history",
17
+ category=CommandCategory.SYSTEM,
18
+ )
19
+
20
+ def __init__(self, process_request_callback: Optional[ProcessRequestCallback] = None):
21
+ self._process_request = process_request_callback
22
+
23
+ async def execute(self, args: List[str], context: CommandContext) -> None:
24
+ # Use the injected callback or get it from context
25
+ process_request = self._process_request or context.process_request
26
+
27
+ if not process_request:
28
+ await ui.error("Compact command not available - process_request not configured")
29
+ return
30
+
31
+ # Count current messages
32
+ original_count = len(context.state_manager.session.messages)
33
+
34
+ # Generate summary with output captured
35
+ summary_prompt = (
36
+ "Summarize the conversation so far in a concise paragraph, "
37
+ "focusing on the main topics discussed and any important context "
38
+ "that should be preserved."
39
+ )
40
+ result = await process_request(
41
+ summary_prompt,
42
+ context.state_manager,
43
+ output=False, # We'll handle the output ourselves
44
+ )
45
+
46
+ # Extract summary text from result
47
+ summary_text = ""
48
+
49
+ # First try: standard result structure
50
+ if (
51
+ result
52
+ and hasattr(result, "result")
53
+ and result.result
54
+ and hasattr(result.result, "output")
55
+ ):
56
+ summary_text = result.result.output
57
+
58
+ # Second try: check messages for assistant response
59
+ if not summary_text:
60
+ messages = context.state_manager.session.messages
61
+ # Look through new messages in reverse order
62
+ for i in range(len(messages) - 1, original_count - 1, -1):
63
+ msg = messages[i]
64
+ # Handle ModelResponse objects
65
+ if hasattr(msg, "parts") and msg.parts:
66
+ for part in msg.parts:
67
+ if hasattr(part, "content") and part.content:
68
+ content = part.content
69
+ # Skip JSON thought objects
70
+ if content.strip().startswith('{"thought"'):
71
+ lines = content.split("\n")
72
+ # Find the actual summary after the JSON
73
+ for i, line in enumerate(lines):
74
+ if (
75
+ line.strip()
76
+ and not line.strip().startswith("{")
77
+ and not line.strip().endswith("}")
78
+ ):
79
+ summary_text = "\n".join(lines[i:]).strip()
80
+ break
81
+ else:
82
+ summary_text = content
83
+ if summary_text:
84
+ break
85
+ # Handle dict-style messages
86
+ elif isinstance(msg, dict):
87
+ if msg.get("role") == "assistant" and msg.get("content"):
88
+ summary_text = msg["content"]
89
+ break
90
+ # Handle other message types
91
+ elif hasattr(msg, "content") and hasattr(msg, "role"):
92
+ if getattr(msg, "role", None) == "assistant":
93
+ summary_text = msg.content
94
+ break
95
+
96
+ if summary_text:
97
+ break
98
+
99
+ if not summary_text:
100
+ await ui.error("Failed to generate summary - no assistant response found")
101
+ return
102
+
103
+ # Display summary in a formatted panel
104
+ from tunacode.ui import panels
105
+
106
+ await panels.panel("Conversation Summary", summary_text, border_style="cyan")
107
+
108
+ # Show statistics
109
+ await ui.info(f"Current message count: {original_count}")
110
+ await ui.info("After compaction: 3 (summary + last 2 messages)")
111
+
112
+ # Truncate the conversation history
113
+ context.state_manager.session.messages = context.state_manager.session.messages[-2:]
114
+
115
+ await ui.success("Context history has been summarized and truncated.")
@@ -0,0 +1,189 @@
1
+ """Debug and troubleshooting commands for TunaCode CLI."""
2
+
3
+ from typing import List
4
+
5
+ from ....types import CommandContext
6
+ from ....ui import console as ui
7
+ from ..base import CommandCategory, CommandSpec, SimpleCommand
8
+
9
+
10
+ class YoloCommand(SimpleCommand):
11
+ """Toggle YOLO mode (skip confirmations)."""
12
+
13
+ spec = CommandSpec(
14
+ name="yolo",
15
+ aliases=["/yolo"],
16
+ description="Toggle YOLO mode (skip tool confirmations)",
17
+ category=CommandCategory.DEVELOPMENT,
18
+ )
19
+
20
+ async def execute(self, args: List[str], context: CommandContext) -> None:
21
+ state = context.state_manager.session
22
+ state.yolo = not state.yolo
23
+ if state.yolo:
24
+ await ui.success("All tools are now active ⚡ Please proceed with caution.\n")
25
+ else:
26
+ await ui.info("Tool confirmations re-enabled for safety.\n")
27
+
28
+
29
+ class DumpCommand(SimpleCommand):
30
+ """Dump message history."""
31
+
32
+ spec = CommandSpec(
33
+ name="dump",
34
+ aliases=["/dump"],
35
+ description="Dump the current message history",
36
+ category=CommandCategory.DEBUG,
37
+ )
38
+
39
+ async def execute(self, args: List[str], context: CommandContext) -> None:
40
+ await ui.dump_messages(context.state_manager.session.messages)
41
+
42
+
43
+ class ThoughtsCommand(SimpleCommand):
44
+ """Toggle display of agent thoughts."""
45
+
46
+ spec = CommandSpec(
47
+ name="thoughts",
48
+ aliases=["/thoughts"],
49
+ description="Show or hide agent thought messages",
50
+ category=CommandCategory.DEBUG,
51
+ )
52
+
53
+ async def execute(self, args: List[str], context: CommandContext) -> None:
54
+ state = context.state_manager.session
55
+
56
+ # No args - toggle
57
+ if not args:
58
+ state.show_thoughts = not state.show_thoughts
59
+ status = "ON" if state.show_thoughts else "OFF"
60
+ await ui.success(f"Thought display {status}")
61
+ return
62
+
63
+ # Parse argument
64
+ arg = args[0].lower()
65
+ if arg in {"on", "1", "true"}:
66
+ state.show_thoughts = True
67
+ elif arg in {"off", "0", "false"}:
68
+ state.show_thoughts = False
69
+ else:
70
+ await ui.error("Usage: /thoughts [on|off]")
71
+ return
72
+
73
+ status = "ON" if state.show_thoughts else "OFF"
74
+ await ui.success(f"Thought display {status}")
75
+
76
+
77
+ class IterationsCommand(SimpleCommand):
78
+ """Configure maximum agent iterations for ReAct reasoning."""
79
+
80
+ spec = CommandSpec(
81
+ name="iterations",
82
+ aliases=["/iterations"],
83
+ description="Set maximum agent iterations for complex reasoning",
84
+ category=CommandCategory.DEBUG,
85
+ )
86
+
87
+ async def execute(self, args: List[str], context: CommandContext) -> None:
88
+ state = context.state_manager.session
89
+
90
+ # Guard clause - handle "no args" case first and return early
91
+ if not args:
92
+ current = state.user_config.get("settings", {}).get("max_iterations", 40)
93
+ await ui.info(f"Current maximum iterations: {current}")
94
+ await ui.muted("Usage: /iterations <number> (1-100)")
95
+ return
96
+
97
+ # update the logic to not be as nested messely, the above guars needing to get as messy
98
+ try:
99
+ new_limit = int(args[0])
100
+ if new_limit < 1 or new_limit > 100:
101
+ await ui.error("Iterations must be between 1 and 100")
102
+ return
103
+
104
+ # Update the user config
105
+ if "settings" not in state.user_config:
106
+ state.user_config["settings"] = {}
107
+ state.user_config["settings"]["max_iterations"] = new_limit
108
+
109
+ await ui.success(f"Maximum iterations set to {new_limit}")
110
+ except ValueError:
111
+ await ui.error("Please provide a valid number")
112
+
113
+
114
+ class FixCommand(SimpleCommand):
115
+ """Fix orphaned tool calls that cause API errors."""
116
+
117
+ spec = CommandSpec(
118
+ name="fix",
119
+ aliases=["/fix"],
120
+ description="Fix orphaned tool calls causing API errors",
121
+ category=CommandCategory.DEBUG,
122
+ )
123
+
124
+ async def execute(self, args: List[str], context: CommandContext) -> None:
125
+ from tunacode.core.agents.main import patch_tool_messages
126
+
127
+ # Count current messages
128
+ before_count = len(context.state_manager.session.messages)
129
+
130
+ # Patch orphaned tool calls
131
+ patch_tool_messages("Tool call resolved by /fix command", context.state_manager)
132
+
133
+ # Count after patching
134
+ after_count = len(context.state_manager.session.messages)
135
+ patched_count = after_count - before_count
136
+
137
+ if patched_count > 0:
138
+ await ui.success(f"Fixed {patched_count} orphaned tool call(s)")
139
+ await ui.muted("You can now continue the conversation normally")
140
+ else:
141
+ await ui.info("No orphaned tool calls found")
142
+
143
+
144
+ class ParseToolsCommand(SimpleCommand):
145
+ """Parse and execute JSON tool calls from the last response."""
146
+
147
+ spec = CommandSpec(
148
+ name="parsetools",
149
+ aliases=["/parsetools"],
150
+ description=("Parse JSON tool calls from last response when structured calling fails"),
151
+ category=CommandCategory.DEBUG,
152
+ )
153
+
154
+ async def execute(self, args: List[str], context: CommandContext) -> None:
155
+ from tunacode.core.agents.main import extract_and_execute_tool_calls
156
+
157
+ # Find the last model response in messages
158
+ messages = context.state_manager.session.messages
159
+ if not messages:
160
+ await ui.error("No message history found")
161
+ return
162
+
163
+ # Look for the most recent response with text content
164
+ found_content = False
165
+ for msg in reversed(messages):
166
+ if hasattr(msg, "parts"):
167
+ for part in msg.parts:
168
+ if hasattr(part, "content") and isinstance(part.content, str):
169
+ # Create tool callback
170
+ from tunacode.cli.repl import _tool_handler
171
+
172
+ def tool_callback_with_state(part, node):
173
+ return _tool_handler(part, node, context.state_manager)
174
+
175
+ try:
176
+ await extract_and_execute_tool_calls(
177
+ part.content,
178
+ tool_callback_with_state,
179
+ context.state_manager,
180
+ )
181
+ await ui.success("JSON tool parsing completed")
182
+ found_content = True
183
+ return
184
+ except Exception as e:
185
+ await ui.error(f"Failed to parse tools: {str(e)}")
186
+ return
187
+
188
+ if not found_content:
189
+ await ui.error("No parseable content found in recent messages")
@@ -0,0 +1,77 @@
1
+ """Development-focused commands for TunaCode CLI."""
2
+
3
+ import os
4
+ import subprocess
5
+ from typing import List
6
+
7
+ from ....types import CommandContext, CommandResult
8
+ from ....ui import console as ui
9
+ from ..base import CommandCategory, CommandSpec, SimpleCommand
10
+
11
+
12
+ class BranchCommand(SimpleCommand):
13
+ """Create and switch to a new git branch."""
14
+
15
+ spec = CommandSpec(
16
+ name="branch",
17
+ aliases=["/branch"],
18
+ description="Create and switch to a new git branch",
19
+ category=CommandCategory.DEVELOPMENT,
20
+ )
21
+
22
+ async def execute(self, args: List[str], context: CommandContext) -> None:
23
+ if not args:
24
+ await ui.error("Usage: /branch <branch-name>")
25
+ return
26
+
27
+ if not os.path.exists(".git"):
28
+ await ui.error("Not a git repository")
29
+ return
30
+
31
+ branch_name = args[0]
32
+
33
+ try:
34
+ subprocess.run(
35
+ ["git", "checkout", "-b", branch_name],
36
+ capture_output=True,
37
+ text=True,
38
+ check=True,
39
+ timeout=5,
40
+ )
41
+ await ui.success(f"Switched to new branch '{branch_name}'")
42
+ except subprocess.TimeoutExpired:
43
+ await ui.error("Git command timed out")
44
+ except subprocess.CalledProcessError as e:
45
+ error_msg = e.stderr.strip() if e.stderr else str(e)
46
+ await ui.error(f"Git error: {error_msg}")
47
+ except FileNotFoundError:
48
+ await ui.error("Git executable not found")
49
+
50
+
51
+ class InitCommand(SimpleCommand):
52
+ """Creates or updates TUNACODE.md with project-specific context."""
53
+
54
+ spec = CommandSpec(
55
+ name="/init",
56
+ aliases=[],
57
+ description="Analyze codebase and create/update TUNACODE.md file",
58
+ category=CommandCategory.DEVELOPMENT,
59
+ )
60
+
61
+ async def execute(self, args, context: CommandContext) -> CommandResult:
62
+ """Execute the init command."""
63
+ # Minimal implementation to make test pass
64
+ prompt = """Please analyze this codebase and create a TUNACODE.md file containing:
65
+ 1. Build/lint/test commands - especially for running a single test
66
+ 2. Code style guidelines including imports, formatting, types, naming conventions, error handling, etc.
67
+
68
+ The file you create will be given to agentic coding agents (such as yourself) that operate in this repository.
69
+ Make it about 20 lines long.
70
+ If there's already a TUNACODE.md, improve it.
71
+ If there are Cursor rules (in .cursor/rules/ or .cursorrules) or Copilot rules (in .github/copilot-instructions.md),
72
+ make sure to include them."""
73
+
74
+ # Call the agent to analyze and create/update the file
75
+ await context.process_request(prompt, context.state_manager)
76
+
77
+ return None
@@ -0,0 +1,61 @@
1
+ """Model management commands for TunaCode CLI."""
2
+
3
+ from typing import Optional
4
+
5
+ from .... import utils
6
+ from ....exceptions import ConfigurationError
7
+ from ....types import CommandArgs, CommandContext
8
+ from ....ui import console as ui
9
+ from ..base import CommandCategory, CommandSpec, SimpleCommand
10
+
11
+
12
+ class ModelCommand(SimpleCommand):
13
+ """Manage model selection."""
14
+
15
+ spec = CommandSpec(
16
+ name="model",
17
+ aliases=["/model"],
18
+ description="Switch model (e.g., /model gpt-4 or /model openai:gpt-4)",
19
+ category=CommandCategory.MODEL,
20
+ )
21
+
22
+ async def execute(self, args: CommandArgs, context: CommandContext) -> Optional[str]:
23
+ # No arguments - show current model
24
+ if not args:
25
+ current_model = context.state_manager.session.current_model
26
+ await ui.info(f"Current model: {current_model}")
27
+ await ui.muted("Usage: /model <provider:model-name> [default]")
28
+ await ui.muted("Example: /model openai:gpt-4.1")
29
+ return None
30
+
31
+ # Get the model name from args
32
+ model_name = args[0]
33
+
34
+ # Check if provider prefix is present
35
+ if ":" not in model_name:
36
+ await ui.error("Model name must include provider prefix")
37
+ await ui.muted("Format: provider:model-name")
38
+ await ui.muted(
39
+ "Examples: openai:gpt-4.1, anthropic:claude-3-opus, google-gla:gemini-2.0-flash"
40
+ )
41
+ return None
42
+
43
+ # No validation - user is responsible for correct model names
44
+ await ui.warning("Model set without validation - verify the model name is correct")
45
+
46
+ # Set the model
47
+ context.state_manager.session.current_model = model_name
48
+
49
+ # Check if setting as default
50
+ if len(args) > 1 and args[1] == "default":
51
+ try:
52
+ utils.user_configuration.set_default_model(model_name, context.state_manager)
53
+ await ui.muted("Updating default model")
54
+ return "restart"
55
+ except ConfigurationError as e:
56
+ await ui.error(str(e))
57
+ return None
58
+
59
+ # Show success message with the new model
60
+ await ui.success(f"Switched to model: {model_name}")
61
+ return None