@click.command()
@click.option("-e", "--engine", "engine_key", default=None, help="Engine backend.")
@click.option("-m", "--model", "model_name", default=None, help="Model to use.")
@click.option("-a", "--agent", "agent_name", default=None, help="Agent type.")
@click.option("--tools", default=None, help="Comma-separated tool names.")
@click.option("--system", "system_prompt", default=None, help="Custom system prompt.")
def chat(
engine_key: str | None,
model_name: str | None,
agent_name: str | None,
tools: str | None,
system_prompt: str | None,
) -> None:
"""Start an interactive multi-turn chat session.
Commands during chat:
/quit, /exit — end session
/clear — clear conversation history
/model — show current model
/help — show available commands
/history — show conversation history
"""
console = Console(stderr=True)
config = load_config()
# Resolve engine
from openjarvis.engine import get_engine
from openjarvis.intelligence import register_builtin_models
register_builtin_models()
resolved = get_engine(config, engine_key)
if resolved is None:
console.print("[red]No inference engine available.[/red]")
sys.exit(1)
engine_name, engine = resolved
model = model_name or config.intelligence.default_model
if not model:
from openjarvis.engine import discover_engines, discover_models
all_engines = discover_engines(config)
all_models = discover_models(all_engines)
engine_models = all_models.get(engine_name, [])
if engine_models:
model = engine_models[0]
else:
console.print("[red]No model available.[/red]")
sys.exit(1)
# Resolve agent (optional)
agent = None
agent_key = agent_name or config.agent.default_agent
if agent_key and agent_key != "none":
try:
import openjarvis.agents # noqa: F401 — trigger registration
from openjarvis.core.events import EventBus
from openjarvis.core.registry import AgentRegistry
if AgentRegistry.contains(agent_key):
agent_cls = AgentRegistry.get(agent_key)
kwargs: dict = {"bus": EventBus()}
if getattr(agent_cls, "accepts_tools", False) and tools:
import openjarvis.tools # noqa: F401 — trigger registration
from openjarvis.core.registry import ToolRegistry
from openjarvis.tools._stubs import BaseTool
tool_instances = []
for tname in tools.split(","):
tname = tname.strip()
if ToolRegistry.contains(tname):
tcls = ToolRegistry.get(tname)
if isinstance(tcls, type) and issubclass(
tcls, BaseTool
):
tool_instances.append(tcls())
elif isinstance(tcls, BaseTool):
tool_instances.append(tcls)
if tool_instances:
kwargs["tools"] = tool_instances
kwargs["max_turns"] = config.agent.max_turns
agent = agent_cls(engine, model, **kwargs)
except Exception as exc:
console.print(f"[yellow]Agent '{agent_key}' failed: {exc}[/yellow]")
# Print banner
console.print(
f"[green bold]OpenJarvis Chat[/green bold]\n"
f" Engine: [cyan]{engine_name}[/cyan] Model: [cyan]{model}[/cyan]"
f" Agent: [cyan]{agent_key or 'direct'}[/cyan]\n"
f" Type /help for commands, /quit to exit.\n"
)
# Conversation state
history: List[Message] = []
if system_prompt:
history.append(Message(role=Role.SYSTEM, content=system_prompt))
# REPL loop
while True:
user_input = _read_input()
if user_input is None:
console.print("\n[dim]Goodbye![/dim]")
break
user_input = user_input.strip()
if not user_input:
continue
# Handle slash commands
cmd = user_input.lower()
if cmd in ("/quit", "/exit", "/q"):
console.print("[dim]Goodbye![/dim]")
break
elif cmd == "/clear":
history = []
if system_prompt:
history.append(Message(role=Role.SYSTEM, content=system_prompt))
console.print("[dim]History cleared.[/dim]")
continue
elif cmd == "/model":
console.print(
f"Model: [cyan]{model}[/cyan] "
f"Engine: [cyan]{engine_name}[/cyan]"
)
continue
elif cmd == "/help":
console.print(
"[bold]Commands:[/bold]\n"
" /quit, /exit — end session\n"
" /clear — clear conversation\n"
" /model — show model info\n"
" /history — show conversation\n"
" /help — this message"
)
continue
elif cmd == "/history":
if not history:
console.print("[dim]No history yet.[/dim]")
else:
for msg in history:
role_str = msg.role if isinstance(msg.role, str) else msg.role.value
role = role_str.upper()
console.print(
f"[bold]{role}:[/bold] {msg.content[:200]}"
)
continue
# Add user message
history.append(Message(role=Role.USER, content=user_input))
# Generate response
try:
if agent is not None:
response = agent.run(user_input)
content = (
response.content
if hasattr(response, "content")
else str(response)
)
else:
result = engine.generate(history, model=model)
content = (
result.get("content", "")
if isinstance(result, dict)
else str(result)
)
history.append(Message(role=Role.ASSISTANT, content=content))
console.print()
console.print(Markdown(content))
console.print()
except KeyboardInterrupt:
console.print("\n[dim]Generation interrupted.[/dim]")
except Exception as exc:
console.print(f"\n[red]Error: {exc}[/red]\n")