1- package api
1+ package aichatbot
22
33// Phase-50 / 0011 — U1 Chatbot LLM upgrade.
44//
5- // ai_chatbot_handler .go implements the real LLM-backed handler that
5+ // handler .go implements the real LLM-backed handler that
66// replaces the F0 stub at POST /api/v1/ai/chatbot. The flow is:
77//
88// request JSON {message, session_id}
@@ -52,11 +52,12 @@ import (
5252 "github.com/ev-dev-labs/teslasync/internal/ai/strategy"
5353 "github.com/ev-dev-labs/teslasync/internal/ai/stream"
5454 "github.com/ev-dev-labs/teslasync/internal/ai/tools"
55+ "github.com/ev-dev-labs/teslasync/internal/api/httpx"
5556 tsauth "github.com/ev-dev-labs/teslasync/internal/auth"
5657 dbnotif "github.com/ev-dev-labs/teslasync/internal/database/notification"
5758)
5859
59- // aiChatbotHistoryLimit is the upper bound on how many prior messages
60+ // historyLimit is the upper bound on how many prior messages
6061// we hand to the LLM as context. Picked to balance:
6162//
6263// - Token budget: ~16 messages × ~80 tokens average ≈ 1.3K input
@@ -67,22 +68,22 @@ import (
6768// History older than this is silently dropped. The full record is
6869// always kept in the chatbot_messages table for audit and the
6970// /chatbot/history endpoint.
70- const aiChatbotHistoryLimit = 16
71+ const historyLimit = 16
7172
72- // aiChatbotMaxIterations bounds the dispatcher's tool-loop. The
73+ // maxIterations bounds the dispatcher's tool-loop. The
7374// chatbot is one-question-one-answer plus optional tool round-trips;
7475// a hard ceiling of 6 protects against pathological model loops
7576// without truncating any realistic conversation. Tested in
76- // TestAIChatbotHandler_OnPathDispatches .
77- const aiChatbotMaxIterations = 6
77+ // TestHandler_OnPathDispatches .
78+ const maxIterations = 6
7879
79- // AIChatbotHandler is the HTTP handler for POST /api/v1/ai/chatbot.
80+ // Handler is the HTTP handler for POST /api/v1/ai/chatbot.
8081//
8182// Construction is in router.go (so the dispatcher's tool registry +
8283// provider registry are wired once at boot). The handler itself is
8384// stateless beyond its constructor inputs and is safe for concurrent
8485// use across requests.
85- type AIChatbotHandler struct {
86+ type Handler struct {
8687 chat * dbnotif.ChatRepo
8788 registry * provider.Registry
8889 tools * tools.Registry
@@ -92,7 +93,7 @@ type AIChatbotHandler struct {
9293 historyN int
9394}
9495
95- // NewAIChatbotHandler constructs the handler. All non-pointer
96+ // NewHandler constructs the handler. All non-pointer
9697// arguments are required; the constructor panics on a nil so the
9798// wiring bug surfaces at boot, not at first request.
9899//
@@ -101,38 +102,38 @@ type AIChatbotHandler struct {
101102// toolReg: process-wide tool registry (Register12Builtins-populated).
102103// strat: the chatbot-llm Strategy (one per process).
103104// headerName: forward-auth header name; used to extract subject for audit.
104- func NewAIChatbotHandler (
105+ func NewHandler (
105106 chat * dbnotif.ChatRepo ,
106107 registry * provider.Registry ,
107108 toolReg * tools.Registry ,
108109 strat strategy.Strategy ,
109110 headerName string ,
110- ) * AIChatbotHandler {
111+ ) * Handler {
111112 switch {
112113 case chat == nil :
113- panic ("api: NewAIChatbotHandler : nil ChatRepo" )
114+ panic ("aichatbot: NewHandler : nil ChatRepo" )
114115 case registry == nil :
115- panic ("api: NewAIChatbotHandler : nil provider.Registry" )
116+ panic ("aichatbot: NewHandler : nil provider.Registry" )
116117 case toolReg == nil :
117- panic ("api: NewAIChatbotHandler : nil tools.Registry" )
118+ panic ("aichatbot: NewHandler : nil tools.Registry" )
118119 case strat == nil :
119- panic ("api: NewAIChatbotHandler : nil strategy.Strategy" )
120+ panic ("aichatbot: NewHandler : nil strategy.Strategy" )
120121 }
121- return & AIChatbotHandler {
122+ return & Handler {
122123 chat : chat ,
123124 registry : registry ,
124125 tools : toolReg ,
125126 strategy : strat ,
126127 headerName : headerName ,
127- maxIters : aiChatbotMaxIterations ,
128- historyN : aiChatbotHistoryLimit ,
128+ maxIters : maxIterations ,
129+ historyN : historyLimit ,
129130 }
130131}
131132
132- // aiChatbotRequest is the wire shape for POST /api/v1/ai/chatbot.
133+ // request is the wire shape for POST /api/v1/ai/chatbot.
133134// Mirrors the existing baseline endpoint so the frontend can call
134135// either route without DTO drift.
135- type aiChatbotRequest struct {
136+ type request struct {
136137 Message string `json:"message"`
137138 SessionID string `json:"session_id"`
138139}
@@ -142,15 +143,15 @@ type aiChatbotRequest struct {
142143// after the SSE stream closes. Every error path writes a structured
143144// frame onto the SSE stream (when the writer has been opened) or a
144145// plain JSON 4xx/5xx (before it has).
145- func (h * AIChatbotHandler ) ServeHTTP (w http.ResponseWriter , r * http.Request ) {
146+ func (h * Handler ) ServeHTTP (w http.ResponseWriter , r * http.Request ) {
146147 // 1) Decode + validate request body.
147- var body aiChatbotRequest
148+ var body request
148149 if err := json .NewDecoder (r .Body ).Decode (& body ); err != nil {
149- writeError (w , http .StatusBadRequest , "invalid request body" )
150+ httpx . WriteError (w , http .StatusBadRequest , "invalid request body" )
150151 return
151152 }
152153 if strings .TrimSpace (body .Message ) == "" {
153- writeError (w , http .StatusBadRequest , "message is required" )
154+ httpx . WriteError (w , http .StatusBadRequest , "message is required" )
154155 return
155156 }
156157 if body .SessionID == "" {
@@ -188,7 +189,7 @@ func (h *AIChatbotHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
188189 // stream — emit JSON 502 so the frontend falls back gracefully.
189190 if _ , err := h .registry .For (r .Context (), chatbotllm .FeatureID ); err != nil {
190191 log .Error ().Err (err ).Msg ("ai chatbot: provider.For failed" )
191- writeError (w , http .StatusBadGateway , "ai provider unavailable" )
192+ httpx . WriteError (w , http .StatusBadGateway , "ai provider unavailable" )
192193 return
193194 }
194195
@@ -208,7 +209,7 @@ func (h *AIChatbotHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
208209 // Non-flushable response writer (test recorder, etc.).
209210 // Emit a plain JSON 500 — the SSE headers were not sent.
210211 log .Error ().Err (err ).Msg ("ai chatbot: stream.New failed (non-flushable writer)" )
211- writeError (w , http .StatusInternalServerError , "streaming not supported" )
212+ httpx . WriteError (w , http .StatusInternalServerError , "streaming not supported" )
212213 return
213214 }
214215
0 commit comments