Resolve a provider credential in precedence order
See the four credential sources in the order Pi consults them, and why a model can load from models.json yet stay unavailable.
Pi resolves a provider credential from four sources and uses the first one that
yields a value: a runtime API key, a stored auth.json credential, an apiKey
in models.json, then the provider’s environment variable. This is a
configuration and decision-table walkthrough of the Chapter 3 tests; every key
below is a placeholder, and it resolves no real credential.
The four sources, in order
| Order | Source | Where it comes from |
|---|---|---|
| 1 | runtime key | --api-key, or a runtime API call |
| 2 | stored credential | <agent-dir>/auth.json |
| 3 | model key | apiKey on the provider in <agent-dir>/models.json |
| 4 | environment | the provider’s environment variable |
The order surprises people: a key in models.json sits below a stored
credential, not above it. It exists to give a custom or self-hosted model its key;
it does not override a key you already logged in with.
A stored credential has this shape (placeholder values):
{ "anthropic": { "type": "api_key", "key": "stored" } }A custom endpoint in models.json supplies its key the same way:
{
"providers": {
"mylocal": {
"baseUrl": "http://localhost:11434/v1",
"api": "openai-completions",
"apiKey": "dummy",
"models": [{ "id": "my-model" }]
}
}
}Observed resolution
The canonical test resolves the same sources through Pi’s real ModelRuntime,
then removes them one at a time. The asserted winner at each step:
| Sources present | Resolved credential |
|---|---|
| runtime + auth.json + models.json + environment | runtime |
| auth.json + models.json + environment | auth.json |
| models.json + environment | models.json |
| environment only | environment |
| none | no credential |
Loading is not availability
The second half of the test is the symptom worth remembering. A provider entry in
models.json can parse and the model can exist (getModel returns it) while it
is absent from the available list until the provider has usable
authentication. Adding a dummy apiKey is enough to make it appear. That is why a
correct models.json can still leave /model empty.
Mechanism and limitations
Documented: the exported ModelRuntime, its getAuth and getAvailable
methods, and the auth.json / models.json file shapes. Observed:
credentials.test.ts drives the real runtime over real files and asserts every
row of the resolution table, including the availability difference. It also shows
that the same script behaves identically whether the provider’s key is written as
a literal, $PI_TEST_KEY, or !command — provider configuration is not agent
behaviour.
The fragments here are illustrative: they show the file shapes and the precedence, not a run on your machine, and nothing here proves how a real model behaves. A provider extension may define its own resolution; the order above is the documented default chain.
Understand this example
Copy this prompt into your AI tool. No code runs here.
Apply this example
Copy this prompt into your AI tool. No code runs here.