# Choose an inference provider

> Set Settings → Router, persist inferenceProvider, and satisfy Cursor session, Claude Code login, Codex auth.json, or OPENROUTER_API_KEY.

- Repository: sashimikun/grok-bot-0.18-reconstructed
- GitHub: https://github.com/sashimikun/grok-bot-0.18-reconstructed
- Human docs: https://grok-wiki.com/public/docs/sashimikun-grok-bot-0-18-reconstructed-c774cc9a5c15
- Complete Markdown: https://grok-wiki.com/public/docs/sashimikun-grok-bot-0-18-reconstructed-c774cc9a5c15/llms-full.txt

## Source Files

- `source/host/extensions/inference/provider-session.ts`
- `source/shared/node/inference-router-local.ts`
- `source/electron-main/main-edge.ts`
- `scripts/lib/router-renderer-patch.mjs`
- `frontend/src/recovered/features/settings/overlay/router.ts`
- `tests/router-settings.test.mjs`

---

---
title: "Choose an inference provider"
description: "Set Settings → Router, persist inferenceProvider, and satisfy Cursor session, Claude Code login, Codex auth.json, or OPENROUTER_API_KEY."
---

The packaged app’s **Settings → Router** panel selects a `SandInferenceProvider` (`cursor`, `claude-code`, `codex`, `openrouter`), persists it as `inferenceProvider` on `settings.json` version `1` (default `cursor`), and uses that value for new turns. The overlay is injected at package time into the checksum-pinned renderer. `window.desktop.agent.setInferenceRouter(provider)` writes the store and mirrors `{ inferenceProvider }` into the box; it does not validate credentials. Auth is checked when a turn runs.

<Frame caption="Settings → Router: provider dropdown, Computer toggle, account status, and per-provider usage">
![Settings Router overlay](/docs/assets/router-settings.png)
</Frame>

<Note>
The Vite workspace under `frontend/` models the same four ids and a local key `settings.router-provider.v1`. Packaged UI does not use that key. Preference for the running app is `inferenceProvider` in host `settings.json`.
</Note>

## Prerequisites

- A packaged reconstructed app (`npm run package` applies the Settings registry and Router panel patches).
- For `claude-code` or `codex`, the matching local login on this Mac (no extra API key in Settings).
- For `openrouter`, an OpenRouter key in the environment or saved through **Settings → Router**.

## Choose a provider

<Steps>
<Step title="Open Settings → Router">
In the packaged app, open Settings and select **Router** (`id: "router"`, icon `git-branch`). The panel loads `window.desktop.agent.getInferenceRouter()` and listens for `sand-router-provider-changed`.
</Step>
<Step title="Select Provider">
Use the **Provider** dropdown (`aria-label="Routing provider"`). Allowed values: `cursor`, `claude-code`, `codex`, `openrouter`. Unknown ids fail with `Unknown inference provider.` and the previous selection is restored in the overlay.
</Step>
<Step title="Satisfy account or key">
Confirm the **Account** / **OpenRouter account** row:

| Provider | Ready signal |
| --- | --- |
| `cursor` | Status **Signed in** (Cursor session already attached to Grok Bot) |
| `claude-code` | **Ready** (CLI found and login present) |
| `codex` | **Ready** (usable private `auth.json`) |
| `openrouter` | Key saved: placeholder **Replace saved key**, or `OPENROUTER_API_KEY` in the process environment |

If Claude Code or Codex is missing, the row shows **Not installed** or **Sign in with claude** / **Sign in with codex login**. Install or log in, then reopen Grok Bot so `getLocalInferenceCliStatus()` refreshes.
</Step>
<Step title="Send a new turn">
The next agent turn reads `SandSettingsStore.getInferenceProvider()`. `cursor` stays on the Cursor session path. Any other id uses `createProviderPromptSession(provider)`.
</Step>
</Steps>

