Ollama HTTP client — native /api/chat streaming.
Purpose
-------
:class:`OllamaClient` implements :class:`BaseClient` against Ollama's
native ``/api/chat`` endpoint (not the OpenAI-compat shim). Streaming is
parsed via :func:`providers.ollama.ndjson.parse_ndjson_stream`.
Responsibilities
----------------
- :class:`OllamaClient` — :class:`BaseClient` for the Ollama provider.
``stream_chat``, ``one_shot``, ``list_models``, ``show_model``.
- :func:`_connect` — build an ``HTTPConnection`` / ``HTTPSConnection``
from the base URL (stdlib only, zero extra deps).
- Friendly ``RuntimeError`` when the Ollama daemon is not running.
Diagnostics
-----------
Domain: OC_CHAT.CLIENT
Levels:
L2 — lifecycle events (connect, response status)
L3 — request details (model, endpoint, chunk counts)
L4 — deep tracing
Contracts
---------
- ``stream_chat`` yields ``("content", str)``, ``("tool_call", dict)``,
and ``("usage", dict)`` tuples. ``"reasoning"`` is never yielded (local
models don't separate reasoning from content in the Ollama protocol).
- ``tool_call`` dict: ``{"id": str, "name": str, "arguments": str}``
where *arguments* is a JSON string (matches the rest of OC Chat).
- ``one_shot`` returns a plain ``str``; uses ``stream: false``.
- ``list_models`` hits ``GET /api/tags``; ``show_model`` hits
``POST /api/show``.
- Connection errors raise ``RuntimeError`` with a user-friendly message
suggesting ``ollama serve``.
Dependencies
------------
- oc_diagnostics (_dbg)
- providers.ollama.ndjson (parse_ndjson_stream)
- http.client (HTTP and HTTPS connections to the Ollama daemon)
- json (encoding request bodies and tool-call arguments, decoding responses)
- urllib.parse (splitting the base URL into scheme, host and port)
- oc_chat.core.egress_guard (lazy; refusing a forbidden internal address as the Ollama host)
- option_c_agent_contracts (lazy; SupportsCancellation type of the stream_chat cancellation token)