Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions cecli/coders/architect_coder.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import asyncio

from ..commands import SwitchCoder
from ..commands import SwitchCoderSignal
from .ask_coder import AskCoder
from .base_coder import Coder

Expand Down Expand Up @@ -60,4 +60,4 @@ async def reply_completed(self):
except Exception as e:
self.io.tool_error(e)

raise SwitchCoder(main_model=self.main_model, edit_format="architect")
raise SwitchCoderSignal(main_model=self.main_model, edit_format="architect")
10 changes: 5 additions & 5 deletions cecli/coders/base_coder.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@

import cecli.prompts.utils.system as prompts
from cecli import __version__, models, urls, utils
from cecli.commands import Commands, SwitchCoder
from cecli.commands import Commands, SwitchCoderSignal
from cecli.exceptions import LiteLLMExceptions
from cecli.helpers import coroutines
from cecli.helpers.profiler import TokenProfiler
Expand Down Expand Up @@ -1370,7 +1370,7 @@ async def _run_parallel(self, with_message=None, preproc=True):
if task.exception():
raise task.exception()

except (SwitchCoder, SystemExit):
except (SwitchCoderSignal, SystemExit):
# Re-raise SwitchCoder to be handled by outer try block
raise
except KeyboardInterrupt:
Expand Down Expand Up @@ -1461,7 +1461,7 @@ async def input_task(self, preproc):
self.io.set_placeholder("")
self.keyboard_interrupt()
await self.io.stop_task_streams()
except (SwitchCoder, SystemExit):
except (SwitchCoderSignal, SystemExit):
raise
except Exception as e:
if self.verbose or self.args.debug:
Expand Down Expand Up @@ -1496,7 +1496,7 @@ async def output_task(self, preproc):
if self.io.output_task.done():
exception = self.io.output_task.exception()
if exception:
if isinstance(exception, SwitchCoder):
if isinstance(exception, SwitchCoderSignal):
await self.io.output_task
raise exception

Expand All @@ -1519,7 +1519,7 @@ async def output_task(self, preproc):
self.io.stop_spinner()
self.keyboard_interrupt()
await self.io.stop_task_streams()
except (SwitchCoder, SystemExit):
except (SwitchCoderSignal, SystemExit):
raise
except Exception as e:
if self.verbose or self.args.debug:
Expand Down
51 changes: 2 additions & 49 deletions cecli/commands/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,6 @@
BaseCommand pattern for modular, testable command execution.
"""

import sys
import traceback
from pathlib import Path

from .add import AddCommand
from .agent import AgentCommand
from .architect import ArchitectCommand
Expand All @@ -22,6 +18,7 @@
from .context_management import ContextManagementCommand
from .copy import CopyCommand
from .copy_context import CopyContextCommand
from .core import Commands, SwitchCoderSignal
from .diff import DiffCommand

# Import and register commands
Expand Down Expand Up @@ -127,50 +124,6 @@
CommandRegistry.register(LoadSkillCommand)
CommandRegistry.register(RemoveSkillCommand)

# Import SwitchCoder and Commands directly from commands.py
# We need to handle the circular import carefully

# Add parent directory to path to import commands.py directly
parent_dir = str(Path(__file__).parent.parent)
if parent_dir not in sys.path:
sys.path.insert(0, parent_dir)

# Import the commands module directly
try:
import importlib.util

spec = importlib.util.spec_from_file_location(
"cecli.commands_module", Path(__file__).parent.parent / "commands.py"
)
commands_module = importlib.util.module_from_spec(spec)
sys.modules["cecli.commands_module"] = commands_module
spec.loader.exec_module(commands_module)

# Get the classes from the module
Commands = getattr(commands_module, "Commands", None)
SwitchCoder = getattr(commands_module, "SwitchCoder", None)

if Commands is None or SwitchCoder is None:
raise ImportError("Commands or SwitchCoder not found in commands.py")

except Exception as e:
# Print the error for debugging
print(f"Error importing commands.py: {e}")
traceback.print_exc()

# Fallback: define simple placeholder classes
class SwitchCoder(Exception):
def __init__(self, placeholder=None, **kwargs):
self.kwargs = kwargs
self.placeholder = placeholder

class Commands:
"""Placeholder for Commands class defined in original commands.py"""

def __init__(self, *args, **kwargs):
# Accept any arguments but do nothing
pass


__all__ = [
"BaseCommand",
Expand Down Expand Up @@ -234,6 +187,6 @@ def __init__(self, *args, **kwargs):
"CommandPrefixCommand",
"LoadSkillCommand",
"RemoveSkillCommand",
"SwitchCoder",
"SwitchCoderSignal",
"Commands",
]
4 changes: 2 additions & 2 deletions cecli/commands/add.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,9 +137,9 @@ async def execute(cls, io, coder, args, **kwargs):
map_tokens = 0
map_mul_no_files = 1

from cecli.commands import SwitchCoder
from cecli.commands import SwitchCoderSignal

raise SwitchCoder(
raise SwitchCoderSignal(
edit_format=coder.edit_format,
summarize_from_coder=False,
from_coder=coder,
Expand Down
22 changes: 19 additions & 3 deletions cecli/commands.py → cecli/commands/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,32 @@
import sys
from pathlib import Path

from cecli.commands.utils.registry import CommandRegistry
from cecli.helpers.file_searcher import handle_core_files
from cecli.repo import ANY_GIT_ERROR

from .commands.utils.registry import CommandRegistry

class SwitchCoderSignal(BaseException):
"""
Signal to switch the current Coder instance to a new configuration.