<Warning>
Changing the dropdown only persists the id. A turn against `claude-code`, `codex`, or `openrouter` without a usable login or key fails at request time, not at save time.
</Warning>

The same Router page includes **Use local Docker VM**. That toggle is `boxRuntime`, not `inferenceProvider`. See [Enable the local Docker sandbox](/enable-local-docker).

## Provider ids

| `inferenceProvider` | Label | Auth surface | New-turn path |
| --- | --- | --- | --- |
| `cursor` | Cursor | Existing Grok Bot / Cursor session | Host Cursor session (`createSession`) |
| `claude-code` | Claude Code | Claude Code CLI + login | `@anthropic-ai/claude-agent-sdk` `query` |
| `codex` | Codex | ChatGPT tokens in Codex `auth.json` | Direct HTTP to `https://chatgpt.com/backend-api/codex/responses` (CLI not on the request path) |
| `openrouter` | OpenRouter | `OPENROUTER_API_KEY` | OpenAI-compatible `https://openrouter.ai/api/v1` |

<Tabs>
<Tab title="cursor">
Default. Overlay description: “Use your signed-in Cursor account.” Status is always **Signed in**; the Router panel does not probe Cursor auth. Tools stay on native Grok Bot plugins.
</Tab>
<Tab title="claude-code">
Resolve the `claude` executable, then sign in.

Search order for the CLI: `CLAUDE_CODE_PATH`, `~/.local/bin/claude`, `~/.claude/local/claude`, `PATH`, `/opt/homebrew/bin/claude`, `/usr/local/bin/claude`.

Status **authenticated** when `~/.claude/.credentials.json` exists or `ANTHROPIC_API_KEY` is non-empty. A turn still requires the executable; missing CLI throws `Claude Code is not installed. Install and sign in to Claude Code, then reopen Grok Bot.`

Optional model: `SAND_CLAUDE_MODEL`. Plugin tools go through the Grok Bot MCP bridge (`mcp__grok_bot_plugins__*`).
</Tab>
<Tab title="codex">
Grok Bot reads tokens and calls ChatGPT itself. The `codex` binary is only used for install/status.

Auth file: `$CODEX_HOME/auth.json` if `CODEX_HOME` is set, otherwise `~/.codex/auth.json`. The file must be a regular file (not a symlink) with no group/other bits (`mode & 0o077 === 0`). Required fields: `auth_mode === "chatgpt"` and non-empty `tokens.access_token`, `refresh_token`, `id_token`, `account_id`.

Otherwise: `Codex login credentials must be a private direct regular file.` or `Codex is not signed in with ChatGPT. Run \`codex login\`, then reopen Grok Bot.`

401 responses refresh at `https://auth.openai.com/oauth/token` and rewrite `auth.json` atomically (`mode 0o600`). Model: `SAND_CODEX_MODEL`, else `model` in `config.toml`, else `gpt-5.4`. Reasoning: `SAND_CODEX_REASONING_EFFORT` or `model_reasoning_effort` in that TOML (`minimal` \| `low` \| `medium` \| `high` \| `xhigh`).
</Tab>
<Tab title="openrouter">
Credential: `process.env.OPENROUTER_API_KEY`, else `OPENROUTER_API_KEY` in `box-secrets.json`. Missing key: `OpenRouter needs OPENROUTER_API_KEY. Add it in Settings → Router.`

Save from the overlay with `window.desktop.secrets.upsert({ OPENROUTER_API_KEY })` (`sand:secrets-upsert`). Environment wins over the secrets file.

Model: `SAND_OPENROUTER_MODEL` or `openai/gpt-5.2`. Tools run in the Grok Bot execution loop (`maxSteps` 8 when tools are present).
</Tab>
</Tabs>

## Persist path

