Skip to content

v0.17.0 quackd pilots through OpenRouter, a twelfth cloud vendor, and costs a turn at what it was billed

Choose a tag to compare

@rokbenko rokbenko released this 07 Oct 07:35
· 254 commits to main since this release

OpenRouter is quackd's twelfth cloud vendor. It is a router that puts models made by many
vendors behind one key and one bill, and --llm openrouter asks openai/gpt-6-sol through it.
Other ids on OpenRouter's public model list with tool calling are taken too, each checked
against that list before the first paid call. A turn OpenRouter bills is costed at that bill. No
OpenRouter model has answered a real quackd request yet: every chat request quackd sent went to
a stand-in.

Six rows, and other ids on the list

OpenRouter speaks OpenAI's API, so quackd[openrouter] installs the same openai wheel as the
nine other vendors that do, and the key is OPENROUTER_API_KEY. quackd carries six rows for it,
the first the default: openai/gpt-6-sol, openai/gpt-6-luna, anthropic/claude-sonnet-5.5,
anthropic/claude-opus-5.5, google/gemini-3.8-flash and x-ai/grok-4.7, priced off
OpenRouter's own model list on 2026-10-06 and dated with that day. quackd list-models prints
123 ids where it printed 117.

Unlike every other vendor's, those rows are a selection. Other ids OpenRouter's public model
list carries with tool calling are taken as --llm openrouter:AUTHOR/MODEL. quackd reads that
list, which needs no key, once per process before the first paid call, and the id's entry sets
its price and whether the model is sent the camera frame. An id the list lacks is refused, and
the refusal names the nearest ids on the list when any come close. One it lists without tool
calling, or as expiring on or before the day it is read, is refused too.

An id is refused on its spelling before a key is read, even where OpenRouter lists it, when its
model or price is not fixed before the call or Chat Completions does not serve it: a ~ alias,
one of OpenRouter's own routers, a :batch entry or a routing variant (:nitro, :floor,
:exacto). So are :thinking and :online, which OpenRouter has replaced, and :extended,
which it says no model offers. :free is the one suffix taken, and OpenRouter limits a free
model to 20 requests a minute and 50 a day until 10 credits have been bought, 1,000 a day
after.

What a request carries

Every request carries provider: {"require_parameters": true}, so OpenRouter routes only to a
provider that honours what was sent. It carries no parallel_tool_calls, which few of
OpenRouter's models list, and it goes to Chat Completions only. The four rows that take a forced
tool call are told to call one. The two Claude rows refuse a forced call and are asked with
auto. Any id quackd does not carry is asked with auto too, or sent no tool_choice at all
where its entry on the list names none. A model's reasoning_details go back unmodified on the
turn that made the call. Requests also carry HTTP-Referer and X-OpenRouter-Title headers
naming quackd, which credit it on OpenRouter's public app rankings and say nothing about the
person running it. --extra-body takes OpenRouter's routing preferences, such as
{"provider":{"data_collection":"deny"}}. The browser demo offers the six rows.

A turn costed at what was billed

OpenRouter says in every response what the call was billed, usage.cost, which already knows
which provider served it, at which tier and with what cache. On OpenRouter that is the turn's
cost_usd, with the upstream charge added on a call made with your own provider key, and the
llm record says billed: true. summary.json counts those calls in billed_calls. A rate
named with --price or QUACKD_PRICE still wins over the bill, a call that brings no bill is
costed at the rate, and every other vendor's records are what they were.

What has not run

No OpenRouter model has answered a real quackd request. The chat requests quackd sent went only
to stand-ins, among them one on 127.0.0.1 that openai 3.22.1 reached in a real quackd run
and the browser demo's client reached under Node. Only the model list check reached OpenRouter
itself, with no key. So it is unproven that OpenRouter's Chat Completions gives the default row
its function tools, that a provider behind it takes the reasoning quackd replays, and that
usage.cost is what your key is charged.
docs/guides/openrouter.md
lists what was read on OpenRouter's own pages and what was assumed, and
ADR-0050
says why.

Documentation

docs/ is sorted by what a reader came to do, into guides/, reference/, concepts/ and
adapters/, where each robot has a folder of its own, and
docs/README.md maps them. A link
to a moved page from the PyPI pages of 0.16.1 and earlier returns 404, and the pages of 0.17.0
link the new places. Pages that said every cloud vendor is asked to call a tool the same way,
that the catalogue's rates were checked on 2026-09-21, and that a System One server answers
without a type field are corrected, and the two issue templates that list vendors name every
one. A test of the arm's simulator no longer races its own pick, Dependabot leaves hatchling
alone, and uv.lock takes Dependabot's updates from #32 and #33, which move no requirement a
user installs against.

No code that drives a body changed, and the arm has not run quackd since 2026-09-23, so the
bench steps 0.14.0 and 0.15.0 owe are still owed.

Eight distributions, one version between them: quackd, and quackd-microduck,
quackd-lerobot, quackd-rosbridge, quackd-open-duck, quackd-xlerobot, quackd-alohamini
and quackd-toddlerbot. The full note is in
CHANGELOG.md.