Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
cfc0dd5
Added Chat API scaffolding, including routes, handlers, middleware, a…
m-messer May 14, 2026
78f3e37
Refactored muEd into evaluate and chat
m-messer May 14, 2026
aaa35b1
Added tests for chat
m-messer May 14, 2026
9b9409c
Implemented chat functionality, added request/response handling, and …
m-messer May 14, 2026
4562d83
Added OpenAPI request/response validation middleware and integrated O…
m-messer May 14, 2026
576037d
Add embedded µEd OpenAPI specification
m-messer May 14, 2026
61590bf
Move µEd OpenAPI spec into runtime/schema
m-messer May 14, 2026
f3eb883
Ignore .idea/ directory
m-messer May 14, 2026
01da9b4
Make OpenAPI response validation strict for µEd routes
m-messer May 14, 2026
42447eb
Merge branch 'feature/mued-schema-validation' into feature/chat
m-messer May 14, 2026
0ef7540
Add X-Api-Version header support to chat handlers
m-messer May 14, 2026
7847763
Merge branch 'main' into feature/chat
m-messer May 29, 2026
68259b4
Removed unused µEd version handling logic and added ChatRequest/ChatR…
m-messer May 29, 2026
daee02a
Add `Chat` and `ChatHealth` methods to `Runtime` interface and implem…
m-messer May 29, 2026
8203a88
Refactor chat and chat health handlers to use `Chat` and `ChatHealth`…
m-messer May 29, 2026
bce16b4
Replace direct `http.Error` calls with `writeMuEdError` utility in ch…
m-messer May 29, 2026
66bd47f
Return 503 status code when ChatHealth status is "UNAVAILABLE" and ad…
m-messer May 29, 2026
bc6ea48
Add `chat` and `chat/health` command mappings to `Runtime`
m-messer May 29, 2026
43eeeed
Merge branch 'main' into feature/chat
m-messer Aug 4, 2026
9aa8db4
Refactor chat data structures for µEd spec compliance
m-messer Aug 4, 2026
d1f6cf9
Rename `CommandHealth` to `CommandEvaluateHealth` for consistency wit…
m-messer Aug 5, 2026
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
117 changes: 117 additions & 0 deletions handler/chat.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
package handler

import (
"encoding/json"
"io"
"net/http"

"github.com/lambda-feedback/shimmy/internal/server"
"github.com/lambda-feedback/shimmy/runtime"
)

// ServeChat handles POST /chat.
func (h *MuEdHandler) ServeChat(w http.ResponseWriter, r *http.Request) {
if !h.checkAuth(w, r) {
return
}

version, ok := h.checkMuEdVersion(w, r)
if !ok {
return
}

if r.Method != http.MethodPost {
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
return
}

body, err := io.ReadAll(r.Body)
if err != nil {
h.writeMuEdError(w, version, http.StatusBadRequest, "VALIDATION_ERROR", "Bad request", "failed to read body", nil)
return
}

var chatReq runtime.MuEdChatRequest
if err := json.Unmarshal(body, &chatReq); err != nil {
h.writeMuEdError(w, version, http.StatusBadRequest, "VALIDATION_ERROR", "Bad request", "invalid request body", nil)
return
}

reqData, err := runtime.MuEdBuildChatRequest(chatReq)
if err != nil {
h.writeMuEdError(w, version, http.StatusBadRequest, "VALIDATION_ERROR", "Bad request", err.Error(), nil)
return
}

resp, err := h.runtime.Chat(r.Context(), runtime.ChatRequest{Data: reqData})
if err != nil {
h.writeMuEdError(w, version, http.StatusInternalServerError, "INTERNAL_ERROR", "Internal server error", "chat failed", nil)
return
}

resultMap, ok := resp.Data["result"].(map[string]any)
if !ok {
h.writeMuEdError(w, version, http.StatusInternalServerError, "INTERNAL_ERROR", "Internal server error", "invalid response from chat function", nil)
return
}

chatResp, err := runtime.MuEdToChatResponse(resultMap)
if err != nil {
h.writeMuEdError(w, version, http.StatusInternalServerError, "INTERNAL_ERROR", "Internal server error", err.Error(), nil)
return
}

w.Header().Set("Content-Type", "application/json")
w.Header().Set(muEdVersionHeader, version)
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(chatResp) //nolint:errcheck
}

// ServeChatHealth handles GET /chat/health.
func (h *MuEdHandler) ServeChatHealth(w http.ResponseWriter, r *http.Request) {
if !h.checkAuth(w, r) {
return
}

version, ok := h.checkMuEdVersion(w, r)
if !ok {
return
}

if r.Method != http.MethodGet {
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
return
}

resp, err := h.runtime.ChatHealth(r.Context())
if err != nil {
h.writeMuEdError(w, version, http.StatusInternalServerError, "INTERNAL_ERROR", "Internal server error", "chat health check failed", nil)
return
}

resultMap, ok := resp.Data["result"].(map[string]any)
if !ok {
h.writeMuEdError(w, version, http.StatusInternalServerError, "INTERNAL_ERROR", "Internal server error", "invalid chat health response", nil)
return
}

healthResp := runtime.MuEdToChatHealthResponse(resultMap)

statusCode := http.StatusOK
if status, ok := healthResp["status"].(string); ok && status == string(runtime.MuEdChatHealthStatusUnavailable) {
statusCode = http.StatusServiceUnavailable
}

w.Header().Set("Content-Type", "application/json")
w.Header().Set(muEdVersionHeader, version)
w.WriteHeader(statusCode)
json.NewEncoder(w).Encode(healthResp) //nolint:errcheck
}

func NewMuEdChatRoute(handler *MuEdHandler) server.HttpHandlerResult {
return server.AsHttpHandler("/chat", http.HandlerFunc(handler.ServeChat))
}

func NewMuEdChatHealthRoute(handler *MuEdHandler) server.HttpHandlerResult {
return server.AsHttpHandler("/chat/health", http.HandlerFunc(handler.ServeChatHealth))
}
Loading
Loading