Abhed — Model providers and sampling parameters

Status: 2026-09-06

Abhed's value is the harness, and a harness is only as good as the models it can drive. The adapter seam is what keeps "swap the model" a config change rather than a port, so the provider list is a capability of the product, not a convenience.

Providers

abhed providers lists what the binary in hand supports — which is the set that registered itself at init, so a build that omits the cloud adapters for an air-gapped install reports honestly.

TypeWire formatNotes
anthropicMessages APIexplicit cache_control on the system block; thinking blocks
openaichat/completions
geminigenerateContentcontents/parts, thought parts, no call ids
watsonxwatsonx text/chatproject- or space-scoped
vllm, sglang, llamacpp, tgi, ollamachat/completionslocal servers; accept top_k, min_p, repetition_penalty
groq, together, openrouter, mistral, deepseek, xaichat/completionshosted gateways
azure-openaichat/completionsbase_url is the deployment endpoint
bedrock-anthropic, vertex-anthropic, vertex-geminias the underlying modelneed a signed endpoint or a supplied token — see below
openai-compatiblechat/completionsgeneric escape hatch, kept for existing configs

Three wire formats cover all of them. The OpenAI-shaped providers share one adapter and differ only in default URL and which sampler knobs the server honours; Anthropic and Gemini are genuinely different shapes and have their own.

Cloud gateways and credentials

Abhed carries no cloud SDK, deliberately: a vendored AWS or Google SDK is a large dependency an air-gapped bundle has to justify. So Bedrock and Vertex take a token the operator supplies through api_key — from a sidecar, a short-lived credential, or a signing proxy named in base_url.

That is a real limitation and it is stated rather than hidden. An adapter that silently cannot authenticate is worse than one that says what it needs.

Sampling parameters

Every provider takes a params object:

{
  "model": {
    "default": "local",
    "providers": {
      "local": {
        "type": "vllm",
        "base_url": "http://127.0.0.1:8000/v1",
        "model": "qwen3-coder:30b",
        "params": {
          "temperature": 0.2,
          "top_p": 0.9,
          "top_k": 40,
          "repetition_penalty": 1.05,
          "seed": 42
        }
      }
    }
  }
}
ParameterMeaning
temperaturelogit scaling; 0 is greedy
top_pnucleus sampling
top_kcandidate-set cap (local servers, Anthropic, Gemini)
min_pprobability floor relative to the top token (local servers)
repetition_penaltydivides logits of tokens already produced (local servers)
frequency_penalty, presence_penaltyOpenAI's two-term formulation
seedreproducible sampling where supported — what makes two eval runs comparable
max_tokensresponse cap
stopstop sequences
effortreasoning budget as a level: low, medium, high
thinkturns a hybrid model's thinking phase on or off
thinking_budgetthinking budget as a token count (Anthropic, Gemini)

An omitted parameter is not the same as zero. Every field is a pointer, so leaving temperature out leaves the model's own default alone, while setting it to 0 asks for greedy decoding. Collapsing those two would make "don't touch it" impossible to express.

A parameter the provider cannot honour is refused at startup, naming the knob and the provider. This matters more than it looks: min_p sent to a hosted API is ignored silently, and the evidence is nowhere in the output — the answers are simply drawn from a distribution nobody chose. Better to fail the config than to run something that looks configured and is not.

Precedence is request > provider params > model default, so a caller can pin one knob without restating the rest.

Adding a provider

One file with an init that calls model.Register. No factory switch to edit, no config parsing to touch. If it speaks the OpenAI format, it is a single line naming its default URL and its sampling set.

Authenticating with a subscription

A Claude Pro or Max token does not work outside Claude Code, and this is enforced rather than merely written down. Anthropic accepts the credential and then refuses the request unless the first system block is Claude Code's own identity line; the refusal arrives as 429 rate_limit_error, which reads as a limit that will clear and is not one.

Abhed still reads CLAUDE_CODE_OAUTH_TOKEN and oauth_token: the mechanism is correct, the restriction may not be permanent, and models outside the check do answer. It recognises the refusal by its shape — a 429 carrying none of the headers a real rate limit carries — reports what it actually is, and does not retry a decision that will not change.

Working around it means sending Claude Code's identity string from something that is not Claude Code. Abhed does not, and nothing built on Abhed should: it circumvents an access control, misrepresents the product, and breaks the moment the check changes.

Use an API key. And note that third-party usage under a subscription is billed as extra usage rather than drawn from the plan, so it is metered in claude.ai settings and not in the Console — a subscription token is not a Console credential.

export ANTHROPIC_API_KEY=sk-ant-api03-...

An OpenAI-shaped endpoint authenticates with a bearer token either way, so a key and a subscription token take the same path there; only the source differs. OPENAI_API_KEY is read when no key is configured.

A token and a key are never sent together. Sending both would let the server choose, which makes "which account paid for this" depend on someone else's precedence rules rather than on what the operator configured.