Files
steve 27f196d333 feat: add Ollama target client, model poller, and native passthrough
Phase 2 of foreman: the daemon now acts as a transparent Ollama proxy.

- internal/ollama: Client interface and HTTP implementation for chat
  (streaming + non-streaming), embed, tags, ps with auth forwarding,
  NDJSON streaming via bufio.Scanner, and connection vs HTTP error
  classification via custom error types.
- internal/ollama: ModelInventory with background poller for /api/tags
  and /api/ps, degraded mode on target unreachable with model retention,
  automatic recovery on reconnect.
- internal/server: Passthrough routes (/api/chat, /api/tags, /api/ps,
  /api/embed, /api/embeddings) with model validation, chat serialization
  gate (capacity-1 channel), concurrent embedding bypass (ADR-0013),
  NDJSON streaming with per-chunk flush, and degraded health reporting.
- cmd/foreman: Full serve wiring with Ollama client, poller goroutine,
  embedder warmup (keep_alive:-1), and signal-based shutdown.

The Mac is now usable as a go-llm target through foreman.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 18:07:33 -04:00

42 lines
1.3 KiB
Go

package ollama
import (
"fmt"
)
// ConnectionError wraps a network-level error when the Ollama target is unreachable.
// Phase 3 uses this to distinguish connection failures (retry-eligible) from HTTP
// errors (usually not retryable).
//
// Why: callers must differentiate "target is down" from "target returned 4xx/5xx"
// to decide on retry strategy.
// What: wraps a net-level error and satisfies the error and Unwrap interfaces.
// Test: create a ConnectionError, verify errors.Is/As can match it.
type ConnectionError struct {
URL string
Err error
}
func (e *ConnectionError) Error() string {
return fmt.Sprintf("connection to ollama target %s failed: %v", e.URL, e.Err)
}
func (e *ConnectionError) Unwrap() error {
return e.Err
}
// HTTPError represents a non-2xx HTTP response from the Ollama target.
//
// Why: callers need the status code to distinguish client errors (4xx) from
// server errors (5xx) and decide on retry logic.
// What: holds the HTTP status code and response body for error diagnosis.
// Test: create an HTTPError with status 500, verify Error() includes the code.
type HTTPError struct {
StatusCode int
Body string
}
func (e *HTTPError) Error() string {
return fmt.Sprintf("ollama target returned HTTP %d: %s", e.StatusCode, e.Body)
}