Providers & configuration
Three providers, one program. Switching is an environment variable, not a code change.
export ECKO_API_KEY=sk-...
export ECKO_AI_PROVIDER=openai # or openrouter, ollama
export ECKO_AI_MODEL=gpt-4o-mini # optional; each provider has a default
Or per run:
ecko report.ecko --provider openrouter --model anthropic/claude-sonnet-4.5
The providers
| provider | notes |
|---|---|
openai | The default. |
openrouter | One key, hundreds of models from many vendors. Model ids are vendor/model, e.g. anthropic/claude-sonnet-4.5 or google/gemini-2.5-pro. |
ollama | Local models. No key needed; runs against your own machine. |
ECKO_AI_BASE_URL must be https, or http to a loopback host - your API key travels on it, and plain http would put it on the wire in cleartext and let the model's reply be altered in transit. Ollama's local default and a dev server on localhost are unaffected; ECKO_ALLOW_HTTP=1 overrides, the same switch package fetching uses.
ECKO_AI_BASE_URL overrides the endpoint, which is how you point at a proxy, a gateway, or any other OpenAI-compatible server.
Reaching Anthropic, Google, Meta and the rest
Through openrouter. Ecko carried a direct Anthropic integration until 0.18 and it was removed: it was a second wire protocol to maintain - its own message shape, tool serialization, streaming frames, image encoding and error body - for one vendor, while OpenRouter reaches that vendor and many others through the protocol Ecko already speaks.
export ECKO_AI_PROVIDER=openrouter
export ECKO_API_KEY=sk-or-v1-...
export ECKO_AI_MODEL=anthropic/claude-haiku-4.5
Setting ECKO_AI_PROVIDER=anthropic now fails with that instruction rather than being silently ignored.
What the runtime normalizes
The differences between providers are real - message shapes, tool-call serialization, streaming frame formats, image encodings, error bodies - and all of them are handled underneath. One ai expression with tools and a session works across all three, and a provider swap does not touch your source.
openai and openrouter share a wire format, so they share an adapter; ollama has its own. That is why adding OpenRouter cost a base URL and a name rather than a second protocol.
Errors are parsed per provider, so a failure reports what the provider actually said rather than an empty string.
Configuration is never code
There is no configure() call and no config file the runtime reads. A program's behaviour is a function of its source and its environment, which means a library cannot change your provider behind your back and a code review shows you everything the program decides.
For a project-level default, use the environment block in ecko.json - applied before evaluation and overriding the shell, so a checkout runs the way its author intended:
{ "environment": { "ECKO_AI_PROVIDER": "ollama", "ECKO_AI_MODEL": "llama3" } }
Never put a key in it. That file is committed.
Local models
ollama is the zero-cost, zero-egress option, and worth reaching for when the data should not leave the machine. Quality differs from a frontier model, so it is a real choice rather than a drop-in - but the code is identical, so trying it costs one variable.
No key at all
Everything still runs. See Mock mode.
The full list
Every setting is in Environment variables.