Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
1ba5c60
docs: design native JEV auto routing
Sep 21, 2026
c4cd3cc
docs: plan native JEV auto routing
Sep 21, 2026
511f59a
feat: add JEV combo decision contract
Sep 21, 2026
cd8d16e
feat: add TypeSafe JEV provider setup
Sep 21, 2026
ec8b6a2
feat: route jev-auto through combo runtime
Sep 21, 2026
9af7a9d
feat(gui): add JEV Auto setup flow
Sep 21, 2026
ab896b9
fix: harden JEV auto routing
Sep 21, 2026
71aab27
fix: align JEV provider registry contracts
Sep 21, 2026
0a148c3
fix: address JEV review findings
Sep 21, 2026
0307590
fix: avoid redundant JEV task scan
Sep 21, 2026
2963460
chore: merge upstream dev
Sep 21, 2026
415a0ca
fix: strip protected envelopes from JEV context
Sep 21, 2026
a87af4e
feat: add per-target JEV effort controls
Sep 22, 2026
7ce2af7
Merge remote-tracking branch 'upstream/dev' into feat/jev-auto-routing
Sep 22, 2026
9ee4f2c
fix: address final JEV review feedback
Sep 22, 2026
90a7666
feat: add JEV routing statistics
Sep 22, 2026
d05dfaf
fix: harden JEV statistics aggregation
Sep 22, 2026
fcbe926
chore: merge upstream dev into JEV routing
kingkej Sep 22, 2026
c7a723d
chore: merge upstream dev into JEV routing
kingkej Sep 22, 2026
5154ccf
chore: merge upstream dev into JEV routing
Sep 22, 2026
5e8ff89
chore: merge upstream dev into JEV routing
Sep 23, 2026
80cb53d
Merge remote-tracking branch 'upstream/dev' into feat/jev-auto-routing
Sep 23, 2026
02fb559
Merge remote-tracking branch 'origin/dev' into feat/jev-auto-routing
lidge-jun Sep 25, 2026
aabd6a7
docs(jev): record TypeSafe retention terms and sync preset counts
lidge-jun Sep 25, 2026
d540cff
test(gui): expect dev's imageInput default in the JEV PUT body
lidge-jun Sep 25, 2026
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
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,20 @@ Omit the `provider/` prefix to use the default provider or auto-match by model n
Provider model ids containing `/` are exposed with inner slashes aliased to `-`; the raw
full-slash form keeps working too. Details: [model routing docs](https://opencodex.me/guides/model-routing/).

### JEV Auto routing (optional)

TypeSafe JEV can choose the first model and reasoning effort for an opt-in Combo while the normal
model picker and every direct route stay unchanged. Add the credential with `ocx login jev`, from
**Providers → TypeSafe JEV → Add API key**, or through `TYPESAFE_API_KEY`/`JEV_API_KEY`. Then open
**Models → Combos → Create JEV Auto**, choose the allowed target models, and check the exact efforts
JEV may select for each target. Leaving a target's effort setting untouched allows all efforts that
model currently advertises.

JEV is consulted only for `jev-auto` and only once per logical model call. Missing credentials,
network failures, or invalid decisions fail open to the first currently eligible target; caller
cancellation still cancels the request. Automated tests use a mocked TypeSafe endpoint and do not
validate a live JEV account.

## Providers & adapters

<!-- sponsors:main-first-mention -->
Expand Down
256 changes: 256 additions & 0 deletions devlog/_fin/260921_jev_auto_routing/010_plan.md

Large diffs are not rendered by default.

383 changes: 383 additions & 0 deletions devlog/_fin/260921_jev_auto_routing/020_design.md

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ ocx init

`ocx init` vous accompagne dans les étapes suivantes :

1. **Choix d’un fournisseur** — sélectionnez l’un des 98 préréglages intégrés au registre, ou `custom` pour saisir une
1. **Choix d’un fournisseur** — sélectionnez l’un des 99 préréglages intégrés au registre, ou `custom` pour saisir une
URL de base et un adaptateur.
2. **Clé API** — collez une clé ou référencez une variable d’environnement telle que `${ANTHROPIC_API_KEY}`.
3. **Modèle par défaut** — pour les fournisseurs clés, locaux et personnalisés, acceptez le préréglage ou saisissez un identifiant de modèle.
Expand Down
2 changes: 1 addition & 1 deletion docs-site/src/content/docs/fr/guides/combos.md
Original file line number Diff line number Diff line change
Expand Up @@ -333,7 +333,7 @@ Les combos sont stockés dans l'objet `combos` de niveau supérieur, saisi par l
| --- | --- | --- | --- |
| `targets` | Oui | — | Tableau ordonné non vide de `{ provider, model, weight? }` cibles configurées. Les paires provider/model en double sont rejetées. |
| `targets[].weight` | Non | `1` | Entier de 1 à 10 000. Utilisé par `round-robin` et `random` ; ignoré par `failover`, `least-used` et `reset-window`. |
| `strategy` | Non | `"failover"` | Valeurs autorisées : `"failover"`, `"round-robin"`, `"random"`, `"least-used"` et `"reset-window"`. |
| `strategy` | Non | `"failover"` | Valeurs autorisées : `"failover"`, `"round-robin"`, `"random"`, `"least-used"`, `"reset-window"` et `"jev"`. JEV décide uniquement de la première cible éligible et de l’effort ; le fallback Combo ordinaire gère les tentatives suivantes. |
| `stickyLimit` | Non | `1` | Nombre entier de 1 à 100 requêtes réussies par sélection à tour de rôle. S’applique uniquement à `round-robin`. |
| `defaultEffort` | Non | `null` | `low`, `medium`, `high`, `xhigh`, `max` ou `ultra` ; appliqué uniquement lorsque l'appelant omet ses efforts et que la cible annonce son soutien. |
| `reasoningEffortMode` | Non | `"strict"` | `strict` ou `adaptive` ; choisit l’intersection des capacités et la normalisation par cible. |
Expand Down
2 changes: 1 addition & 1 deletion docs-site/src/content/docs/fr/guides/providers.md
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,7 @@ existante n'est pas concernée.

## 3. Catalogue des clés API

opencodex fournit 98 préréglages intégrés : 81 à clé, 13 OAuth, trois locaux et un préréglage par défaut de
opencodex fournit 99 préréglages intégrés : 82 à clé, 13 OAuth, trois locaux et un préréglage par défaut de
transfert ChatGPT. Dans le tableau de bord, le sélecteur **Ajouter un fournisseur** ouvre le tableau de bord du
fournisseur à clé, valide la clé et l'enregistre ; la validation dépend du fournisseur. Parmi les entrées notables :

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ Chaque clé de combinaison est un identifiant conforme à `[A-Za-z0-9][A-Za-z0-9
| Clé | Type | Valeur par défaut | Signification |
| --- | --- | --- | --- |
| `targets` | `{ provider: string; model: string; weight?: number }[]` | requis | Routes concrètes ordonnées. `weight` est compris entre 1 et 10000 et vaut `1` par défaut. |
| `strategy?` | `"failover" \| "round-robin" \| "random" \| "least-used" \| "reset-window"` | `"failover"` | Stratégie de sélection. L’ordre des cibles définit la priorité de `failover` ; les poids déterminent les sélections de `round-robin` et de `random` ; `least-used` suit les réussites enregistrées ; `reset-window` suit la réinitialisation de quota la plus proche. |
| `strategy?` | `"failover" \| "round-robin" \| "random" \| "least-used" \| "reset-window" \| "jev"` | `"failover"` | Stratégie de sélection. L’ordre des cibles définit la priorité de `failover` ; les poids déterminent les sélections de `round-robin` et de `random` ; `least-used` suit les réussites enregistrées ; `reset-window` suit la réinitialisation de quota la plus proche ; `jev` effectue une décision limitée pour la première cible éligible et l’effort, puis utilise le fallback ordonné habituel. |
| `stickyLimit?` | `number` | `1` | Nombre de requêtes réussies conservées dans un même lot de rotation. Plage de 1 à 100. |
| `defaultEffort?` | `"low" \| "medium" \| "high" \| "xhigh" \| "max" \| "ultra" \| null` | non défini | `defaultEffort` complète un `reasoning.effort` absent si le combo possède une valeur par défaut non nulle et si la liste des niveaux acceptés par la cible est connue et non vide. La valeur configurée est conservée si elle est acceptée ; sinon, le niveau accepté le plus élevé ne la dépassant pas est choisi, ou le niveau le plus bas si aucun n’est inférieur. Une liste inconnue ou vide n’ajoute aucune valeur par défaut. |
| `reasoningEffortMode?` | `"strict" \| "adaptive"` | `"strict"` | `"strict"` calcule l’intersection des listes connues, y compris les listes vides ; `"adaptive"` exclut les listes vides. Les listes inconnues ne limitent l’intersection dans aucun des deux modes. À l’envoi, les listes explicitement vides suppriment les paramètres effort/thinking dans les deux modes ; les listes inconnues les suppriment seulement en adaptive. `reasoning.summary` est conservé. La résolution des listes connues non vides ainsi que le choix et l’ordre des cibles restent inchangés. |
Expand Down
2 changes: 1 addition & 1 deletion docs-site/src/content/docs/getting-started/quickstart.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ ocx init

`ocx init` walks you through:

1. **Pick a provider** — choose one of the 98 built-in registry presets or `custom` to type a base
1. **Pick a provider** — choose one of the 99 built-in registry presets or `custom` to type a base
URL and adapter.
2. **API key** — paste a key, or reference an environment variable like `${ANTHROPIC_API_KEY}`.
3. **Default model** — for key, local, and custom providers, accept the preset or enter a model id.
Expand Down
107 changes: 104 additions & 3 deletions docs-site/src/content/docs/guides/combos.md
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,105 @@ order. Weights and `stickyLimit` do not affect this strategy.

This ranking and provider exclusion before dispatch require fresh model-inference limits that apply to the current single API key as a whole. OAuth/current-account summaries, caller-forward routes, multiple keys, and snapshots with changed credentials or destinations are display-only for this early decision. The same applies when `Authorization`, `x-api-key`, or `x-goog-api-key` headers override credentials; search-only and MCP-only windows are excluded. If no eligible target has an applicable reset, configuration order wins. Account selection and retries still enforce their normal limits.

### JEV: decision-guided first pick

`jev` asks [TypeSafe JEV](https://console.typesafe.ai) to choose the first eligible target and a
compatible reasoning effort for the current request. It is opt-in: adding the TypeSafe credential
does not change existing models, aliases, defaults, or Combo behavior. A JEV-backed model appears
only after you create a Combo whose strategy is `jev`.

The quickest setup is:

1. Open **Providers**, add **TypeSafe JEV**, enter the TypeSafe API key, and test the connection.
2. From that provider's Overview, choose **Create JEV Auto**. You can also use the same action under
**Models → Combos**.
3. Review the prefilled Astra → Sol → Luna targets. Add, remove, reorder, or replace them before
creating the Combo. The first currently eligible row is marked as the fail-open target, and each
known reasoning ladder is shown beside its row.

The template creates id and alias `jev-auto`, uses adaptive reasoning capability, and remains an
ordinary editable Combo. It does not become the default model. Its targets are the complete
allowlist: JEV can never select a provider/model pair outside that list, and the original target
models remain available in their normal picker groups.

For headless setup, store the key explicitly or reference the TypeSafe environment variable:

```bash
ocx provider add jev --api-key "${TYPESAFE_API_KEY}"
```

When the provider has no saved key, the decision client also accepts `TYPESAFE_API_KEY` directly and
the standard provider-derived alias `JEV_API_KEY` printed by `ocx provider add`.

```json
{
"providers": {
"jev": {
"adapter": "jev-decision",
"baseUrl": "https://api.typesafe.ai/v1/systemone",
"authMode": "key",
"apiKey": "${TYPESAFE_API_KEY}",
"liveModels": false
}
},
"combos": {
"jev-auto": {
"alias": "jev-auto",
"strategy": "jev",
"reasoningEffortMode": "adaptive",
"targets": [
{ "provider": "openai", "model": "gpt-6-astra" },
{ "provider": "openai", "model": "gpt-5.6-sol" },
{ "provider": "openai", "model": "gpt-5.6-luna" }
]
}
}
}
```

OpenCodex sends one bounded decision request to the fixed
`https://api.typesafe.ai/v1/systemone` endpoint with model `jev-latest`. Only currently eligible
configured targets are offered. JEV chooses the target and effort together; the effort is still
constrained by that target's advertised ladder. JEV is not asked again if the selected target has a
retryable failure—the existing Combo cooldown and fallback loop continues through the remaining
configured targets.

Each logical model call is decided on its own; there is no per-conversation pin. Consecutive turns of
one session can therefore land on different targets, and every switch starts a cold provider prompt
cache, so a mix of very different targets can cost more input tokens than it saves. Keep the
allowlist to targets you are content to alternate between. Targets marked `lastResort` are withheld
from JEV under `cooldownWaitPolicy: "before-last-resort"` while any normal target is offered, and
offered only when nothing else is reachable.

The decision boundary fails open when the key is missing, no safe task/tool/image decision state is
available, the four-second decision deadline expires, the service redirects or returns an error, or
the response is malformed or selects an unlisted choice. In those cases OpenCodex uses the first
currently eligible target, preferring `medium` when that target supports it. Caller cancellation is
different: it cancels the decision and the model request instead of dispatching the fail-open target.

The decision state is deliberately bounded: up to 500 characters of the current user task, a
240-character previous-assistant tail, a 520-character latest-tool-output tail, the tool name, and
boolean image/tool signals may be sent to TypeSafe. It excludes the JEV credential, request headers,
raw image bytes, tool arguments, encrypted reasoning, and full conversation history. Do not select
`jev-auto` for content you do not want TypeSafe to process. Recognized OpenCodex machine-context
envelopes are removed from all three text samples, but ordinary assistant and tool-output text is
not a secret scanner and may still contain sensitive content. TypeSafe states that Jev is not
trained on customer requests, but its terms set no fixed retention period for submitted state and
offer zero data retention only on enterprise plans
([models](https://docs.typesafe.ai/models), [legal](https://docs.typesafe.ai/legal)). TypeSafe
also documents English as Jev's most accurate language, so check decisions on non-English work
before relying on them. Logs contain only the selected
target/effort, a coarse decision gate, latency, optional confidence/probability, and numeric usage.
Automated tests use mocked TypeSafe responses plus a no-key fail-open smoke; a live TypeSafe decision
requires an operator-supplied key and is not run implicitly.

After the Combo has served requests, open **Models → Combos → jev-auto → Stats** to inspect JEV's
picks without replacing the normal model picker or Usage page. The tab separates TypeSafe decision
tokens from tokens reported by physical model sends, and shows decision gates, fail-open picks,
reasoning efforts, retries/fallbacks, cache tokens, latency, confidence, and per-model totals for 7
days, 30 days, or all available history. Statistics come from the local append-only usage ledger;
they contain the bounded decision metadata described above, not prompts or credentials.

## What happens when a target fails

Combo failures are divided into **hop** failures and **terminal** failures.
Expand Down Expand Up @@ -401,7 +500,9 @@ task workflow.
### Dashboard

Open the local dashboard and choose **Models → Combos**. The workspace creates, edits, renames, and removes
combos, and its target picker excludes disabled models and nested combos.
combos, and its target picker excludes disabled models, nested combos, and the credential-only JEV
provider. **Create JEV Auto** opens the same Combo editor with an editable decision target template;
an existing `jev-auto` id or alias is reported instead of creating a duplicate.

Each target also shows a live quota badge: **Available**, **Out of quota**, or **Quota unknown**. The editor blocks Save and Create for quota only when every usable target has a current server-confirmed exhausted inference limit for its configured credential. Display-only account, model, search and MCP quota, or missing or expired routing evidence, does not cause this block. The block expires at the applicable reset or freshness boundary and is rechecked when the page becomes active or visible; Refresh reloads both Combo data and quota. The dashboard
editor does not yet expose `cooldownMs` or `waitForCooldownMs`; use the configuration file or management
Expand Down Expand Up @@ -466,9 +567,9 @@ Combos are stored in the top-level `combos` object, keyed by combo id:
| Field | Required | Default | Rules |
| --- | --- | --- | --- |
| `targets` | Yes | — | Non-empty ordered array of configured `{ provider, model, weight?, lastResort? }` targets. Duplicate provider/model pairs are rejected. |
| `targets[].weight` | No | `1` | Integer from 1 to 10,000. Used by round-robin and random; ignored by failover, least-used, and reset-window. |
| `targets[].weight` | No | `1` | Integer from 1 to 10,000. Used by round-robin and random; ignored by failover, least-used, reset-window, and JEV. |
| `targets[].lastResort` | No | `false` | Marks an emergency-only target. Inert unless `cooldownWaitPolicy` is set. Never makes a target permanently ineligible: when no normal target can be reached it is dispatched as usual. |
| `strategy` | No | `"failover"` | `"failover"`, `"round-robin"`, `"random"`, `"least-used"`, or `"reset-window"`. |
| `strategy` | No | `"failover"` | `"failover"`, `"round-robin"`, `"random"`, `"least-used"`, `"reset-window"`, or `"jev"`. JEV decides only the initial eligible target and effort; ordinary Combo fallback owns later attempts. |
| `stickyLimit` | No | `1` | Integer from 1 to 100 successful requests per round-robin selection. Applies only to round-robin. |
| `cooldownMs` | No | unset → upstream fallback (5 s for request-rate 429 codes `1302`/`1305`, otherwise 60 s) | Integer from 1 to 600000. When set, applies as the per-target cooldown whenever no usable upstream `Retry-After` or Codex reset signal exists, including request-rate 429s; when unset, uses the upstream fallback. |
| `waitForCooldownMs` | No | `0` | Integer from 0 to 600000. Maximum time to wait for the earliest eligible cooling target before returning `combo_unavailable`; abort cancels the wait. |
Expand Down
2 changes: 1 addition & 1 deletion docs-site/src/content/docs/guides/providers.md
Original file line number Diff line number Diff line change
Expand Up @@ -443,7 +443,7 @@ selectors, then retry. Signing in from a machine with no existing `kiro-cli` ses

## 3. API-key catalog

opencodex ships 98 built-in presets: 81 key-based, 13 OAuth, three local, and one default
opencodex ships 99 built-in presets: 82 key-based, 13 OAuth, three local, and one default
ChatGPT-forward preset. The dashboard's **Add provider** picker opens a key provider's dashboard,
validates the key, and stores it; validation is provider-specific. Notable entries:

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ ocx init

`ocx init` では次の手順を説明します。

1. **プロバイダーを選択してください** — 98 個の組み込みレジストリプリセットのいずれか、または `custom` を選択してベース URL とアダプターを入力します。
1. **プロバイダーを選択してください** — 99 個の組み込みレジストリプリセットのいずれか、または `custom` を選択してベース URL とアダプターを入力します。
2. **API キー** — キーを貼り付けるか、`${ANTHROPIC_API_KEY}` のような環境変数を参照します。
3. **デフォルト モデル** — キー、ローカル、カスタム プロバイダーの場合は、プリセットを受け入れるか、モデル ID を入力します。
4. **プロキシ ポート** — デフォルトは `10100` です。
Expand Down
2 changes: 1 addition & 1 deletion docs-site/src/content/docs/ja/guides/combos.md
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,7 @@ ocx combo remove <id> --yes
| `targets` |はい | — |構成された `{ provider, model, weight? }` ターゲットの空でない順序付けされた配列。重複するプロバイダーとモデルのペアは拒否されます。 |
| `targets[].weight` |いいえ | `1` | 1 ~ 10,000 の整数。`round-robin` と `random` で使用され、`failover`、`least-used`、`reset-window` では無視されます。 |
| `targets[].lastResort` | いいえ | `false` | 緊急時専用のターゲットを示します。`cooldownWaitPolicy` を設定しない限り無効です。ターゲットを恒久的に除外することはありません。通常のターゲットに到達できない場合は通常どおりディスパッチされます。 |
| `strategy` |いいえ | `"failover"` | `"failover"`、`"round-robin"`、`"random"`、`"least-used"`、`"reset-window"`。 |
| `strategy` |いいえ | `"failover"` | `"failover"`、`"round-robin"`、`"random"`、`"least-used"`、`"reset-window"`、`"jev"`。JEV が決定するのは最初の適格なターゲットと effort だけで、それ以降の試行は通常の Combo フォールバックが処理します。 |
| `stickyLimit` |いいえ | `1` | `round-robin` の 1 回の選択あたり、成功したリクエスト数を指定する 1 ~ 100 の整数。`round-robin` にのみ適用されます。 |
| `cooldownMs` |いいえ | 未設定 → アップストリーム フォールバック(リクエストレート 429 コード `1302`/`1305` では 5 秒、それ以外では 60 秒) | 1 ~ 600000 の整数。設定時は、使用可能なアップストリーム `Retry-After` または Codex リセットシグナルがない場合に、リクエストレート 429 を含むターゲットごとのクールダウンとして適用されます。未設定時はアップストリーム フォールバックを使用します。 |
| `waitForCooldownMs` |いいえ | `0` | 0 ~ 600000 の整数。最も早く利用可能になる冷却中のターゲットを待ってから `combo_unavailable` を返すまでの最大待機時間。中止すると待機はキャンセルされます。 |
Expand Down
Loading
Loading