This is NOT an error - it's a control flow signal used to propagate
coder switching requests up through the async call stack. It carries
the kwargs needed to create a new Coder instance.

Note: Inherits from BaseException (like KeyboardInterrupt and SystemExit)
to avoid being caught by generic `except Exception` handlers, making the
non-error nature of this signal explicit.

Attributes:
kwargs: Configuration dict passed to Coder.create() for the new instance
placeholder: Optional placeholder text for the input prompt
"""

class SwitchCoder(Exception):
def __init__(self, placeholder=None, **kwargs):
self.kwargs = kwargs
self.placeholder = placeholder
super().__init__()


class Commands:
Expand Down Expand Up @@ -119,7 +135,7 @@ async def do_run(self, cmd_name, args, **kwargs):
except ANY_GIT_ERROR as err:
self.io.tool_error(f"Unable to complete {cmd_name}: {err}")
return
except SwitchCoder as e:
except SwitchCoderSignal as e:
raise e
except Exception as e:
self.io.tool_error(f"Error executing command {cmd_name}: {str(e)}")
Expand Down
8 changes: 4 additions & 4 deletions cecli/commands/drop.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,18 +82,18 @@ async def execute(cls, io, coder, args, **kwargs):
return format_command_result(io, "drop", "Removed files from chat")

finally:
# This mimics the SwitchCoder behavior in the original cmd_drop
# This mimics the SwitchCoderSignal behavior in the original cmd_drop
if coder.repo_map:
map_tokens = coder.repo_map.max_map_tokens
map_mul_no_files = coder.repo_map.map_mul_no_files
else:
map_tokens = 0
map_mul_no_files = 1

# Raise SwitchCoder to trigger coder recreation
from . import SwitchCoder
# Raise SwitchCoderSignal to trigger coder recreation
from . import SwitchCoderSignal

raise SwitchCoder(
raise SwitchCoderSignal(
edit_format=coder.edit_format,
summarize_from_coder=False,
from_coder=coder,
Expand Down
4 changes: 2 additions & 2 deletions cecli/commands/help.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,9 @@ async def execute(cls, io, coder, args, **kwargs):
map_tokens = 0
map_mul_no_files = 1

from cecli.commands import SwitchCoder
from cecli.commands import SwitchCoderSignal

raise SwitchCoder(
raise SwitchCoderSignal(
edit_format=coder.edit_format,
summarize_from_coder=False,
from_coder=help_coder,
Expand Down
6 changes: 3 additions & 3 deletions cecli/commands/load.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,9 @@ async def execute(cls, io, coder, args, **kwargs):
try:
await commands_instance.run(cmd)
except Exception as e:
# Handle SwitchCoder exception specifically
if type(e).__name__ == "SwitchCoder":
# SwitchCoder is raised when switching between coder types (e.g., /architect, /ask).
# Handle SwitchCoderSignal exception specifically
if type(e).__name__ == "SwitchCoderSignal":
# SwitchCoderSignal is raised when switching between coder types (e.g., /architect, /ask).
# This is expected behavior, not an error. But this gets in the way when running `/load` so we
# ignore it and continue processing remaining commands.
should_raise_at_end = e
Expand Down
16 changes: 9 additions & 7 deletions cecli/commands/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,23 +73,25 @@ async def execute(cls, io, coder, args, **kwargs):
coder.coder_commit_hashes = temp_coder.coder_commit_hashes

# Restore the original model configuration
from cecli.commands import SwitchCoder
from cecli.commands import SwitchCoderSignal

raise SwitchCoder(main_model=original_main_model, edit_format=original_edit_format)
raise SwitchCoderSignal(
main_model=original_main_model, edit_format=original_edit_format
)
except Exception as e:
# If there's an error, still restore the original model
if not isinstance(e, SwitchCoder):
if not isinstance(e, SwitchCoderSignal):
io.tool_error(str(e))
raise SwitchCoder(
raise SwitchCoderSignal(
main_model=original_main_model, edit_format=original_edit_format
)
else:
# Re-raise SwitchCoder if that's what was thrown
# Re-raise SwitchCoderSignal if that's what was thrown
raise
else:
from cecli.commands import SwitchCoder
from cecli.commands import SwitchCoderSignal

raise SwitchCoder(main_model=model, edit_format=new_edit_format)
raise SwitchCoderSignal(main_model=model, edit_format=new_edit_format)

@classmethod
def get_completions(cls, io, coder, args) -> List[str]:
Expand Down
6 changes: 3 additions & 3 deletions cecli/commands/reset.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,10 @@ async def execute(cls, io, coder, args, **kwargs):
map_tokens = 0
map_mul_no_files = 1

# Raise SwitchCoder to trigger coder recreation
from . import SwitchCoder
# Raise SwitchCoderSignal to trigger coder recreation
from . import SwitchCoderSignal

raise SwitchCoder(
raise SwitchCoderSignal(
edit_format=coder.edit_format,
summarize_from_coder=False,
from_coder=coder,
Expand Down
8 changes: 4 additions & 4 deletions cecli/commands/utils/base_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,9 +96,9 @@ async def _generic_chat_command(cls, io, coder, args, edit_format, placeholder=N
"""
if not args.strip():
# Switch to the corresponding chat mode
from cecli.commands import SwitchCoder
from cecli.commands import SwitchCoderSignal

