Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs-site/astro.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ export default defineConfig({
{ label: "Grok Build", translations: { ko: "Grok Build", "zh-CN": "Grok Build", ru: "Grok Build", ja: "Grok Build" }, slug: "guides/grok-build" },
{ label: "opencode", translations: { ko: "opencode", "zh-CN": "opencode", ru: "opencode", ja: "opencode" }, slug: "guides/opencode" },
{ label: "Pi", translations: { ko: "Pi", "zh-CN": "Pi", ru: "Pi", ja: "Pi" }, slug: "guides/pi" },
{ label: "Oh My Pi", translations: { ko: "Oh My Pi", "zh-CN": "Oh My Pi", ru: "Oh My Pi", ja: "Oh My Pi" }, slug: "guides/oh-my-pi" },
{ label: "Integrations", translations: { ko: "연동", "zh-CN": "集成", ru: "Интеграции", ja: "連携" }, slug: "guides/integrations" },
{ label: "Sidecars: Web Search & Vision", translations: { ko: "사이드카: 웹 검색 & 비전", "zh-CN": "边车:网络搜索与视觉", ru: "Сайдкары: веб-поиск и зрение", ja: "サイドカー: ウェブ検索 & ビジョン" }, slug: "guides/sidecars" },
{ label: "Image Bridge", translations: { ko: "이미지 브릿지", "zh-CN": "图像桥接", ru: "Image Bridge", ja: "画像ブリッジ" }, slug: "guides/image-bridge" },
Expand Down
7 changes: 4 additions & 3 deletions docs-site/src/content/docs/guides/integrations.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
---
title: Integrations
description: Connect opencodex to OpenCode, Pi, Hermes, OpenClaw, Kimi Code and Gajae Code from the dashboard — one switch per client, with a backup taken before every write.
description: Connect opencodex to OpenCode, Pi, Oh My Pi, Hermes, OpenClaw, Kimi Code and Gajae Code from the dashboard — one switch per client, with a backup taken before every write.
---

The **Integrations** tab writes opencodex's provider block into a client's own config
file, and removes it again. Six clients work this way, each with a switch:
file, and removes it again. Seven clients work this way, each with a switch:

| Client | Config file | Format | When the change takes effect | Credential |
|---|---|---|---|---|
| OpenCode | `~/.config/opencode/opencode.json` | JSON | next direct launch | `OPENCODEX_OPENCODE_API_KEY` |
| Pi | `~/.pi/agent/models.json` | JSON | new sessions | `OPENCODEX_API_KEY` |
| Oh My Pi | `~/.omp/agent/models.yml` | YAML | new sessions | `OPENCODEX_OMP_API_KEY` |
| Hermes | `~/.hermes/config.yaml` | YAML | new sessions | `OPENCODEX_HERMES_API_KEY` |
| OpenClaw | `~/.openclaw/openclaw.json` | JSON5 | immediately, on a running gateway | `OPENCODEX_OPENCLAW_API_KEY` |
| Kimi Code | `~/.kimi-code/config.toml` | TOML | on restart, or `/reload` | loopback placeholder |
Expand Down Expand Up @@ -75,7 +76,7 @@ changed value and calling it success. You will see the file named and nothing on
disk will have moved. Editing that file by hand still works; it is only our
automatic rewrite that declines.

**Pi, Kimi Code and Gajae Code only work against a loopback bind.** None of their config
**Pi, Oh My Pi, Kimi Code and Gajae Code only work against a loopback bind.** None of their config
schemas has a place for the `x-opencodex-api-key` header that a non-loopback bind
requires, so a generated config would simply be rejected — and writing one by hand does
not help, because there is nowhere in the file to put the header either. Reaching a
Expand Down
63 changes: 63 additions & 0 deletions docs-site/src/content/docs/guides/oh-my-pi.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
---
title: Oh My Pi
description: Use any routed model from Oh My Pi (omp) — ocx export prints a custom provider block for omp's ~/.omp/agent/models.yml, wired to the running proxy.
---

Oh My Pi (`omp`) reads custom providers from `~/.omp/agent/models.yml`, so
opencodex does not launch it. Instead, `ocx export` serializes the `opencodex`
provider block — base URL, model list, and the env reference omp interpolates —
and you merge it into that file.

## Quickstart

Start the proxy, then print the config:

```bash
ocx start
ocx export --client omp
```

The output leads with the YAML, then prints the destination path, the merge
warning, the env export line, and how many models carry authoritative context
limits.

```yaml
providers:
opencodex:
baseUrl: http://127.0.0.1:10100/v1
api: openai-completions
apiKey: "$OPENCODEX_OMP_API_KEY"
Comment thread
coderabbitai[bot] marked this conversation as resolved.
models:
- id: anthropic/claude-opus-5
name: "Claude Opus 5 (anthropic)"
input:
- text
contextWindow: 200000
maxTokens: 32000
```

`api: openai-completions` selects omp's OpenAI-compatible adapter, which talks
to the proxy's `/v1` surface. `$OPENCODEX_OMP_API_KEY` is only omp's local
provider key for the `opencodex` entry — it is not an opencodex OAuth or
access-token credential, and opencodex never sees its value.
Comment on lines +39 to +42

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail
rg -n -C 3 'openai-chat|openai-completions|Chat Completions' \
  src/clients/config-export.ts \
  docs-site/src/content/docs/reference/adapters.md \
  docs-site/src/content/docs/guides/providers.md \
  docs-site/src/content/docs/guides/oh-my-pi.md

Repository: lidge-jun/opencodex

Length of output: 15601


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- adapter reference relevant section ---'
sed -n '1,80p' docs-site/src/content/docs/reference/adapters.md | cat -n

printf '%s\n' '--- config-export OMP dialect/use sites ---'
sed -n '130,190p' src/clients/config-export.ts | cat -n
sed -n '900,995p' src/clients/config-export.ts | cat -n

printf '%s\n' '--- semantic check for wire format mentions in OMP provider docs ---'
python3 - <<'PY'
from pathlib import Path
paths = [
    Path("src/clients/config-export.ts"),
    Path("docs-site/src/content/docs/reference/adapters.md"),
    Path("docs-site/src/content/docs/guides/providers.md"),
    Path("docs-site/src/content/docs/guides/oh-my-pi.md"),
]
for p in paths:
    text = p.read_text()
    print(p, {
        "api:openai-completions": 'api: "openai-completions"' in text or "api: openai-completions" in text,
        "openai-chat": text.count("`openai-chat`"),
        "Chat Completions": text.count("Chat Completions"),
        "COMPLETIONS": text.count("/chat/completions"),
    })
PY

Repository: lidge-jun/opencodex

Length of output: 13597


Use the documented OpenAI Chat Completions terminology.

In docs-site/src/content/docs/guides/oh-my-pi.md:39-40, replace the vague “OpenAI-compatible adapter” description with the documented wire format. OMP’s api: openai-completions selects the proxy wire dialect, while openai-chat is the Chat Completions adapter used by the generated OpenCodex provider; identify the generated provider as targeting OpenAI Chat Completions through the openai-chat-compatible wire format.

Proposed wording
-`api: openai-completions` selects omp's OpenAI-compatible adapter, which talks
-to the proxy's `/v1` surface.
+`api: openai-completions` configures omp to target OpenAI Chat Completions
+through the `openai-chat`-compatible wire format at the proxy's `/v1` surface.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs-site/src/content/docs/guides/oh-my-pi.md` around lines 39 - 42, Update
the documentation around api: openai-completions to use the documented OpenAI
Chat Completions terminology: describe it as selecting OMP’s proxy wire dialect,
and identify the generated OpenCodex provider as using the openai-chat Chat
Completions-compatible wire format. Preserve the existing clarification about
OPENCODEX_OMP_API_KEY.

Source: Path instructions


Merge the `providers.opencodex` block into `~/.omp/agent/models.yml` (do not
replace the rest of the file), then start a new omp session. The models appear
under the `opencodex/` prefix.

## Credential

The config references `$OPENCODEX_OMP_API_KEY` and never holds the real key.
Export it before launching omp:

```bash
export OPENCODEX_OMP_API_KEY=<your key>
```

Like Pi, omp's provider block has no place for the `x-opencodex-api-key` header
a non-loopback bind requires, so this integration works against a loopback
bind. Reach a remote opencodex through an SSH tunnel or a local forwarder that
adds the header instead.

The dashboard's **Integrations → Oh My Pi** tab does the same thing with a
switch, and takes a backup before every write so the change can be undone.
Binary file added gui-screenshot.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions gui/src/app-routing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ export const INTEGRATION_TAB_HASHES = [
"integrations/grok",
"integrations/opencode",
"integrations/pi",
"integrations/omp",
"integrations/hermes",
"integrations/openclaw",
"integrations/kimi",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,13 @@
* with EXPORT_CLIENT_IDS by hand; adding a client server-side renders no row
* until this tuple changes.
*/
export const CLIENTS = ["opencode", "pi", "hermes", "openclaw", "kimi", "gajae"] as const;
export const CLIENTS = ["opencode", "pi", "omp", "hermes", "openclaw", "kimi", "gajae"] as const;
export type ExportClientId = (typeof CLIENTS)[number];

export const CLIENT_LABEL_KEYS = {
opencode: "api.clientConfig.clientOpencode",
pi: "api.clientConfig.clientPi",
omp: "api.clientConfig.clientOmp",
Comment thread
coderabbitai[bot] marked this conversation as resolved.
hermes: "api.clientConfig.clientHermes",
openclaw: "api.clientConfig.clientOpenclaw",
kimi: "api.clientConfig.clientKimi",
Expand Down
15 changes: 9 additions & 6 deletions gui/src/i18n/de.ts
Original file line number Diff line number Diff line change
Expand Up @@ -751,6 +751,7 @@ export const de: Record<TKey, string> = {
"integrations.tab.grok": "Grok Build",
"integrations.tab.opencode": "OpenCode",
"integrations.tab.pi": "Pi",
"integrations.tab.omp": "Oh My Pi",
"integrations.tab.hermes": "Hermes",
"integrations.tab.openclaw": "OpenClaw",
"integrations.tab.kimi": "Kimi Code",
Expand Down Expand Up @@ -860,6 +861,7 @@ export const de: Record<TKey, string> = {
"integrations.kind.restore": "Wiederhergestellt",
"integrations.semantics.opencode": "Gilt nur für direkte Starts von der Festplatte; die Umgebungsinjektion von ocx opencode hat Vorrang.",
"integrations.semantics.pi": "Gilt für neue Sitzungen.",
"integrations.semantics.omp": "Gilt für neue Sitzungen.",
"integrations.semantics.hermes": "Gilt für neue Sitzungen.",
"integrations.semantics.openclaw": "Wird sofort auf ein laufendes Gateway angewendet.",
"integrations.semantics.kimi": "Zum Anwenden neu starten oder /reload ausführen (v2 überwacht die Datei).",
Expand Down Expand Up @@ -1121,30 +1123,31 @@ export const de: Record<TKey, string> = {
"api.clientConfig.rowsLabel": "Client verbinden",
"api.clientConfig.details": "Details",
"api.clientConfig.detailsAria": "Details zur {client}-Konfiguration",
"api.clientConfig.copyAria": "{client}-Konfigurations-JSON kopieren",
"api.clientConfig.copyAria": "{client}-Konfiguration kopieren",
"api.clientConfig.downloadAria": "{client}-Konfiguration herunterladen",
"api.clientConfig.rowMeta": "{destination} · {count} Modell(e)",
"api.clientConfig.rowError": "Die {client}-Konfiguration konnte nicht erstellt werden.",
"api.clientConfig.copiedAnnounceClient": "{client}-Konfigurations-JSON in die Zwischenablage kopiert.",
"api.clientConfig.copiedAnnounceClient": "{client}-Konfiguration in die Zwischenablage kopiert.",
"api.clientConfig.clientOpencode": "OpenCode",
"api.clientConfig.clientPi": "Pi",
"api.clientConfig.clientOmp": "Oh My Pi",
"api.clientConfig.clientHermes": "Hermes",
"api.clientConfig.clientOpenclaw": "OpenClaw",
"api.clientConfig.clientKimi": "Kimi Code",
"api.clientConfig.clientGajae": "Gajae Code",
"api.clientConfig.copy": "JSON kopieren",
"api.clientConfig.copy": "Konfiguration kopieren",
"api.clientConfig.download": "Herunterladen",
"api.clientConfig.loading": "Client-Konfiguration wird erstellt…",
"api.clientConfig.jsonLabel": "{client}-Konfigurations-JSON",
"api.clientConfig.jsonLabel": "{client}-Konfiguration",
"api.clientConfig.destination": "Zieldatei",
"api.clientConfig.envHint": "Schlüssel vor dem Start setzen",
"api.clientConfig.mergeWarning": "Führe dies in die Zieldatei ein. Ein Ersetzen würde deine anderen Provider und MCP-Einstellungen entfernen.",
"api.clientConfig.modelCount": "{count} Modell(e) exportiert",
"api.clientConfig.missingLimits": "{count} von {total} Modell(en) haben kein Kontextlimit; der Client verwendet dafür seine eigenen Vorgaben.",
"api.clientConfig.noKeyYet": "Für {env} existiert noch kein Schlüssel. Erzeuge oben einen Schlüssel, bevor du diese Konfiguration außerhalb von Loopback nutzt.",
"api.clientConfig.loadFailed": "Die Modellliste konnte nicht gelesen werden, daher wurde keine Client-Konfiguration erzeugt.",
"api.clientConfig.copiedAnnounce": "Client-Konfigurations-JSON in die Zwischenablage kopiert.",
"api.clientConfig.copyFailed": "Client-Konfigurations-JSON konnte nicht kopiert werden.",
"api.clientConfig.copiedAnnounce": "Client-Konfiguration in die Zwischenablage kopiert.",
"api.clientConfig.copyFailed": "Die Client-Konfiguration konnte nicht kopiert werden.",
"api.clientConfig.downloadedAnnounce": "{filename} heruntergeladen. Es hat sich noch nichts geändert — führe die Datei selbst in {destination} ein.",
"api.clientConfig.whereDisclosure": "Wohin diese Datei gehört",
"api.clientConfig.whereBody": "Der Pfad oben ist der globale Speicherort. Eine projektlokale Konfigurationsdatei im Arbeitsverzeichnis hat Vorrang, und der Schlüssel wird aus der in der Konfiguration genannten Umgebungsvariable gelesen — nie aus dieser Datei.",
Expand Down
15 changes: 9 additions & 6 deletions gui/src/i18n/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1206,6 +1206,7 @@ export const en = {
"integrations.tab.grok": "Grok Build",
"integrations.tab.opencode": "OpenCode",
"integrations.tab.pi": "Pi",
"integrations.tab.omp": "Oh My Pi",
"integrations.tab.hermes": "Hermes",
"integrations.tab.openclaw": "OpenClaw",
"integrations.tab.kimi": "Kimi Code",
Expand Down Expand Up @@ -1315,6 +1316,7 @@ export const en = {
"integrations.kind.restore": "Restored",
"integrations.semantics.opencode": "Direct disk launches only; ocx opencode environment injection takes precedence.",
"integrations.semantics.pi": "Applies to new sessions.",
"integrations.semantics.omp": "Applies to new sessions.",
"integrations.semantics.hermes": "Applies to new sessions.",
"integrations.semantics.openclaw": "Applies immediately to a running gateway.",
"integrations.semantics.kimi": "Restart or run /reload to apply it (v2 watches the file).",
Expand Down Expand Up @@ -1584,30 +1586,31 @@ export const en = {
"api.clientConfig.rowsLabel": "Connect a client",
"api.clientConfig.details": "Details",
"api.clientConfig.detailsAria": "{client} config details",
"api.clientConfig.copyAria": "Copy {client} config JSON",
"api.clientConfig.copyAria": "Copy {client} config",
"api.clientConfig.downloadAria": "Download {client} config",
"api.clientConfig.rowMeta": "{destination} · {count} model(s)",
"api.clientConfig.rowError": "Could not build the {client} config.",
"api.clientConfig.copiedAnnounceClient": "{client} config JSON copied to the clipboard.",
"api.clientConfig.copiedAnnounceClient": "{client} config copied to the clipboard.",
"api.clientConfig.clientOpencode": "OpenCode",
"api.clientConfig.clientPi": "Pi",
"api.clientConfig.clientOmp": "Oh My Pi",
"api.clientConfig.clientHermes": "Hermes",
"api.clientConfig.clientOpenclaw": "OpenClaw",
"api.clientConfig.clientKimi": "Kimi Code",
"api.clientConfig.clientGajae": "Gajae Code",
"api.clientConfig.copy": "Copy JSON",
"api.clientConfig.copy": "Copy config",
"api.clientConfig.download": "Download",
"api.clientConfig.loading": "Building client config…",
"api.clientConfig.jsonLabel": "{client} config JSON",
"api.clientConfig.jsonLabel": "{client} config",
"api.clientConfig.destination": "Destination file",
"api.clientConfig.envHint": "Set the key before launching",
"api.clientConfig.mergeWarning": "Merge this into the destination file. Replacing it would drop your other providers and MCP settings.",
"api.clientConfig.modelCount": "{count} model(s) exported",
"api.clientConfig.missingLimits": "{count} of {total} model(s) ship without a context limit; the client applies its own defaults.",
"api.clientConfig.noKeyYet": "{env} has no key behind it yet. Generate a key above before using this config off loopback.",
"api.clientConfig.loadFailed": "Could not read the model list, so no client config was produced.",
"api.clientConfig.copiedAnnounce": "Client config JSON copied to the clipboard.",
"api.clientConfig.copyFailed": "Could not copy the client config JSON.",
"api.clientConfig.copiedAnnounce": "Client config copied to the clipboard.",
"api.clientConfig.copyFailed": "Could not copy the client config.",
"api.clientConfig.downloadedAnnounce": "Downloaded {filename}. Nothing changed yet — merge it into {destination} yourself.",
"api.clientConfig.whereDisclosure": "Where this file goes",
"api.clientConfig.whereBody": "The destination above is the global path. A project-local config file in the working directory takes precedence over it, and the client reads the key from the environment variable named in the config — never from this file.",
Expand Down
15 changes: 9 additions & 6 deletions gui/src/i18n/ja.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1154,6 +1154,7 @@ export const ja: Record<TKey, string> = {
"integrations.tab.grok": "Grok Build",
"integrations.tab.opencode": "OpenCode",
"integrations.tab.pi": "Pi",
"integrations.tab.omp": "Oh My Pi",
"integrations.tab.hermes": "Hermes",
"integrations.tab.openclaw": "OpenClaw",
"integrations.tab.kimi": "Kimi Code",
Expand Down Expand Up @@ -1263,6 +1264,7 @@ export const ja: Record<TKey, string> = {
"integrations.kind.restore": "復元",
"integrations.semantics.opencode": "ディスクから直接起動した場合にのみ適用されます。ocx opencode の環境注入が優先されます。",
"integrations.semantics.pi": "新しいセッションから適用されます。",
"integrations.semantics.omp": "新しいセッションから適用されます。",
"integrations.semantics.hermes": "新しいセッションから適用されます。",
"integrations.semantics.openclaw": "実行中のゲートウェイにすぐ適用されます。",
"integrations.semantics.kimi": "再起動するか /reload を実行すると適用されます(v2 はファイルを監視します)。",
Expand Down Expand Up @@ -1529,30 +1531,31 @@ export const ja: Record<TKey, string> = {
"api.clientConfig.rowsLabel": "クライアントを接続",
"api.clientConfig.details": "詳細",
"api.clientConfig.detailsAria": "{client} 設定の詳細",
"api.clientConfig.copyAria": "{client} 設定 JSON をコピー",
"api.clientConfig.copyAria": "{client} 設定をコピー",
"api.clientConfig.downloadAria": "{client} 設定をダウンロード",
"api.clientConfig.rowMeta": "{destination} · モデル {count} 件",
"api.clientConfig.rowError": "{client} の設定を生成できませんでした。",
"api.clientConfig.copiedAnnounceClient": "{client} の設定 JSON をクリップボードにコピーしました。",
"api.clientConfig.copiedAnnounceClient": "{client} の設定をクリップボードにコピーしました。",
"api.clientConfig.clientOpencode": "OpenCode",
"api.clientConfig.clientPi": "Pi",
"api.clientConfig.clientOmp": "Oh My Pi",
"api.clientConfig.clientHermes": "Hermes",
"api.clientConfig.clientOpenclaw": "OpenClaw",
"api.clientConfig.clientKimi": "Kimi Code",
"api.clientConfig.clientGajae": "Gajae Code",
"api.clientConfig.copy": "JSON をコピー",
"api.clientConfig.copy": "設定をコピー",
"api.clientConfig.download": "ダウンロード",
"api.clientConfig.loading": "クライアント設定を生成中…",
"api.clientConfig.jsonLabel": "{client} 設定 JSON",
"api.clientConfig.jsonLabel": "{client} 設定",
"api.clientConfig.destination": "配置先ファイル",
"api.clientConfig.envHint": "起動前にキーを設定",
"api.clientConfig.mergeWarning": "配置先ファイルにマージしてください。置き換えると既存のプロバイダーや MCP 設定が失われます。",
"api.clientConfig.modelCount": "{count} 件のモデルを書き出しました",
"api.clientConfig.missingLimits": "{total} 件中 {count} 件のモデルにコンテキスト上限がないため、クライアント側の既定値が使われます。",
"api.clientConfig.noKeyYet": "{env} に対応するキーがまだありません。ループバック外で使う前に上でキーを発行してください。",
"api.clientConfig.loadFailed": "モデル一覧を読み取れなかったため、クライアント設定を生成できませんでした。",
"api.clientConfig.copiedAnnounce": "クライアント設定 JSON をクリップボードにコピーしました。",
"api.clientConfig.copyFailed": "クライアント設定 JSON をコピーできませんでした。",
"api.clientConfig.copiedAnnounce": "クライアント設定をクリップボードにコピーしました。",
"api.clientConfig.copyFailed": "クライアント設定をコピーできませんでした。",
"api.clientConfig.downloadedAnnounce": "{filename} をダウンロードしました。まだ何も変わっていません。{destination} に自分でマージしてください。",
"api.clientConfig.whereDisclosure": "このファイルの置き場所",
"api.clientConfig.whereBody": "上のパスはグローバル設定の場所です。作業ディレクトリのプロジェクト設定ファイルが優先され、キーは設定に書かれた環境変数から読み込まれ、このファイルには保存されません。",
Expand Down
Loading
Loading