Skip to content

feat(adopt): connect Pi and oh-my-pi; list the shared skills folder - #263

Merged
fylorn merged 2 commits into
devfrom
adopt-pi
Oct 1, 2026
Merged

fylorn merged 2 commits into
devfrom
adopt-pi

Conversation

@fylorn

@fylorn fylorn commented Oct 1, 2026

Copy link
Copy Markdown
Contributor

What this changes

Pi and oh-my-pi are now clients that can be pointed at the gateway in one step. Pi (earendil-works/pi, formerly badlogic/pi-mono) gets a provider in ~/.pi/agent/models.json; its fork oh-my-pi (can1357/oh-my-pi, omp) gets the same provider in ~/.omp/agent/models.yml (or models.yaml when that is the file omp reads). The plan dialog shows the full diff, the file is backed up, and restore removes exactly what was written (a file created here is deleted again). Both are marked fields_only: the field names are checked against the source, nothing was run on this machine.

Pi, ~/.pi/agent/models.json (merged into the existing file; the record goes into the models.json.thinkwatch.json sidecar because the file is JSON):

{
  "providers": {
    "thinkwatch": {
      "name": "ThinkWatch",
      "baseUrl": "http://127.0.0.1:8788/v1",
      "api": "openai-completions",
      "apiKey": "tw-…",
      "models": [
        { "id": "claude-sonnet-5", "api": "anthropic-messages", "baseUrl": "http://127.0.0.1:8788" },
        { "id": "gpt-5.5", "api": "openai-responses" },
        { "id": "gemini-3-pro", "api": "google-generative-ai", "baseUrl": "http://127.0.0.1:8788/v1beta" },
        { "id": "deepseek-chat" }
      ]
    }
  }
}

oh-my-pi, ~/.omp/agent/models.yml (sentinel comment block at the top, as for the other YAML clients):

