mindfork mindfork

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.