MCPcopy Index your code
hub / github.com/codehamr/codehamr / readSSE

Function readSSE

internal/llm/llm.go:468–536  ·  view source on GitHub ↗

readSSE reads OpenAI SSE frames until [DONE] or EOF, forwarding content/reasoning/tool-call events to out. Returns the final assistant message (content + accumulated tool calls), the server completion and prompt token counts, and any scanner error. parent is threaded through so sends abort on cancel

(parent context.Context, body io.Reader, budget cloud.BudgetStatus, out chan<- Event, onFrame func())

Source from the content-addressed store, hash-verified

466 // Drain before the deferred Close so the keep-alive connection returns
467 // to the pool instead of being discarded, same as the 402/default arms.
468 _, _ = io.Copy(io.Discard, resp.Body)
469 return nil, cloud.BudgetStatus{}, nil, cloud.ErrUnauthorized
470 case 402:
471 // Pass depleted. Body ignored: the status code is the whole signal,
472 // the UI banner is fixed text, the returned snapshot reflects it.
473 _, _ = io.Copy(io.Discard, resp.Body)
474 return nil, cloud.BudgetStatus{Set: true, Remaining: 0}, nil, cloud.ErrBudgetExhausted
475 default:
476 b, _ := io.ReadAll(resp.Body)
477 return nil, cloud.BudgetStatus{}, b, &httpStatusError{status: resp.StatusCode, msg: errorMessageFromBody(b)}
478 }
479}
480
481// httpStatusError preserves the HTTP status behind the user-facing message so
482// retryable can classify transience; Error() keeps the exact "status: message"
483// string the error banner has always shown.
484type httpStatusError struct {
485 status int
486 msg string
487}
488
489func (e *httpStatusError) Error() string { return fmt.Sprintf("%d: %s", e.status, e.msg) }
490
491// retryable reports whether a pre-stream failure is worth resending: transport
492// errors and the statuses that signal a transient server state. 404 is
493// semantically permanent, but LiteLLM-style proxies emit it for transient
494// upstream misses (the Agnes-AI freeze in issue #7), and the rising backoff
495// bounds the cost of a truly permanent one. 401/402 arrive as typed sentinels
496// the UI handles; 400 and other client errors fail identically on every
497// resend.
498func retryable(err error) bool {
499 var un cloud.ErrUnreachable
500 if errors.As(err, &un) {
501 return true
502 }
503 var hs *httpStatusError
504 if errors.As(err, &hs) {
505 return hs.status == 404 || hs.status == 408 || hs.status == 429 || hs.status >= 500
506 }
507 return false
508}
509
510// errorMessageFromBody extracts the user-facing string from a non-2xx body.
511// hamrpass wraps errors as `{"error":{"message":...,"provider_hint":...}}`; we
512// prefer provider_hint (providers stash the human diagnostic there), fall back
513// to message, then to the raw first line so non-hamrpass backends still surface
514// whatever they emit.
515func errorMessageFromBody(b []byte) string {
516 var env struct {
517 Error struct {
518 Message string `json:"message"`
519 ProviderHint string `json:"provider_hint"`
520 } `json:"error"`
521 }
522 if json.Unmarshal(b, &env) == nil {
523 if env.Error.ProviderHint != "" {
524 return env.Error.ProviderHint
525 }

Callers 1

runMethod · 0.85

Calls 4

dispatchDeltaFunction · 0.85
sendEventFunction · 0.85
resolveMethod · 0.80
StringMethod · 0.80

Tested by

no test coverage detected