RESENTMENT OS 2.0.0

Providers

Bring your own key. Every provider is reduced to one internal shape, so adding one is a table row and, at most, a new wire format.

Supported

ProviderWire formatKeyNotes
In your browser (WebGPU)WebLLM, in a workernonethe model runs on your own GPU inside the tab; no server, nothing leaves the machine; see below
AnthropicMessages APIconsolestreaming, tool use; adaptive thinking is the model's default and nothing is sent to change it
OpenAI/chat/completionsplatform
Google GeminigenerateContentAI Studiofunction calls get client-side ids
Groq/chat/completionsconsole
Mistral/chat/completionsconsole
xAI/chat/completionsconsole
DeepSeek/chat/completionsplatform
OpenRouter/chat/completionskeyssends the referer header OpenRouter asks for
Together/chat/completionssettings
Ollama/chat/completionsnonelocal; see below
LM Studio/chat/completionsnonelocal; see below
Anything OpenAI-compatible/chat/completionsas neededvLLM, llama.cpp, LiteLLM, a proxy: set the base URL

The internal shape

Messages are the Anthropic shape: {role, content: [blocks]} where a block is text, tool_use or tool_result. A tool_result also carries the tool's name, which Gemini needs and the others ignore. buildRequest() turns that into each wire format; makeParser() turns each wire format's stream events into text, tool, usage and stop. Both are pure functions with tests against recorded events.

Inside the browser, on your GPU

Pick In your browser (WebGPU, no server) in Settings. There is no key and no server: the model runs inside the tab on your GPU through the WebLLM runtime, in a web worker so the desktop stays responsive. The first use downloads the weights (0.5 to 5 GB depending on the model) into the browser's cache and reports progress in the chat's status line; after that it is instant and works offline.

This is the one place the desktop loads code from a CDN (cdn.jsdelivr.net/npm/@mlc-ai/web-llm, pinned to a version), and it does so only when this provider is chosen. A GPU inference engine is not something to rewrite for the sake of a rule; the rule's purpose, that you can read what runs, still holds: the runtime is open source and the pin is in providers.js.

For local models with more control (any GGUF, CPU inference, bigger contexts), run Ollama or LM Studio and pick those instead; they speak /chat/completions and are listed above.

Calling from a browser

The page is the client. There is no server between you and the provider, so the provider must allow cross-origin requests from a browser.

Where the key lives

In this browser's IndexedDB, under the vault's own keys, which the digest does not read. Plain by default. With a passphrase, sealed under AES-GCM with a key derived by PBKDF2 (200 000 rounds, SHA-256) and unlocked once per session. Export never includes it. Nothing in the desktop makes a request to anything but the provider endpoint you configured.

Anthropic refusal fallbacks

Anthropic's newest models can decline a request; the API can re-run a declined request on another Claude model server-side. It is a beta, so it is off by default and a checkbox in Settings turns it on. When on, the desktop sends anthropic-beta: server-side-fallback-2026-07-01 and "fallbacks": "default".

Adding a provider

Add a row to PROVIDERS in os/desktop/js/providers.js with an id, a name, a kind (anthropic, openai, gemini or webllm), a base URL, a few models and a keys link. If it speaks one of the three wire formats, that is the whole change. If it does not, add a branch to buildRequest() and a parser to makeParser(), and a test in tests/providers.test.js with a recorded stream.