feat: OpenAI, Anthropic, and native-Ollama providers + media pipeline

Phase 3:
- provider/openai: Chat Completions for OpenAI + compat endpoints (SSE
  streaming with by-index tool-call assembly, response_format json_schema,
  legacy max_tokens option, reasoning_effort)
- provider/anthropic: Messages API (tool_use/tool_result, GA structured
  output via output_config.format, full SSE event parser, 529 transient)
- provider/ollama: one native /api/chat client behind the ollama,
  ollama-cloud, and foreman built-ins (presets; NDJSON streaming tolerant
  of foreman's buffered single-object responses; object tool arguments;
  format-schema structured output; think mapping)
- media/: capability normalization (sniff, downscale, transcode, byte
  ladder, ErrUnsupported), wired into the chain executor per target with
  penalty-free advance past incapable elements
- registry: real provider + scheme wiring, WithHTTPClient option, required
  env-foreman TLS chat round-trip test
- ADR-0009 multimodal strategy, ADR-0010 tools/structured mapping; README
  matrix + CLAUDE.md synced

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-06-10 12:58:08 +02:00
parent 323558ed72
commit 043249e0e1
31 changed files with 6194 additions and 74 deletions
+16 -6
View File
@@ -2,6 +2,7 @@ package majordomo
import (
"fmt"
"net/http"
"os"
"strings"
"sync"
@@ -73,11 +74,12 @@ func (c ChainConfig) classify(err error) llm.ErrorClass {
}
type registryConfig struct {
health health.Config
chain ChainConfig
envLookup func(string) string
environ func() []string
skipEnv bool
health health.Config
chain ChainConfig
envLookup func(string) string
environ func() []string
skipEnv bool
httpClient *http.Client
}
// RegistryOption configures New.
@@ -115,6 +117,14 @@ func WithoutEnvProviders() RegistryOption {
return func(rc *registryConfig) { rc.skipEnv = true }
}
// WithHTTPClient sets the HTTP client used by built-in providers and
// env-DSN scheme factories created by this registry (proxies, custom TLS,
// test servers). Providers registered explicitly via RegisterProvider keep
// whatever client they were built with.
func WithHTTPClient(c *http.Client) RegistryOption {
return func(rc *registryConfig) { rc.httpClient = c }
}
// New creates a Registry with all built-in providers and scheme factories
// registered, then loads LLM_* env-DSN providers from the process
// environment (unless WithoutEnvProviders is given). Malformed LLM_* entries
@@ -139,7 +149,7 @@ func New(opts ...RegistryOption) *Registry {
envLookup: cfg.envLookup,
}
registerBuiltins(r)
registerBuiltins(r, cfg.httpClient)
if !cfg.skipEnv {
env := make(map[string]string)