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
9 changes: 9 additions & 0 deletions src/iac_code/cli/headless.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ def __init__(
verbose: bool = False,
progress_stream: IO[str] | None = None,
resume_session_id: str | bool | None = None,
thinking_enabled: bool | None = True,
) -> None:
self._model = model
self._output_format = output_format
Expand All @@ -135,6 +136,7 @@ def __init__(
self._verbose = verbose
self._progress_stream = progress_stream or sys.stderr
self._resume_session_id = resume_session_id
self._thinking_enabled = thinking_enabled
self._mcp_config_warnings: list[Any] = []
self._mcp_warnings_printed_count = 0
self._runtime: Any | None = None
Expand Down Expand Up @@ -162,17 +164,24 @@ def _record_structured_error(self, writer: Any, exc: Exception) -> None:

def _create_agent_loop(self) -> Any:
"""Create and return a fully configured AgentLoop."""
from iac_code.providers.request_policy import ProviderRequestPolicy
from iac_code.services.agent_factory import AgentFactoryOptions, create_agent_runtime

cwd = os.getcwd()
session_id, resume_messages = self._resolve_resume_options()
request_policy_override = (
ProviderRequestPolicy(thinking_enabled=self._thinking_enabled)
if self._thinking_enabled is not None
else None
)

runtime = create_agent_runtime(
AgentFactoryOptions(
model=self._model,
session_id=session_id,
cwd=cwd,
max_turns=self._max_turns,
request_policy_override=request_policy_override,
cli_allowed_tools=self._cli_allowed_tools,
cli_disallowed_tools=self._cli_disallowed_tools,
cli_permission_mode=self._cli_permission_mode,
Expand Down
6 changes: 6 additions & 0 deletions src/iac_code/cli/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,11 @@ def main(
max_turns: int = typer.Option(100, "--max-turns", help=_("Maximum agent turns in headless mode")),
debug: bool = typer.Option(False, "--debug", "-d", help=_("Enable debug logging")),
verbose: bool = typer.Option(False, "--verbose", help=_("Show headless progress on stderr")),
thinking_enabled: bool | None = typer.Option(
True,
"--thinking-enabled/--no-thinking-enabled",
help=_("Headless mode: whether to enable thinking for this request"),
),
version: bool = typer.Option(False, "--version", "-v", "-V", is_eager=True, help=_("Show version and exit")),
resume: str = typer.Option("", "--resume", "-r", help=_("Resume a session by ID or name")),
continue_session: bool = typer.Option(False, "--continue", "-c", help=_("Resume the most recent session")),
Expand Down Expand Up @@ -282,6 +287,7 @@ async def _run_with_handler(coro):
cli_permission_mode=permission_mode or None,
verbose=verbose,
resume_session_id=True if continue_session else (resume or None),
thinking_enabled=thinking_enabled,
)
exit_code = asyncio.run(_run_with_handler(runner.run(prompt)))
except _QwenPawError as exc:
Expand Down
10 changes: 10 additions & 0 deletions src/iac_code/commands/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from iac_code.commands.resume import resume_command
from iac_code.commands.skills import skills_command
from iac_code.commands.status import status_command
from iac_code.commands.thinking_enabled import thinking_enabled_command
from iac_code.i18n import _


Expand Down Expand Up @@ -56,6 +57,15 @@ def create_default_registry() -> CommandRegistry:
history_mode="session",
)
)
registry.register(
LocalCommand(
name="thinking_enabled",
description=_("Show or switch whether thinking is enabled"),
handler=thinking_enabled_command,
arg_names=["on|off"],
history_mode="session",
)
)
registry.register(
LocalCommand(
name="compact",
Expand Down
8 changes: 7 additions & 1 deletion src/iac_code/commands/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,11 @@ def save_llm_key(key_name: str, api_key: str) -> None:


def save_active_provider_config(
provider: LLMProvider | dict, model: str, effort: str | None = None, api_base: str | None = None
provider: LLMProvider | dict,
model: str,
effort: str | None = None,
api_base: str | None = None,
thinking_enabled: bool | None = None,
) -> None:
"""Persist the provider's per-provider config and mark it active."""
settings_path = get_settings_path()
Expand All @@ -152,6 +156,8 @@ def save_active_provider_config(
entry["apiBase"] = effective_api_base
if effort is not None:
entry["effort"] = effort
if thinking_enabled is not None:
entry["thinkingEnabled"] = thinking_enabled

providers[key_name] = entry
for legacy, canonical in _LEGACY_KEY_NAME_ALIASES.items():
Expand Down
117 changes: 117 additions & 0 deletions src/iac_code/commands/thinking_enabled.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
"""Thinking-enabled command -- show or change provider thinking mode."""

from __future__ import annotations

import sys
from typing import TYPE_CHECKING

from iac_code.commands.auth import PROVIDERS, LLMProvider, _select, save_active_provider_config
from iac_code.config import get_active_provider_key, get_provider_config
from iac_code.i18n import _
from iac_code.providers.request_policy import bool_or_none

if TYPE_CHECKING:
from iac_code.ui.repl import CommandContext


class ThinkingEnabledCommand:
"""Handle the /thinking_enabled command for the active provider."""

def __init__(self, context: "CommandContext | None", kwargs: dict) -> None:
self.context = context
self.store = context.store if context else kwargs.get("store")

async def run(self, args: list[str] | None = None) -> str:
args = args or []
provider = self._active_provider()
if provider is None:
return _("No configured providers. Run /auth first.")

current_model = self._current_model()
if not current_model:
return _("No model selected. Run /model first.")

if args:
enabled = self._parse_enabled(args[0])
if enabled is None:
return _("Invalid thinking_enabled. Allowed: on, off.")
return self._apply_enabled(provider, current_model, enabled)

saved = bool_or_none(get_provider_config(str(provider["key_name"])).get("thinkingEnabled"))
if not self.context or not self.context.console:
return self._current_message(saved)

selected = self._select_enabled(saved)
if selected is None:
return self._current_message(saved)
return self._apply_enabled(provider, current_model, selected)

def _active_provider(self) -> LLMProvider | None:
key = get_active_provider_key()
if not key:
return None
for provider in PROVIDERS:
if str(provider["key_name"]) == key:
return provider
return None

def _current_model(self) -> str:
if self.store is None:
return ""
state = self.store.get_state()
model = getattr(state, "model", "")
return model if isinstance(model, str) else ""

@staticmethod
def _parse_enabled(value: str) -> bool | None:
return bool_or_none(value)

def _select_enabled(self, current: bool | None) -> bool | None:
options = [self._option_label(True), self._option_label(False)]
default_idx = 1 if current is False else 0
sys.stdout.write("\033[?1049h")
sys.stdout.flush()
try:
idx = _select(
self._current_message(current),
options,
default_index=default_idx,
)
finally:
sys.stdout.write("\033[?1049l")
sys.stdout.flush()
if idx is None:
return None
if idx < 0 or idx >= len(options):
return None
return idx == 0

@staticmethod
def _option_label(enabled: bool) -> str:
return _("on") if enabled else _("off")

def _apply_enabled(self, provider: LLMProvider, model: str, enabled: bool) -> str:
save_active_provider_config(provider, model, thinking_enabled=enabled)
if self.store is not None:
self.store.set_state(thinking_enabled=enabled)
return _("thinking_enabled switched to: {state}").format(state=self._state_label(enabled))

def _current_message(self, enabled: bool | None) -> str:
return _("Current thinking_enabled: {state}").format(state=self._state_label(enabled))

@staticmethod
def _state_label(enabled: bool | None) -> str:
if enabled is True:
return _("enabled")
if enabled is False:
return _("disabled")
return _("not configured")


async def thinking_enabled_command(
context: "CommandContext | None" = None,
args: list[str] | None = None,
**kwargs,
) -> str:
"""Show or change whether the active provider should request thinking."""
return await ThinkingEnabledCommand(context, kwargs).run(args)
82 changes: 43 additions & 39 deletions src/iac_code/i18n/locales/de/LC_MESSAGES/messages.po
Original file line number Diff line number Diff line change
Expand Up @@ -719,6 +719,10 @@ msgstr "Debug-Protokollierung aktivieren"
msgid "Show headless progress on stderr"
msgstr "Headless-Fortschritt auf stderr anzeigen"

#: src/iac_code/cli/main.py
msgid "Headless mode: whether to enable thinking for this request"
msgstr "Headless-Modus: ob Thinking fuer diese Anfrage aktiviert wird"

#: src/iac_code/cli/main.py
msgid "Show version and exit"
msgstr "Version anzeigen und beenden"
Expand Down Expand Up @@ -1088,6 +1092,10 @@ msgstr "Modell anzeigen oder wechseln"
msgid "Show or switch thinking effort"
msgstr "Thinking-Effort anzeigen oder wechseln"

#: src/iac_code/commands/__init__.py
msgid "Show or switch whether thinking is enabled"
msgstr "Thinking-Aktivierung anzeigen oder umschalten"

#: src/iac_code/commands/__init__.py
msgid "Compact conversation context"
msgstr "Konversationskontext komprimieren"
Expand Down Expand Up @@ -1462,10 +1470,11 @@ msgid "No active session."
msgstr "Keine aktive Sitzung."

#: src/iac_code/commands/effort.py src/iac_code/commands/model.py
#: src/iac_code/commands/thinking_enabled.py
msgid "No configured providers. Run /auth first."
msgstr "Keine konfigurierten Anbieter. Führen Sie zuerst /auth aus."

#: src/iac_code/commands/effort.py
#: src/iac_code/commands/effort.py src/iac_code/commands/thinking_enabled.py
msgid "No model selected. Run /model first."
msgstr "Kein Modell ausgewählt. Führen Sie zuerst /model aus."

Expand All @@ -1485,6 +1494,7 @@ msgid "Current effort: {effort}"
msgstr "Aktueller Thinking-Effort: {effort}"

#: src/iac_code/commands/effort.py src/iac_code/commands/status.py
#: src/iac_code/commands/thinking_enabled.py
msgid "not configured"
msgstr "nicht konfiguriert"

Expand Down Expand Up @@ -2143,6 +2153,38 @@ msgstr "{session_id} (fortgesetzt)"
msgid "{percent} used ({total} / {window})"
msgstr "{percent} verwendet ({total} / {window})"

#: src/iac_code/commands/thinking_enabled.py
msgid "Invalid thinking_enabled. Allowed: on, off."
msgstr "Ungueltiger thinking_enabled-Wert. Erlaubt: on, off."

#: src/iac_code/commands/thinking_enabled.py src/iac_code/ui/dialogs/memory.py
#: src/iac_code/ui/dialogs/skills_picker.py
msgid "on"
msgstr "aktiviert"

#: src/iac_code/commands/thinking_enabled.py src/iac_code/ui/dialogs/memory.py
#: src/iac_code/ui/dialogs/skills_picker.py
msgid "off"
msgstr "deaktiviert"

#: src/iac_code/commands/thinking_enabled.py
#, python-brace-format
msgid "thinking_enabled switched to: {state}"
msgstr "thinking_enabled umgeschaltet auf: {state}"

#: src/iac_code/commands/thinking_enabled.py
#, python-brace-format
msgid "Current thinking_enabled: {state}"
msgstr "Aktueller thinking_enabled-Wert: {state}"

#: src/iac_code/commands/thinking_enabled.py
msgid "enabled"
msgstr "aktiviert"

#: src/iac_code/commands/thinking_enabled.py
msgid "disabled"
msgstr "deaktiviert"

# Typer/Click built-in strings
#: src/iac_code/i18n/__init__.py
msgid "Options"
Expand Down Expand Up @@ -6257,14 +6299,6 @@ msgstr "Speicher"
msgid "Auto-memory: {state}"
msgstr "Auto-Speicher: {state}"

#: src/iac_code/ui/dialogs/memory.py src/iac_code/ui/dialogs/skills_picker.py
msgid "on"
msgstr "aktiviert"

#: src/iac_code/ui/dialogs/memory.py src/iac_code/ui/dialogs/skills_picker.py
msgid "off"
msgstr "deaktiviert"

#: src/iac_code/ui/dialogs/memory.py
msgid "Enter to confirm · Esc to cancel"
msgstr "Enter zum Bestätigen · Esc zum Abbrechen"
Expand Down Expand Up @@ -6804,33 +6838,3 @@ msgstr "Öffnen einer nicht regulären Datei verweigert: {path}"
#~ msgid "feature"
#~ msgstr "Funktion"

#~ msgid "ECS instance"
#~ msgstr "ECS-Instanz"

#~ msgid "ECS instance group"
#~ msgstr "ECS-Instanzgruppe"

#~ msgid "Elastic IP address"
#~ msgstr "Elastische IP-Adresse"

#~ msgid "SLB load balancer"
#~ msgstr "SLB-Load-Balancer"

#~ msgid "NAT gateway"
#~ msgstr "NAT-Gateway"

#~ msgid "Shared bandwidth package"
#~ msgstr "Paket für gemeinsam genutzte Bandbreite"

#~ msgid "Database instance"
#~ msgstr "Datenbankinstanz"

#~ msgid "VPC"
#~ msgstr "VPC"

#~ msgid "VSwitch"
#~ msgstr "VSwitch"

#~ msgid "Security group"
#~ msgstr "Sicherheitsgruppe"

Loading
Loading