From 2608ad2f09c1429a4e4f4aef561249e07fd11779 Mon Sep 17 00:00:00 2001 From: Mathias Date: Fri, 2 Oct 2026 16:39:38 +0200 Subject: [PATCH] feat(llm): HTTPError -- a status code to branch on, a bounded body in the text (review F3) Co-Authored-By: Claude Sonnet 5.5 --- internal/adapters/llm/client.go | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/internal/adapters/llm/client.go b/internal/adapters/llm/client.go index 5ced970..33a3688 100644 --- a/internal/adapters/llm/client.go +++ b/internal/adapters/llm/client.go @@ -55,6 +55,29 @@ func WithMaxTokens(n int) Option { } } +// maxErrorBody bounds how much of a failing response ends up in an error message. +const maxErrorBody = 512 + +// HTTPError is a non-200 answer from the gateway. Its text keeps the form callers that +// log it already rely on, with the body bounded; its Code is what code may branch on and +// what errkind turns into a kind. The body is provider text and can echo the prompt, so +// nothing private may persist or display Error() (ADR-035 review F3). +type HTTPError struct { + Code int + Body string +} + +func (e *HTTPError) Error() string { + body := e.Body + if len(body) > maxErrorBody { + body = body[:maxErrorBody] + "…" + } + return fmt.Sprintf("LLM returned %d: %s", e.Code, body) +} + +// HTTPStatus implements errkind.HTTPStatuser. +func (e *HTTPError) HTTPStatus() int { return e.Code } + // WithTemperature overrides the sampling temperature. Zero IS a valid value and is sent // as zero: the reconciler's accuracy was measured at temperature 0 and must run there. func WithTemperature(t float64) Option { @@ -166,7 +189,7 @@ func (c *Client) Complete(ctx context.Context, system, user string) (string, err return "", fmt.Errorf("read response: %w", err) } if resp.StatusCode != http.StatusOK { - return "", fmt.Errorf("LLM returned %d: %s", resp.StatusCode, out) + return "", &HTTPError{Code: resp.StatusCode, Body: string(out)} } var cr chatResponse