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

providernotes
openaiThe default.
openrouterOne 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.
ollamaLocal 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.