raise SwitchCoder(edit_format=edit_format)
raise SwitchCoderSignal(edit_format=edit_format)

from cecli.coders.base_coder import Coder

Expand All @@ -121,9 +121,9 @@ async def _generic_chat_command(cls, io, coder, args, edit_format, placeholder=N
await new_coder.generate(user_message=user_msg, preproc=False)
coder.coder_commit_hashes = new_coder.coder_commit_hashes

from cecli.commands import SwitchCoder
from cecli.commands import SwitchCoderSignal

raise SwitchCoder(
raise SwitchCoderSignal(
main_model=original_main_model,
edit_format=original_edit_format,
done_messages=new_coder.done_messages,
Expand Down
16 changes: 9 additions & 7 deletions cecli/commands/weak_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,23 +70,25 @@ async def execute(cls, io, coder, args, **kwargs):
coder.coder_commit_hashes = temp_coder.coder_commit_hashes

# Restore the original model configuration
from cecli.commands import SwitchCoder
from cecli.commands import SwitchCoderSignal

raise SwitchCoder(main_model=original_main_model, edit_format=original_edit_format)
raise SwitchCoderSignal(
main_model=original_main_model, edit_format=original_edit_format
)
except Exception as e:
# If there's an error, still restore the original model
if not isinstance(e, SwitchCoder):
if not isinstance(e, SwitchCoderSignal):
io.tool_error(str(e))
raise SwitchCoder(
raise SwitchCoderSignal(
main_model=original_main_model, edit_format=original_edit_format
)
else:
# Re-raise SwitchCoder if that's what was thrown
# Re-raise SwitchCoderSignal if that's what was thrown
raise
else:
from cecli.commands import SwitchCoder
from cecli.commands import SwitchCoderSignal

raise SwitchCoder(main_model=model, edit_format=coder.edit_format)
raise SwitchCoderSignal(main_model=model, edit_format=coder.edit_format)

@classmethod
def get_completions(cls, io, coder, args) -> List[str]:
Expand Down
8 changes: 4 additions & 4 deletions cecli/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
from cecli.args import get_parser
from cecli.coders import Coder
from cecli.coders.base_coder import UnknownEditFormat
from cecli.commands import Commands, SwitchCoder
from cecli.commands import Commands, SwitchCoderSignal
from cecli.deprecated_args import handle_deprecated_model_args
from cecli.format_settings import format_settings, scrub_sensitive_info
from cecli.helpers.copypaste import ClipboardWatcher
Expand Down Expand Up @@ -1127,15 +1127,15 @@ def apply_model_overrides(model_name):
io.tool_output()
try:
await coder.run(with_message=args.message)
except (SwitchCoder, KeyboardInterrupt, SystemExit):
except (SwitchCoderSignal, KeyboardInterrupt, SystemExit):
pass
return await graceful_exit(coder)
if args.message_file:
try:
message_from_file = io.read_text(args.message_file)
io.tool_output()
await coder.run(with_message=message_from_file)
except (SwitchCoder, KeyboardInterrupt, SystemExit):
except (SwitchCoderSignal, KeyboardInterrupt, SystemExit):
pass
except FileNotFoundError:
io.tool_error(f"Message file not found: {args.message_file}")
Expand Down Expand Up @@ -1167,7 +1167,7 @@ def apply_model_overrides(model_name):
coder.ok_to_warm_cache = bool(args.cache_keepalive_pings)
await coder.run()
return await graceful_exit(coder)
except SwitchCoder as switch:
except SwitchCoderSignal as switch:
coder.ok_to_warm_cache = False
if hasattr(switch, "placeholder") and switch.placeholder is not None:
io.placeholder = switch.placeholder
Expand Down
4 changes: 2 additions & 2 deletions cecli/tui/worker.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from typing import Optional

from cecli.coders import Coder
from cecli.commands import SwitchCoder
from cecli.commands import SwitchCoderSignal

# Suppress asyncio task destroyed warnings during shutdown
logging.getLogger("asyncio").setLevel(logging.CRITICAL)
Expand Down Expand Up @@ -91,7 +91,7 @@ async def _async_run(self):
break # Normal exit
except asyncio.CancelledError:
break
except SwitchCoder as switch:
except SwitchCoderSignal as switch:
# Handle chat mode switches (e.g., /chat-mode architect)
try:
kwargs = dict(io=self.coder.io, from_coder=self.coder)
Expand Down
Loading