```mermaid
flowchart TB
  subgraph renderer [Packaged Settings overlay]
    Panel["RRouterPanel Provider dropdown"]
    Event["sand-router-provider-changed"]
  end
  subgraph electron [Electron main edge]
    GetIR["getInferenceRouter"]
    SetIR["setInferenceRouter"]
    Local["getLocalInferenceCliStatus"]
  end
  subgraph store [Host settings]
    File["settings.json version 1"]
    Field["inferenceProvider"]
    Usage["inferenceRouterUsage schemaVersion 1"]
  end
  subgraph turns [Next turn]
    Cursor["cursor createSession"]
    Routed["createProviderPromptSession"]
  end
  Panel --> SetIR
  SetIR --> Field
  Field --> File
  SetIR -->|"syncHostSettingsToBox best-effort"| File
  GetIR --> Field
  GetIR --> Usage
  GetIR --> Local
  GetIR --> Panel
  SetIR --> Event
  Field -->|"cursor"| Cursor
  Field -->|"claude-code, codex, openrouter"| Routed
```

<ParamField body="inferenceProvider" type="SandInferenceProvider" required>
Stored on `settings.json`. Allowed: `cursor` \| `claude-code` \| `codex` \| `openrouter`. Omitted or invalid values load as `cursor`. Atomic write: `settings.json.<pid>.tmp` then `rename`.
</ParamField>

Typical packaged path is `~/.grokbot/settings.json` (`getSandProductionRootDir`). Overrides: absolute `SAND_DATA_ROOT`, or `SAND_USER_DATA_DIR` / `--user-data-dir` (then `sand-data/settings.json`). Unpackaged variant uses `~/.cursor/<variant>/settings.json`.

`setInferenceRouter` always updates the local store first. `syncHostSettingsToBox({ inferenceProvider })` failures are swallowed; the RPC still returns the local provider, usage, and CLI status.

## Desktop RPC

Preload: `window.desktop.agent.getInferenceRouter()` and `window.desktop.agent.setInferenceRouter(provider)` (`setInferenceRouter` args: `{ provider }`).

:::endpoint GET getInferenceRouter
Load current route, local usage, and Claude/Codex CLI status.

**Returns**

<ResponseField name="provider" type="SandInferenceProvider">
Current `inferenceProvider`, or `cursor` if the store value is not a known id.
</ResponseField>
<ResponseField name="usage" type="SandInferenceRouterUsage | null">
Box copy of `inferenceRouterUsage` when readable, else the local store. `schemaVersion` is `1`.
</ResponseField>
<ResponseField name="local" type="{ codex: LocalInferenceCliStatus, 'claude-code': LocalInferenceCliStatus }">
`installed`, `authenticated`, `executablePath` for Codex and Claude Code.
</ResponseField>
:::

:::endpoint POST setInferenceRouter
Persist a provider id.

**Body**

<ParamField body="provider" type="string" required>
Must pass `isSandInferenceProvider`. Otherwise the edge throws `Unknown inference provider.`
</ParamField>

**Returns** the same `{ provider, usage, local }` shape as get.
:::

<RequestExample>
```js title="Renderer overlay"
const current = await window.desktop.agent.getInferenceRouter();
const next = await window.desktop.agent.setInferenceRouter("openrouter");
window.dispatchEvent(new CustomEvent("sand-router-provider-changed", { detail: next }));
await window.desktop.secrets.upsert({ OPENROUTER_API_KEY: key });
```
</RequestExample>

<ResponseExample>
```json title="getInferenceRouter / setInferenceRouter"
{
  "provider": "openrouter",
  "usage": {
    "schemaVersion": 1,
    "providers": {
      "cursor": { "requests": 0, "inputTokens": 0, "outputTokens": 0, "cacheReadTokens": 0, "cacheWriteTokens": 0, "lastUsedAt": null },
      "claude-code": { "requests": 0, "inputTokens": 0, "outputTokens": 0, "cacheReadTokens": 0, "cacheWriteTokens": 0, "lastUsedAt": null },
      "codex": { "requests": 0, "inputTokens": 0, "outputTokens": 0, "cacheReadTokens": 0, "cacheWriteTokens": 0, "lastUsedAt": null },
      "openrouter": { "requests": 0, "inputTokens": 0, "outputTokens": 0, "cacheReadTokens": 0, "cacheWriteTokens": 0, "lastUsedAt": null }
    }
  },
  "local": {
    "codex": { "installed": true, "authenticated": true, "executablePath": "/opt/homebrew/bin/codex" },
    "claude-code": { "installed": true, "authenticated": true, "executablePath": "/opt/homebrew/bin/claude" }
  }
}
```
</ResponseExample>

