← back to Exo
feat(dashboard): add Pi integration tab (#1925)
4939fbe9952b187dc1adf34efce234f36413b6cc · 2026-04-22 21:29:47 +0400 · Nadeem Hilal Wani
## Summary
Adds a new **Pi** tab to the Integrations page (`/#/integrations`)
alongside the existing Claude Code, OpenCode, Codex, OpenClaw, Open
WebUI, n8n, and Firefox tabs.
[pi](https://pi.dev) (`@mariozechner/pi-coding-agent`) is a terminal
coding agent that supports custom OpenAI-compatible providers via
`~/.pi/agent/models.json`.
This tab gives users a copy-pasteable config to wire pi up to their exo
cluster.
## What's in the tab
- **Model selector** (shown when multiple models are running) — picks
the default model for the generated shell command.
- **Models Config card** — generates `~/.pi/agent/models.json`
registering `exo` as a custom provider:
- `baseUrl` → `<apiUrl>/v1`
- `api` → `openai-completions`
- `apiKey` → `"exo"` (placeholder; exo ignores it)
- `compat.supportsDeveloperRole: false` and
`compat.supportsReasoningEffort: false`, per pi docs recommendation for
local OpenAI-compatible servers
- Auto-populates every running model with `id`, `contextWindow` (from
`/v1/models`), and `input: ["text", "image"]` for vision-capable models
- **Shell Command card** — `pi --provider exo --model <model>` for quick
launch.
The tab gracefully falls back to `your-model-id` when no models are
running, matching the behavior of the other tabs.
## Usage
1. `npm install -g @mariozechner/pi-coding-agent`
2. Paste the generated config into `~/.pi/agent/models.json`
3. Run `pi` and pick an exo model via `/model` — or run the shell
command directly
## Changes
- `dashboard/src/routes/integrations/+page.svelte` — adds `"Pi"` to the
`tabs` tuple, `piModel` state, `piModelsJson` + `piShellCommand`
derivations, and the tab content block.
Single-file, scoped change — no backend or type changes.
## Testing
- `cd dashboard && npm run build` — ✅ builds cleanly
- `svelte-check` on the edited file — no new errors
- Manually verified the tab renders, the model selector updates the
generated JSON, and the config reflects `/v1/models` capabilities
(vision → `input: ["text","image"]`,
`context_length` → `contextWindow`).
## Screenshots
<img width="1545" height="1236" alt="pi-tab"
src="https://github.com/user-attachments/assets/38aa179f-4ed9-4a1e-9783-d3baa7738263"
/>
Files touched
M dashboard/src/routes/integrations/+page.svelteA package-lock.json
Diff
commit 4939fbe9952b187dc1adf34efce234f36413b6cc
Author: Nadeem Hilal Wani <nadeem.09wani@gmail.com>
Date: Wed Apr 22 21:29:47 2026 +0400
feat(dashboard): add Pi integration tab (#1925)
## Summary
Adds a new **Pi** tab to the Integrations page (`/#/integrations`)
alongside the existing Claude Code, OpenCode, Codex, OpenClaw, Open
WebUI, n8n, and Firefox tabs.
[pi](https://pi.dev) (`@mariozechner/pi-coding-agent`) is a terminal
coding agent that supports custom OpenAI-compatible providers via
`~/.pi/agent/models.json`.
This tab gives users a copy-pasteable config to wire pi up to their exo
cluster.
## What's in the tab
- **Model selector** (shown when multiple models are running) — picks
the default model for the generated shell command.
- **Models Config card** — generates `~/.pi/agent/models.json`
registering `exo` as a custom provider:
- `baseUrl` → `<apiUrl>/v1`
- `api` → `openai-completions`
- `apiKey` → `"exo"` (placeholder; exo ignores it)
- `compat.supportsDeveloperRole: false` and
`compat.supportsReasoningEffort: false`, per pi docs recommendation for
local OpenAI-compatible servers
- Auto-populates every running model with `id`, `contextWindow` (from
`/v1/models`), and `input: ["text", "image"]` for vision-capable models
- **Shell Command card** — `pi --provider exo --model <model>` for quick
launch.
The tab gracefully falls back to `your-model-id` when no models are
running, matching the behavior of the other tabs.
## Usage
1. `npm install -g @mariozechner/pi-coding-agent`
2. Paste the generated config into `~/.pi/agent/models.json`
3. Run `pi` and pick an exo model via `/model` — or run the shell
command directly
## Changes
- `dashboard/src/routes/integrations/+page.svelte` — adds `"Pi"` to the
`tabs` tuple, `piModel` state, `piModelsJson` + `piShellCommand`
derivations, and the tab content block.
Single-file, scoped change — no backend or type changes.
## Testing
- `cd dashboard && npm run build` — ✅ builds cleanly
- `svelte-check` on the edited file — no new errors
- Manually verified the tab renders, the model selector updates the
generated JSON, and the config reflects `/v1/models` capabilities
(vision → `input: ["text","image"]`,
`context_length` → `contextWindow`).
## Screenshots
<img width="1545" height="1236" alt="pi-tab"
src="https://github.com/user-attachments/assets/38aa179f-4ed9-4a1e-9783-d3baa7738263"
/>
---
dashboard/src/routes/integrations/+page.svelte | 79 ++++++++++++++++++++++++++
package-lock.json | 6 ++
2 files changed, 85 insertions(+)
diff --git a/dashboard/src/routes/integrations/+page.svelte b/dashboard/src/routes/integrations/+page.svelte
index 924cfdf7..95a646e1 100644
--- a/dashboard/src/routes/integrations/+page.svelte
+++ b/dashboard/src/routes/integrations/+page.svelte
@@ -88,10 +88,12 @@
let codexModel = $state("");
let codexMcpPath = $state("/Users/username");
let openClawModel = $state("");
+ let piModel = $state("");
$effect(() => {
const def = modelsBySize.length > 0 ? modelsBySize[0] : "your-model-id";
codexModel = def;
openClawModel = def;
+ piModel = def;
});
const claudeShellCommand = $derived(
@@ -218,6 +220,55 @@
),
);
+ const piModelsJson = $derived.by(() => {
+ const models: Record<string, unknown>[] = [];
+ for (const modelId of runningModels) {
+ const caps = modelCapabilities[modelId] || [];
+ const ctxLen = modelContextLengths[modelId] || 0;
+ const entry: Record<string, unknown> = { id: modelId };
+ if (caps.includes("vision")) {
+ entry.input = ["text", "image"];
+ }
+ // Mark thinking-capable models so pi surfaces its thinking-level selector
+ // for them. exo capability strings: "thinking" (model emits reasoning
+ // content) and "thinking_toggle" (user can turn it on/off).
+ if (caps.includes("thinking") || caps.includes("thinking_toggle")) {
+ entry.reasoning = true;
+ }
+ if (ctxLen > 0) {
+ entry.contextWindow = ctxLen;
+ }
+ models.push(entry);
+ }
+ if (models.length === 0) {
+ models.push({ id: "your-model-id" });
+ }
+ return JSON.stringify(
+ {
+ providers: {
+ exo: {
+ baseUrl: `${apiUrl}/v1`,
+ api: "openai-completions",
+ apiKey: "exo",
+ compat: {
+ supportsDeveloperRole: false,
+ // exo's OpenAI surface takes a boolean `enable_thinking` toggle,
+ // not graded effort levels, so disable pi's `reasoning_effort`
+ // parameter and use the matching top-level-boolean format.
+ supportsReasoningEffort: false,
+ thinkingFormat: "qwen",
+ },
+ models,
+ },
+ },
+ },
+ null,
+ 2,
+ );
+ });
+
+ const piShellCommand = $derived(`pi --provider exo --model ${piModel}`);
+
const ollamaCommand = $derived(
`OLLAMA_HOST=${apiUrl}/ollama ollama run ${modelsBySize.length > 0 ? modelsBySize[0] : "your-model-id"}`,
);
@@ -277,6 +328,7 @@
"OpenCode",
"Codex",
"OpenClaw",
+ "Pi",
"Open WebUI",
"n8n",
"Firefox",
@@ -515,6 +567,33 @@
config={`openclaw doctor --fix${(modelCapabilities[openClawModel] || []).includes("vision") ? `\nopenclaw models set-image exo/${openClawModel}` : ""}\nopenclaw gateway &\nopenclaw dashboard`}
language="bash"
/>
+ {:else if activeTab === "Pi"}
+ {#if runningModels.length > 1}
+ <div class="text-xs">
+ <span
+ class="text-exo-light-gray/50 text-[10px] uppercase tracking-wider block mb-1"
+ >Model</span
+ >
+ <select bind:value={piModel} class={selectClass}>
+ {#each runningModels as model}
+ <option value={model}>{model.split("/").pop()}</option>
+ {/each}
+ </select>
+ </div>
+ {/if}
+ <IntegrationCard
+ title="Models Config"
+ subtitle="~/.pi/agent/models.json"
+ description="Register exo as a custom provider in pi. Create or edit this file, then run pi and pick an exo model via /model. Install pi with: npm install -g @mariozechner/pi-coding-agent"
+ config={piModelsJson}
+ />
+ <IntegrationCard
+ title="Shell Command"
+ subtitle="Run in terminal"
+ description="Launch pi directly with the exo provider and model selected."
+ config={piShellCommand}
+ language="bash"
+ />
{:else if activeTab === "Open WebUI"}
<IntegrationCard
title="1. Start Open WebUI"
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 00000000..6768af15
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,6 @@
+{
+ "name": "exo",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {}
+}
← 73782ecc Fix event mutation causing indexed vs event mismatch (#1964)
·
back to Exo
·
feat(app): add friendly context message to bug report prompt 8993ccaf →