mindfork 0.13.0 — OpenRouter as a mode of its own
mindfork 0.13.0 is out. It has one subject — OpenRouter, the gateway in front of every vendor's models, is a mode of its own — and a handful of fixes that the work on it turned up, some of them for every setup.
A mode, not an address
OpenRouter speaks the protocol a local server speaks, so it has always been
reachable: choose external, type the gateway's address over your server's,
and chat. What that cost was the address you typed over. Going back to the local
model meant typing it again, with the model's name and the key's; the embedder,
impersonation and speech each had an address of their own to retype; and some
things did not work that way at all — speech never did, because the gateway
refuses the audio format an external server is asked for.
Settings → Model/server → Mode → openrouter is that, done properly. The
gateway's section sits beside external, so a local server's address and the
gateway's key are both kept: moving between a local model and OpenRouter is the
Mode row and nothing else. One stored key serves every slot that uses the
gateway — the assistant, impersonation, the embedder, speech and the YouTube
tool. external pointed at openrouter.ai keeps working as it did, and its
address row now says that the mode exists.
The list says what a model is
Enter on the model row lists the gateway's catalogue, filtered as you type,
and each row says what you would otherwise look up: the model's context
window, its price per million tokens in and out, and no tools where the
gateway says the model takes none. With a key the list is what your account's
privacy settings allow, and the entries that cannot answer a chat request are
left out. Typing a name by hand is still the list's first row.
A key the gateway refuses is said before your first message: it is checked
once when the mode is applied, and a refused one is the status line, in
OpenRouter's own words, rather than a 401 on your first question.
And Thinking: off reaches the models that cannot stop thinking. Some refuse a request to switch reasoning off; through the gateway such a model is asked for the lowest effort it lists instead — measured on Gemini 3.5 Flash, that is no reasoning tokens at all.
Memory, speech and video on the same key
The embedder takes the mode like the chat engine does, with the list of the gateway's embedding models behind its model row. Two things built for it are now every cloud embedder's: a request that meets a rate limit or an overloaded provider is repeated, up to three attempts, instead of failing an indexing run; and no request carries more than 64 texts — Gemini refuses the hundred-and-first, which is how a long text could fail to enter the knowledge base.
Speech: /tts reads messages aloud through the gateway's speech models —
some twenty, of a dozen vendors. The model row lists them with the number of
voices each has, and the voice row lists the voices of the model you chose.
There is no default model: choose one. The audio format and the sample rate
differ between models; the app settles them with the gateway, and there is
nothing to set.
Video: Settings → Tools → Video (YouTube) → Provider → openrouter, and
youtube_watch watches through the Gemini models behind the gateway's key. Two
things are worth knowing. Through the gateway the whole video is read and
charged, even when you ask about a part of it — so the ceiling on a video's
length measures the whole video there, and the answer says that the whole of it
was read. Google's own API stays the default, and the cheaper way to look at a
part of a long video. And an answer that came back without the video having
been read is refused rather than shown: the gateway can answer with a
plausible description of a link it never opened, and the app tells the two apart
by what the gateway counted, not by what it wrote.
Where a request goes
A request through a gateway has two readers: OpenRouter, and the provider behind the model you chose. Where the trust boundaries are and the privacy policy say what each request carries, slot by slot.
Requests to the gateway also carry two headers that name this application — its site and its name — which OpenRouter counts its public app rankings by. They say nothing about you. Settings → Model/server → Name the app to OpenRouter: on by default; off, and the headers are not sent.
Fixed on the way, for every setup
- A message read aloud could stop half way, or not start. A speech server that streams an MP3 writes the clip's length into its first frame before it knows it, and the player believed the number: one model's sentence ended at 2.35 s of 4.78, and another's count of zero was an error inside the player. The clip is played to its end now — in every speech mode that plays a container, a server of your own included.
- An edit of the embedding settings switched two things off until the
restart: the
query:/passage:markers of the model's input convention, and the check that the stored vectors belong to the model now answering. A change of the embedding model made in the settings went unnoticed until the next start, and searches ran against another model's vectors. Both follow every change now. - The first knowledge-base search after a change of the embedding model
returned passages ranked by the wrong model. It refuses, like every search
after it, until
/reindex. - A settings edit could leave the previous engine's facts in charge. Moving from a local server to a cloud kept the context window of the engine before it, so compaction fired by the wrong window until the restart.
Your data
settings.json moves from version 3 to 4. Nothing in it is rewritten — the step
stamps the version, because openrouter is a value an older version does not
know, and a version that does not know a value refuses the whole file rather
than reading the rest of it. As with every schema change, the file is backed up
before it is migrated; 0.12.0 refuses a file this version has written, and that
backup is the way back.
The full list is in the
changelog;
downloads are on the
releases page, and
cargo install mindfork builds the same version from
crates.io.