e2e.config.ts that builds the instance decides who serves it and how it
authenticates.
Pick a gateway or a provider
A gateway reaches every vendor’s models with one key. A provider package calls one vendor directly.e2e init writes the one you chose.
- Vercel AI Gateway
- OpenRouter
- OpenAI-compatible endpoint
- A provider directly
e2e.config.ts
gateway() ships with ai, so no extra package. It reads AI_GATEWAY_API_KEY.
The model id is whatever the package accepts. Gateways take
vendor/model;
a direct provider takes the vendor’s bare id; a local server takes the name
it serves the model under.
Where the key comes from
The provider package reads its key, not the runner. Every AI SDK provider acceptsapiKey at construction and otherwise reads its own environment
variable on the first request.
- The key is read late. Constructing
gateway('...')needs no variable. Deterministic suites,e2e list, and config loading all work without a key. Only a step that calls a model needs the provider’s key. - There is no
E2E_*key variable. The runner never sees the key, so it has nothing to name. Set the variable the package you imported documents. - The runner never logs it. The key lives in your config module and the
provider’s request headers. It does not enter the report, the trace, or
automatically inherited environment of the app process. Explicitly putting
a key in
command.envforwards it to that process.
Several models in one config
Each model slot is its own instance, so they can come from different packages:e2e.config.ts
model drives agent.act and the judgments,
pixels included: the act loop shows it screenshots, so pick one that accepts
images and grounds a point in them well. The provider needs its key when the
model is first used.
An agents.<name>.model that names a different model than the one passed to
createAgent({ model }) is INVALID_CONFIG.
A configured executor, including createAgent(...), handles agent.act
and agent.assert through runStep. That assertion path rejects vision
and screenshot options. An executor may implement both methods without a
model. agent.waitFor and agent.extract still use the built-in judgment
tier and its model routing; see Custom executors.
To switch models per run without editing the config, read a variable of your
own:
Agent options fragment
Models that refuse forced tool calls
The built-inagent.act loop asks the model for a tool call on every turn.
Some models reject that request shape. Anthropic’s Claude Fable 5.1 answers HTTP 400 to any
forced tool choice. The runner recognizes the refusal, retries with the
choice left to the model and a tools-only rule in its instructions, and keeps
that mode for the rest of the process. The refused request does not count as
a model call.
What fails, and where
A missing model is checked once per run, so a suite without one reports one
run-level error rather than one blocked step per test. A missing key is not
checked ahead of time: the runner does not know which variable to look for,
so the provider reports it on the first call.
Agents and personas
Several agents in one suite.
Config reference
The
model slot and every other key.