118 lines
3.8 KiB
Python
118 lines
3.8 KiB
Python
|
|
"""Assistance orchestrator (T06).
|
||
|
|
|
||
|
|
The piece that turns raw user intent + collected context into a well-formed
|
||
|
|
request for the LLM adapter (T04), then turns the adapter response into the
|
||
|
|
final terminal output the user sees.
|
||
|
|
|
||
|
|
Responsibilities in this slice:
|
||
|
|
- Own the end-to-end happy path after Typer argument parsing.
|
||
|
|
- Coordinate context collector (T02), risk classifier (T03), and LLMAdapter (T04).
|
||
|
|
- Keep the CLI surface (main.py) thin — it should only do argument parsing,
|
||
|
|
help/version, and delegation to this orchestrator.
|
||
|
|
- Be testable in isolation with the FakeLLMAdapter (critical for T07).
|
||
|
|
|
||
|
|
This module is the natural home for future prompt framing, context packing
|
||
|
|
with token awareness, safety charter injection, and response post-processing.
|
||
|
|
|
||
|
|
See workplan CYA-WP-0001-T06.
|
||
|
|
"""
|
||
|
|
|
||
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
from rich.console import Console
|
||
|
|
from rich.panel import Panel
|
||
|
|
|
||
|
|
from cya.context.collector import collect, render_explanation
|
||
|
|
from cya.safety.risk import classify, get_user_confirmation
|
||
|
|
from cya.llm.adapter import AssistanceRequest, FakeLLMAdapter
|
||
|
|
|
||
|
|
|
||
|
|
console = Console()
|
||
|
|
|
||
|
|
|
||
|
|
def handle_request(
|
||
|
|
user_request: str,
|
||
|
|
*,
|
||
|
|
explain_context: bool = False,
|
||
|
|
dry_run: bool = False,
|
||
|
|
) -> None:
|
||
|
|
"""Primary orchestrator entry point.
|
||
|
|
|
||
|
|
This is what the CLI (and future tests / other front-ends) should call.
|
||
|
|
It coordinates the full current flow:
|
||
|
|
context → safety (with mandatory confirmation) → LLMAdapter → render
|
||
|
|
"""
|
||
|
|
# 1. Context (always cheap; needed for safety "affected" and for the adapter)
|
||
|
|
try:
|
||
|
|
envelope = collect(".")
|
||
|
|
except Exception:
|
||
|
|
envelope = None
|
||
|
|
|
||
|
|
if explain_context and envelope:
|
||
|
|
try:
|
||
|
|
explanation = render_explanation(envelope)
|
||
|
|
console.print(
|
||
|
|
Panel(
|
||
|
|
explanation,
|
||
|
|
title="Context Envelope (T02)",
|
||
|
|
border_style="green",
|
||
|
|
padding=(1, 1),
|
||
|
|
)
|
||
|
|
)
|
||
|
|
except Exception as exc:
|
||
|
|
console.print(f"[red]Context explanation error: {exc}[/red]")
|
||
|
|
|
||
|
|
# 2. Risk classification + mandatory confirmation (T03)
|
||
|
|
assessment = classify(user_request, envelope)
|
||
|
|
|
||
|
|
if assessment.requires_confirmation:
|
||
|
|
from rich.table import Table
|
||
|
|
|
||
|
|
table = Table(
|
||
|
|
title=f"Risk Assessment — {assessment.level.value.upper()}",
|
||
|
|
show_header=False,
|
||
|
|
border_style="red",
|
||
|
|
)
|
||
|
|
table.add_row("Rationale", assessment.rationale)
|
||
|
|
if assessment.preview:
|
||
|
|
table.add_row("Preview", assessment.preview)
|
||
|
|
if assessment.affected_summary:
|
||
|
|
table.add_row("Would affect", assessment.affected_summary)
|
||
|
|
table.add_row("Rules", ", ".join(assessment.rules_triggered[:3]))
|
||
|
|
console.print(table)
|
||
|
|
|
||
|
|
if not get_user_confirmation(assessment):
|
||
|
|
console.print("[yellow]Action cancelled by user. No changes made.[/yellow]")
|
||
|
|
return
|
||
|
|
|
||
|
|
if dry_run:
|
||
|
|
console.print("[green]--dry-run acknowledged.[/green] No side-effects.")
|
||
|
|
return
|
||
|
|
|
||
|
|
# 3. Call through the single LLMAdapter boundary (T04)
|
||
|
|
adapter = FakeLLMAdapter()
|
||
|
|
llm_request = AssistanceRequest(
|
||
|
|
user_request=user_request,
|
||
|
|
context=envelope.to_dict() if envelope else None,
|
||
|
|
)
|
||
|
|
llm_response = adapter.complete(llm_request)
|
||
|
|
|
||
|
|
# 4. Render final user-facing artifact (T06 responsibility)
|
||
|
|
console.print(
|
||
|
|
Panel(
|
||
|
|
f"[bold]Suggestion:[/bold]\n{llm_response.suggestion}\n\n"
|
||
|
|
f"[dim]{llm_response.explanation}\n"
|
||
|
|
f"Rationale: {llm_response.rationale}[/dim]",
|
||
|
|
title="LLM Response (via T04 seam)",
|
||
|
|
border_style="magenta",
|
||
|
|
padding=(1, 1),
|
||
|
|
)
|
||
|
|
)
|
||
|
|
|
||
|
|
console.print(
|
||
|
|
"[green]✓[/green] Request processed by orchestrator (T02+T03+T04 coordinated by T06)."
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
__all__ = ["handle_request"]
|