File size: 4,285 Bytes
990895d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
"""Authenticated debug routes for coach traces and prompt previews.

Paste bundles are self-contained for an external AI with no user history.
"""

from fastapi import APIRouter, Depends, Query, Request, status
from fastapi.responses import PlainTextResponse

from app.deps import require_login
from app.models import ApiEnvelope, CoachRequest, err, ok
from app.paste_bundle import render_paste_bundle

router = APIRouter(
    prefix="/api/debug",
    tags=["debug"],
    dependencies=[Depends(require_login)],
)


def _settings(request: Request):
    return request.app.state.settings


def _paste(request: Request, trace: dict) -> PlainTextResponse:
    settings = _settings(request)
    body = render_paste_bundle(
        trace,
        app_name=settings.app_name,
        shrink_k=settings.stats_shrink_k,
        match_alpha=settings.match_alpha,
        min_n=settings.min_stats_n,
    )
    return PlainTextResponse(content=body, media_type="text/markdown")


@router.get("/traces", response_model=ApiEnvelope)
def list_traces(
    request: Request,
    limit: int = Query(default=20, ge=1, le=200),
) -> dict[str, object]:
    """Return newest-first trace summaries."""

    records = request.app.state.trace_store.list_traces(limit=limit)
    summaries = [
        {
            "trace_id": r.get("trace_id"),
            "ts": r.get("ts"),
            "source": r.get("source"),
            "flags": r.get("flags"),
            "model": r.get("model_id"),
        }
        for r in records
    ]
    return ok({"items": summaries})


@router.get("/traces/{trace_id}", response_model=ApiEnvelope)
def get_trace(request: Request, trace_id: str) -> object:
    """Return one full JSON trace."""

    trace = request.app.state.trace_store.get_trace(trace_id)
    if trace is None:
        return err("not_found", "Trace not found", status.HTTP_404_NOT_FOUND)
    return ok(trace)


@router.get("/traces/{trace_id}/paste")
def paste_trace(request: Request, trace_id: str) -> object:
    """Return the context-free markdown paste bundle for one trace."""

    trace = request.app.state.trace_store.get_trace(trace_id)
    if trace is None:
        return err("not_found", "Trace not found", status.HTTP_404_NOT_FOUND)
    return _paste(request, trace)


@router.get("/last/paste")
def paste_last(request: Request) -> object:
    """Return the newest trace paste bundle."""

    trace = request.app.state.trace_store.latest_trace()
    if trace is None:
        return err("not_found", "No traces yet", status.HTTP_404_NOT_FOUND)
    return _paste(request, trace)


@router.post("/prompt-preview", response_model=ApiEnvelope)
def prompt_preview(request: Request, body: CoachRequest) -> object:
    """Build evidence and messages without calling the model."""

    if not body.text and not body.entry_id:
        return err(
            "validation_error",
            "Provide text or entry_id",
            status.HTTP_422_UNPROCESSABLE_ENTITY,
        )
    try:
        preview = request.app.state.coach_service.prompt_preview(
            text=body.text,
            entry_id=body.entry_id,
            include_history=body.include_history,
        )
    except KeyError:
        return err("not_found", "Entry not found", status.HTTP_404_NOT_FOUND)
    except ValueError as exc:
        return err("validation_error", str(exc), status.HTTP_422_UNPROCESSABLE_ENTITY)
    return ok(preview)


@router.post("/coach-test", response_model=ApiEnvelope)
def coach_test(request: Request, body: CoachRequest) -> object:
    """Run coach and force a trace; optional force_backup."""

    if not body.text and not body.entry_id:
        return err(
            "validation_error",
            "Provide text or entry_id",
            status.HTTP_422_UNPROCESSABLE_ENTITY,
        )
    try:
        result = request.app.state.coach_service.coach(
            text=body.text,
            entry_id=body.entry_id,
            include_history=body.include_history,
            persist=body.persist,
            force_backup=body.force_backup,
        )
    except KeyError:
        return err("not_found", "Entry not found", status.HTTP_404_NOT_FOUND)
    except ValueError as exc:
        return err("validation_error", str(exc), status.HTTP_422_UNPROCESSABLE_ENTITY)
    return ok(result)