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
607b851
feat(routing): add routing profile management endpoints
Wibias Aug 6, 2026
f91fcf7
feat(gui): add routing profile editor data model
Wibias Aug 6, 2026
8cebab7
feat(gui): add routing profiles editor
Wibias Aug 6, 2026
455a112
test(gui): cover routing profile editor payloads
Wibias Aug 6, 2026
4241d88
test(routing): cover routing profile CRUD
Wibias Aug 6, 2026
80c9dac
test(gui): require routing profile editor wiring
Wibias Aug 6, 2026
5b8ea06
docs(routing): document dashboard profile editor
Wibias Aug 6, 2026
c316431
Merge dev into feat/routing-profiles-editor
Wibias Aug 6, 2026
3ba5c4a
fix(routing): prevent create-mode profile overwrites
Wibias Aug 6, 2026
e4986e1
fix(gui): send explicit routing profile write modes
Wibias Aug 6, 2026
1b229a0
test(routing): reject duplicate create-mode profiles
Wibias Aug 6, 2026
a71034c
docs(routing): document explicit profile write modes
Wibias Aug 6, 2026
0874ef8
test(gui): assert refreshed routing editor values
Wibias Aug 6, 2026
94d3530
fix(gui): avoid i18n lint on model dedupe key
Wibias Aug 6, 2026
0acda36
chore(ci): expose temporary React Doctor diagnostics
Wibias Aug 6, 2026
ba12066
chore(ci): remove temporary React Doctor diagnostics
Wibias Aug 6, 2026
d77c5b7
chore(ci): restore workflow byte-for-byte
Wibias Aug 6, 2026
7538fe0
fix(routing): route config saves through the guarded ClaudeCode-prese…
Wibias Aug 6, 2026
fbef064
refactor(routing): simplify routing editor state and numeric requirem…
Wibias Aug 6, 2026
1cca4c8
fix(routing): give minContextWindow spec a uniform max member
Wibias Aug 6, 2026
f67753e
Merge remote-tracking branch 'refs/remotes/upstream/dev' into pr-1108…
Wibias Aug 6, 2026
4bfc4cc
fix(routing): satisfy react-doctor on the routing editor
Wibias Aug 6, 2026
cf3d4b5
fix(routing): address full reviewer set on the profiles editor
Wibias Aug 6, 2026
bcec06d
fix(routing): migrate subagentModelFallback and modelMap keys on alia…
Wibias Aug 6, 2026
fcd713b
fix(routing): reject modelMap key collisions on profile alias change
Wibias Aug 6, 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
69 changes: 69 additions & 0 deletions docs-site/src/content/docs/guides/routing-profile-editor.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
---
title: Routing Profile Editor
description: Create, edit, validate, dry-run, and remove routing policy profiles from the OpenCodex dashboard.
---

The **Routing** page in the OpenCodex dashboard can manage `config.routingProfiles` without editing `config.json` by hand.

## Create a profile

1. Open **Routing** in the dashboard.
2. Select **Create profile**.
3. Enter an `id`. The canonical model id is `policy/<id>`.
4. Add one or more explicit provider/model candidates.
5. Configure optional requirements, scoring weights, cost limits, and unknown-evidence behavior.
6. Save the profile.

Profile ids are immutable after creation. To use a different id, create a new profile and remove the old one after updating callers.

## Validation and persistence

The dashboard sends the same profile object used by `config.routingProfiles` to the management API. The server validates the complete candidate before writing it:

- ids and aliases must follow the routing-profile naming and collision rules;
- every candidate provider must exist and be enabled;
- duplicate candidates are rejected;
- numeric limits and requirements must stay inside their supported ranges; and
- at least one optimization weight must be positive.

A successful save persists the profile through the normal config writer, reconciles live state, and refreshes the model catalog. Validation failures leave the previous configuration unchanged and are shown in the editor.

## Dry-run a saved profile

Select a saved profile and use **Dry-run evaluation** to add request evidence such as context-window size, tool use, image input, or structured output. Dry-run evaluates eligibility and scoring but never sends an upstream model request.

Unsaved edits are not used by dry-run. Save the profile first so the displayed revision and evaluation refer to the same configuration.

## Management API

The editor uses these endpoints:

- `GET /api/routing-profiles` lists normalized profiles and revisions.
- `PUT /api/routing-profiles` creates or updates one profile. Send `mode: "create"` or `mode: "update"`; create mode refuses to overwrite an existing id.
- `DELETE /api/routing-profiles?id=<id>` removes one profile.
- `POST /api/routing-profiles/dry-run` evaluates a saved profile without dispatching upstream.

Example save payload:

```json
{
"id": "fast",
"mode": "create",
"profile": {
"alias": "ocx/fast",
"candidates": [
{ "provider": "anthropic", "model": "claude-sonnet-5" },
{ "provider": "openai", "model": "gpt-5.6" }
],
"require": { "tools": true, "minContextWindow": 128000 },
"optimize": { "latency": 0.55, "health": 0.25, "cost": 0.1, "quota": 0.1 },
"limits": { "maxEstimatedCostUsd": 0.5 },
"unknownEvidence": {
"capability": "exclude",
"health": "penalize",
"quota": "penalize",
"cost": "penalize"
}
}
}
```
7 changes: 7 additions & 0 deletions gui/src/i18n/de.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,13 @@ export const de: Record<TKey, string> = {
"routing.empty": "Keine Routing-Profile konfiguriert. Fügen Sie `routingProfiles` zur config.json hinzu.",
"routing.revision": "rev",
"routing.detail": "Profil",
"routing.createProfile": "Profil erstellen",
"routing.dryRunError": "Trockenlauf fehlgeschlagen (HTTP {status})",
"routing.removeConfirm": "Profil {id} entfernen?",
"routing.unknownEvidence.allow": "zulassen",
"routing.unknownEvidence.penalize": "bestrafen",
"routing.unknownEvidence.exclude": "ausschließen",
"routing.removeCandidate": "Kandidat {provider}/{model} entfernen",
"routing.candidates": "Kandidaten",
"routing.require": "Harte Anforderungen",
"routing.optimize": "Optimierungsgewichte",
Expand Down
7 changes: 7 additions & 0 deletions gui/src/i18n/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,13 @@ export const en = {
"routing.empty": "No routing profiles configured. Add `routingProfiles` to config.json.",
"routing.revision": "rev",
"routing.detail": "Profile",
"routing.createProfile": "Create profile",
"routing.dryRunError": "Dry-run failed (HTTP {status})",
"routing.removeConfirm": "Remove profile {id}?",
"routing.unknownEvidence.allow": "allow",
"routing.unknownEvidence.penalize": "penalize",
"routing.unknownEvidence.exclude": "exclude",
"routing.removeCandidate": "Remove candidate {provider}/{model}",
"routing.candidates": "Candidates",
"routing.require": "Hard requirements",
"routing.optimize": "Optimization weights",
Expand Down
7 changes: 7 additions & 0 deletions gui/src/i18n/ja.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,13 @@ export const ja: Record<TKey, string> = {
"routing.empty": "ルーティングプロファイルが設定されていません。config.json に `routingProfiles` を追加してください。",
"routing.revision": "rev",
"routing.detail": "プロファイル",
"routing.createProfile": "プロファイルを作成",
"routing.dryRunError": "ドライラン失敗 (HTTP {status})",
"routing.removeConfirm": "プロファイル {id} を削除しますか?",
"routing.unknownEvidence.allow": "許可",
"routing.unknownEvidence.penalize": "ペナルティ",
"routing.unknownEvidence.exclude": "除外",
"routing.removeCandidate": "候補 {provider}/{model} を削除",
"routing.candidates": "候補",
"routing.require": "必須要件",
"routing.optimize": "最適化ウェイト",
Expand Down
7 changes: 7 additions & 0 deletions gui/src/i18n/ko.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,13 @@ export const ko: Record<TKey, string> = {
"routing.empty": "라우팅 프로필이 구성되지 않았습니다. config.json에 `routingProfiles`를 추가하세요.",
"routing.revision": "rev",
"routing.detail": "프로필",
"routing.createProfile": "프로필 만들기",
"routing.dryRunError": "드라이런 실패 (HTTP {status})",
"routing.removeConfirm": "프로필 {id}을(를) 제거할까요?",
"routing.unknownEvidence.allow": "허용",
"routing.unknownEvidence.penalize": "불이익",
"routing.unknownEvidence.exclude": "제외",
"routing.removeCandidate": "후보 {provider}/{model} 제거",
"routing.candidates": "후보",
"routing.require": "필수 요구사항",
"routing.optimize": "최적화 가중치",
Expand Down
7 changes: 7 additions & 0 deletions gui/src/i18n/ru.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,13 @@ export const ru: Record<TKey, string> = {
"routing.empty": "Профили маршрутизации не настроены. Добавьте `routingProfiles` в config.json.",
"routing.revision": "rev",
"routing.detail": "Профиль",
"routing.createProfile": "Создать профиль",
"routing.dryRunError": "Ошибка пробного запуска (HTTP {status})",
"routing.removeConfirm": "Удалить профиль {id}?",
"routing.unknownEvidence.allow": "разрешить",
"routing.unknownEvidence.penalize": "штрафовать",
"routing.unknownEvidence.exclude": "исключить",
"routing.removeCandidate": "Удалить кандидата {provider}/{model}",
"routing.candidates": "Кандидаты",
"routing.require": "Жёсткие требования",
"routing.optimize": "Веса оптимизации",
Expand Down
7 changes: 7 additions & 0 deletions gui/src/i18n/zh.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,13 @@ export const zh: Record<TKey, string> = {
"routing.empty": "未配置路由策略。请在 config.json 中添加 `routingProfiles`。",
"routing.revision": "rev",
"routing.detail": "配置文件",
"routing.createProfile": "创建配置文件",
"routing.dryRunError": "试运行失败 (HTTP {status})",
"routing.removeConfirm": "删除配置文件 {id}?",
"routing.unknownEvidence.allow": "允许",
"routing.unknownEvidence.penalize": "惩罚",
"routing.unknownEvidence.exclude": "排除",
"routing.removeCandidate": "移除候选 {provider}/{model}",
"routing.candidates": "候选",
"routing.require": "硬性要求",
"routing.optimize": "优化权重",
Expand Down
Loading
Loading