Routed system prompt (Claude, Codex, OpenRouter) states the process is Grok Bot, not Codex CLI or Claude Code, and that connected plugins are already available.

## Environment and secrets

| Variable | Role |
| --- | --- |
| `OPENROUTER_API_KEY` | Preferred OpenRouter credential |
| `SAND_OPENROUTER_MODEL` | OpenRouter model id; default `openai/gpt-5.2` |
| `CLAUDE_CODE_PATH` | Explicit `claude` executable |
| `ANTHROPIC_API_KEY` | Counts as Claude Code **authenticated** for status only |
| `SAND_CLAUDE_MODEL` | Optional Claude Agent SDK model |
| `CODEX_HOME` | Directory for `auth.json` and `config.toml` (default `~/.codex`) |
| `CODEX_PATH` | Codex CLI path for status only |
| `SAND_CODEX_MODEL` | Codex model override |
| `SAND_CODEX_REASONING_EFFORT` | Codex reasoning override |
| `SAND_DATA_ROOT` / `SAND_USER_DATA_DIR` | Host settings root |

## Usage & Billing

**Usage & Billing** is patched to `RRouterUsage`. It lists the current provider plus any provider with `requests > 0`. Counters (`requests`, `inputTokens`, `outputTokens`, `cacheReadTokens`, `cacheWriteTokens`, `lastUsedAt`) are local activity, not a provider invoice. `cursor` still renders the original Cursor usage panel (`Na`) under the router summary.

## Verify

<Check>
After a successful set, reopen **Settings → Router**: the dropdown matches the saved id, **Usage for \<label\>** tracks that provider, and a new chat turn follows Cursor vs `createProviderPromptSession`.
</Check>

<AccordionGroup>
<Accordion title="Turn-time errors">
- `Unknown inference provider.` — RPC `provider` not in `SAND_INFERENCE_PROVIDERS`.
- `Claude Code is not installed. Install and sign in to Claude Code, then reopen Grok Bot.`
- `Claude Code ended without a result.` / `Claude Code failed (<subtype>).`
- `Codex login credentials must be a private direct regular file.`
- `Codex is not signed in with ChatGPT. Run \`codex login\`, then reopen Grok Bot.`
- `Codex login expired and could not be refreshed. Run \`codex login\` again.`
- `OpenRouter needs OPENROUTER_API_KEY. Add it in Settings → Router.`
</Accordion>
</AccordionGroup>

Full failure catalog: [Router and sandbox failures](/router-failures).

## Next

<CardGroup>
<Card title="Inference router" href="/inference-router">
Provider ids, default `cursor`, transcript `schemaVersion` 2, usage `schemaVersion` 1.
</Card>
<Card title="Provider clients" href="/provider-clients">
Cursor path, Claude Agent SDK, Codex Responses URL, OpenRouter default `openai/gpt-5.2`.
</Card>
<Card title="Settings schema" href="/settings-schema">
`settings.json` version 1, `inferenceProvider`, usage, atomic persist.
</Card>
<Card title="Desktop RPC" href="/desktop-rpc">
`getInferenceRouter`, `setInferenceRouter`, secrets upsert/list.
</Card>
<Card title="Route MCP tools" href="/route-mcp-tools">
Claude Code MCP bridge vs Codex/OpenRouter direct tool execution.
</Card>
<Card title="Enable local Docker" href="/enable-local-docker">
Computer toggle on the same Router page.
</Card>
</CardGroup>
