ModelAdapter for one decision at a time and receives a stream of
ModelEvents: text deltas, tool calls, usage. Everything above the adapter is vendor-blind,
and swapping one changes no line of the harness.
The adapters
Scripted, for tests
ScriptedAdapter from @nativeharness/agent plays a fixed sequence of decisions: text, tool
calls, or a thrown error to exercise the failure path. It is how the harness’s own suite runs
a whole turn — a real command, in a real sandbox — with no key, and how the CLI’s
--model scripted:<file> works.
It also declares itself deterministic, which is the one case the agent will run on a
non-isolating sandbox provider: no model is deciding anything, so there is nothing to contain.
Choosing from the CLI
nhar chat takes --model, then $NH_MODEL, then the first key it finds in the environment,
in this order: Anthropic, OpenAI, Google, Cohere, xAI, DeepSeek, Groq, Mistral. Adapters are
loaded only when chosen, so an Anthropic user never loads the OpenAI SDK.
Writing one
AModelAdapter is one method, decide, taking the system prompt, the messages, the tools and
a token budget, and yielding ModelEvents — plus an info block naming the model, its
context window and its capabilities. The interface is
model-adapter.ts;
the Ollama adapter is the smallest real one to read.