backend: extract stream parsers, shared HTTP, url.URL, comprehensive tests
- Extract streamOpenAISSE, streamOllamaNDJSON from ChatStream goroutines
into standalone functions taking io.Reader for direct testability
- Remove ctx parameter from streamAnthropicSSE (caller handles cancellation)
- Extract streamRequest into backend.go: shared HTTP execution, 429/error
handling, and goroutine lifecycle for all three backends
- Extract encodeOpenAIMessages, encodeOpenAITools from ChatStream
- Replace string baseURL with *url.URL; validate in constructors
- Constructors (NewOpenAI, NewOllama, NewAnthropic, NewCopilot) now return error
- Remove unreachable json.Marshal and http.NewRequestWithContext error branches
ChatStream coverage: 100% (OpenAI, Ollama, Anthropic)
Stream parsers: 100% (OpenAI, Ollama), 96.6% (Anthropic)
streamRequest: 100%
Test files:
- openai_encode_test.go: message/tool encoding, JSON round-trip
- openai_stream_test.go: SSE parsing via io.Reader
- ollama_stream_test.go: NDJSON parsing via io.Reader
- anthropic_stream_test.go: SSE parsing, mapAnthropicStopReason
- openai_test.go: HTTP integration, error paths, models, context length
- backend_test.go: contract checks (all interface methods), stream text/usage/
tool calls/tool conversation, HTTP errors, rate limits for all backends