providers:
  thinkwatch:
    baseUrl: http://127.0.0.1:8788/v1
    api: openai-completions
    apiKey: tw-…
    auth: apiKey
    models:
      - id: claude-sonnet-5
        api: anthropic-messages
        baseUrl: http://127.0.0.1:8788
      - id: deepseek-chat
  • A provider of its own (thinkwatch). Both clients keep credentials per provider, and /login stores them under built-in ids (anthropic, openai-codex, …). Rewriting a built-in provider's baseUrl would send those credentials to the gateway.
  • The model list is written, like opencode (writes_models): the gateway's GET /v1/models for the client's key. Each model uses its family's API so a same-format upstream is passed through unconverted: Claude → anthropic-messages (base URL without /v1), Gemini → google-generative-ai (/v1beta), GPT / o-series / Codex → openai-responses, everything else → the provider default openai-completions (/v1). The Clients page flags a stale list and re-plans it through the same diff.
  • The default model is left alone, as for opencode: settings.json (Pi) and config.yml (omp) are not touched. The plan says where the ThinkWatch models are chosen.
  • oh-my-pi always says how it authenticates. Without auth, omp shapes custom anthropic-messages models as Claude Code (OAuth-style request shaping). auth: apiKey is written with a key, auth: none without one (omp rejects a provider with models and neither).
  • Pi's key is written so it reads back literally. Pi interprets $NAME / ${NAME} and a leading ! in apiKey; $ is written as $$ and a leading ! as $!. Gateway keys (tw- + 24 base32 characters) never contain either, so in practice the value is unchanged. omp only interprets a leading ! and a value that is exactly an environment variable name, which a tw- key cannot be.
  • Both reload without a restart: Pi re-reads models.json when /model opens, omp's model picker re-reads models.yml when its mtime changed. takes_effect: immediately, reloads: true.
  • omp reads only one of models.yml / models.yaml, the first that exists. The takeover writes into that one; if models.yml appears later over a models.yaml takeover, the plan and the diagnosis say that nothing written here is used (new messages, not the generic "a setting of the same name wins").
  • Env vars that move the config directory are diagnosed like CODEX_HOME: PI_CODING_AGENT_DIR (Pi); PI_CODING_AGENT_DIR, PI_CONFIG_DIR, OMP_PROFILE, PI_PROFILE (omp). Both clients are movable on the Clients page (layout: the agent directory).
  • MCP: ~/.pi/agent/mcp.json and ~/.omp/agent/mcp.json (mcpServers) appear on the MCP page, read-only (adopt.mcp.unverified_format, no local sample). The scan covers both MCP files (plus omp's .mcp.json and its disabledServers / enabledServers lists), skills/, Pi's prompts/ and omp's commands/ + prompts/ as slash commands, and the instruction files read into context (AGENTS.md, AGENTS.override.md, CLAUDE.md, SYSTEM.md, APPEND_SYSTEM.md for Pi; AGENTS.md, SYSTEM.md, RULES.md for omp). Project level: .pi/ and .omp/ MCP and skills.
  • Logos: Pi uses the Lobe Icons "Pi Agent" mark (MIT); oh-my-pi has no mark and gets the letter tile.

Caveats shown in the plan (new message codes, Chinese in core.zh.json):

  • Pi: "The default model stays as it is; ThinkWatch models are chosen in /model, where Ctrl+S makes one the default." (adopt.cost.pi.default_model)
  • oh-my-pi: "The default model stays as it is; ThinkWatch models are chosen in /model, where assigning the default role makes one the default." (adopt.cost.omp.default_model)
  • Pi, only when it applies: "{name} is set and NO_PROXY does not list {host}, so Pi sends its requests for the gateway through that proxy. Adding {host} to NO_PROXY sends them directly." (adopt.plan.pi.proxy_env), or the same sentence for Pi's own httpProxy setting (adopt.plan.pi.proxy_setting). Pi uses undici's EnvHttpProxyAgent, which proxies every host, 127.0.0.1 included, when NO_PROXY is empty; the check follows undici's rules (http_proxy before HTTP_PROXY, an empty value means no proxy, https falls back to the HTTP proxy, NO_PROXY host/port/suffix/* matching). The proxy variables come from the login-shell environment: user_env still keeps them away from core, but now sets them aside for this check. omp needs no note: it bypasses the proxy for loopback and private ranges.
  • The usual adopt.plan.fields_only note, and adopt.plan.no_models when the key has no models.

DeepSeek Harness desktop and web. Both read the home patch layer that the existing DSH takeover writes, so they were already covered (checked in 0.2.0-rc.2, see below). Added: the installed desktop app (DeepSeek Harness.app in /Applications or ~/Applications; %LOCALAPPDATA%\Programs\DeepSeek Harness\DeepSeek Harness.exe on Windows) marks DSH as installed even before ~/.dsh exists, which also makes its MCP column present. New caveat: "Models used through a DeepSeek account signed in to the desktop app go straight to api.deepseek.com and do not pass through the gateway." (adopt.cost.dsh.account_direct). The community dsh-desktop is out of scope.

Shared skills folder. ~/.agents/skills and a project's .agents/skills are listed under a shared folder (agents, shown as 共用目录 / Shared folder) instead of DeepSeek Harness and Antigravity CLI. Pi, oh-my-pi, DSH, agy, Copilot, Kimi, Goose, Crush, Kilo, Cline and MiMo read it.

Codex. OPENAI_BASE_URL is no longer among Codex's diagnostic env vars: openai/codex 4b8bab6 (2026-04-03, "Remove OPENAI_BASE_URL config fallback") stopped reading it, so an exported one was misreported as overriding the takeover.

YAML writer. tw_adopt::yaml::set can now replace a whole value (a list or a map) through tw_yaml::put (the same self-checked path core uses for config.yaml), and write a nested key into a file that has no nodes yet. get returns a container in one-line form so its original can be recorded. Strings inside written blocks are plain only when they are unambiguous (letter first, [A-Za-z0-9-_./:@+], not a bool/null/number lookalike), otherwise quoted.

What was checked in the sources

Pi at 0f8740b (coding-agent 0.99.2):

oh-my-pi at 6e4ac4a (coding-agent 18.4.8):

DeepSeek Harness at tag dsh-v0.2.0-rc.2 (639ed01):

Pi logo: Lobe Icons pi.svg ("Pi Agent", pi.dev, MIT).

How it was verified

Nothing was installed or run; no third-party client, no login.

  • cargo fmt --manifest-path src-tauri/Cargo.toml --all -- --check: clean
  • cargo clippy --manifest-path src-tauri/Cargo.toml --all-targets -- -D warnings: clean
  • cargo test --manifest-path src-tauri/Cargo.toml -p tw-adopt -p tw-scan: 237 + 17 + 55 + 31 + 31 passed
  • cargo test --manifest-path src-tauri/Cargo.toml --lib: 337 passed (plus the two crates)
  • cargo test --manifest-path src-tauri/Cargo.toml --test msg_codes --test ts_bindings: passed (msg-codes.txt and lite-api.ts regenerated)
  • npx tsc --noEmit, npx tsc --noEmit -p scripts/shots: clean; npx vitest run: 583 passed

New tests: byte-identical restore of a Pi models.json with comments and another provider, and of an omp models.yml with comments and another provider; adopting omp twice is a no-op; rewriting the model list keeps the first record; files created here are removed again; omp writes into models.yaml when that is the file it reads and reports a later models.yml as hiding everything; the proxy note (undici precedence, NO_PROXY matching, Pi's httpProxy, shell-file fallback, nothing for omp); Pi key escaping checked against Pi's parsing rules; per-model API choice; stale model list flagged for Pi and omp on the Clients listing; YAML whole-value write/remove and quoting; scan sources and MCP listing for Pi and omp (incl. disabledServers), the shared skills folder; the DSH desktop app counted as installed; Codex no longer reports OPENAI_BASE_URL.

Not verified / open

  • Pi and oh-my-pi were not run against the gateway (hence fields_only). The gateway's conversions for Pi's google-generative-ai and openai-responses requests are untested end to end.
  • The DSH desktop app is only found at the default install locations; a Windows install moved to another directory is not (its InstallLocation registry value is not read). Linux has no published desktop build.
  • omp migrates a legacy models.json into models.yml only while neither YAML file exists; a takeover on a machine where that migration has not run yet would create models.yml first and the old models.json would stay unmigrated. Not handled.
  • Core's request-header hint does not recognise Pi or omp (the Traffic "app" column stays empty for them); that would be a core change.
  • Screenshots were not retaken (scripts/shots/mock updated); the feature docs on thinkwat.ch were not touched.

🤖 Generated with Claude Code

fylorn and others added 2 commits October 1, 2026 22:24
Pi (`~/.pi/agent/models.json`) and its fork oh-my-pi (`~/.omp/agent/models.yml`)
can now be pointed at the gateway in one step. Both get a provider of their
own (`thinkwatch`), never a built-in one, so `/login` credentials are not sent
to the gateway. The provider carries the gateway's model list for the
client's key; each model uses its family's API (Claude: anthropic-messages,
Gemini: google-generative-ai, GPT/o-series/Codex: openai-responses, others:
openai-completions) with the base URL that API expects. The default model is
left alone, like opencode. oh-my-pi always gets `auth: apiKey`, otherwise its
custom anthropic-messages models are shaped as Claude Code. Pi's key is
escaped so `$` and a leading `!` are taken literally. When HTTP(S)_PROXY or
Pi's httpProxy setting applies and NO_PROXY does not list the gateway host,
the plan says Pi will send gateway requests through the proxy. Both MCP files
are listed read-only and scanned along with skills, prompts/commands and
instruction files.

`~/.agents/skills` (and a project's `.agents/skills`) is attributed to a
shared folder instead of DeepSeek Harness / Antigravity CLI. The DeepSeek
Harness desktop app counts as an installed DSH, and the DSH plan notes that
models used through a DeepSeek account in the desktop app go straight to
api.deepseek.com. Codex no longer lists OPENAI_BASE_URL, which it stopped
reading in openai/codex 4b8bab6.

YAML writing can now replace a whole value (via tw_yaml::put) and write a
nested key into an empty file, which oh-my-pi's model list needs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@fylorn
fylorn merged commit 02c6188 into dev Oct 1, 2026
4 checks passed
@fylorn
fylorn deleted the adopt-pi branch October 1, 2026 14:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant