mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-07-26 09:52:11 +03:00
52
.agents/skills/capture-release-evidences/SKILL.md
Normal file
52
.agents/skills/capture-release-evidences/SKILL.md
Normal file
@@ -0,0 +1,52 @@
|
||||
---
|
||||
name: capture-release-evidences-cx
|
||||
description: Automatically run a browser-automation agent to visually validate all new UI features from the current release and capture evidence WebP recordings of the changes.
|
||||
---
|
||||
|
||||
# Capture Release Evidences Workflow
|
||||
|
||||
Use this workflow to automatically drive a browser-automation agent to explore the newly deployed or locally running application and record evidence of the UI changes introduced in the latest release.
|
||||
|
||||
> **Tool mapping note (v3.8):** The `browser_subagent` tool referenced below is specific to an earlier agent runtime. In Claude Code, substitute with the available browser MCP tools (e.g. `mcp__claude-in-chrome__*`) for navigation/screenshots, plus the `Write` tool for saving artifacts. The high-level steps remain the same regardless of the browser-automation surface in use.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- OmniRoute must be actively running and accessible (e.g. locally at `http://localhost:20128` or on the Local VPS at `http://192.168.0.15:20128`).
|
||||
- The user must provide the target URL to be tested, or default to `http://192.168.0.15:20128`.
|
||||
|
||||
## Workflow Steps
|
||||
|
||||
### 1. Identify Target Features
|
||||
|
||||
Review the `CHANGELOG.md` for the latest version to map out the new UI elements. For example:
|
||||
|
||||
- **CLI Tools Settings**
|
||||
- **New Provider/Model Listings (e.g., Gemini 3.1, Qoder PAT)**
|
||||
- **New Feature Modals**
|
||||
|
||||
### 2. Run the Browser Subagent
|
||||
|
||||
For each identified feature, invoke the `browser_subagent` using the `default_api:browser_subagent` tool.
|
||||
**Important Task Guidelines for the Subagent:**
|
||||
|
||||
- `TaskName`: Give it a clear name like "Validate CLIProxyAPI Tool Tab".
|
||||
- `TaskSummary`: "Navigate to the CLI Tools tab and verify the new Integration settings."
|
||||
- `Task`: Provide unambiguous instructions for the subagent, such as: "Navigate to http://192.168.0.15:20128/dashboard. Click on the 'Settings' or 'CLI Tools' nav link. Scroll down to find the CLIProxyAPI integration card. Hover over it to trigger UI state. Verify the components render correctly and exit."
|
||||
- `RecordingName`: Ensure it describes the feature (e.g. `v3_4_5_cli_proxy_api`). This is required and strictly automatically saved as a WebP artifacts video by the system.
|
||||
|
||||
_(Note: The `browser_subagent` automatically creates a WebP recording named by the `RecordingName` parameter. No additional tools for screenshots are needed.)_
|
||||
|
||||
### 3. Generate Report Artifact
|
||||
|
||||
After the `browser_subagent` finishes its sessions, generate a final Markdown artifact (using `Write` and `IsArtifact=true`) to present the recordings inline to the user using the `` syntax.
|
||||
|
||||
### Example Invocation
|
||||
|
||||
\```json
|
||||
{
|
||||
"TaskName": "Validating Qoder PAT Configuration UI",
|
||||
"TaskSummary": "Validates the Qoder provider configuration modal",
|
||||
"Task": "Go to http://192.168.0.15:20128/dashboard. Click on the 'Providers' tab. Find 'Qoder' in the list. Click 'Add Token' or 'Configure'. Type 'test_token' and submit. Return when done.",
|
||||
"RecordingName": "qoder_pat_ui_validation"
|
||||
}
|
||||
\```
|
||||
45
.agents/skills/deploy-vps-akamai/SKILL.md
Normal file
45
.agents/skills/deploy-vps-akamai/SKILL.md
Normal file
@@ -0,0 +1,45 @@
|
||||
---
|
||||
name: deploy-vps-akamai-cx
|
||||
description: Deploy the latest OmniRoute code to the Akamai VPS (69.164.221.35)
|
||||
---
|
||||
|
||||
# Deploy to Akamai VPS Workflow
|
||||
|
||||
Deploy OmniRoute to the Akamai VPS using `npm pack + scp` + PM2.
|
||||
|
||||
## Codex Execution Notes
|
||||
|
||||
- Treat `// turbo` / `// turbo-all` as instructions to use `multi_tool_use.parallel` only for independent commands. Do not parallelize dependent build, copy, install, restart, and verification steps.
|
||||
- Report each remote result explicitly before finishing.
|
||||
|
||||
**Akamai VPS:** `69.164.221.35`
|
||||
**Process manager:** PM2 (`omniroute`)
|
||||
**Port:** `20128`
|
||||
|
||||
## Steps
|
||||
|
||||
### 1. Build + pack locally
|
||||
|
||||
// turbo
|
||||
|
||||
```bash
|
||||
cd /home/diegosouzapw/dev/proxys/OmniRoute && rm -f omniroute-*.tgz && rm -rf .next/cache app/.next/cache && npm run build:cli && rm -rf app/logs app/coverage app/.git app/.app-build-backup* && npm pack --ignore-scripts
|
||||
```
|
||||
|
||||
### 2. Copy to Akamai VPS and install
|
||||
|
||||
// turbo-all
|
||||
|
||||
```bash
|
||||
scp omniroute-*.tgz root@69.164.221.35:/tmp/
|
||||
```
|
||||
|
||||
```bash
|
||||
ssh root@69.164.221.35 "npm install -g /tmp/omniroute-*.tgz --ignore-scripts && cd /usr/lib/node_modules/omniroute/app && npm rebuild better-sqlite3 && pm2 delete omniroute 2>/dev/null; pm2 start /root/.omniroute/ecosystem.config.cjs --update-env && pm2 save && echo '✅ Akamai done'"
|
||||
```
|
||||
|
||||
### 3. Verify the deployment
|
||||
|
||||
```bash
|
||||
curl -s -o /dev/null -w 'AKAMAI HTTP %{http_code}\n' http://69.164.221.35:20128/
|
||||
```
|
||||
56
.agents/skills/deploy-vps-both/SKILL.md
Normal file
56
.agents/skills/deploy-vps-both/SKILL.md
Normal file
@@ -0,0 +1,56 @@
|
||||
---
|
||||
name: deploy-vps-both-cx
|
||||
description: Deploy the latest OmniRoute code to BOTH the Akamai VPS and the Local VPS
|
||||
---
|
||||
|
||||
# Deploy to VPS (Both) Workflow
|
||||
|
||||
Deploy OmniRoute to the production VPSs using `npm pack + scp` + PM2.
|
||||
|
||||
**Akamai VPS:** `69.164.221.35`
|
||||
**Local VPS:** `192.168.0.15`
|
||||
**Process manager:** PM2 (`omniroute`)
|
||||
**Port:** `20128`
|
||||
**PM2 entry:** `/usr/lib/node_modules/omniroute/app/server.js`
|
||||
|
||||
> [!IMPORTANT]
|
||||
> The npm registry rejects packages > 100MB, so deployment uses **npm pack + scp**.
|
||||
|
||||
## Codex Execution Notes
|
||||
|
||||
- Treat `// turbo` / `// turbo-all` as instructions to use `multi_tool_use.parallel` only for independent commands.
|
||||
- Build/package once first. After the artifact exists, copy/install/verify on Akamai and Local may run in parallel if they do not depend on each other.
|
||||
- Report each VPS result explicitly before finishing.
|
||||
|
||||
## Steps
|
||||
|
||||
### 1. Build + pack locally
|
||||
|
||||
// turbo
|
||||
|
||||
```bash
|
||||
cd /home/diegosouzapw/dev/proxys/OmniRoute && rm -f omniroute-*.tgz && rm -rf .next/cache app/.next/cache && npm run build:cli && rm -rf app/logs app/coverage app/.git app/.app-build-backup* && npm pack --ignore-scripts
|
||||
```
|
||||
|
||||
### 2. Copy to both VPS and install
|
||||
|
||||
// turbo-all
|
||||
|
||||
```bash
|
||||
scp omniroute-*.tgz root@69.164.221.35:/tmp/ && scp omniroute-*.tgz root@192.168.0.15:/tmp/
|
||||
```
|
||||
|
||||
```bash
|
||||
ssh root@69.164.221.35 "npm install -g /tmp/omniroute-*.tgz --ignore-scripts && cd /usr/lib/node_modules/omniroute/app && npm rebuild better-sqlite3 && pm2 delete omniroute 2>/dev/null; pm2 start /root/.omniroute/ecosystem.config.cjs --update-env && pm2 save && echo '✅ Akamai done'"
|
||||
```
|
||||
|
||||
```bash
|
||||
ssh root@192.168.0.15 "npm install -g /tmp/omniroute-*.tgz --ignore-scripts && cd /usr/lib/node_modules/omniroute/app && npm rebuild better-sqlite3 && pm2 delete omniroute 2>/dev/null; pm2 start /root/.omniroute/ecosystem.config.cjs --update-env && pm2 save && echo '✅ Local done'"
|
||||
```
|
||||
|
||||
### 3. Verify the deployment
|
||||
|
||||
```bash
|
||||
curl -s -o /dev/null -w 'AKAMAI HTTP %{http_code}\n' http://69.164.221.35:20128/
|
||||
curl -s -o /dev/null -w 'LOCAL HTTP %{http_code}\n' http://192.168.0.15:20128/
|
||||
```
|
||||
45
.agents/skills/deploy-vps-local/SKILL.md
Normal file
45
.agents/skills/deploy-vps-local/SKILL.md
Normal file
@@ -0,0 +1,45 @@
|
||||
---
|
||||
name: deploy-vps-local-cx
|
||||
description: Deploy the latest OmniRoute code to the Local VPS (192.168.0.15)
|
||||
---
|
||||
|
||||
# Deploy to Local VPS Workflow
|
||||
|
||||
Deploy OmniRoute to the Local VPS using `npm pack + scp` + PM2.
|
||||
|
||||
## Codex Execution Notes
|
||||
|
||||
- Treat `// turbo` / `// turbo-all` as instructions to use `multi_tool_use.parallel` only for independent commands. Do not parallelize dependent build, copy, install, restart, and verification steps.
|
||||
- Report each remote result explicitly before finishing.
|
||||
|
||||
**Local VPS:** `192.168.0.15`
|
||||
**Process manager:** PM2 (`omniroute`)
|
||||
**Port:** `20128`
|
||||
|
||||
## Steps
|
||||
|
||||
### 1. Build + pack locally
|
||||
|
||||
// turbo
|
||||
|
||||
```bash
|
||||
cd /home/diegosouzapw/dev/proxys/OmniRoute && rm -f omniroute-*.tgz && rm -rf .next/cache app/.next/cache && npm run build:cli && rm -rf app/logs app/coverage app/.git app/.app-build-backup* && npm pack --ignore-scripts
|
||||
```
|
||||
|
||||
### 2. Copy to Local VPS and install
|
||||
|
||||
// turbo-all
|
||||
|
||||
```bash
|
||||
scp omniroute-*.tgz root@192.168.0.15:/tmp/
|
||||
```
|
||||
|
||||
```bash
|
||||
ssh root@192.168.0.15 "npm install -g /tmp/omniroute-*.tgz --ignore-scripts && cd /usr/lib/node_modules/omniroute/app && npm rebuild better-sqlite3 && pm2 delete omniroute 2>/dev/null; pm2 start /root/.omniroute/ecosystem.config.cjs --update-env && pm2 save && echo '✅ Local done'"
|
||||
```
|
||||
|
||||
### 3. Verify the deployment
|
||||
|
||||
```bash
|
||||
curl -s -o /dev/null -w 'LOCAL HTTP %{http_code}\n' http://192.168.0.15:20128/
|
||||
```
|
||||
368
.agents/skills/generate-release/SKILL.md
Normal file
368
.agents/skills/generate-release/SKILL.md
Normal file
@@ -0,0 +1,368 @@
|
||||
---
|
||||
name: generate-release-cx
|
||||
description: Create a new release, bump version up to the .10 patch threshold, update changelog, and manage Pull Requests
|
||||
---
|
||||
|
||||
# Generate Release Workflow
|
||||
|
||||
Bump version, finalize CHANGELOG, commit, open a **PR to main** and wait for user confirmation before tagging, publishing, and deploying.
|
||||
|
||||
## Codex Execution Notes
|
||||
|
||||
- Treat `// turbo` / `// turbo-all` as instructions to use `multi_tool_use.parallel` for independent reads, checks, and GitHub calls.
|
||||
- When the workflow says `notify_user` or `BlockedOnUser: true`, present the report/status in the final response and stop. Do not continue into the next phase until the user explicitly approves.
|
||||
|
||||
> **VERSION RULE: Always use PATCH bumps (3.x.y → 3.x.y+1)**
|
||||
> NEVER use `npm version minor` or `npm version major`.
|
||||
> Always use: `npm version patch --no-git-tag-version`
|
||||
> The threshold rule: when `y` reaches 10, bump to `3.(x+1).0` — e.g. `3.8.10` → `3.9.0`.
|
||||
|
||||
> **🔴 SINGLE BRANCH RULE**: The `release/vX.Y.Z` branch is the **ONLY** development branch for the entire release cycle. ALL work — bug fixes, feature implementations, PR integrations, issue resolutions — MUST be committed directly on this branch. Never create separate `fix/`, `feat/`, or topic branches. When running `/resolve-issues`, `/implement-features`, or `/review-prs`, always work on the current release branch.
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ Two-Phase Flow
|
||||
|
||||
```
|
||||
Phase 1 (automated): bump → docs → i18n → commit → push → open PR
|
||||
↕ 🛑 STOP: Notify user, wait for PR confirmation
|
||||
Phase 2 (post-merge): tag → publish → GitHub release → Docker → deploy
|
||||
```
|
||||
|
||||
**NEVER push directly to main or create tags before the user confirms the PR.**
|
||||
|
||||
---
|
||||
|
||||
## Phase 0: Security Verification (MANDATORY)
|
||||
|
||||
Before creating the release, you must ensure the codebase and supply chain are secure and free of known vulnerabilities.
|
||||
|
||||
1. **Run Local Dependencies Audit:**
|
||||
|
||||
```bash
|
||||
npm audit
|
||||
```
|
||||
|
||||
_Fix any `high` or `critical` vulnerabilities identified._
|
||||
|
||||
2. **Check GitHub CodeQL & Dependabot Alerts:**
|
||||
Navigate to the repository's **Security** tab on GitHub, or use the project's `vulnerability-scanner` skill to analyze active alerts. Ensure all static analysis findings (e.g., prototype pollution, insecure randomness, ReDoS, shell injections) are addressed and logically committed on a target branch.
|
||||
|
||||
---
|
||||
|
||||
## Phase 1: Pre-Merge
|
||||
|
||||
### 1. Create release branch
|
||||
|
||||
```bash
|
||||
git checkout -b release/v3.x.y
|
||||
```
|
||||
|
||||
### 2. Determine and sync version
|
||||
|
||||
Check current version in `package.json`:
|
||||
|
||||
```bash
|
||||
grep '"version"' package.json
|
||||
```
|
||||
|
||||
> **🔴 BRANCH-VERSION PARITY RULE**: The logical version in `package.json` MUST exactly match the release branch name. For example, if you are on `release/v3.7.0`, the version in `package.json` MUST be `3.7.0`.
|
||||
>
|
||||
> - If this is the FIRST time generating a release for a new minor/major branch (e.g., bumping from 3.6.9 to 3.7.0), you MUST ensure the version is bumped to match the new branch logic.
|
||||
> - If you are just bumping a patch on the current branch (e.g., 3.6.9 to 3.6.10), use:
|
||||
> `npm version patch --no-git-tag-version`
|
||||
|
||||
> **⚠️ ATOMIC COMMIT RULE — Version bump MUST happen before committing feature files.**
|
||||
>
|
||||
> **CORRECT order:**
|
||||
>
|
||||
> 1. `npm version patch --no-git-tag-version` ← bump first
|
||||
> 2. implement features / fix bugs
|
||||
> 3. `git add -A && git commit -m "chore(release): v3.x.y — all changes in ONE commit"`
|
||||
>
|
||||
> **OR if features are already staged:**
|
||||
>
|
||||
> 1. implement features (do NOT commit yet)
|
||||
> 2. `npm version patch --no-git-tag-version` ← bump before committing
|
||||
> 3. `git add -A && git commit -m "chore(release): v3.x.y — all changes in ONE commit"`
|
||||
>
|
||||
> **NEVER do this (creates version mismatch in git history):**
|
||||
>
|
||||
> - ~~commit features → then bump version → commit package.json separately~~
|
||||
>
|
||||
> This ensures that `git show v3.x.y` always contains both code changes and the version bump together.
|
||||
> The GitHub release tag will point to a commit that includes ALL changes for that version.
|
||||
|
||||
### 3. Regenerate lock file (REQUIRED after version bump)
|
||||
|
||||
**Mandatory** — skipping causes `@swc/helpers` lock mismatch and CI failures:
|
||||
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
### 4. Finalize CHANGELOG.md
|
||||
|
||||
> **🔴 NO MIXUPS RULE**: Ensure you do NOT mix the backlog of the previous version with the new one. The new version section must ONLY contain the features and fixes for the current release.
|
||||
|
||||
Replace the `[Unreleased]` header with the new version and date.
|
||||
Keep an empty `## [Unreleased]` section above it, separated by a horizontal rule (`---`).
|
||||
|
||||
```markdown
|
||||
## [Unreleased]
|
||||
|
||||
---
|
||||
|
||||
## [3.7.0] — 2026-04-19
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
- ...
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- ...
|
||||
|
||||
---
|
||||
|
||||
## [3.6.9] — 2026-04-19
|
||||
```
|
||||
|
||||
### 5. Update openapi.yaml version ⚠️ MANDATORY
|
||||
|
||||
> **CI will fail** if `docs/reference/openapi.yaml` version ≠ `package.json` version (`check:docs-sync` enforces this).
|
||||
|
||||
// turbo
|
||||
|
||||
```bash
|
||||
VERSION=$(node -p "require('./package.json').version")
|
||||
sed -i "s/ version: .*/ version: $VERSION/" docs/reference/openapi.yaml
|
||||
echo "✓ openapi.yaml → $VERSION"
|
||||
|
||||
for dir in electron open-sse; do
|
||||
if [ -d "$dir" ] && [ -f "$dir/package.json" ]; then
|
||||
(cd "$dir" && npm version "$VERSION" --no-git-tag-version --allow-same-version > /dev/null)
|
||||
echo "✓ $dir/package.json → $VERSION"
|
||||
fi
|
||||
done
|
||||
# Re-run install to assert the workspace lockfile is updated
|
||||
npm install
|
||||
```
|
||||
|
||||
### 6. Update README.md and i18n docs
|
||||
|
||||
Manually perform these documentation updates (there is no `/update-docs` workflow — it was deprecated in v3.8):
|
||||
|
||||
- Update feature table rows and "What's new in vX.Y.Z" section in `README.md`
|
||||
- Sync feature changes to all 40 language `docs/i18n/*/README.md` files (use the same row edits across each translated README)
|
||||
- Update the relevant `docs/<AREA>.md` if architecture or counts changed
|
||||
- Re-run `npm run check:docs-sync` and `npm run check:docs-all` to catch drift
|
||||
|
||||
### 7. Run tests
|
||||
|
||||
// turbo
|
||||
|
||||
```bash
|
||||
npm test
|
||||
```
|
||||
|
||||
All tests must pass before creating the PR.
|
||||
|
||||
### 8. Stage, commit, and push
|
||||
|
||||
// turbo-all
|
||||
|
||||
```bash
|
||||
git add -A
|
||||
git commit -m "chore(release): v3.x.y — summary of changes"
|
||||
git push origin release/v3.x.y
|
||||
```
|
||||
|
||||
### 9. Open PR to main
|
||||
|
||||
### 9. Open PR to main
|
||||
|
||||
// turbo
|
||||
|
||||
```bash
|
||||
VERSION=$(node -p "require('./package.json').version")
|
||||
|
||||
# Extract the exact changelog entry for this version from the root CHANGELOG.md
|
||||
awk "/^## \\[$VERSION\\]/{flag=1; print; next} /^---/{if(flag) {flag=0; exit}} flag" CHANGELOG.md > /tmp/changelog_body.txt
|
||||
|
||||
# Append test status and next steps
|
||||
echo "" >> /tmp/changelog_body.txt
|
||||
echo "### Tests" >> /tmp/changelog_body.txt
|
||||
echo "- All tests pass" >> /tmp/changelog_body.txt
|
||||
echo "" >> /tmp/changelog_body.txt
|
||||
echo "### ⚠️ After merging: run Phase 2 steps to tag, publish, and deploy." >> /tmp/changelog_body.txt
|
||||
|
||||
gh pr create \
|
||||
--repo diegosouzapw/OmniRoute \
|
||||
--base main \
|
||||
--head release/v$VERSION \
|
||||
--title "Release v$VERSION" \
|
||||
--body-file /tmp/changelog_body.txt
|
||||
```
|
||||
|
||||
### 10. 🛑 STOP — Notify User & Await PR Confirmation
|
||||
|
||||
**This is a mandatory stop point.** Present the report in the final response and stop. Do not continue to the next phase until the user explicitly approves.
|
||||
|
||||
Inform the user:
|
||||
|
||||
- PR URL
|
||||
- Summary of changes
|
||||
- Test results
|
||||
- List of files changed
|
||||
|
||||
**DO NOT proceed to Phase 2 until the user confirms the PR looks good and merges it.**
|
||||
|
||||
---
|
||||
|
||||
## Phase 2: Post-Merge Validation (Local VPS)
|
||||
|
||||
> Run these steps only AFTER the user has merged the PR into `main` and all CI jobs have passed.
|
||||
|
||||
### 11. Deploy to Local VPS for Final Validation (MANDATORY)
|
||||
|
||||
Before cutting the official git tag and publishing to the world, deploy the `main` branch to the Local VPS for a final homologation test.
|
||||
|
||||
```bash
|
||||
git checkout main
|
||||
git pull origin main
|
||||
|
||||
# Build and pack locally
|
||||
cd /home/diegosouzapw/dev/proxys/OmniRoute && rm -f omniroute-*.tgz && rm -rf .next/cache app/.next/cache && npm run build:cli && rm -rf app/logs app/coverage app/.git app/.app-build-backup* && npm pack --ignore-scripts
|
||||
|
||||
# Deploy to LOCAL VPS (192.168.0.15)
|
||||
scp omniroute-*.tgz root@192.168.0.15:/tmp/
|
||||
ssh root@192.168.0.15 "npm install -g /tmp/omniroute-*.tgz --ignore-scripts && cd /usr/lib/node_modules/omniroute/app && npm rebuild better-sqlite3 && pm2 delete omniroute 2>/dev/null; pm2 start /root/.omniroute/ecosystem.config.cjs --update-env && pm2 save && echo '✅ Local done'"
|
||||
|
||||
# Verify
|
||||
curl -s -o /dev/null -w "LOCAL: HTTP %{http_code}\n" http://192.168.0.15:20128/
|
||||
```
|
||||
|
||||
### 12. 🛑 STOP — Notify User & Await Final OK
|
||||
|
||||
**This is a mandatory stop point.**
|
||||
Inform the user that the `main` branch is now running on the Local VPS.
|
||||
Wait for the user to manually test and give the **OK**.
|
||||
**DO NOT proceed to Phase 3 until the user confirms the local deploy is stable.**
|
||||
|
||||
---
|
||||
|
||||
## Phase 3: Official Launch
|
||||
|
||||
> Run these steps only AFTER the user gives the final OK from the Phase 2 local validation.
|
||||
|
||||
### 13. Create Git Tag and GitHub Release (MANDATORY)
|
||||
|
||||
// turbo
|
||||
|
||||
```bash
|
||||
git checkout main
|
||||
git pull origin main
|
||||
VERSION=$(node -p "require('./package.json').version")
|
||||
|
||||
# Extracts the changelog section for this version
|
||||
NOTES=$(awk "/^## \\[$VERSION\\]/{flag=1; next} /^---/{if(flag) {flag=0; exit}} flag" CHANGELOG.md | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')
|
||||
if [ -z "$NOTES" ]; then NOTES="OmniRoute v$VERSION Release"; fi
|
||||
|
||||
git tag -a "v$VERSION" -m "Release v$VERSION"
|
||||
git push origin "v$VERSION"
|
||||
gh release create "v$VERSION" --repo diegosouzapw/OmniRoute --title "v$VERSION" --notes "$NOTES" --target main || gh release edit "v$VERSION" --repo diegosouzapw/OmniRoute --title "v$VERSION" --notes "$NOTES"
|
||||
```
|
||||
|
||||
### 14. 🐳 Trigger Docker Hub build (MANDATORY — keep npm and Docker in sync)
|
||||
|
||||
> **CRITICAL**: Docker Hub and npm MUST always publish the same version.
|
||||
> The Docker image is built automatically via GitHub Actions when a new tag is pushed.
|
||||
> After pushing the tag in step 13, **verify the workflow runs**:
|
||||
|
||||
```bash
|
||||
# Verify the Docker workflow triggered
|
||||
gh run list --repo diegosouzapw/OmniRoute --workflow docker-publish.yml --limit 3
|
||||
|
||||
# Wait for the Docker build to complete (usually 5–10 min)
|
||||
gh run watch --repo diegosouzapw/OmniRoute
|
||||
```
|
||||
|
||||
### 15. Publish to NPM (Optional/Automated)
|
||||
|
||||
Normally handled by CI, but if manual publish is required:
|
||||
|
||||
```bash
|
||||
npm publish
|
||||
```
|
||||
|
||||
### 16. Deploy to AKAMAI VPS (Production)
|
||||
|
||||
Now that the release is officially cut, deploy it to the Akamai VPS.
|
||||
|
||||
```bash
|
||||
# Deploy to AKAMAI VPS (69.164.221.35)
|
||||
scp omniroute-*.tgz root@69.164.221.35:/tmp/
|
||||
ssh root@69.164.221.35 "npm install -g /tmp/omniroute-*.tgz --ignore-scripts && cd /usr/lib/node_modules/omniroute/app && npm rebuild better-sqlite3 && pm2 delete omniroute 2>/dev/null; pm2 start /root/.omniroute/ecosystem.config.cjs --update-env && pm2 save && echo '✅ Akamai done'"
|
||||
|
||||
# Verify
|
||||
curl -s -o /dev/null -w "AKAMAI: HTTP %{http_code}\n" http://69.164.221.35:20128/
|
||||
```
|
||||
|
||||
## Phase 4: Release Monitoring & Artifact Validation
|
||||
|
||||
> After triggering the official release, actively monitor the CI pipelines until all artifacts are successfully generated. If any pipeline fails, stop and apply the necessary corrections before continuing.
|
||||
|
||||
### 18. Monitor CI Pipelines
|
||||
|
||||
Wait for and verify the successful completion of the following automated jobs:
|
||||
|
||||
1. **Docker Hub Publish**
|
||||
2. **Electron Build**
|
||||
3. **NPM Registry Publish** (Check with `npm info omniroute version`)
|
||||
|
||||
```bash
|
||||
# Monitor Docker Hub workflow
|
||||
gh run list --repo diegosouzapw/OmniRoute --workflow docker-publish.yml --limit 1
|
||||
gh run watch <RUN_ID>
|
||||
|
||||
# Monitor Electron build
|
||||
gh run list --repo diegosouzapw/OmniRoute --workflow electron-release.yml --limit 1
|
||||
gh run watch <RUN_ID>
|
||||
|
||||
# Verify NPM version
|
||||
npm info omniroute version
|
||||
```
|
||||
|
||||
### 19. Handle Failures (If Any)
|
||||
|
||||
If a workflow fails:
|
||||
|
||||
- Use `gh run view <RUN_ID> --log-failed` to identify the error.
|
||||
- Apply the fix on the `main` branch.
|
||||
- If necessary, re-trigger the workflow using `gh workflow run <workflow_name.yml> --repo diegosouzapw/OmniRoute --ref v3.x.y`
|
||||
|
||||
### 20. Preserve release branch
|
||||
|
||||
```bash
|
||||
# Branch is kept for historical purposes. Do not delete.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Notes
|
||||
|
||||
- Ensure CHANGELOG, README and `docs/*` are current BEFORE this workflow — run `npm run check:docs-all` and `/version-bump` first (there is no `/update-docs` workflow anymore)
|
||||
- The `prepublishOnly` script runs `npm run build:cli` automatically during `npm publish`
|
||||
- After npm publish, verify with `npm info omniroute version`
|
||||
- Lock file sync errors are caused by skipping `npm install` after version bump
|
||||
- Use `gh auth switch -u diegosouzapw` if git push fails with wrong account
|
||||
|
||||
## Known CI Pitfalls
|
||||
|
||||
| CI failure | Cause | Fix |
|
||||
| ------------------------------------------------------------------------- | ------------------------------------------------------------------ | ---------------------------------------------------------------------- |
|
||||
| `[docs-sync] FAIL - OpenAPI version differs from package.json` | Skipped step 5 — `docs/reference/openapi.yaml` version not updated | Run step 5 (`sed -i ...`) and commit |
|
||||
| `[docs-sync] FAIL - CHANGELOG.md first section must be "## [Unreleased]"` | `## [Unreleased]` missing or not at top of CHANGELOG | Add `## [Unreleased]\n\n---\n` before the first versioned `## [x.y.z]` |
|
||||
| Electron Linux `.deb` build fails (`FpmTarget` error) | `fpm` Ruby gem not installed on `ubuntu-latest` runner | Already fixed in `electron-release.yml` (`gem install fpm` step) |
|
||||
| Docker Hub `502 error writing layer blob` | Transient Docker Hub network error during ARM64 push | Re-run the Docker publish workflow; no code change needed |
|
||||
713
.agents/skills/implement-features/SKILL.md
Normal file
713
.agents/skills/implement-features/SKILL.md
Normal file
@@ -0,0 +1,713 @@
|
||||
---
|
||||
name: implement-features-cx
|
||||
description: Analyze open feature request issues, implement viable ones on dedicated branches, and respond to authors
|
||||
---
|
||||
|
||||
# /implement-features — Feature Request Harvest, Research & Implementation Workflow
|
||||
|
||||
## Overview
|
||||
|
||||
A **5-phase** workflow that systematically harvests feature requests from GitHub issues, creates structured idea files, researches solutions across the internet and Git repositories, presents a consolidated report for user approval, then generates detailed implementation plans and executes them.
|
||||
|
||||
## Codex Execution Notes
|
||||
|
||||
- Treat `// turbo` / `// turbo-all` as instructions to use `multi_tool_use.parallel` for independent reads, checks, and GitHub calls.
|
||||
- Approval gates are hard stops. Present the report/plan in the final response and do not move to implementation phases until the user explicitly approves.
|
||||
- Keep harvest/research bounded enough to produce the approval report quickly; do not start implementation while still in report phases.
|
||||
|
||||
**Output directory structure:**
|
||||
|
||||
```
|
||||
_ideia/
|
||||
├── viable/ # Features approved for implementation
|
||||
│ ├── need_details/ # ❓ Good idea but waiting for author clarification (issues stay OPEN)
|
||||
│ │ └── 1015-warp-terminal-mitm.md
|
||||
│ ├── 1046-native-playground.md # ✅ Ready — researched and planned
|
||||
│ └── 1046-native-playground.requirements.md
|
||||
├── defer/ # ⏭️ Good ideas deferred for future cycles (issues CLOSED)
|
||||
│ └── 1041-smart-auto-combos.md
|
||||
└── notfit/ # ❌ Out of scope / already exists (issues CLOSED)
|
||||
└── 945-telegram-integration.md
|
||||
|
||||
_tasks/features-vX.Y.Z/ # Implementation plans (per-release)
|
||||
└── 1046-native-playground.plan.md
|
||||
```
|
||||
|
||||
> **LIFECYCLE RULE:** `viable/` files are **DELETED** once the feature is implemented — they are not moved. Only unimplemented features live in `viable/` (or `viable/need_details/`). Files in `defer/` and `notfit/` remain as permanent reference.
|
||||
|
||||
> **BRANCH RULE**: All implementation work MUST happen on the current `release/vX.Y.Z` branch. Never create separate `feat/` branches. If no release branch exists yet, create one first using `/generate-release` Phase 1 steps 1–5.
|
||||
|
||||
---
|
||||
|
||||
## Phase 1 — Harvest: Collect & Catalog Feature Ideas
|
||||
|
||||
### 1.1 Identify the Repository
|
||||
|
||||
// turbo
|
||||
|
||||
- Run: `git -C <project_root> remote get-url origin` to extract owner/repo.
|
||||
|
||||
### 1.2 Ensure Release Branch Exists
|
||||
|
||||
// turbo
|
||||
|
||||
Before doing any work, ensure you are on the current release branch:
|
||||
|
||||
```bash
|
||||
# Check current branch
|
||||
git branch --show-current
|
||||
|
||||
# If on main, determine next version and create the release branch
|
||||
VERSION=$(node -p "require('./package.json').version")
|
||||
NEXT=$(node -p "const [a,b,c]=('$VERSION').split('.').map(Number); c>=9?a+'.'+(b+1)+'.0':a+'.'+b+'.'+(c+1)")
|
||||
git checkout -b release/v$NEXT
|
||||
npm version patch --no-git-tag-version
|
||||
npm install
|
||||
```
|
||||
|
||||
If already on a `release/vX.Y.Z` branch, continue working there.
|
||||
|
||||
### 1.3 Fetch ALL Open Feature Requests
|
||||
|
||||
// turbo-all
|
||||
|
||||
**⚠️ CRITICAL**: The JSON output of `gh issue list` can be truncated by the tool, silently hiding issues. You MUST use the two-step approach below.
|
||||
|
||||
**Step 1 — Get Issue numbers only** (small output, never truncated):
|
||||
|
||||
```bash
|
||||
# Fetch issues with feature/enhancement labels
|
||||
gh issue list --repo <owner>/<repo> --state open -l "enhancement" --limit 500 --json number --jq '.[].number'
|
||||
|
||||
# Also check for [Feature] in title (common pattern when no labels are set)
|
||||
gh issue list --repo <owner>/<repo> --state open --limit 500 --json number,title --jq '.[] | select(.title | test("\\[Feature\\]|\\[feature\\]|feature request"; "i")) | .number'
|
||||
```
|
||||
|
||||
- Merge both lists, deduplicate. Count and confirm the total.
|
||||
|
||||
**Step 2 — Fetch full metadata for each Issue** (one call per issue):
|
||||
|
||||
```bash
|
||||
gh issue view <NUMBER> --repo <owner>/<repo> --json number,title,labels,body,comments,createdAt,author,assignees
|
||||
```
|
||||
|
||||
- Read the **entire body** — including description, use cases, screenshots, mockups, and any embedded images.
|
||||
- Read **ALL comments** — community discussion, agreements, restrictions, owner responses, and linked PRs.
|
||||
- **Images**: If the body or comments contain image URLs (`` or `https://...png/jpg/gif`), note them — they may contain UI mockups, wireframes, or architecture diagrams that are essential to understanding the request.
|
||||
- You may batch these into parallel calls (up to 4 at a time).
|
||||
- Sort by oldest first (FIFO).
|
||||
|
||||
### 1.4 Create Idea Files (initially in `_ideia/` root)
|
||||
|
||||
For each feature request, create a structured idea file in `<project_root>/_ideia/`:
|
||||
|
||||
**Filename convention**: `<NUMBER>-<kebab-case-short-title>.md`
|
||||
Example: `1046-native-playground.md`, `1041-smart-auto-combos.md`
|
||||
|
||||
#### 1.4a — If the idea file does NOT exist yet, create it:
|
||||
|
||||
```markdown
|
||||
# Feature: <Title from Issue>
|
||||
|
||||
> GitHub Issue: #<NUMBER> — opened by @<author> on <date>
|
||||
> Status: 📋 Cataloged | Priority: TBD
|
||||
|
||||
## 📝 Original Request
|
||||
|
||||
<Paste the FULL issue body here, preserving all formatting, images, and code blocks>
|
||||
|
||||
## 💬 Community Discussion
|
||||
|
||||
<Summarize ALL comments chronologically, noting who said what and any decisions or objections raised>
|
||||
|
||||
### Participants
|
||||
|
||||
- @<author> — Original requester
|
||||
- @<commenter1> — <brief role/opinion>
|
||||
- ...
|
||||
|
||||
### Key Points
|
||||
|
||||
- <bullet list of the most important discussion points>
|
||||
- <agreements reached>
|
||||
- <objections raised>
|
||||
|
||||
## 🎯 Refined Feature Description
|
||||
|
||||
<YOUR interpretation and enrichment of the feature request. Expand on what was asked, fill in logical gaps, provide concrete examples of how it would work. This section should be MORE detailed and clearer than the original request.>
|
||||
|
||||
### What it solves
|
||||
|
||||
- <problem 1>
|
||||
- <problem 2>
|
||||
|
||||
### How it should work (high level)
|
||||
|
||||
1. <step 1>
|
||||
2. <step 2>
|
||||
3. ...
|
||||
|
||||
### Affected areas
|
||||
|
||||
- <list of codebase areas, modules, files likely affected>
|
||||
|
||||
## 📎 Attachments & References
|
||||
|
||||
- <any image URLs, mockup links, or external references from the issue>
|
||||
|
||||
## 🔗 Related Ideas
|
||||
|
||||
- <links to related \_ideia/ files if any overlap found>
|
||||
```
|
||||
|
||||
#### 1.4b — If the idea file ALREADY exists, update it:
|
||||
|
||||
- Append new comments from the issue to the **Community Discussion** section.
|
||||
- Update the **Refined Feature Description** if new information changes the understanding.
|
||||
- Add any new **Related Ideas** cross-references found.
|
||||
- **Do NOT overwrite** existing content — append and enrich it.
|
||||
|
||||
### 1.5 Cross-Reference & Deduplication
|
||||
|
||||
After processing all issues:
|
||||
|
||||
- Scan all `_ideia/*.md` files for overlapping features.
|
||||
- If two features are substantially the same, add `🔗 Related Ideas` cross-references to both.
|
||||
- If one is a strict subset of another, note it in the smaller file: `> ℹ️ This feature is a subset of #<OTHER_NUMBER>. Consider implementing together.`
|
||||
|
||||
---
|
||||
|
||||
## Phase 2 — Research: Find Solutions & Build Requirements
|
||||
|
||||
For each cataloged idea that is **viable** (aligns with the project's goals):
|
||||
|
||||
### 2.1 Viability Pre-Check
|
||||
|
||||
Before investing in research, quickly assess:
|
||||
|
||||
- [ ] Does this feature align with the project's goals and architecture?
|
||||
- [ ] Is it technically feasible with the current codebase?
|
||||
- [ ] Does it duplicate existing functionality?
|
||||
- [ ] Would it introduce breaking changes or security risks?
|
||||
- [ ] Is there enough detail to understand what's needed?
|
||||
|
||||
**Verdict options:**
|
||||
|
||||
| Verdict | When | Action |
|
||||
| --------------------- | ------------------------------------- | --------------------------- |
|
||||
| ✅ **VIABLE** | Good idea, enough context | Proceed to Research |
|
||||
| ❓ **NEEDS DETAIL** | Good idea, insufficient spec | Skip research, ask author |
|
||||
| ⏭️ **DEFER** | Good idea, too complex for this cycle | Catalog only, skip research |
|
||||
| ❌ **NOT FIT** | Doesn't fit the project | Explain why |
|
||||
| 🔁 **ALREADY EXISTS** | Feature already implemented | Point to existing feature |
|
||||
|
||||
### 2.2 Internet Research (for VIABLE features)
|
||||
|
||||
For each viable feature, perform systematic research:
|
||||
|
||||
**Step 1 — Web search for similar implementations:**
|
||||
|
||||
```
|
||||
WebSearch("how to implement <feature description> in <tech stack>")
|
||||
WebSearch("<feature keyword> implementation nextjs typescript 2025 2026")
|
||||
WebSearch("<feature keyword> open source library npm")
|
||||
```
|
||||
|
||||
**Step 2 — Find reference Git repositories:**
|
||||
|
||||
```
|
||||
WebSearch("site:github.com <feature keyword> <tech stack> stars:>100")
|
||||
WebSearch("github <feature keyword> implementation recently updated 2026")
|
||||
```
|
||||
|
||||
- Find **up to 10 relevant repositories**, sorted by most recently updated.
|
||||
- For each repository:
|
||||
- Note the repo URL, star count, last commit date
|
||||
- Read its README and relevant source files via `WebFetch`
|
||||
- Extract the architectural approach, patterns used, and key code snippets
|
||||
|
||||
**Step 3 — Read API docs and standards:**
|
||||
|
||||
If the feature involves an external API, protocol, or standard:
|
||||
|
||||
- Find and read the official documentation
|
||||
- Note version requirements, authentication patterns, rate limits
|
||||
|
||||
### 2.3 Create Requirements File
|
||||
|
||||
For each researched feature, create a requirements file alongside its idea file:
|
||||
|
||||
**Filename**: `<NUMBER>-<kebab-case-short-title>.requirements.md`
|
||||
|
||||
```markdown
|
||||
# Requirements: <Feature Title>
|
||||
|
||||
> Feature Idea: [#<NUMBER>](./<NUMBER>-<kebab-case-short-title>.md)
|
||||
> Research Date: <YYYY-MM-DD>
|
||||
> Verdict: ✅ VIABLE
|
||||
|
||||
## 🔍 Research Summary
|
||||
|
||||
<Brief summary of what was found during research>
|
||||
|
||||
## 📚 Reference Implementations
|
||||
|
||||
| # | Repository | Stars | Last Updated | Approach | Relevance |
|
||||
| --- | ---------------- | ----- | ------------ | -------- | ------------ |
|
||||
| 1 | [repo/name](url) | ⭐ N | YYYY-MM-DD | <brief> | High/Med/Low |
|
||||
| 2 | ... | | | | |
|
||||
|
||||
### Key Patterns Found
|
||||
|
||||
- <pattern 1 with code snippet or link>
|
||||
- <pattern 2>
|
||||
|
||||
## 📐 Proposed Solution Architecture
|
||||
|
||||
### Approach
|
||||
|
||||
<Describe the chosen approach based on research findings>
|
||||
|
||||
### New Files
|
||||
|
||||
| File | Purpose |
|
||||
| --------------------- | ------------- |
|
||||
| `path/to/new/file.ts` | <description> |
|
||||
|
||||
### Modified Files
|
||||
|
||||
| File | Changes |
|
||||
| -------------------------- | -------------- |
|
||||
| `path/to/existing/file.ts` | <what changes> |
|
||||
|
||||
### Database Changes
|
||||
|
||||
- <migrations needed, if any>
|
||||
|
||||
### API Changes
|
||||
|
||||
- <new/modified endpoints, if any>
|
||||
|
||||
### UI Changes
|
||||
|
||||
- <new/modified pages/components, if any>
|
||||
|
||||
## ⚙️ Implementation Effort
|
||||
|
||||
- **Estimated complexity**: Low / Medium / High / Very High
|
||||
- **Estimated files changed**: ~N
|
||||
- **Dependencies needed**: <new npm packages, if any>
|
||||
- **Breaking changes**: Yes/No — <details>
|
||||
- **i18n impact**: <number of new translation keys>
|
||||
- **Test coverage needed**: <brief description>
|
||||
|
||||
## ⚠️ Open Questions
|
||||
|
||||
- <question 1>
|
||||
- <question 2>
|
||||
|
||||
## 🔗 External References
|
||||
|
||||
- <documentation URLs>
|
||||
- <API references>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Phase 2.5 — Organize & Respond: Sort Files and Post GitHub Comments
|
||||
|
||||
### 2.5.1 Create Directory Structure
|
||||
|
||||
// turbo
|
||||
|
||||
```bash
|
||||
mkdir -p <project_root>/_ideia/viable
|
||||
mkdir -p <project_root>/_ideia/viable/need_details
|
||||
mkdir -p <project_root>/_ideia/defer
|
||||
mkdir -p <project_root>/_ideia/notfit
|
||||
```
|
||||
|
||||
### 2.5.2 Move Idea Files to Category Subdirectories
|
||||
|
||||
After classification, move EVERY idea file to its correct subdirectory:
|
||||
|
||||
```bash
|
||||
# ✅ VIABLE — move idea + requirements files
|
||||
mv _ideia/<NUMBER>-*.md _ideia/viable/
|
||||
mv _ideia/<NUMBER>-*.requirements.md _ideia/viable/
|
||||
|
||||
# ❓ NEEDS DETAIL — viable but waiting for author response
|
||||
mv _ideia/<NUMBER>-*.md _ideia/viable/need_details/
|
||||
|
||||
# ⏭️ DEFER — move idea files only
|
||||
mv _ideia/<NUMBER>-*.md _ideia/defer/
|
||||
|
||||
# ❌ NOT FIT & 🔁 ALREADY EXISTS — move idea files only
|
||||
mv _ideia/<NUMBER>-*.md _ideia/notfit/
|
||||
```
|
||||
|
||||
No files should remain in `_ideia/` root after this step (except subdirectories).
|
||||
|
||||
### 2.5.3 Post GitHub Comments by Category
|
||||
|
||||
**Each category has a specific comment template and action:**
|
||||
|
||||
---
|
||||
|
||||
#### For 🔁 ALREADY EXISTS — Comment + CLOSE issue
|
||||
|
||||
// turbo
|
||||
|
||||
The feature already exists in the system. Explain WHERE it is and HOW to use it.
|
||||
|
||||
```markdown
|
||||
Hi @<author>! Thanks for the suggestion! 🙏
|
||||
|
||||
Great news — this functionality **already exists** in OmniRoute:
|
||||
|
||||
**📍 Where to find it:** <exact dashboard path or settings location>
|
||||
|
||||
**🔧 How to use it:**
|
||||
|
||||
1. <step 1>
|
||||
2. <step 2>
|
||||
3. <step 3>
|
||||
|
||||
If you have any trouble finding or using it, feel free to ask in a Discussion. We're always happy to help!
|
||||
|
||||
Closing this as the feature is already available. 🎉
|
||||
```
|
||||
|
||||
```bash
|
||||
gh issue close <NUMBER> --repo <owner>/<repo> --comment "<comment above>"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### For ⏭️ DEFER — Comment + CLOSE issue
|
||||
|
||||
// turbo
|
||||
|
||||
Thank the user, explain the idea was cataloged, and that we'll study it before implementing.
|
||||
|
||||
```markdown
|
||||
Hi @<author>! Thanks for this thoughtful feature request! 🙏
|
||||
|
||||
We really appreciate the detailed proposal. We've **cataloged your idea** and it's now part of our improvement backlog.
|
||||
|
||||
Due to the **significant architectural impact** of this feature, we'll need to conduct thorough use-case studies and architectural analysis before we start development. This ensures we build it right and don't introduce regressions.
|
||||
|
||||
**What happens next:**
|
||||
|
||||
- Your idea is saved in our internal feature backlog
|
||||
- We'll conduct architecture studies when this area is prioritized
|
||||
- We'll notify you here when development begins
|
||||
|
||||
Thank you for contributing to OmniRoute's roadmap! Your input helps shape the product. 🚀
|
||||
```
|
||||
|
||||
```bash
|
||||
gh issue close <NUMBER> --repo <owner>/<repo> --comment "<comment above>"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### For ❌ NOT FIT — Comment + CLOSE issue
|
||||
|
||||
// turbo
|
||||
|
||||
Politely explain why the feature doesn't fit the project scope.
|
||||
|
||||
```markdown
|
||||
Hi @<author>! Thanks for the suggestion! 🙏
|
||||
|
||||
After careful analysis, we've determined that this feature **falls outside OmniRoute's core scope** as a proxy/router.
|
||||
|
||||
**Reason:** <explain why — e.g., "Telegram integration belongs in the application/orchestrator layer that consumes OmniRoute's API, not inside the router itself.">
|
||||
|
||||
**Alternative:** <suggest an alternative approach if possible>
|
||||
|
||||
We appreciate you thinking of ways to improve OmniRoute! If you'd like to discuss this further, feel free to open a Discussion. 🙏
|
||||
```
|
||||
|
||||
```bash
|
||||
gh issue close <NUMBER> --repo <owner>/<repo> --comment "<comment above>"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### For ❓ NEEDS DETAIL — Comment (keep OPEN)
|
||||
|
||||
// turbo
|
||||
|
||||
Ask for the specific missing details needed.
|
||||
|
||||
```markdown
|
||||
Hi @<author>! Thanks for the feature request — it's an interesting idea and we'd love to explore it further. 🙏
|
||||
|
||||
To move forward, we need a few more details:
|
||||
|
||||
1. <specific question 1>
|
||||
2. <specific question 2>
|
||||
3. <specific question 3>
|
||||
|
||||
If you know of any **open-source projects or repositories** that implement something similar, please share links — it would help us design the best solution.
|
||||
|
||||
Looking forward to your response! 🚀
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### For ✅ VIABLE — Comment (keep OPEN)
|
||||
|
||||
// turbo
|
||||
|
||||
Thank the user, confirm we've cataloged their idea, and explain it may be implemented in future versions.
|
||||
|
||||
```markdown
|
||||
Hi @<author>! Thanks for the great feature suggestion! 🙏
|
||||
|
||||
We've analyzed your request and it aligns well with OmniRoute's roadmap. We've **cataloged this feature** and it's in our implementation backlog.
|
||||
|
||||
**Status:** 📋 Cataloged for future implementation
|
||||
|
||||
This feature may be included in upcoming releases. We'll **respond to this issue and tag you** as soon as implementation begins so you can test it.
|
||||
|
||||
Thank you for helping improve OmniRoute! 🚀
|
||||
```
|
||||
|
||||
**⚠️ Do NOT close viable issues — they remain OPEN for tracking.**
|
||||
|
||||
---
|
||||
|
||||
## Phase 3 — Report: Present Findings to User
|
||||
|
||||
### 3.1 🛑 MANDATORY STOP — Present Consolidated Report
|
||||
|
||||
After completing Phase 1, Phase 2, and Phase 2.5, **STOP and present the following report** in the chat. Do NOT proceed to implementation.
|
||||
|
||||
Present a structured report containing:
|
||||
|
||||
#### 3.1a — Feature Summary Table
|
||||
|
||||
| # | Issue | Title | Verdict | Location | Action |
|
||||
| --- | ----- | ----- | --------------- | ----------------------------- | ----------------------------- |
|
||||
| 1 | #N | Title | ✅ VIABLE | `_ideia/viable/` | Issue OPEN, comment posted |
|
||||
| 2 | #N | Title | ⏭️ DEFER | `_ideia/defer/` | Issue CLOSED with explanation |
|
||||
| 3 | #N | Title | ❌ NOT FIT | `_ideia/notfit/` | Issue CLOSED with explanation |
|
||||
| 4 | #N | Title | 🔁 EXISTS | `_ideia/notfit/` | Issue CLOSED with guidance |
|
||||
| 5 | #N | Title | ❓ NEEDS DETAIL | `_ideia/viable/need_details/` | Issue OPEN, questions posted |
|
||||
|
||||
#### 3.1b — Viable Features Detail
|
||||
|
||||
For each VIABLE feature, provide a brief paragraph:
|
||||
|
||||
- What was found during research
|
||||
- The proposed approach
|
||||
- Key risks or unknowns
|
||||
- Which reference repositories were most useful
|
||||
|
||||
#### 3.1c — Issues Requiring Author Feedback
|
||||
|
||||
For features marked ❓ NEEDS DETAIL, list:
|
||||
|
||||
- What specific information is missing
|
||||
- What examples or repository references would help
|
||||
|
||||
#### 3.1d — Ask for User Confirmation
|
||||
|
||||
End the report with:
|
||||
|
||||
> **Ready to proceed with implementation?**
|
||||
>
|
||||
> - Reply **"sim"** or **"yes"** to generate full implementation plans for all VIABLE features.
|
||||
> - Reply with specific issue numbers to select only certain features.
|
||||
> - Reply **"não"** or **"no"** to stop here.
|
||||
|
||||
---
|
||||
|
||||
## Phase 4 — Plan: Generate Implementation Plans (after user says "yes")
|
||||
|
||||
> **⚠️ Do NOT enter this phase without explicit user approval from Phase 3.**
|
||||
|
||||
### 4.1 Create Task Directory
|
||||
|
||||
```bash
|
||||
mkdir -p <project_root>/_tasks/features-vX.Y.Z/
|
||||
```
|
||||
|
||||
### 4.2 Generate One Implementation Plan Per Feature
|
||||
|
||||
For each VIABLE feature approved by the user, create:
|
||||
|
||||
**Filename**: `_tasks/features-vX.Y.Z/<NUMBER>-<kebab-case-title>.plan.md`
|
||||
|
||||
```markdown
|
||||
# Implementation Plan: <Feature Title>
|
||||
|
||||
> Issue: #<NUMBER>
|
||||
> Idea: [\_ideia/viable/<NUMBER>-title.md](../../_ideia/viable/<NUMBER>-title.md)
|
||||
> Requirements: [\_ideia/viable/<NUMBER>-title.requirements.md](../../_ideia/viable/<NUMBER>-title.requirements.md)
|
||||
> Branch: `release/vX.Y.Z`
|
||||
|
||||
## Overview
|
||||
|
||||
<Brief description of what will be built>
|
||||
|
||||
## Pre-Implementation Checklist
|
||||
|
||||
- [ ] Read all related source files listed below
|
||||
- [ ] Confirm no conflicts with in-flight PRs
|
||||
- [ ] Verify database migration numbering
|
||||
|
||||
## Implementation Steps
|
||||
|
||||
### Step 1: <Title>
|
||||
|
||||
**Files:**
|
||||
|
||||
- `path/to/file.ts` — <what to change>
|
||||
|
||||
**Details:**
|
||||
<Detailed description of the change, including code patterns to follow, function signatures, etc.>
|
||||
|
||||
### Step 2: <Title>
|
||||
|
||||
...
|
||||
|
||||
### Step N: Tests
|
||||
|
||||
**New test files:**
|
||||
|
||||
- `tests/unit/<test-file>.test.mjs` — <what to test>
|
||||
|
||||
**Test cases:**
|
||||
|
||||
- [ ] <test case 1>
|
||||
- [ ] <test case 2>
|
||||
|
||||
### Step N+1: i18n
|
||||
|
||||
**Translation keys to add:**
|
||||
|
||||
- `<namespace>.<key>` — "<English value>"
|
||||
|
||||
### Step N+2: Documentation
|
||||
|
||||
- [ ] Update CHANGELOG.md
|
||||
- [ ] Update relevant docs/ files
|
||||
|
||||
## Verification Plan
|
||||
|
||||
1. Run `npm run build` — must pass
|
||||
2. Run `npm test` — all tests must pass
|
||||
3. Run `npm run lint` — no new errors
|
||||
4. <Manual verification steps>
|
||||
|
||||
## Commit Plan
|
||||
```
|
||||
|
||||
feat: <description> (#<NUMBER>)
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
|
||||
### 4.3 Present Plans for Final Approval
|
||||
|
||||
Present a summary of all generated plans:
|
||||
|
||||
> **Implementation plans generated:**
|
||||
>
|
||||
> | # | Feature | Plan File | Steps | Effort |
|
||||
> | --- | ------- | ---------------------------------------- | ------- | ------ |
|
||||
> | 1 | <title> | `_tasks/features-vX.Y.Z/N-title.plan.md` | N steps | Medium |
|
||||
>
|
||||
> Reply **"sim"** or **"yes"** to begin implementation of all features.
|
||||
> Reply with specific issue numbers to implement only certain ones.
|
||||
|
||||
---
|
||||
|
||||
## Phase 5 — Execute: Implement the Plans (after user says "yes")
|
||||
|
||||
> **⚠️ Do NOT enter this phase without explicit user approval from Phase 4.**
|
||||
|
||||
### 5.1 Implement Each Feature
|
||||
|
||||
For each approved plan, execute it step by step:
|
||||
|
||||
1. **Follow the plan** — implement exactly as specified in the `.plan.md` file
|
||||
2. **Build** — Run `npm run build` after each feature to verify compilation
|
||||
3. **Test** — Run `npm test` to ensure no regressions
|
||||
4. **Commit** — Commit with: `feat: <description> (#<NUMBER>)`
|
||||
5. **Update the plan** — Mark completed steps with `[x]` in the plan file
|
||||
6. **Continue** — Move to the next feature (do NOT switch branches)
|
||||
|
||||
### 5.2 Respond to Authors (Update Viable Issues)
|
||||
|
||||
For each implemented feature, **close the issue with a final comment**:
|
||||
|
||||
````markdown
|
||||
✅ **Implemented in `release/vX.Y.Z`!**
|
||||
|
||||
Hi @<author>! Great news — your feature request has been implemented! 🎉
|
||||
|
||||
**What was done:**
|
||||
|
||||
- <bullet list of what was built>
|
||||
|
||||
**How to try it:**
|
||||
|
||||
```bash
|
||||
git fetch origin && git checkout release/vX.Y.Z
|
||||
npm install && npm run dev
|
||||
```
|
||||
````
|
||||
|
||||
This will be included in the upcoming **vX.Y.Z** release. Feel free to reopen if you spot any issues! 🚀
|
||||
|
||||
````
|
||||
|
||||
```bash
|
||||
gh issue close <NUMBER> --repo <owner>/<repo> --comment "<comment above>"
|
||||
````
|
||||
|
||||
Then **DELETE the idea file** — it has served its purpose:
|
||||
|
||||
```bash
|
||||
# ✅ Implemented files are DELETED (not moved)
|
||||
rm _ideia/viable/<NUMBER>-<title>.md
|
||||
rm _ideia/viable/<NUMBER>-<title>.requirements.md # if exists
|
||||
```
|
||||
|
||||
> **Why delete?** `viable/` only holds features that still NEED to be done. Once implemented, the commit history and CHANGELOG are the source of truth. Keeping the file would be confusing.
|
||||
|
||||
### 5.3 Finalize & Push
|
||||
|
||||
After implementing all approved features:
|
||||
|
||||
1. **Update CHANGELOG.md** on the release branch with all new feature entries
|
||||
2. Push the release branch: `git push origin release/vX.Y.Z`
|
||||
3. Run `/generate-release` workflow Phase 1 steps 7–10 (tests → commit → push → open PR to main → wait for user)
|
||||
|
||||
### 5.4 Final Summary Report
|
||||
|
||||
Present a final summary report to the user:
|
||||
|
||||
| Issue | Title | Verdict | Action | Commit |
|
||||
| ----- | ----- | --------------- | -------------------------------------------------- | --------- |
|
||||
| #N | Title | ✅ Implemented | Issue closed, idea file deleted | `abc1234` |
|
||||
| #N | Title | ⏭️ Deferred | Issue closed + saved in `_ideia/defer/` | — |
|
||||
| #N | Title | ❌ Not Fit | Issue closed + saved in `_ideia/notfit/` | — |
|
||||
| #N | Title | 🔁 Exists | Issue closed + saved in `_ideia/notfit/` | — |
|
||||
| #N | Title | ❓ Needs Detail | Issue OPEN, moved to `_ideia/viable/need_details/` | — |
|
||||
|
||||
Include:
|
||||
|
||||
- Total features harvested
|
||||
- Total ideas cataloged (`viable/need_details/` + `defer/` + `notfit/`)
|
||||
- Total features implemented (idea files deleted, issues closed)
|
||||
- Total features deferred
|
||||
- Total issues closed
|
||||
- Total issues left open (needs detail only — viable are closed after implementation)
|
||||
- Test results (pass/fail count)
|
||||
51
.agents/skills/issue-triage/SKILL.md
Normal file
51
.agents/skills/issue-triage/SKILL.md
Normal file
@@ -0,0 +1,51 @@
|
||||
---
|
||||
name: issue-triage-cx
|
||||
description: How to respond to GitHub issues with insufficient information
|
||||
---
|
||||
|
||||
# Issue Triage Workflow
|
||||
|
||||
Respond to GitHub issues that need more information before they can be investigated.
|
||||
|
||||
## Steps
|
||||
|
||||
### 1. Identify issues needing triage
|
||||
|
||||
```bash
|
||||
gh issue list --state open --limit 20
|
||||
```
|
||||
|
||||
### 2. Evaluate each issue
|
||||
|
||||
Check if the issue has:
|
||||
|
||||
- Clear reproduction steps
|
||||
- Environment details (OS, Node.js version, OmniRoute version)
|
||||
- Error logs/screenshots
|
||||
- Expected vs actual behavior
|
||||
|
||||
### 3. Respond with triage template
|
||||
|
||||
For issues missing information:
|
||||
|
||||
```markdown
|
||||
Thank you for reporting this issue! To help us investigate, please provide:
|
||||
|
||||
1. **OmniRoute version**: (`omniroute --version`)
|
||||
2. **Node.js version**: (`node --version`)
|
||||
3. **Operating system**: (e.g., Ubuntu 24.04, macOS 15, Windows 11)
|
||||
4. **Installation method**: (npm, Docker, source)
|
||||
5. **Steps to reproduce**: (exact commands/actions that trigger the issue)
|
||||
6. **Error logs**: (paste relevant logs from the console)
|
||||
7. **Expected behavior**: (what should happen)
|
||||
|
||||
This will help us debug and resolve your issue faster. 🙏
|
||||
```
|
||||
|
||||
### 4. Label the issue
|
||||
|
||||
Add appropriate labels: `needs-info`, `bug`, `enhancement`, `question`, etc.
|
||||
|
||||
```bash
|
||||
gh issue edit <NUMBER> --add-label "needs-info"
|
||||
```
|
||||
173
.agents/skills/resolve-issues/SKILL.md
Normal file
173
.agents/skills/resolve-issues/SKILL.md
Normal file
@@ -0,0 +1,173 @@
|
||||
---
|
||||
name: resolve-issues-cx
|
||||
description: Fetch all open GitHub issues, analyze bugs, resolve what's possible, triage the rest, wait for user validation, then commit and release
|
||||
---
|
||||
|
||||
# /resolve-issues — Automated Issue Resolution Workflow
|
||||
|
||||
## Overview
|
||||
|
||||
This workflow fetches all open issues from the project's GitHub repository, classifies them, analyzes bugs, proposes a resolution plan, waits for user validation, and ONLY THEN implements the fixes, commits, and closes the issues on the current release branch (`release/vX.Y.Z`). It does NOT merge or release automatically — the release branch is later merged via PR to main.
|
||||
|
||||
## Codex Execution Notes
|
||||
|
||||
- Treat `// turbo` / `// turbo-all` as instructions to use `multi_tool_use.parallel` for independent reads, checks, and GitHub calls.
|
||||
- The initial report/plan is a hard stop. Do not edit code, close issues, or commit until the user explicitly approves the report.
|
||||
- Keep classification and bug analysis bounded enough to produce the user-facing report before deep implementation work.
|
||||
|
||||
> **BRANCH RULE**: All work MUST happen on the current `release/vX.Y.Z` branch. Never create separate `fix/` branches. If no release branch exists yet, create one first using `/generate-release` Phase 1 steps 1–5.
|
||||
|
||||
> **⛔ PR PROHIBITION**: If a fix is associated with a contributor's PR, you MUST merge their PR — NEVER close it and re-implement the fix yourself. See `/review-prs` workflow for the full policy. The `gh pr close` command is FORBIDDEN unless the repository owner explicitly requests it.
|
||||
|
||||
## Steps
|
||||
|
||||
### 1. Identify the GitHub Repository
|
||||
|
||||
// turbo
|
||||
|
||||
- Run: `git -C <project_root> remote get-url origin` to extract the owner/repo
|
||||
- Parse the owner and repo name from the URL
|
||||
|
||||
### 2. Ensure Release Branch Exists
|
||||
|
||||
// turbo
|
||||
|
||||
Before doing any work, ensure you are on the current release branch:
|
||||
|
||||
```bash
|
||||
# Check current branch
|
||||
git branch --show-current
|
||||
|
||||
# If on main, determine next version and create the release branch
|
||||
VERSION=$(node -p "require('./package.json').version")
|
||||
NEXT=$(node -p "const [a,b,c]=('$VERSION').split('.').map(Number); c>=9?a+'.'+(b+1)+'.0':a+'.'+b+'.'+(c+1)")
|
||||
git checkout -b release/v$NEXT
|
||||
npm version patch --no-git-tag-version
|
||||
npm install
|
||||
```
|
||||
|
||||
If already on a `release/vX.Y.Z` branch, continue working there.
|
||||
|
||||
### 3. Fetch All Open Issues
|
||||
|
||||
// turbo-all
|
||||
|
||||
**⚠️ CRITICAL**: The JSON output of `gh issue list` can be truncated by the tool, silently hiding issues. You MUST use the two-step approach below to guarantee **all** issues are fetched.
|
||||
|
||||
**Step 3a — Get Issue numbers only** (small output, never truncated):
|
||||
|
||||
- Run: `gh issue list --repo <owner>/<repo> --state open --limit 500 --json number --jq '.[].number'`
|
||||
- This outputs one issue number per line. Count them and confirm total.
|
||||
|
||||
**Step 3b — Fetch full metadata for each Issue** (one call per issue):
|
||||
|
||||
- For each issue number from step 3a, run:
|
||||
`gh issue view <NUMBER> --repo <owner>/<repo> --json number,title,labels,body,comments,createdAt,author`
|
||||
- You may batch these into parallel calls (up to 4 at a time).
|
||||
- Sort by oldest first (FIFO).
|
||||
|
||||
### 4. Classify Each Issue
|
||||
|
||||
For each issue, determine its type:
|
||||
|
||||
- **Bug** — Has `bug` label, or body contains error messages, stack traces, "doesn't work", "broken", "crash", "error"
|
||||
- **Feature Request** — Has `enhancement`/`feature` label, or body describes new functionality
|
||||
- **Question** — Has `question` label, or is asking "how to" something
|
||||
- **Other** — Anything else
|
||||
|
||||
Focus ONLY on **Bugs** for resolution. Feature requests and questions should be skipped with a note in the final report.
|
||||
|
||||
### 5. Deep-Read Each Bug Issue (One-by-One Analysis)
|
||||
|
||||
**IMPORTANT**: Read each bug issue thoroughly, one at a time, before moving to the next. This is NOT a batch process — each issue needs focused attention.
|
||||
|
||||
#### 5a. Understand the Problem
|
||||
|
||||
For each bug issue, perform the full analysis:
|
||||
|
||||
1. **Read the entire body** — including Description, Steps to Reproduce, Expected/Actual Behavior, Error Logs, and Screenshots
|
||||
2. **Read ALL comments** — including bot triage comments (Kilo, etc.) and owner/community responses. Pay attention to:
|
||||
- Whether someone already responded with a fix
|
||||
- Whether a community member confirmed the issue is resolved
|
||||
- Whether the issue was marked as duplicate by a bot. **WARNING: DO NOT blindly trust bot duplicate labels (e.g., kilo-duplicate). Bots make mistakes. You MUST read the full conversation and do your own independent analysis to determine if it is truly a duplicate or a distinct bug.**
|
||||
3. **Identify the claimed error** — extract the exact error message, status code, and provider/model involved
|
||||
|
||||
#### 5b. Check Information Sufficiency
|
||||
|
||||
Verify the issue contains enough to act on:
|
||||
|
||||
- [ ] Clear description of the problem
|
||||
- [ ] Steps to reproduce OR error logs
|
||||
- [ ] Provider/model/version information
|
||||
- [ ] Expected vs actual behavior
|
||||
|
||||
#### 5c. Determine Issue Disposition
|
||||
|
||||
For each bug, classify into one of 5 actions:
|
||||
|
||||
| Disposition | When to Apply | Action |
|
||||
| ---------------------------- | ---------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------- |
|
||||
| **✅ CLOSE — Already Fixed** | Owner responded with fix + no user follow-up, OR community confirmed fix | Close with comment citing which version fixed it |
|
||||
| **✅ CLOSE — Duplicate** | You have independently verified the issue is a duplicate (do NOT rely solely on bot flags) + user provides no new info | Close referencing the original issue |
|
||||
| **✅ CLOSE — Stale** | We requested logs/info > 7 days ago with no reply | Close thanking the user, invite to reopen if needed |
|
||||
| **📝 RESPOND — Needs Info** | Issue is real but missing critical reproduction details | Comment asking for specifics per `/issue-triage` |
|
||||
| **📝 RESPOND — User Config** | Error is caused by unsupported env (Node version, wrong model path, missing API enablement) | Comment explaining the user-side fix |
|
||||
| **🔧 FIX — Code Change** | Root cause is confirmed in the codebase | Research, propose solution in report, wait for approval |
|
||||
|
||||
#### 5d. For "FIX — Code Change" Issues
|
||||
|
||||
Before coding, perform deep source analysis to formulate a plan:
|
||||
|
||||
1. **Search the codebase** — `grep_search` for error strings, relevant function names, affected files
|
||||
2. **Search the web** — for upstream API changes, SDK updates, or breaking changes that explain the bug
|
||||
3. **Read the full source file** — don't rely on grep snippets; understand the surrounding logic
|
||||
4. **Verify the root cause** — confirm the bug is reproducible based on the code, not just a user misconfiguration
|
||||
5. **Formulate a proposed solution** — detail the exact files and lines you will change and how you will solve it.
|
||||
6. **Create an Implementation Plan file** — write your proposed solution to `_tasks/features-vX.Y.Z/<ISSUE_NUMBER>-<short-description>.plan.md` (e.g. `_tasks/features-v3.7.6/1810-auto-restore-probe-failed-db.plan.md`) where `vX.Y.Z` is the current branch version. The plan should contain an Overview, Pre-Implementation Checklist, and detailed Implementation Steps (Files, Changes).
|
||||
7. **DO NOT modify the codebase yet** — wait for user approval on your report and plan first.
|
||||
|
||||
#### 5e. For "RESPOND" Issues
|
||||
|
||||
Post a substantive comment that:
|
||||
|
||||
- Acknowledges the specific error they reported
|
||||
- Explains the likely root cause
|
||||
- Provides concrete steps to resolve (version upgrade, env var fix, model path correction)
|
||||
- Asks for follow-up info if needed
|
||||
|
||||
**Do NOT post generic template responses.** Every comment should reference the user's specific error messages and environment.
|
||||
|
||||
### 6. Generate Report & Wait for Validation
|
||||
|
||||
Present a summary report to the user detailing your proposed actions. For any bugs that need fixing, explicitly explain your proposed solution (files to change and logic) and point out that it will be implemented on the release branch (`release/vX.Y.Z`) after approval.
|
||||
|
||||
| Issue | Title | Status | Proposed Action / Version |
|
||||
| ----- | ----- | ------------- | ----------------------------------------- |
|
||||
| #N | Title | ✅ Close | Already fixed / duplicate (explain why) |
|
||||
| #N | Title | 🔧 Propose | Explanation of the code fix to be applied |
|
||||
| #N | Title | 📝 Respond | Guidance comment to be posted |
|
||||
| #N | Title | ❓ Needs Info | Triage comment to be posted |
|
||||
| #N | Title | ⏭️ Skip | Feature request / not a bug |
|
||||
|
||||
> **⚠️ IMPORTANT**: Do NOT implement code changes, commit, push, or close issues at this step.
|
||||
> Wait for the user to review the proposed fixes and respond with **OK** before proceeding.
|
||||
|
||||
- If the user says **OK** or approves → Proceed to step 7
|
||||
- If the user requests changes → Adjust the proposed solution and present the report again
|
||||
- If the user rejects → Revert any accidental changes and stop
|
||||
|
||||
### 7. Implement Fixes, Run Tests & Commit (only after user approval)
|
||||
|
||||
After the user validates and gives the OK:
|
||||
|
||||
1. **Implement the fixes** — modify the codebase according to the approved plan.
|
||||
2. **Run tests** — `npm run test:all` (or the specific test file) to ensure 100% pass.
|
||||
3. **Update CHANGELOG.md** with all new bug fix entries.
|
||||
4. **Commit** each fix individually on the release branch with message format: `fix: <description> (#<issue_number>)`.
|
||||
5. **Push** the release branch: `git push origin release/vX.Y.Z`.
|
||||
6. **Close resolved issues immediately**. For each issue that was marked as Fixed, run:
|
||||
`gh issue close <NUMBER> --repo <owner>/<repo> --comment "Thank you for reporting! This issue has been fixed and will be included in the next release (vX.Y.Z)."`
|
||||
7. Likewise, close `Duplicate` issues referencing the original, close `Needs Info` if stale, and post the required comments.
|
||||
8. If the project runs automatic releases or needs a PR, proceed to run `/generate-release` workflow Phase 1 steps 7–10 (tests → commit → push → open PR to main → wait for user).
|
||||
|
||||
If NO fixes were committed, skip closing and source control steps and just conclude the workflow.
|
||||
126
.agents/skills/review-discussions/SKILL.md
Normal file
126
.agents/skills/review-discussions/SKILL.md
Normal file
@@ -0,0 +1,126 @@
|
||||
---
|
||||
name: review-discussions-cx
|
||||
description: Read all open GitHub Discussions, summarize them, respond to pending ones, and create issues from actionable feature requests
|
||||
---
|
||||
|
||||
# /review-discussions — GitHub Discussions Review & Response Workflow
|
||||
|
||||
## Overview
|
||||
|
||||
This workflow reads all open GitHub Discussions, generates a categorized summary, identifies which ones need a response, drafts and posts replies, and optionally creates issues from actionable feature requests. It follows the same flow used for Issues but adapted for the Discussions forum.
|
||||
|
||||
> **Tool mapping note (v3.8):** Where steps below say `browser_subagent`, modern runtimes should substitute with the `gh` CLI — `gh api graphql` for reading discussions and mutations for posting comments. `WebFetch` is acceptable for read-only HTML scraping when GraphQL is overkill, but prefer `gh` for any write actions.
|
||||
|
||||
## Codex Execution Notes
|
||||
|
||||
- Treat `// turbo` / `// turbo-all` as instructions to use `multi_tool_use.parallel` for independent reads and GitHub/browser fetches.
|
||||
- The summary report is a hard stop. Do not post discussion replies or create issues until the user explicitly approves.
|
||||
|
||||
// turbo-all
|
||||
|
||||
## Steps
|
||||
|
||||
### 1. Identify the GitHub Repository
|
||||
|
||||
- Run: `git -C <project_root> remote get-url origin` to extract the owner/repo
|
||||
- Parse the owner and repo name from the URL
|
||||
|
||||
### 2. Fetch All Open Discussions
|
||||
|
||||
- Use `WebFetch` to fetch `https://github.com/<owner>/<repo>/discussions`
|
||||
- Parse the discussion list to get all discussion titles, IDs, authors, categories, and dates
|
||||
- For each discussion, fetch the individual page to read the full content and all comments/replies
|
||||
|
||||
### 3. Summarize All Discussions
|
||||
|
||||
For each discussion, extract:
|
||||
|
||||
- **Title** and **#Number**
|
||||
- **Author** (GitHub username)
|
||||
- **Category** (Announcements, General, Ideas, Q&A, Show and tell)
|
||||
- **Date** created
|
||||
- **Summary** of the original post (1-2 sentences)
|
||||
- **Comments count** and key participants
|
||||
- **Your previous response** (if any)
|
||||
- **Pending action** — whether a response or follow-up is needed
|
||||
|
||||
### 4. Present Summary Report to User
|
||||
|
||||
Present the full summary to the user organized by category, using a table:
|
||||
|
||||
| # | Category | Title | Author | Date | Status |
|
||||
| --- | -------- | ----- | ------ | ------ | ----------------- |
|
||||
| #N | Ideas | Title | @user | Mar 23 | ⚠️ Needs response |
|
||||
| #N | Q&A | Title | @user | Mar 9 | ✅ Answered |
|
||||
| #N | General | Title | @user | Mar 19 | ⚠️ Needs response |
|
||||
|
||||
Highlight:
|
||||
|
||||
- **⚠️ Needs response** — No reply from maintainer, or a follow-up comment was left unanswered
|
||||
- **✅ Answered** — Maintainer already responded
|
||||
- **🐛 Bug reported** — A bug was mentioned that needs tracking
|
||||
- **💡 Actionable** — Contains a concrete feature request that could become an issue
|
||||
|
||||
### 5. Draft & Post Responses
|
||||
|
||||
For each discussion that needs a response, draft a reply following these guidelines:
|
||||
|
||||
#### Response Style
|
||||
|
||||
- **Friendly and professional** — Start with "Hey @username!"
|
||||
- **Acknowledge the contribution** — Thank the user for their input
|
||||
- **Be specific** — Reference existing features, settings, or dashboard pages if the feature already exists
|
||||
- **Provide workarounds** — If the request isn't implemented yet, suggest current alternatives
|
||||
- **Commit to action** — If the request is valid, state that you'll open an issue or add it to the roadmap
|
||||
- **Keep it concise** — 3-5 paragraphs max
|
||||
|
||||
#### Posting via Browser
|
||||
|
||||
- Use `browser_subagent` to navigate to each discussion and post the comment
|
||||
- **IMPORTANT**: When typing text in GitHub comment boxes via the browser, use only plain ASCII characters:
|
||||
- Use regular hyphens `-` instead of em-dashes
|
||||
- Use `->` instead of arrow symbols
|
||||
- Do NOT use emoji Unicode characters (the browser keyboard may fail on them)
|
||||
- Use `**bold**` and `\`code\`` markdown formatting
|
||||
- Click the green "Comment" button (or "Reply" for threaded replies) after typing
|
||||
- Verify the comment was posted by checking the page shows the new comment
|
||||
|
||||
### 6. Create Issues from Actionable Feature Requests
|
||||
|
||||
For discussions that contain concrete, actionable feature requests:
|
||||
|
||||
1. Ask the user which ones should become issues
|
||||
2. For each approved request, create a GitHub issue via `browser_subagent`:
|
||||
- Navigate to `https://github.com/<owner>/<repo>/issues/new`
|
||||
- **Title**: `<Feature Name> - <Short description>`
|
||||
- **Body** should include:
|
||||
- `## Feature Request` header
|
||||
- `**Source:** Discussion #N by @author`
|
||||
- `## Problem` — What limitation the user hit
|
||||
- `## Proposed Solution` — How it could work
|
||||
- `### Implementation Ideas` — Technical approach
|
||||
- `### Current Workarounds` — What users can do today
|
||||
- `## Additional Context` — Links to related issues/discussions
|
||||
- Add `enhancement` label
|
||||
- Click "Submit new issue" / "Create"
|
||||
3. After creation, go back to the original discussion and post a comment linking to the new issue:
|
||||
- "I've opened Issue #N to track this feature request. Follow along there for updates!"
|
||||
|
||||
### 7. Final Report
|
||||
|
||||
Present a final summary to the user:
|
||||
|
||||
| Discussion | Action Taken |
|
||||
| ---------- | ---------------------------------- |
|
||||
| #N — Title | Responded with workarounds |
|
||||
| #N — Title | Responded + created Issue #N |
|
||||
| #N — Title | Already answered, no action needed |
|
||||
| #N — Title | Responded to follow-up comment |
|
||||
|
||||
## Notes
|
||||
|
||||
- This workflow is **interactive** — always present the summary and wait for user approval before posting responses or creating issues
|
||||
- If the user says "pode responder" (or similar approval), proceed with posting all drafted responses
|
||||
- For discussions in non-English languages, respond in the same language as the original post
|
||||
- Always reference specific dashboard paths, config options, or code files when explaining existing features
|
||||
- When a discussion reveals a bug, note it separately from feature requests
|
||||
268
.agents/skills/review-prs/SKILL.md
Normal file
268
.agents/skills/review-prs/SKILL.md
Normal file
@@ -0,0 +1,268 @@
|
||||
---
|
||||
name: review-prs-cx
|
||||
description: Analyze open Pull Requests from the project's GitHub repository, generate a critical report, and optionally implement approved changes
|
||||
---
|
||||
|
||||
# /review-prs — PR Review & Analysis Workflow
|
||||
|
||||
## ⛔ ABSOLUTE PROHIBITION — Read Before Anything Else
|
||||
|
||||
> **NEVER close a contributor's PR if you intend to use ANY of their code, ideas, or fixes.**
|
||||
>
|
||||
> **NEVER manually integrate contributor code into a release branch and then close their PR.**
|
||||
>
|
||||
> These actions are **STRICTLY FORBIDDEN** under all circumstances:
|
||||
>
|
||||
> 1. ❌ Closing a PR and cherry-picking/copying its code into a release branch
|
||||
> 2. ❌ Closing a PR "because of conflicts" and re-implementing the same fix yourself
|
||||
> 3. ❌ Closing a PR and committing a "similar" solution inspired by it
|
||||
> 4. ❌ Using `gh pr close` on any PR whose content was or will be used
|
||||
>
|
||||
> **Why**: Closing a PR after taking the contributor's work means they get ZERO credit on GitHub — no "Merged" badge, no contribution graph entry, no public record. This is effectively stealing their contribution. An audit found this happened to **37 PRs** in the past.
|
||||
>
|
||||
> **The ONLY acceptable flow**: Resolve conflicts IN the contributor's branch, push fixes TO their branch, then merge THEIR PR via `gh pr merge`. See Step 7 and Step 8 for the exact procedure.
|
||||
>
|
||||
> **When to close a PR**: ONLY when the user (repository owner) explicitly requests it, OR when the PR is clearly spam/malicious, OR when the author themselves asks to close it. In ALL other cases, leave it open.
|
||||
|
||||
## Overview
|
||||
|
||||
This workflow fetches all open PRs from the project's GitHub repository, performs a critical analysis of each one, generates a detailed report, and waits for user approval before proceeding with implementation. **All improvements are committed on the current release branch** (`release/vX.Y.Z`).
|
||||
|
||||
> **BRANCH RULE**: PRs are ALWAYS merged into the current `release/vX.Y.Z` branch, NEVER directly into `main`. The release branch acts as a staging area — only after all PRs are integrated and tests pass does the release branch get merged into `main` via the `/generate-release` workflow.
|
||||
|
||||
## Codex Execution Notes
|
||||
|
||||
The source Claude command uses `// turbo` and `// turbo-all` as execution hints. In Codex, treat them explicitly as follows:
|
||||
|
||||
- `// turbo`: batch independent local reads and small `gh`/`git` calls with `multi_tool_use.parallel`.
|
||||
- `// turbo-all`: fan out independent per-PR/per-issue calls in practical batches, usually up to 4 GitHub calls at a time.
|
||||
- Do not expand Step 4 into exhaustive CI-log debugging before Step 6. Fetch numbers, metadata, diffs/review comments, quick merge/conflict status, and only inspect extra logs when they directly affect the verdict.
|
||||
- Step 6 is a hard stop. In Codex, present the report in the final response and wait for the user before Step 7/8.
|
||||
- Do not checkout PR branches, edit files, post PR comments, close PRs, merge, cherry-pick, or run broad fix/test loops until the user explicitly approves the report.
|
||||
- If `gh pr diff` is too large, record the limit and use `gh pr view --json files` plus `git fetch refs/pull/...` with `git diff --stat` / `git diff --name-status`; only read targeted hunks needed for confirmed findings.
|
||||
|
||||
## Steps
|
||||
|
||||
### 1. Identify the GitHub Repository
|
||||
|
||||
- Read `package.json` to get the repository URL, or use the git remote origin URL
|
||||
// turbo
|
||||
- Run: `git -C <project_root> remote get-url origin` to extract the owner/repo
|
||||
|
||||
### 2. Ensure Release Branch Exists
|
||||
|
||||
// turbo
|
||||
|
||||
Before doing any work, ensure you are on the current release branch:
|
||||
|
||||
```bash
|
||||
# Check current branch
|
||||
git branch --show-current
|
||||
|
||||
# If on main, determine next version and create the release branch
|
||||
VERSION=$(node -p "require('./package.json').version")
|
||||
# Bump patch: e.g. 3.3.11 → 3.3.12
|
||||
NEXT=$(node -p "const [a,b,c]=('$VERSION').split('.').map(Number); c>=9?a+'.'+(b+1)+'.0':a+'.'+b+'.'+(c+1)")
|
||||
git checkout -b release/v$NEXT
|
||||
npm version patch --no-git-tag-version
|
||||
npm install
|
||||
```
|
||||
|
||||
If already on a `release/vX.Y.Z` branch, continue working there.
|
||||
|
||||
### 3. Fetch Open Pull Requests
|
||||
|
||||
// turbo-all
|
||||
|
||||
**⚠️ CRITICAL**: The JSON output of `gh pr list` can be truncated by the tool, silently hiding PRs. You MUST use the two-step approach below to guarantee **all** PRs are fetched.
|
||||
|
||||
**Step 3a — Get PR numbers only** (small output, never truncated):
|
||||
|
||||
- Run: `gh pr list --repo <owner>/<repo> --state open --limit 500 --json number --jq '.[].number'`
|
||||
- This outputs one PR number per line. Count them and confirm total.
|
||||
|
||||
**Step 3b — Fetch full metadata for each PR** (one call per PR):
|
||||
|
||||
- For each PR number from step 3a, run:
|
||||
`gh pr view <NUMBER> --repo <owner>/<repo> --json number,title,author,headRefName,baseRefName,body,createdAt,additions,deletions,files`
|
||||
- You may batch these into parallel calls (up to 4 at a time).
|
||||
|
||||
**Step 3c — Fetch diffs for each PR** (one call per PR, saved to /tmp):
|
||||
|
||||
- For each PR number, run:
|
||||
`gh pr diff <NUMBER> --repo <owner>/<repo> > /tmp/pr<NUMBER>.diff`
|
||||
- Then read each diff file with the appropriate file-read tool (`Read` in Claude Code; equivalent in your agent runtime).
|
||||
|
||||
- For each open PR, collect:
|
||||
- PR number, title, author, branch, number of commits, date
|
||||
- PR description/body
|
||||
- Files changed (diff)
|
||||
- Existing review comments (from bots or humans)
|
||||
|
||||
**Verification**: Confirm the count of PRs analyzed matches the count from step 3a before proceeding.
|
||||
|
||||
### 3.5 Redirect PR Base Branches to Release Branch
|
||||
|
||||
// turbo-all
|
||||
|
||||
**⚠️ CRITICAL**: Contributors typically open PRs targeting `main`. Before analyzing or merging, redirect ALL open PRs to target the current release branch instead.
|
||||
|
||||
```bash
|
||||
# Get the current release branch name
|
||||
RELEASE_BRANCH=$(git branch --show-current) # e.g. release/v3.5.4
|
||||
|
||||
# For each open PR that targets main, change its base to the release branch
|
||||
for PR_NUM in $(gh pr list --repo <owner>/<repo> --state open --json number,baseRefName --jq '.[] | select(.baseRefName == "main") | .number'); do
|
||||
echo "Redirecting PR #$PR_NUM → $RELEASE_BRANCH"
|
||||
gh pr edit "$PR_NUM" --repo <owner>/<repo> --base "$RELEASE_BRANCH"
|
||||
done
|
||||
```
|
||||
|
||||
This ensures:
|
||||
|
||||
1. PRs merge into the release branch, not directly into `main`
|
||||
2. Merge conflict detection is accurate against the release branch
|
||||
3. The release branch accumulates all changes before the final merge to `main`
|
||||
4. If the release branch doesn't exist on remote yet, push it first: `git push origin $RELEASE_BRANCH`
|
||||
|
||||
### 4. Analyze Each PR — For each open PR, perform the following analysis:
|
||||
|
||||
#### 4a. Feature Assessment
|
||||
|
||||
- **Does it make sense?** Evaluate if the feature fills a real gap or solves a valid problem
|
||||
- **Alignment** — Check if it aligns with the project's architecture and roadmap
|
||||
- **Complexity** — Assess if the scope is reasonable or if it should be split
|
||||
|
||||
#### 4b. Code Quality Review
|
||||
|
||||
- Check for code duplication
|
||||
- Evaluate error handling patterns (consistent with existing codebase?)
|
||||
- Check naming conventions and code style
|
||||
- Verify TypeScript types (any `any` usage, missing types?)
|
||||
|
||||
#### 4c. Security Review
|
||||
|
||||
- Check for missing authentication/authorization on new endpoints
|
||||
- Check for injection vulnerabilities (URL params, SQL, XSS)
|
||||
- Verify input validation on all user-controlled data
|
||||
- Check for hardcoded secrets or credentials
|
||||
|
||||
#### 4d. Architecture Review
|
||||
|
||||
- Does the change follow existing patterns?
|
||||
- Are there any breaking changes to public APIs?
|
||||
- Is the database schema affected? Migration needed?
|
||||
- Impact on performance (N+1 queries, missing indexes?)
|
||||
|
||||
#### 4e. Test Coverage
|
||||
|
||||
- Does the PR include tests?
|
||||
- Are edge cases covered?
|
||||
- Would existing tests break?
|
||||
|
||||
#### 4f. Cross-Layer (Global) Analysis
|
||||
|
||||
Perform a **global impact assessment** to verify whether the PR changes are complete across all layers of the application:
|
||||
|
||||
- **Backend → Frontend check**: If the PR adds or modifies backend-only resources (new endpoints, services, data models), evaluate whether corresponding frontend changes are missing:
|
||||
- Does a new endpoint require a new screen/page in the dashboard?
|
||||
- Should there be a new action button, menu item, or navigation link?
|
||||
- Are there new data fields that should be displayed or editable in the UI?
|
||||
- Does a new feature need a toggle, configuration panel, or status indicator?
|
||||
- **Frontend → Backend check**: If the PR adds frontend elements, verify the backend support exists:
|
||||
- Are the required API endpoints implemented?
|
||||
- Is the data model sufficient for the new UI components?
|
||||
- **Cross-cutting concerns**: Check shared layers (types, DTOs, validation schemas, routes, middleware) for completeness
|
||||
- **Document gaps** — If missing layers are detected, list them as **IMPORTANT** issues in the report with concrete suggestions for what should be added
|
||||
|
||||
### 5. Generate Report — Create a markdown report for each PR including:
|
||||
|
||||
- **PR Summary** — What it does, files affected, commit count
|
||||
- **Improvements/Benefits** — Numbered list with impact level (HIGH/MEDIUM/LOW)
|
||||
- **Risks & Issues** — Categorized as CRITICAL / IMPORTANT / MINOR
|
||||
- **Scoring Table** — Rate across: Feature Relevance, Code Quality, Security, Robustness, Tests
|
||||
- **Verdict** — Ready to merge? With mandatory vs optional fixes
|
||||
- **Next Steps** — What will happen if approved
|
||||
|
||||
### 6. Present to User
|
||||
|
||||
- Show the report in the final response and stop. Mark this as a blocking checkpoint awaiting explicit user approval before continuing.
|
||||
- Wait for user decision:
|
||||
- **Approved** → Proceed to step 7
|
||||
- **Approved with changes** → Implement the fixes and corrections before merging
|
||||
- **Rejected** → Close the PR or leave a review comment
|
||||
|
||||
### 7. Pre-Merge Fixes & CI Green-Lighting (if approved)
|
||||
|
||||
> **⚠️ Fixes and Conflict Resolutions MUST be pushed back to the PR branch before merging.** We want the PR itself to be green and fully valid before it integrates.
|
||||
|
||||
- **Sync latest fixes & Resolve Conflicts:** Merge the current `release` branch into the PR branch. If there are merge conflicts, you MUST resolve them inside the author's PR branch. NEVER resolve conflicts by closing their PR and doing the work in a separate branch, as this steals credit from the original author.
|
||||
- **Implement improvements:** Apply the required fixes identified in the analysis directly on the PR branch (e.g., adding missing API routes, fixing SSRF, applying comments from other agents).
|
||||
- **Pushing changes to PR branches:**
|
||||
|
||||
```bash
|
||||
# Checkout the PR locally
|
||||
gh pr checkout <NUMBER>
|
||||
|
||||
# Apply fixes, commit your changes
|
||||
git commit -m "chore: apply review suggestions and missing layers"
|
||||
|
||||
# Attempt to push directly to the PR branch
|
||||
git push
|
||||
```
|
||||
|
||||
- **Fallback (ONLY for external forks without maintainer edit access):**
|
||||
Using `cherry-pick` instead of fixing the contributor's PR directly is a **LAST RESORT**. You MUST ALWAYS attempt to `git push` your fixes to their branch first.
|
||||
**ONLY if `git push` explicitly fails with a permission/access error** (meaning the contributor unchecked "Allow edits from maintainers" or it's a locked fork), you may use `git cherry-pick` to bring their changes into the release branch and fix the issues locally.
|
||||
Even then, ensure you preserve the contributor's authorship (`git commit --author="Contributor Name <email>"` if creating new commits).
|
||||
Once you have integrated their work into the release branch, **DO NOT close their PR**. Leave it open so the contributor retains credit. Under NO CIRCUMSTANCES should you use `gh pr close`.
|
||||
|
||||
- Run the project's test suite locally to verify nothing breaks:
|
||||
// turbo
|
||||
- Run: `npm test` or equivalent test command
|
||||
|
||||
### 8. Merge into Release Branch (NEVER CLOSE!)
|
||||
|
||||
> **⚠️ CRITICAL**: NEVER use `gh pr close` for a PR whose idea or code was accepted. Closing a PR in a contributor's face after taking their idea—or closing it just because it had conflicts—is unacceptable.
|
||||
> You MUST ALWAYS resolve conflicts and apply fixes ON THE AUTHOR'S PR BRANCH (unless explicitly locked from edits), and then merge the PR using GitHub so the contributor gets the official "Merged" badge and proper credit on their profile. **Do not use cherry-pick just because it is "easier" than resolving conflicts on their branch.**
|
||||
|
||||
Even if the PR had severe conflicts or required significant architectural adjustments, you MUST:
|
||||
|
||||
1. Resolve any conflicts and apply the fixes directly to their PR branch (as detailed in step 7) or use cherry-picking into the release branch.
|
||||
2. If you managed to fix their branch, merge it into the release branch using the GitHub CLI:
|
||||
`gh pr merge <NUMBER> --repo <owner>/<repo> --squash --body "Integrated into release/vX.Y.Z"`
|
||||
3. If you had to use cherry-picking because you couldn't push to their branch, DO NOT close the PR. GitHub will sometimes auto-detect the cherry-picked commits and mark it as Merged. If it doesn't, leave it open. The repository owner will handle it. NEVER run `gh pr close`.
|
||||
|
||||
In ALL cases:
|
||||
|
||||
- Post a **thank-you comment** on the PR via the GitHub API before or immediately after merging.
|
||||
- The message should:
|
||||
- Thank the author by name/username for their contribution.
|
||||
- Explain what was adjusted or improved (if we pushed fixes to their branch or cherry-picked).
|
||||
- Note it will be included in the upcoming release.
|
||||
- Be friendly, professional, and encouraging.
|
||||
|
||||
> **⚠️ MANDATORY CHANGELOG CREDIT**: When cherry-picking is used (because the PR branch couldn't be pushed to or `gh pr merge` failed), the contributor does NOT get the automatic GitHub "Merged" badge. In this case, you MUST compensate by adding an explicit entry to `CHANGELOG.md` in the `[Unreleased]` section with `(#PR_NUMBER — thanks @username)` format. This ensures the contributor gets public credit in the release notes even if GitHub doesn't auto-detect the cherry-pick. This is NOT optional — skipping it effectively erases the contributor's work from the release record.
|
||||
|
||||
### 9. Sync Local Release Branch
|
||||
|
||||
After merging PRs, sync the local release branch to include the new changes:
|
||||
|
||||
```bash
|
||||
git fetch origin
|
||||
git pull origin release/vX.Y.Z
|
||||
```
|
||||
|
||||
### 10. Continue or Finalize
|
||||
|
||||
After processing all approved PRs:
|
||||
|
||||
- If more PRs remain, go back to step 7
|
||||
- When all PRs are processed, **update CHANGELOG.md** on the release branch with all new entries
|
||||
- Run **test coverage** to verify the gate (≥75% statements/lines/functions, ≥70% branches — measured ~82%):
|
||||
```bash
|
||||
npm run test:coverage
|
||||
```
|
||||
- Fix any test regressions introduced by merged PRs
|
||||
- Run `/generate-release` workflow Phase 1 steps 7–10 (tests → commit → push → open PR to main → wait for user)
|
||||
- The `/generate-release` workflow handles the final merge from `release/vX.Y.Z` → `main`
|
||||
347
.agents/skills/version-bump/SKILL.md
Normal file
347
.agents/skills/version-bump/SKILL.md
Normal file
@@ -0,0 +1,347 @@
|
||||
---
|
||||
name: version-bump-cx
|
||||
description: Bump version, auto-generate CHANGELOG from git commits, update all versioned files, and refresh root + docs/ documentation to reflect the current project state
|
||||
---
|
||||
|
||||
# Version Bump Workflow
|
||||
|
||||
Automatically bump the project version, generate CHANGELOG entries from git history since the last tag, update every file that references the version, and refresh project documentation to reflect the current state.
|
||||
|
||||
## Codex Execution Notes
|
||||
|
||||
- Treat `// turbo` / `// turbo-all` as instructions to use `multi_tool_use.parallel` for independent reads, checks, and GitHub calls.
|
||||
- Any user-approval phase is a hard stop: present the report/status in the final response and wait before committing, pushing, tagging, publishing, or deploying.
|
||||
|
||||
> **VERSION RULE: Always use PATCH bumps (3.x.y → 3.x.y+1)**
|
||||
> NEVER use `npm version minor` or `npm version major`.
|
||||
> Always use: `npm version patch --no-git-tag-version`
|
||||
> The threshold rule: when `y` reaches 10, bump to `3.(x+1).0` — e.g. `3.4.10` → `3.5.0`.
|
||||
|
||||
---
|
||||
|
||||
## Phase 1: Determine Version
|
||||
|
||||
### 1. Read current version and last tag
|
||||
|
||||
// turbo
|
||||
|
||||
```bash
|
||||
cd /home/diegosouzapw/dev/proxys/OmniRoute
|
||||
CURRENT_VERSION=$(node -p "require('./package.json').version")
|
||||
LAST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "")
|
||||
CURRENT_BRANCH=$(git branch --show-current)
|
||||
echo "Current version: $CURRENT_VERSION"
|
||||
echo "Last tag: $LAST_TAG"
|
||||
echo "Current branch: $CURRENT_BRANCH"
|
||||
```
|
||||
|
||||
### 2. Calculate new version
|
||||
|
||||
Apply the patch bump rule:
|
||||
|
||||
- If the current patch number is `9`, the new version is `3.(minor+1).0`
|
||||
- Otherwise, increment patch: `3.x.y` → `3.x.(y+1)`
|
||||
|
||||
If the version was ALREADY bumped (e.g. you are on a release branch and package.json already has the new version), **skip the npm version bump** and use the existing version.
|
||||
|
||||
### 3. Bump package.json (if needed)
|
||||
|
||||
// turbo
|
||||
|
||||
```bash
|
||||
# Only if version hasn't been bumped yet
|
||||
npm version patch --no-git-tag-version
|
||||
```
|
||||
|
||||
Or for threshold (y=10):
|
||||
|
||||
```bash
|
||||
# Manual threshold bump
|
||||
VERSION="3.X.0" # compute manually
|
||||
npm version "$VERSION" --no-git-tag-version
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Phase 2: Generate CHANGELOG from Git History
|
||||
|
||||
### 4. Collect commits since last tag
|
||||
|
||||
// turbo
|
||||
|
||||
```bash
|
||||
cd /home/diegosouzapw/dev/proxys/OmniRoute
|
||||
LAST_TAG=$(git describe --tags --abbrev=0 2>/dev/null)
|
||||
echo "=== Commits since $LAST_TAG ==="
|
||||
git log "$LAST_TAG"..HEAD --pretty=format:"%h %s" --no-merges | head -100
|
||||
echo ""
|
||||
echo "=== Merge commits ==="
|
||||
git log "$LAST_TAG"..HEAD --merges --pretty=format:"%h %s" | head -50
|
||||
```
|
||||
|
||||
### 5. Classify commits and generate CHANGELOG section
|
||||
|
||||
Analyze each commit message and classify into categories based on the conventional-commit prefix and content:
|
||||
|
||||
| Category | Patterns |
|
||||
| ------------------- | ------------------------------------------------ |
|
||||
| ✨ New Features | `feat:`, `feat(*):` |
|
||||
| 🐛 Bug Fixes | `fix:`, `fix(*):` |
|
||||
| ⚠️ Breaking Changes | `BREAKING CHANGE`, `!:` suffix |
|
||||
| 🛠️ Maintenance | `chore:`, `refactor:`, `perf:`, `build:` |
|
||||
| 🧪 Tests | `test:`, `tests:` |
|
||||
| 📝 Documentation | `docs:` |
|
||||
| 🔒 Security | `security:`, CVE references, vulnerability fixes |
|
||||
| 🌍 i18n | translation updates, locale changes |
|
||||
|
||||
For each category with entries, create a markdown section with descriptive bullet points. Use the commit messages but rewrite them to be human-readable and descriptive (not raw commit messages).
|
||||
|
||||
**If a commit references a PR number** (e.g. `#880`, `PR #885`), include it in the description.
|
||||
|
||||
### 6. Update CHANGELOG.md
|
||||
|
||||
Replace the `## [Unreleased]` section content with the generated entries, then add the new versioned section:
|
||||
|
||||
```markdown
|
||||
## [Unreleased]
|
||||
|
||||
---
|
||||
|
||||
## [NEW_VERSION] — YYYY-MM-DD
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
- **Feature name:** Description (#PR)
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- **Fix name:** Description (#PR)
|
||||
|
||||
### 🛠️ Maintenance
|
||||
|
||||
- **Item:** Description
|
||||
|
||||
---
|
||||
|
||||
## [PREVIOUS_VERSION] — YYYY-MM-DD
|
||||
|
||||
...
|
||||
```
|
||||
|
||||
The date must be today's date in `YYYY-MM-DD` format.
|
||||
|
||||
---
|
||||
|
||||
## Phase 3: Sync Version Across All Files
|
||||
|
||||
### 7. Update workspace package.json files and openapi.yaml
|
||||
|
||||
// turbo
|
||||
|
||||
```bash
|
||||
cd /home/diegosouzapw/dev/proxys/OmniRoute
|
||||
VERSION=$(node -p "require('./package.json').version")
|
||||
|
||||
# Update docs/reference/openapi.yaml version
|
||||
sed -i "s/ version: .*/ version: $VERSION/" docs/reference/openapi.yaml
|
||||
echo "✓ docs/reference/openapi.yaml → $VERSION"
|
||||
|
||||
# Update workspace packages (open-sse, electron)
|
||||
for dir in electron open-sse; do
|
||||
if [ -d "$dir" ] && [ -f "$dir/package.json" ]; then
|
||||
(cd "$dir" && npm version "$VERSION" --no-git-tag-version --allow-same-version > /dev/null)
|
||||
echo "✓ $dir/package.json → $VERSION"
|
||||
fi
|
||||
done
|
||||
|
||||
echo "✓ All workspace packages synced to $VERSION"
|
||||
```
|
||||
|
||||
### 8. Update llm.txt version references
|
||||
|
||||
// turbo
|
||||
|
||||
```bash
|
||||
cd /home/diegosouzapw/dev/proxys/OmniRoute
|
||||
VERSION=$(node -p "require('./package.json').version")
|
||||
OLD_VERSION_PATTERN='[0-9]\+\.[0-9]\+\.[0-9]\+'
|
||||
|
||||
# Update "Current version:" line
|
||||
sed -i "s/\*\*Current version:\*\* $OLD_VERSION_PATTERN/**Current version:** $VERSION/" llm.txt
|
||||
|
||||
# Update "Key Features (vX.Y.Z)" header
|
||||
sed -i "s/## Key Features (v$OLD_VERSION_PATTERN)/## Key Features (v$VERSION)/" llm.txt
|
||||
|
||||
echo "✓ llm.txt → $VERSION"
|
||||
```
|
||||
|
||||
### 9. Regenerate lock file
|
||||
|
||||
// turbo
|
||||
|
||||
```bash
|
||||
cd /home/diegosouzapw/dev/proxys/OmniRoute
|
||||
npm install
|
||||
echo "✓ Lock file regenerated"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Phase 4: Update Root Documentation
|
||||
|
||||
Based on the CHANGELOG entries generated in Phase 2, review and update these root-level files if relevant changes warrant updates:
|
||||
|
||||
### 10. Review and update root documentation files
|
||||
|
||||
For each file below, read the current content and determine if the CHANGELOG entries require any updates. Only modify files where substantive changes have occurred:
|
||||
|
||||
| File | When to update |
|
||||
| ----------------- | --------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `README.md` | New providers, major features, stats changes (test count, provider count), badges, installation instructions, feature table |
|
||||
| `AGENTS.md` | Architecture changes, new modules, new commands, new providers, new services/handlers/executors |
|
||||
| `CONTRIBUTING.md` | Dev workflow changes, new tooling, test infrastructure changes |
|
||||
| `SECURITY.md` | Security fixes, new auth mechanisms, vulnerability disclosures |
|
||||
| `llm.txt` | Provider count changes, new features, architecture changes |
|
||||
|
||||
**Update rules:**
|
||||
|
||||
- **README.md**: Update provider count, test count, feature highlights table, badges if any numbers changed. If a new provider was added, add it to the provider table. If a major feature was added, add it to the features section.
|
||||
- **AGENTS.md**: If new architecture components (handlers, executors, services, DB modules) were added, update the Architecture section. If new commands were added, update the Build/Test table.
|
||||
- **SECURITY.md**: Add new vulnerability fixes or security improvements to the relevant section.
|
||||
- **llm.txt**: Update provider count, feature list, version references.
|
||||
|
||||
### 11. Review and update docs/ files (excluding i18n/)
|
||||
|
||||
For each file in `docs/` (excluding `docs/i18n/`), review if CHANGELOG changes affect it:
|
||||
|
||||
| File | When to update |
|
||||
| --------------------------------------------- | ------------------------------------------------------------------ |
|
||||
| `docs/reference/API_REFERENCE.md` | New API endpoints, changed request/response formats |
|
||||
| `docs/architecture/ARCHITECTURE.md` | New modules, new services, changed data flow |
|
||||
| `docs/architecture/CODEBASE_DOCUMENTATION.md` | New files, architectural changes, module reorganization |
|
||||
| `docs/architecture/REPOSITORY_MAP.md` | New folders / files / one-line descriptions |
|
||||
| `docs/reference/CLI-TOOLS.md` | New CLI tool integrations, config format changes |
|
||||
| `docs/guides/USER_GUIDE.md` | UX changes, new dashboard pages, settings changes |
|
||||
| `docs/reference/PROVIDER_REFERENCE.md` | New providers (regenerate via `scripts/gen-provider-reference.ts`) |
|
||||
| `docs/frameworks/MCP-SERVER.md` | New MCP tools, changed tool signatures, scope changes |
|
||||
| `docs/frameworks/A2A-SERVER.md` | New A2A skills, protocol changes |
|
||||
| `docs/frameworks/AGENT_PROTOCOLS_GUIDE.md` | New external agent protocols supported |
|
||||
| `docs/frameworks/CLOUD_AGENT.md` | Cloud agent additions (codex-cloud, devin, jules) or API changes |
|
||||
| `docs/architecture/AUTHZ_GUIDE.md` | New route classifications, policy changes |
|
||||
| `docs/security/GUARDRAILS.md` | New guardrails registered, priority/order changes |
|
||||
| `docs/security/COMPLIANCE.md` | Audit log / retention / no-log policy changes |
|
||||
| `docs/frameworks/SKILLS.md` | Skill framework / registry / built-in skill changes |
|
||||
| `docs/frameworks/MEMORY.md` | Memory pipeline / extraction / injection / Qdrant changes |
|
||||
| `docs/frameworks/EVALS.md` | Evaluation framework changes, new evaluators |
|
||||
| `docs/frameworks/WEBHOOKS.md` | New webhook events, payload schema changes |
|
||||
| `docs/routing/REASONING_REPLAY.md` | Reasoning capture/replay pipeline changes |
|
||||
| `docs/routing/AUTO-COMBO.md` | Routing changes, new strategies, scoring weight changes |
|
||||
| `docs/architecture/RESILIENCE_GUIDE.md` | Circuit breaker / cooldown / lockout behavior changes |
|
||||
| `docs/security/STEALTH_GUIDE.md` | TLS / CLI fingerprint changes |
|
||||
| `docs/ops/TUNNELS_GUIDE.md` | Cloudflare tunnel feature changes |
|
||||
| `docs/guides/ELECTRON_GUIDE.md` | Electron build / signing / packaging changes |
|
||||
| `docs/guides/TROUBLESHOOTING.md` | New known issues, resolved problems |
|
||||
| `docs/ops/RELEASE_CHECKLIST.md` | Process changes |
|
||||
| `docs/ops/COVERAGE_PLAN.md` | Coverage gate adjustments, target metrics |
|
||||
| `docs/reference/openapi.yaml` | Already updated in step 7 |
|
||||
|
||||
**Only update files where the CHANGELOG entries directly affect the documented content.** Do NOT update files just to bump a version number — only when the documented behavior, features, or architecture has actually changed.
|
||||
|
||||
---
|
||||
|
||||
## Phase 5: Verify
|
||||
|
||||
### 12. Run lint check
|
||||
|
||||
// turbo
|
||||
|
||||
```bash
|
||||
cd /home/diegosouzapw/dev/proxys/OmniRoute
|
||||
npm run lint
|
||||
```
|
||||
|
||||
### 13. Run tests
|
||||
|
||||
// turbo
|
||||
|
||||
```bash
|
||||
cd /home/diegosouzapw/dev/proxys/OmniRoute
|
||||
npm test
|
||||
```
|
||||
|
||||
### 14. Verify version sync across all files
|
||||
|
||||
// turbo
|
||||
|
||||
```bash
|
||||
cd /home/diegosouzapw/dev/proxys/OmniRoute
|
||||
VERSION=$(node -p "require('./package.json').version")
|
||||
echo "Expected version: $VERSION"
|
||||
echo ""
|
||||
|
||||
echo "--- package.json ---"
|
||||
grep '"version"' package.json | head -1
|
||||
|
||||
echo "--- open-sse/package.json ---"
|
||||
grep '"version"' open-sse/package.json | head -1
|
||||
|
||||
echo "--- electron/package.json ---"
|
||||
[ -f electron/package.json ] && grep '"version"' electron/package.json | head -1
|
||||
|
||||
echo "--- docs/reference/openapi.yaml ---"
|
||||
grep " version:" docs/reference/openapi.yaml | head -1
|
||||
|
||||
echo "--- llm.txt ---"
|
||||
grep "Current version:" llm.txt
|
||||
|
||||
echo "--- CHANGELOG.md (first versioned entry) ---"
|
||||
grep "^## \[" CHANGELOG.md | head -2
|
||||
```
|
||||
|
||||
### 15. 🛑 STOP — Present Summary to User
|
||||
|
||||
**STOP** and present a summary to the user including:
|
||||
|
||||
- Old version → New version
|
||||
- CHANGELOG entries generated
|
||||
- Files modified
|
||||
- Test results
|
||||
- Any documentation updates made
|
||||
|
||||
**Wait for the user to confirm before committing.**
|
||||
|
||||
---
|
||||
|
||||
## Phase 6: Commit (only after user approval)
|
||||
|
||||
### 16. Stage and commit
|
||||
|
||||
// turbo-all
|
||||
|
||||
```bash
|
||||
cd /home/diegosouzapw/dev/proxys/OmniRoute
|
||||
git add -A
|
||||
VERSION=$(node -p "require('./package.json').version")
|
||||
git commit -m "chore(release): bump to v$VERSION — changelog, docs, version sync"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Notes
|
||||
|
||||
- This workflow does **NOT** create tags, releases, or deploy. Use `/generate-release` for the full release cycle after this.
|
||||
- This workflow does **NOT** update `docs/i18n/` translations. Translation updates are handled manually or via release tooling — there is no `/update-i18n` workflow shipped in this repo.
|
||||
- The CHANGELOG generation is based on git commits since the last tag. If there are no new commits, the workflow should inform the user and stop.
|
||||
- Always verify the generated CHANGELOG entries make sense — raw commit messages may need rewriting for clarity.
|
||||
- If the version was already bumped (e.g. you're on a `release/vX.Y.Z` branch), skip the `npm version` step and use the existing version.
|
||||
|
||||
## Version Touchpoints Checklist
|
||||
|
||||
| File | Field/Pattern |
|
||||
| ----------------------------- | ----------------------------------------------------------- |
|
||||
| `package.json` | `"version": "X.Y.Z"` |
|
||||
| `open-sse/package.json` | `"version": "X.Y.Z"` |
|
||||
| `electron/package.json` | `"version": "X.Y.Z"` |
|
||||
| `docs/reference/openapi.yaml` | `version: X.Y.Z` |
|
||||
| `llm.txt` | `**Current version:** X.Y.Z` and `## Key Features (vX.Y.Z)` |
|
||||
| `CHANGELOG.md` | `## [X.Y.Z] — YYYY-MM-DD` |
|
||||
@@ -1,10 +1,12 @@
|
||||
---
|
||||
description: Automatically run the browser_subagent to visually validate all new UI features from the current release and capture evidence WebP recordings of the changes.
|
||||
description: Automatically run a browser-automation agent to visually validate all new UI features from the current release and capture evidence WebP recordings of the changes.
|
||||
---
|
||||
|
||||
# Capture Release Evidences Workflow
|
||||
|
||||
Use this workflow to automatically drive the `browser_subagent` to explore the newly deployed or locally running application and record evidence of the UI changes introduced in the latest release.
|
||||
Use this workflow to automatically drive a browser-automation agent to explore the newly deployed or locally running application and record evidence of the UI changes introduced in the latest release.
|
||||
|
||||
> **Tool mapping note (v3.8):** The `browser_subagent` tool referenced below is specific to an earlier agent runtime. In Claude Code, substitute with the available browser MCP tools (e.g. `mcp__claude-in-chrome__*`) for navigation/screenshots, plus the `Write` tool for saving artifacts. The high-level steps remain the same regardless of the browser-automation surface in use.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
@@ -35,7 +37,7 @@ _(Note: The `browser_subagent` automatically creates a WebP recording named by t
|
||||
|
||||
### 3. Generate Report Artifact
|
||||
|
||||
After the `browser_subagent` finishes its sessions, generate a final Markdown artifact (using `write_to_file` and `IsArtifact=true`) to present the recordings inline to the user using the `` syntax.
|
||||
After the `browser_subagent` finishes its sessions, generate a final Markdown artifact (using `Write` and `IsArtifact=true`) to present the recordings inline to the user using the `` syntax.
|
||||
|
||||
### Example Invocation
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
---
|
||||
description: Create a new release, bump version up to 1.x.10 threshold, update changelog, and manage Pull Requests
|
||||
description: Create a new release, bump version up to the .10 patch threshold, update changelog, and manage Pull Requests
|
||||
---
|
||||
|
||||
# Generate Release Workflow
|
||||
|
||||
Bump version, finalize CHANGELOG, commit, open a **PR to main** and wait for user confirmation before tagging, publishing, and deploying.
|
||||
|
||||
> **VERSION RULE: Always use PATCH bumps (2.x.y → 2.x.y+1)**
|
||||
> **VERSION RULE: Always use PATCH bumps (3.x.y → 3.x.y+1)**
|
||||
> NEVER use `npm version minor` or `npm version major`.
|
||||
> Always use: `npm version patch --no-git-tag-version`
|
||||
> The threshold rule: when `y` reaches 10, bump to `2.(x+1).0` — e.g. `2.1.10` → `2.2.0`.
|
||||
> The threshold rule: when `y` reaches 10, bump to `3.(x+1).0` — e.g. `3.8.10` → `3.9.0`.
|
||||
|
||||
> **🔴 SINGLE BRANCH RULE**: The `release/vX.Y.Z` branch is the **ONLY** development branch for the entire release cycle. ALL work — bug fixes, feature implementations, PR integrations, issue resolutions — MUST be committed directly on this branch. Never create separate `fix/`, `feat/`, or topic branches. When running `/resolve-issues`, `/implement-features`, or `/review-prs`, always work on the current release branch.
|
||||
|
||||
@@ -49,7 +49,7 @@ Before creating the release, you must ensure the codebase and supply chain are s
|
||||
### 1. Create release branch
|
||||
|
||||
```bash
|
||||
git checkout -b release/v2.x.y
|
||||
git checkout -b release/v3.x.y
|
||||
```
|
||||
|
||||
### 2. Determine and sync version
|
||||
@@ -72,19 +72,19 @@ grep '"version"' package.json
|
||||
>
|
||||
> 1. `npm version patch --no-git-tag-version` ← bump first
|
||||
> 2. implement features / fix bugs
|
||||
> 3. `git add -A && git commit -m "chore(release): v2.x.y — all changes in ONE commit"`
|
||||
> 3. `git add -A && git commit -m "chore(release): v3.x.y — all changes in ONE commit"`
|
||||
>
|
||||
> **OR if features are already staged:**
|
||||
>
|
||||
> 1. implement features (do NOT commit yet)
|
||||
> 2. `npm version patch --no-git-tag-version` ← bump before committing
|
||||
> 3. `git add -A && git commit -m "chore(release): v2.x.y — all changes in ONE commit"`
|
||||
> 3. `git add -A && git commit -m "chore(release): v3.x.y — all changes in ONE commit"`
|
||||
>
|
||||
> **NEVER do this (creates version mismatch in git history):**
|
||||
>
|
||||
> - ~~commit features → then bump version → commit package.json separately~~
|
||||
>
|
||||
> This ensures that `git show v2.x.y` always contains both code changes and the version bump together.
|
||||
> This ensures that `git show v3.x.y` always contains both code changes and the version bump together.
|
||||
> The GitHub release tag will point to a commit that includes ALL changes for that version.
|
||||
|
||||
### 3. Regenerate lock file (REQUIRED after version bump)
|
||||
@@ -124,13 +124,13 @@ Keep an empty `## [Unreleased]` section above it, separated by a horizontal rule
|
||||
|
||||
### 5. Update openapi.yaml version ⚠️ MANDATORY
|
||||
|
||||
> **CI will fail** if `docs/openapi.yaml` version ≠ `package.json` version (`check:docs-sync` enforces this).
|
||||
> **CI will fail** if `docs/reference/openapi.yaml` version ≠ `package.json` version (`check:docs-sync` enforces this).
|
||||
|
||||
// turbo
|
||||
|
||||
```bash
|
||||
VERSION=$(node -p "require('./package.json').version")
|
||||
sed -i "s/ version: .*/ version: $VERSION/" docs/openapi.yaml
|
||||
sed -i "s/ version: .*/ version: $VERSION/" docs/reference/openapi.yaml
|
||||
echo "✓ openapi.yaml → $VERSION"
|
||||
|
||||
for dir in electron open-sse; do
|
||||
@@ -145,11 +145,12 @@ npm install
|
||||
|
||||
### 6. Update README.md and i18n docs
|
||||
|
||||
Run `/update-docs` workflow steps to:
|
||||
Manually perform these documentation updates (there is no `/update-docs` workflow — it was deprecated in v3.8):
|
||||
|
||||
- Update feature table rows in `README.md`
|
||||
- Sync changes to all 29 language `docs/i18n/*/README.md` files
|
||||
- Update `docs/FEATURES.md` if Settings section changed
|
||||
- Update feature table rows and "What's new in vX.Y.Z" section in `README.md`
|
||||
- Sync feature changes to all 40 language `docs/i18n/*/README.md` files (use the same row edits across each translated README)
|
||||
- Update the relevant `docs/<AREA>.md` if architecture or counts changed
|
||||
- Re-run `npm run check:docs-sync` and `npm run check:docs-all` to catch drift
|
||||
|
||||
### 7. Run tests
|
||||
|
||||
@@ -167,8 +168,8 @@ All tests must pass before creating the PR.
|
||||
|
||||
```bash
|
||||
git add -A
|
||||
git commit -m "chore(release): v2.x.y — summary of changes"
|
||||
git push origin release/v2.x.y
|
||||
git commit -m "chore(release): v3.x.y — summary of changes"
|
||||
git push origin release/v3.x.y
|
||||
```
|
||||
|
||||
### 9. Open PR to main
|
||||
@@ -200,7 +201,7 @@ gh pr create \
|
||||
|
||||
### 10. 🛑 STOP — Notify User & Await PR Confirmation
|
||||
|
||||
**This is a mandatory stop point.** Use `notify_user` with `BlockedOnUser: true`:
|
||||
**This is a mandatory stop point.** Present the report in the final response and stop. Do not continue to the next phase until the user explicitly approves.
|
||||
|
||||
Inform the user:
|
||||
|
||||
@@ -333,7 +334,7 @@ If a workflow fails:
|
||||
|
||||
- Use `gh run view <RUN_ID> --log-failed` to identify the error.
|
||||
- Apply the fix on the `main` branch.
|
||||
- If necessary, re-trigger the workflow using `gh workflow run <workflow_name.yml> --repo diegosouzapw/OmniRoute --ref v2.x.y`
|
||||
- If necessary, re-trigger the workflow using `gh workflow run <workflow_name.yml> --repo diegosouzapw/OmniRoute --ref v3.x.y`
|
||||
|
||||
### 20. Preserve release branch
|
||||
|
||||
@@ -345,7 +346,7 @@ If a workflow fails:
|
||||
|
||||
## Notes
|
||||
|
||||
- Always run `/update-docs` BEFORE this workflow (ensures CHANGELOG and README are current)
|
||||
- Ensure CHANGELOG, README and `docs/*` are current BEFORE this workflow — run `npm run check:docs-all` and `/version-bump` first (there is no `/update-docs` workflow anymore)
|
||||
- The `prepublishOnly` script runs `npm run build:cli` automatically during `npm publish`
|
||||
- After npm publish, verify with `npm info omniroute version`
|
||||
- Lock file sync errors are caused by skipping `npm install` after version bump
|
||||
@@ -353,9 +354,9 @@ If a workflow fails:
|
||||
|
||||
## Known CI Pitfalls
|
||||
|
||||
| CI failure | Cause | Fix |
|
||||
| ------------------------------------------------------------------------- | -------------------------------------------------------- | ---------------------------------------------------------------------- |
|
||||
| `[docs-sync] FAIL - OpenAPI version differs from package.json` | Skipped step 5 — `docs/openapi.yaml` version not updated | Run step 5 (`sed -i ...`) and commit |
|
||||
| `[docs-sync] FAIL - CHANGELOG.md first section must be "## [Unreleased]"` | `## [Unreleased]` missing or not at top of CHANGELOG | Add `## [Unreleased]\n\n---\n` before the first versioned `## [x.y.z]` |
|
||||
| Electron Linux `.deb` build fails (`FpmTarget` error) | `fpm` Ruby gem not installed on `ubuntu-latest` runner | Already fixed in `electron-release.yml` (`gem install fpm` step) |
|
||||
| Docker Hub `502 error writing layer blob` | Transient Docker Hub network error during ARM64 push | Re-run the Docker publish workflow; no code change needed |
|
||||
| CI failure | Cause | Fix |
|
||||
| ------------------------------------------------------------------------- | ------------------------------------------------------------------ | ---------------------------------------------------------------------- |
|
||||
| `[docs-sync] FAIL - OpenAPI version differs from package.json` | Skipped step 5 — `docs/reference/openapi.yaml` version not updated | Run step 5 (`sed -i ...`) and commit |
|
||||
| `[docs-sync] FAIL - CHANGELOG.md first section must be "## [Unreleased]"` | `## [Unreleased]` missing or not at top of CHANGELOG | Add `## [Unreleased]\n\n---\n` before the first versioned `## [x.y.z]` |
|
||||
| Electron Linux `.deb` build fails (`FpmTarget` error) | `fpm` Ruby gem not installed on `ubuntu-latest` runner | Already fixed in `electron-release.yml` (`gem install fpm` step) |
|
||||
| Docker Hub `502 error writing layer blob` | Transient Docker Hub network error during ARM64 push | Re-run the Docker publish workflow; no code change needed |
|
||||
@@ -201,22 +201,22 @@ For each viable feature, perform systematic research:
|
||||
**Step 1 — Web search for similar implementations:**
|
||||
|
||||
```
|
||||
search_web("how to implement <feature description> in <tech stack>")
|
||||
search_web("<feature keyword> implementation nextjs typescript 2025 2026")
|
||||
search_web("<feature keyword> open source library npm")
|
||||
WebSearch("how to implement <feature description> in <tech stack>")
|
||||
WebSearch("<feature keyword> implementation nextjs typescript 2025 2026")
|
||||
WebSearch("<feature keyword> open source library npm")
|
||||
```
|
||||
|
||||
**Step 2 — Find reference Git repositories:**
|
||||
|
||||
```
|
||||
search_web("site:github.com <feature keyword> <tech stack> stars:>100")
|
||||
search_web("github <feature keyword> implementation recently updated 2026")
|
||||
WebSearch("site:github.com <feature keyword> <tech stack> stars:>100")
|
||||
WebSearch("github <feature keyword> implementation recently updated 2026")
|
||||
```
|
||||
|
||||
- Find **up to 10 relevant repositories**, sorted by most recently updated.
|
||||
- For each repository:
|
||||
- Note the repo URL, star count, last commit date
|
||||
- Read its README and relevant source files via `read_url_content`
|
||||
- Read its README and relevant source files via `WebFetch`
|
||||
- Extract the architectural approach, patterns used, and key code snippets
|
||||
|
||||
**Step 3 — Read API docs and standards:**
|
||||
@@ -8,6 +8,8 @@ description: Read all open GitHub Discussions, summarize them, respond to pendin
|
||||
|
||||
This workflow reads all open GitHub Discussions, generates a categorized summary, identifies which ones need a response, drafts and posts replies, and optionally creates issues from actionable feature requests. It follows the same flow used for Issues but adapted for the Discussions forum.
|
||||
|
||||
> **Tool mapping note (v3.8):** Where steps below say `browser_subagent`, in modern Claude Code substitute with the `gh` CLI via Bash — `gh api graphql` for reading discussions and mutations for posting comments. `WebFetch` is acceptable for read-only HTML scraping when GraphQL is overkill, but prefer `gh` for any write actions.
|
||||
|
||||
// turbo-all
|
||||
|
||||
## Steps
|
||||
@@ -19,7 +21,7 @@ This workflow reads all open GitHub Discussions, generates a categorized summary
|
||||
|
||||
### 2. Fetch All Open Discussions
|
||||
|
||||
- Use `read_url_content` to fetch `https://github.com/<owner>/<repo>/discussions`
|
||||
- Use `WebFetch` to fetch `https://github.com/<owner>/<repo>/discussions`
|
||||
- Parse the discussion list to get all discussion titles, IDs, authors, categories, and dates
|
||||
- For each discussion, fetch the individual page to read the full content and all comments/replies
|
||||
|
||||
256
.agents/workflows/review-prs-ag.md
Normal file
256
.agents/workflows/review-prs-ag.md
Normal file
@@ -0,0 +1,256 @@
|
||||
---
|
||||
description: Analyze open Pull Requests from the project's GitHub repository, generate a critical report, and optionally implement approved changes
|
||||
---
|
||||
|
||||
# /review-prs — PR Review & Analysis Workflow
|
||||
|
||||
## ⛔ ABSOLUTE PROHIBITION — Read Before Anything Else
|
||||
|
||||
> **NEVER close a contributor's PR if you intend to use ANY of their code, ideas, or fixes.**
|
||||
>
|
||||
> **NEVER manually integrate contributor code into a release branch and then close their PR.**
|
||||
>
|
||||
> These actions are **STRICTLY FORBIDDEN** under all circumstances:
|
||||
>
|
||||
> 1. ❌ Closing a PR and cherry-picking/copying its code into a release branch
|
||||
> 2. ❌ Closing a PR "because of conflicts" and re-implementing the same fix yourself
|
||||
> 3. ❌ Closing a PR and committing a "similar" solution inspired by it
|
||||
> 4. ❌ Using `gh pr close` on any PR whose content was or will be used
|
||||
>
|
||||
> **Why**: Closing a PR after taking the contributor's work means they get ZERO credit on GitHub — no "Merged" badge, no contribution graph entry, no public record. This is effectively stealing their contribution. An audit found this happened to **37 PRs** in the past.
|
||||
>
|
||||
> **The ONLY acceptable flow**: Resolve conflicts IN the contributor's branch, push fixes TO their branch, then merge THEIR PR via `gh pr merge`. See Step 7 and Step 8 for the exact procedure.
|
||||
>
|
||||
> **When to close a PR**: ONLY when the user (repository owner) explicitly requests it, OR when the PR is clearly spam/malicious, OR when the author themselves asks to close it. In ALL other cases, leave it open.
|
||||
|
||||
## Overview
|
||||
|
||||
This workflow fetches all open PRs from the project's GitHub repository, performs a critical analysis of each one, generates a detailed report, and waits for user approval before proceeding with implementation. **All improvements are committed on the current release branch** (`release/vX.Y.Z`).
|
||||
|
||||
> **BRANCH RULE**: PRs are ALWAYS merged into the current `release/vX.Y.Z` branch, NEVER directly into `main`. The release branch acts as a staging area — only after all PRs are integrated and tests pass does the release branch get merged into `main` via the `/generate-release` workflow.
|
||||
|
||||
## Steps
|
||||
|
||||
### 1. Identify the GitHub Repository
|
||||
|
||||
- Read `package.json` to get the repository URL, or use the git remote origin URL
|
||||
// turbo
|
||||
- Run: `git -C <project_root> remote get-url origin` to extract the owner/repo
|
||||
|
||||
### 2. Ensure Release Branch Exists
|
||||
|
||||
// turbo
|
||||
|
||||
Before doing any work, ensure you are on the current release branch:
|
||||
|
||||
```bash
|
||||
# Check current branch
|
||||
git branch --show-current
|
||||
|
||||
# If on main, determine next version and create the release branch
|
||||
VERSION=$(node -p "require('./package.json').version")
|
||||
# Bump patch: e.g. 3.3.11 → 3.3.12
|
||||
NEXT=$(node -p "const [a,b,c]=('$VERSION').split('.').map(Number); c>=9?a+'.'+(b+1)+'.0':a+'.'+b+'.'+(c+1)")
|
||||
git checkout -b release/v$NEXT
|
||||
npm version patch --no-git-tag-version
|
||||
npm install
|
||||
```
|
||||
|
||||
If already on a `release/vX.Y.Z` branch, continue working there.
|
||||
|
||||
### 3. Fetch Open Pull Requests
|
||||
|
||||
// turbo-all
|
||||
|
||||
**⚠️ CRITICAL**: The JSON output of `gh pr list` can be truncated by the tool, silently hiding PRs. You MUST use the two-step approach below to guarantee **all** PRs are fetched.
|
||||
|
||||
**Step 3a — Get PR numbers only** (small output, never truncated):
|
||||
|
||||
- Run: `gh pr list --repo <owner>/<repo> --state open --limit 500 --json number --jq '.[].number'`
|
||||
- This outputs one PR number per line. Count them and confirm total.
|
||||
|
||||
**Step 3b — Fetch full metadata for each PR** (one call per PR):
|
||||
|
||||
- For each PR number from step 3a, run:
|
||||
`gh pr view <NUMBER> --repo <owner>/<repo> --json number,title,author,headRefName,baseRefName,body,createdAt,additions,deletions,files`
|
||||
- You may batch these into parallel calls (up to 4 at a time).
|
||||
|
||||
**Step 3c — Fetch diffs for each PR** (one call per PR, saved to /tmp):
|
||||
|
||||
- For each PR number, run:
|
||||
`gh pr diff <NUMBER> --repo <owner>/<repo> > /tmp/pr<NUMBER>.diff`
|
||||
- Then read each diff file with the appropriate file-read tool (`Read` in Claude Code; equivalent in your agent runtime).
|
||||
|
||||
- For each open PR, collect:
|
||||
- PR number, title, author, branch, number of commits, date
|
||||
- PR description/body
|
||||
- Files changed (diff)
|
||||
- Existing review comments (from bots or humans)
|
||||
|
||||
**Verification**: Confirm the count of PRs analyzed matches the count from step 3a before proceeding.
|
||||
|
||||
### 3.5 Redirect PR Base Branches to Release Branch
|
||||
|
||||
// turbo-all
|
||||
|
||||
**⚠️ CRITICAL**: Contributors typically open PRs targeting `main`. Before analyzing or merging, redirect ALL open PRs to target the current release branch instead.
|
||||
|
||||
```bash
|
||||
# Get the current release branch name
|
||||
RELEASE_BRANCH=$(git branch --show-current) # e.g. release/v3.5.4
|
||||
|
||||
# For each open PR that targets main, change its base to the release branch
|
||||
for PR_NUM in $(gh pr list --repo <owner>/<repo> --state open --json number,baseRefName --jq '.[] | select(.baseRefName == "main") | .number'); do
|
||||
echo "Redirecting PR #$PR_NUM → $RELEASE_BRANCH"
|
||||
gh pr edit "$PR_NUM" --repo <owner>/<repo> --base "$RELEASE_BRANCH"
|
||||
done
|
||||
```
|
||||
|
||||
This ensures:
|
||||
|
||||
1. PRs merge into the release branch, not directly into `main`
|
||||
2. Merge conflict detection is accurate against the release branch
|
||||
3. The release branch accumulates all changes before the final merge to `main`
|
||||
4. If the release branch doesn't exist on remote yet, push it first: `git push origin $RELEASE_BRANCH`
|
||||
|
||||
### 4. Analyze Each PR — For each open PR, perform the following analysis:
|
||||
|
||||
#### 4a. Feature Assessment
|
||||
|
||||
- **Does it make sense?** Evaluate if the feature fills a real gap or solves a valid problem
|
||||
- **Alignment** — Check if it aligns with the project's architecture and roadmap
|
||||
- **Complexity** — Assess if the scope is reasonable or if it should be split
|
||||
|
||||
#### 4b. Code Quality Review
|
||||
|
||||
- Check for code duplication
|
||||
- Evaluate error handling patterns (consistent with existing codebase?)
|
||||
- Check naming conventions and code style
|
||||
- Verify TypeScript types (any `any` usage, missing types?)
|
||||
|
||||
#### 4c. Security Review
|
||||
|
||||
- Check for missing authentication/authorization on new endpoints
|
||||
- Check for injection vulnerabilities (URL params, SQL, XSS)
|
||||
- Verify input validation on all user-controlled data
|
||||
- Check for hardcoded secrets or credentials
|
||||
|
||||
#### 4d. Architecture Review
|
||||
|
||||
- Does the change follow existing patterns?
|
||||
- Are there any breaking changes to public APIs?
|
||||
- Is the database schema affected? Migration needed?
|
||||
- Impact on performance (N+1 queries, missing indexes?)
|
||||
|
||||
#### 4e. Test Coverage
|
||||
|
||||
- Does the PR include tests?
|
||||
- Are edge cases covered?
|
||||
- Would existing tests break?
|
||||
|
||||
#### 4f. Cross-Layer (Global) Analysis
|
||||
|
||||
Perform a **global impact assessment** to verify whether the PR changes are complete across all layers of the application:
|
||||
|
||||
- **Backend → Frontend check**: If the PR adds or modifies backend-only resources (new endpoints, services, data models), evaluate whether corresponding frontend changes are missing:
|
||||
- Does a new endpoint require a new screen/page in the dashboard?
|
||||
- Should there be a new action button, menu item, or navigation link?
|
||||
- Are there new data fields that should be displayed or editable in the UI?
|
||||
- Does a new feature need a toggle, configuration panel, or status indicator?
|
||||
- **Frontend → Backend check**: If the PR adds frontend elements, verify the backend support exists:
|
||||
- Are the required API endpoints implemented?
|
||||
- Is the data model sufficient for the new UI components?
|
||||
- **Cross-cutting concerns**: Check shared layers (types, DTOs, validation schemas, routes, middleware) for completeness
|
||||
- **Document gaps** — If missing layers are detected, list them as **IMPORTANT** issues in the report with concrete suggestions for what should be added
|
||||
|
||||
### 5. Generate Report — Create a markdown report for each PR including:
|
||||
|
||||
- **PR Summary** — What it does, files affected, commit count
|
||||
- **Improvements/Benefits** — Numbered list with impact level (HIGH/MEDIUM/LOW)
|
||||
- **Risks & Issues** — Categorized as CRITICAL / IMPORTANT / MINOR
|
||||
- **Scoring Table** — Rate across: Feature Relevance, Code Quality, Security, Robustness, Tests
|
||||
- **Verdict** — Ready to merge? With mandatory vs optional fixes
|
||||
- **Next Steps** — What will happen if approved
|
||||
|
||||
### 6. Present to User
|
||||
|
||||
- Show the report in the final response and stop. Mark this as a blocking checkpoint awaiting explicit user approval.
|
||||
- Wait for user decision:
|
||||
- **Approved** → Proceed to step 7
|
||||
- **Approved with changes** → Implement the fixes and corrections before merging
|
||||
- **Rejected** → Close the PR or leave a review comment
|
||||
|
||||
### 7. Pre-Merge Fixes & CI Green-Lighting (if approved)
|
||||
|
||||
> **⚠️ Fixes and Conflict Resolutions MUST be pushed back to the PR branch before merging.** We want the PR itself to be green and fully valid before it integrates.
|
||||
|
||||
- **Sync latest fixes & Resolve Conflicts:** Merge the current `release` branch into the PR branch. If there are merge conflicts, you MUST resolve them inside the author's PR branch. NEVER resolve conflicts by closing their PR and doing the work in a separate branch, as this steals credit from the original author.
|
||||
- **Implement improvements:** Apply the required fixes identified in the analysis directly on the PR branch (e.g., adding missing API routes, fixing SSRF, applying comments from other agents).
|
||||
- **Pushing changes to PR branches:**
|
||||
|
||||
```bash
|
||||
# Checkout the PR locally
|
||||
gh pr checkout <NUMBER>
|
||||
|
||||
# Apply fixes, commit your changes
|
||||
git commit -m "chore: apply review suggestions and missing layers"
|
||||
|
||||
# Attempt to push directly to the PR branch
|
||||
git push
|
||||
```
|
||||
|
||||
- **Fallback (ONLY for external forks without maintainer edit access):**
|
||||
Using `cherry-pick` instead of fixing the contributor's PR directly is a **LAST RESORT**. You MUST ALWAYS attempt to `git push` your fixes to their branch first.
|
||||
**ONLY if `git push` explicitly fails with a permission/access error** (meaning the contributor unchecked "Allow edits from maintainers" or it's a locked fork), you may use `git cherry-pick` to bring their changes into the release branch and fix the issues locally.
|
||||
Even then, ensure you preserve the contributor's authorship (`git commit --author="Contributor Name <email>"` if creating new commits).
|
||||
Once you have integrated their work into the release branch, **DO NOT close their PR**. Leave it open so the contributor retains credit. Under NO CIRCUMSTANCES should you use `gh pr close`.
|
||||
|
||||
- Run the project's test suite locally to verify nothing breaks:
|
||||
// turbo
|
||||
- Run: `npm test` or equivalent test command
|
||||
|
||||
### 8. Merge into Release Branch (NEVER CLOSE!)
|
||||
|
||||
> **⚠️ CRITICAL**: NEVER use `gh pr close` for a PR whose idea or code was accepted. Closing a PR in a contributor's face after taking their idea—or closing it just because it had conflicts—is unacceptable.
|
||||
> You MUST ALWAYS resolve conflicts and apply fixes ON THE AUTHOR'S PR BRANCH (unless explicitly locked from edits), and then merge the PR using GitHub so the contributor gets the official "Merged" badge and proper credit on their profile. **Do not use cherry-pick just because it is "easier" than resolving conflicts on their branch.**
|
||||
|
||||
Even if the PR had severe conflicts or required significant architectural adjustments, you MUST:
|
||||
|
||||
1. Resolve any conflicts and apply the fixes directly to their PR branch (as detailed in step 7) or use cherry-picking into the release branch.
|
||||
2. If you managed to fix their branch, merge it into the release branch using the GitHub CLI:
|
||||
`gh pr merge <NUMBER> --repo <owner>/<repo> --squash --body "Integrated into release/vX.Y.Z"`
|
||||
3. If you had to use cherry-picking because you couldn't push to their branch, DO NOT close the PR. GitHub will sometimes auto-detect the cherry-picked commits and mark it as Merged. If it doesn't, leave it open. The repository owner will handle it. NEVER run `gh pr close`.
|
||||
|
||||
In ALL cases:
|
||||
|
||||
- Post a **thank-you comment** on the PR via the GitHub API before or immediately after merging.
|
||||
- The message should:
|
||||
- Thank the author by name/username for their contribution.
|
||||
- Explain what was adjusted or improved (if we pushed fixes to their branch or cherry-picked).
|
||||
- Note it will be included in the upcoming release.
|
||||
- Be friendly, professional, and encouraging.
|
||||
|
||||
> **⚠️ MANDATORY CHANGELOG CREDIT**: When cherry-picking is used (because the PR branch couldn't be pushed to or `gh pr merge` failed), the contributor does NOT get the automatic GitHub "Merged" badge. In this case, you MUST compensate by adding an explicit entry to `CHANGELOG.md` in the `[Unreleased]` section with `(#PR_NUMBER — thanks @username)` format. This ensures the contributor gets public credit in the release notes even if GitHub doesn't auto-detect the cherry-pick. This is NOT optional — skipping it effectively erases the contributor's work from the release record.
|
||||
|
||||
### 9. Sync Local Release Branch
|
||||
|
||||
After merging PRs, sync the local release branch to include the new changes:
|
||||
|
||||
```bash
|
||||
git fetch origin
|
||||
git pull origin release/vX.Y.Z
|
||||
```
|
||||
|
||||
### 10. Continue or Finalize
|
||||
|
||||
After processing all approved PRs:
|
||||
|
||||
- If more PRs remain, go back to step 7
|
||||
- When all PRs are processed, **update CHANGELOG.md** on the release branch with all new entries
|
||||
- Run **test coverage** to verify the gate (≥75% statements/lines/functions, ≥70% branches — measured ~82%):
|
||||
```bash
|
||||
npm run test:coverage
|
||||
```
|
||||
- Fix any test regressions introduced by merged PRs
|
||||
- Run `/generate-release` workflow Phase 1 steps 7–10 (tests → commit → push → open PR to main → wait for user)
|
||||
- The `/generate-release` workflow handles the final merge from `release/vX.Y.Z` → `main`
|
||||
@@ -136,9 +136,9 @@ The date must be today's date in `YYYY-MM-DD` format.
|
||||
cd /home/diegosouzapw/dev/proxys/OmniRoute
|
||||
VERSION=$(node -p "require('./package.json').version")
|
||||
|
||||
# Update docs/openapi.yaml version
|
||||
sed -i "s/ version: .*/ version: $VERSION/" docs/openapi.yaml
|
||||
echo "✓ docs/openapi.yaml → $VERSION"
|
||||
# Update docs/reference/openapi.yaml version
|
||||
sed -i "s/ version: .*/ version: $VERSION/" docs/reference/openapi.yaml
|
||||
echo "✓ docs/reference/openapi.yaml → $VERSION"
|
||||
|
||||
# Update workspace packages (open-sse, electron)
|
||||
for dir in electron open-sse; do
|
||||
@@ -208,22 +208,36 @@ For each file below, read the current content and determine if the CHANGELOG ent
|
||||
|
||||
For each file in `docs/` (excluding `docs/i18n/`), review if CHANGELOG changes affect it:
|
||||
|
||||
| File | When to update |
|
||||
| -------------------------------- | --------------------------------------------------- |
|
||||
| `docs/API_REFERENCE.md` | New API endpoints, changed request/response formats |
|
||||
| `docs/ARCHITECTURE.md` | New modules, new services, changed data flow |
|
||||
| `docs/CLI-TOOLS.md` | New CLI tool integrations, config format changes |
|
||||
| `docs/FEATURES.md` | New features, removed features, changed settings |
|
||||
| `docs/MCP-SERVER.md` | New MCP tools, changed tool signatures |
|
||||
| `docs/A2A-SERVER.md` | New A2A skills, protocol changes |
|
||||
| `docs/USER_GUIDE.md` | UX changes, new dashboard pages, settings changes |
|
||||
| `docs/VM_DEPLOYMENT_GUIDE.md` | Deployment changes, new env vars |
|
||||
| `docs/TROUBLESHOOTING.md` | New known issues, resolved problems |
|
||||
| `docs/AUTO-COMBO.md` | Routing changes, new strategies |
|
||||
| `docs/CODEBASE_DOCUMENTATION.md` | New files, architectural changes |
|
||||
| `docs/RELEASE_CHECKLIST.md` | Process changes |
|
||||
| `docs/COVERAGE_PLAN.md` | Test changes |
|
||||
| `docs/openapi.yaml` | Already updated in step 7 |
|
||||
| File | When to update |
|
||||
| --------------------------------------------- | ------------------------------------------------------------------ |
|
||||
| `docs/reference/API_REFERENCE.md` | New API endpoints, changed request/response formats |
|
||||
| `docs/architecture/ARCHITECTURE.md` | New modules, new services, changed data flow |
|
||||
| `docs/architecture/CODEBASE_DOCUMENTATION.md` | New files, architectural changes, module reorganization |
|
||||
| `docs/architecture/REPOSITORY_MAP.md` | New folders / files / one-line descriptions |
|
||||
| `docs/reference/CLI-TOOLS.md` | New CLI tool integrations, config format changes |
|
||||
| `docs/guides/USER_GUIDE.md` | UX changes, new dashboard pages, settings changes |
|
||||
| `docs/reference/PROVIDER_REFERENCE.md` | New providers (regenerate via `scripts/gen-provider-reference.ts`) |
|
||||
| `docs/frameworks/MCP-SERVER.md` | New MCP tools, changed tool signatures, scope changes |
|
||||
| `docs/frameworks/A2A-SERVER.md` | New A2A skills, protocol changes |
|
||||
| `docs/frameworks/AGENT_PROTOCOLS_GUIDE.md` | New external agent protocols supported |
|
||||
| `docs/frameworks/CLOUD_AGENT.md` | Cloud agent additions (codex-cloud, devin, jules) or API changes |
|
||||
| `docs/architecture/AUTHZ_GUIDE.md` | New route classifications, policy changes |
|
||||
| `docs/security/GUARDRAILS.md` | New guardrails registered, priority/order changes |
|
||||
| `docs/security/COMPLIANCE.md` | Audit log / retention / no-log policy changes |
|
||||
| `docs/frameworks/SKILLS.md` | Skill framework / registry / built-in skill changes |
|
||||
| `docs/frameworks/MEMORY.md` | Memory pipeline / extraction / injection / Qdrant changes |
|
||||
| `docs/frameworks/EVALS.md` | Evaluation framework changes, new evaluators |
|
||||
| `docs/frameworks/WEBHOOKS.md` | New webhook events, payload schema changes |
|
||||
| `docs/routing/REASONING_REPLAY.md` | Reasoning capture/replay pipeline changes |
|
||||
| `docs/routing/AUTO-COMBO.md` | Routing changes, new strategies, scoring weight changes |
|
||||
| `docs/architecture/RESILIENCE_GUIDE.md` | Circuit breaker / cooldown / lockout behavior changes |
|
||||
| `docs/security/STEALTH_GUIDE.md` | TLS / CLI fingerprint changes |
|
||||
| `docs/ops/TUNNELS_GUIDE.md` | Cloudflare tunnel feature changes |
|
||||
| `docs/guides/ELECTRON_GUIDE.md` | Electron build / signing / packaging changes |
|
||||
| `docs/guides/TROUBLESHOOTING.md` | New known issues, resolved problems |
|
||||
| `docs/ops/RELEASE_CHECKLIST.md` | Process changes |
|
||||
| `docs/ops/COVERAGE_PLAN.md` | Coverage gate adjustments, target metrics |
|
||||
| `docs/reference/openapi.yaml` | Already updated in step 7 |
|
||||
|
||||
**Only update files where the CHANGELOG entries directly affect the documented content.** Do NOT update files just to bump a version number — only when the documented behavior, features, or architecture has actually changed.
|
||||
|
||||
@@ -268,8 +282,8 @@ grep '"version"' open-sse/package.json | head -1
|
||||
echo "--- electron/package.json ---"
|
||||
[ -f electron/package.json ] && grep '"version"' electron/package.json | head -1
|
||||
|
||||
echo "--- docs/openapi.yaml ---"
|
||||
grep " version:" docs/openapi.yaml | head -1
|
||||
echo "--- docs/reference/openapi.yaml ---"
|
||||
grep " version:" docs/reference/openapi.yaml | head -1
|
||||
|
||||
echo "--- llm.txt ---"
|
||||
grep "Current version:" llm.txt
|
||||
@@ -310,18 +324,18 @@ git commit -m "chore(release): bump to v$VERSION — changelog, docs, version sy
|
||||
## Notes
|
||||
|
||||
- This workflow does **NOT** create tags, releases, or deploy. Use `/generate-release` for the full release cycle after this.
|
||||
- This workflow does **NOT** update `docs/i18n/` translations. Use `/update-i18n` separately after committing.
|
||||
- This workflow does **NOT** update `docs/i18n/` translations. Translation updates are handled manually or via release tooling — there is no `/update-i18n` workflow shipped in this repo.
|
||||
- The CHANGELOG generation is based on git commits since the last tag. If there are no new commits, the workflow should inform the user and stop.
|
||||
- Always verify the generated CHANGELOG entries make sense — raw commit messages may need rewriting for clarity.
|
||||
- If the version was already bumped (e.g. you're on a `release/vX.Y.Z` branch), skip the `npm version` step and use the existing version.
|
||||
|
||||
## Version Touchpoints Checklist
|
||||
|
||||
| File | Field/Pattern |
|
||||
| ----------------------- | ----------------------------------------------------------- |
|
||||
| `package.json` | `"version": "X.Y.Z"` |
|
||||
| `open-sse/package.json` | `"version": "X.Y.Z"` |
|
||||
| `electron/package.json` | `"version": "X.Y.Z"` |
|
||||
| `docs/openapi.yaml` | `version: X.Y.Z` |
|
||||
| `llm.txt` | `**Current version:** X.Y.Z` and `## Key Features (vX.Y.Z)` |
|
||||
| `CHANGELOG.md` | `## [X.Y.Z] — YYYY-MM-DD` |
|
||||
| File | Field/Pattern |
|
||||
| ----------------------------- | ----------------------------------------------------------- |
|
||||
| `package.json` | `"version": "X.Y.Z"` |
|
||||
| `open-sse/package.json` | `"version": "X.Y.Z"` |
|
||||
| `electron/package.json` | `"version": "X.Y.Z"` |
|
||||
| `docs/reference/openapi.yaml` | `version: X.Y.Z` |
|
||||
| `llm.txt` | `**Current version:** X.Y.Z` and `## Key Features (vX.Y.Z)` |
|
||||
| `CHANGELOG.md` | `## [X.Y.Z] — YYYY-MM-DD` |
|
||||
51
.claude/commands/capture-release-evidences-cc.md
Normal file
51
.claude/commands/capture-release-evidences-cc.md
Normal file
@@ -0,0 +1,51 @@
|
||||
---
|
||||
description: Automatically run a browser-automation agent to visually validate all new UI features from the current release and capture evidence WebP recordings of the changes.
|
||||
---
|
||||
|
||||
# Capture Release Evidences Workflow
|
||||
|
||||
Use this workflow to automatically drive a browser-automation agent to explore the newly deployed or locally running application and record evidence of the UI changes introduced in the latest release.
|
||||
|
||||
> **Tool mapping note (v3.8):** This workflow references a `browser_subagent` tool that was specific to an earlier agent runtime. In Claude Code, substitute with the available browser MCP tools (e.g. `mcp__claude-in-chrome__*`) for navigation/screenshots, plus the `Write` tool for saving artifacts. The high-level steps below remain the same regardless of which browser-automation surface is used.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- OmniRoute must be actively running and accessible (e.g. locally at `http://localhost:20128` or on the Local VPS at `http://192.168.0.15:20128`).
|
||||
- The user must provide the target URL to be tested, or default to `http://192.168.0.15:20128`.
|
||||
|
||||
## Workflow Steps
|
||||
|
||||
### 1. Identify Target Features
|
||||
|
||||
Review the `CHANGELOG.md` for the latest version to map out the new UI elements. For example:
|
||||
|
||||
- **CLI Tools Settings**
|
||||
- **New Provider/Model Listings (e.g., Gemini 3.1, Qoder PAT)**
|
||||
- **New Feature Modals**
|
||||
|
||||
### 2. Run the Browser Subagent
|
||||
|
||||
For each identified feature, invoke the `browser_subagent` using the `default_api:browser_subagent` tool.
|
||||
**Important Task Guidelines for the Subagent:**
|
||||
|
||||
- `TaskName`: Give it a clear name like "Validate CLIProxyAPI Tool Tab".
|
||||
- `TaskSummary`: "Navigate to the CLI Tools tab and verify the new Integration settings."
|
||||
- `Task`: Provide unambiguous instructions for the subagent, such as: "Navigate to http://192.168.0.15:20128/dashboard. Click on the 'Settings' or 'CLI Tools' nav link. Scroll down to find the CLIProxyAPI integration card. Hover over it to trigger UI state. Verify the components render correctly and exit."
|
||||
- `RecordingName`: Ensure it describes the feature (e.g. `v3_4_5_cli_proxy_api`). This is required and strictly automatically saved as a WebP artifacts video by the system.
|
||||
|
||||
_(Note: The `browser_subagent` automatically creates a WebP recording named by the `RecordingName` parameter. No additional tools for screenshots are needed.)_
|
||||
|
||||
### 3. Generate Report Artifact
|
||||
|
||||
After the `browser_subagent` finishes its sessions, generate a final Markdown artifact (using `Write` and `IsArtifact=true`) to present the recordings inline to the user using the `` syntax.
|
||||
|
||||
### Example Invocation
|
||||
|
||||
\```json
|
||||
{
|
||||
"TaskName": "Validating Qoder PAT Configuration UI",
|
||||
"TaskSummary": "Validates the Qoder provider configuration modal",
|
||||
"Task": "Go to http://192.168.0.15:20128/dashboard. Click on the 'Providers' tab. Find 'Qoder' in the list. Click 'Add Token' or 'Configure'. Type 'test_token' and submit. Return when done.",
|
||||
"RecordingName": "qoder_pat_ui_validation"
|
||||
}
|
||||
\```
|
||||
39
.claude/commands/deploy-vps-akamai-cc.md
Normal file
39
.claude/commands/deploy-vps-akamai-cc.md
Normal file
@@ -0,0 +1,39 @@
|
||||
---
|
||||
description: Deploy the latest OmniRoute code to the Akamai VPS (69.164.221.35)
|
||||
---
|
||||
|
||||
# Deploy to Akamai VPS Workflow
|
||||
|
||||
Deploy OmniRoute to the Akamai VPS using `npm pack + scp` + PM2.
|
||||
|
||||
**Akamai VPS:** `69.164.221.35`
|
||||
**Process manager:** PM2 (`omniroute`)
|
||||
**Port:** `20128`
|
||||
|
||||
## Steps
|
||||
|
||||
### 1. Build + pack locally
|
||||
|
||||
// turbo
|
||||
|
||||
```bash
|
||||
cd /home/diegosouzapw/dev/proxys/OmniRoute && rm -f omniroute-*.tgz && rm -rf .next/cache app/.next/cache && npm run build:cli && rm -rf app/logs app/coverage app/.git app/.app-build-backup* && npm pack --ignore-scripts
|
||||
```
|
||||
|
||||
### 2. Copy to Akamai VPS and install
|
||||
|
||||
// turbo-all
|
||||
|
||||
```bash
|
||||
scp omniroute-*.tgz root@69.164.221.35:/tmp/
|
||||
```
|
||||
|
||||
```bash
|
||||
ssh root@69.164.221.35 "npm install -g /tmp/omniroute-*.tgz --ignore-scripts && cd /usr/lib/node_modules/omniroute/app && npm rebuild better-sqlite3 && pm2 delete omniroute 2>/dev/null; pm2 start /root/.omniroute/ecosystem.config.cjs --update-env && pm2 save && echo '✅ Akamai done'"
|
||||
```
|
||||
|
||||
### 3. Verify the deployment
|
||||
|
||||
```bash
|
||||
curl -s -o /dev/null -w 'AKAMAI HTTP %{http_code}\n' http://69.164.221.35:20128/
|
||||
```
|
||||
49
.claude/commands/deploy-vps-both-cc.md
Normal file
49
.claude/commands/deploy-vps-both-cc.md
Normal file
@@ -0,0 +1,49 @@
|
||||
---
|
||||
description: Deploy the latest OmniRoute code to BOTH the Akamai VPS and the Local VPS
|
||||
---
|
||||
|
||||
# Deploy to VPS (Both) Workflow
|
||||
|
||||
Deploy OmniRoute to the production VPSs using `npm pack + scp` + PM2.
|
||||
|
||||
**Akamai VPS:** `69.164.221.35`
|
||||
**Local VPS:** `192.168.0.15`
|
||||
**Process manager:** PM2 (`omniroute`)
|
||||
**Port:** `20128`
|
||||
**PM2 entry:** `/usr/lib/node_modules/omniroute/app/server.js`
|
||||
|
||||
> [!IMPORTANT]
|
||||
> The npm registry rejects packages > 100MB, so deployment uses **npm pack + scp**.
|
||||
|
||||
## Steps
|
||||
|
||||
### 1. Build + pack locally
|
||||
|
||||
// turbo
|
||||
|
||||
```bash
|
||||
cd /home/diegosouzapw/dev/proxys/OmniRoute && rm -f omniroute-*.tgz && rm -rf .next/cache app/.next/cache && npm run build:cli && rm -rf app/logs app/coverage app/.git app/.app-build-backup* && npm pack --ignore-scripts
|
||||
```
|
||||
|
||||
### 2. Copy to both VPS and install
|
||||
|
||||
// turbo-all
|
||||
|
||||
```bash
|
||||
scp omniroute-*.tgz root@69.164.221.35:/tmp/ && scp omniroute-*.tgz root@192.168.0.15:/tmp/
|
||||
```
|
||||
|
||||
```bash
|
||||
ssh root@69.164.221.35 "npm install -g /tmp/omniroute-*.tgz --ignore-scripts && cd /usr/lib/node_modules/omniroute/app && npm rebuild better-sqlite3 && pm2 delete omniroute 2>/dev/null; pm2 start /root/.omniroute/ecosystem.config.cjs --update-env && pm2 save && echo '✅ Akamai done'"
|
||||
```
|
||||
|
||||
```bash
|
||||
ssh root@192.168.0.15 "npm install -g /tmp/omniroute-*.tgz --ignore-scripts && cd /usr/lib/node_modules/omniroute/app && npm rebuild better-sqlite3 && pm2 delete omniroute 2>/dev/null; pm2 start /root/.omniroute/ecosystem.config.cjs --update-env && pm2 save && echo '✅ Local done'"
|
||||
```
|
||||
|
||||
### 3. Verify the deployment
|
||||
|
||||
```bash
|
||||
curl -s -o /dev/null -w 'AKAMAI HTTP %{http_code}\n' http://69.164.221.35:20128/
|
||||
curl -s -o /dev/null -w 'LOCAL HTTP %{http_code}\n' http://192.168.0.15:20128/
|
||||
```
|
||||
39
.claude/commands/deploy-vps-local-cc.md
Normal file
39
.claude/commands/deploy-vps-local-cc.md
Normal file
@@ -0,0 +1,39 @@
|
||||
---
|
||||
description: Deploy the latest OmniRoute code to the Local VPS (192.168.0.15)
|
||||
---
|
||||
|
||||
# Deploy to Local VPS Workflow
|
||||
|
||||
Deploy OmniRoute to the Local VPS using `npm pack + scp` + PM2.
|
||||
|
||||
**Local VPS:** `192.168.0.15`
|
||||
**Process manager:** PM2 (`omniroute`)
|
||||
**Port:** `20128`
|
||||
|
||||
## Steps
|
||||
|
||||
### 1. Build + pack locally
|
||||
|
||||
// turbo
|
||||
|
||||
```bash
|
||||
cd /home/diegosouzapw/dev/proxys/OmniRoute && rm -f omniroute-*.tgz && rm -rf .next/cache app/.next/cache && npm run build:cli && rm -rf app/logs app/coverage app/.git app/.app-build-backup* && npm pack --ignore-scripts
|
||||
```
|
||||
|
||||
### 2. Copy to Local VPS and install
|
||||
|
||||
// turbo-all
|
||||
|
||||
```bash
|
||||
scp omniroute-*.tgz root@192.168.0.15:/tmp/
|
||||
```
|
||||
|
||||
```bash
|
||||
ssh root@192.168.0.15 "npm install -g /tmp/omniroute-*.tgz --ignore-scripts && cd /usr/lib/node_modules/omniroute/app && npm rebuild better-sqlite3 && pm2 delete omniroute 2>/dev/null; pm2 start /root/.omniroute/ecosystem.config.cjs --update-env && pm2 save && echo '✅ Local done'"
|
||||
```
|
||||
|
||||
### 3. Verify the deployment
|
||||
|
||||
```bash
|
||||
curl -s -o /dev/null -w 'LOCAL HTTP %{http_code}\n' http://192.168.0.15:20128/
|
||||
```
|
||||
362
.claude/commands/generate-release-cc.md
Normal file
362
.claude/commands/generate-release-cc.md
Normal file
@@ -0,0 +1,362 @@
|
||||
---
|
||||
description: Create a new release, bump version up to the .10 patch threshold, update changelog, and manage Pull Requests
|
||||
---
|
||||
|
||||
# Generate Release Workflow
|
||||
|
||||
Bump version, finalize CHANGELOG, commit, open a **PR to main** and wait for user confirmation before tagging, publishing, and deploying.
|
||||
|
||||
> **VERSION RULE: Always use PATCH bumps (3.x.y → 3.x.y+1)**
|
||||
> NEVER use `npm version minor` or `npm version major`.
|
||||
> Always use: `npm version patch --no-git-tag-version`
|
||||
> The threshold rule: when `y` reaches 10, bump to `3.(x+1).0` — e.g. `3.8.10` → `3.9.0`.
|
||||
|
||||
> **🔴 SINGLE BRANCH RULE**: The `release/vX.Y.Z` branch is the **ONLY** development branch for the entire release cycle. ALL work — bug fixes, feature implementations, PR integrations, issue resolutions — MUST be committed directly on this branch. Never create separate `fix/`, `feat/`, or topic branches. When running `/resolve-issues`, `/implement-features`, or `/review-prs`, always work on the current release branch.
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ Two-Phase Flow
|
||||
|
||||
```
|
||||
Phase 1 (automated): bump → docs → i18n → commit → push → open PR
|
||||
↕ 🛑 STOP: Notify user, wait for PR confirmation
|
||||
Phase 2 (post-merge): tag → publish → GitHub release → Docker → deploy
|
||||
```
|
||||
|
||||
**NEVER push directly to main or create tags before the user confirms the PR.**
|
||||
|
||||
---
|
||||
|
||||
## Phase 0: Security Verification (MANDATORY)
|
||||
|
||||
Before creating the release, you must ensure the codebase and supply chain are secure and free of known vulnerabilities.
|
||||
|
||||
1. **Run Local Dependencies Audit:**
|
||||
|
||||
```bash
|
||||
npm audit
|
||||
```
|
||||
|
||||
_Fix any `high` or `critical` vulnerabilities identified._
|
||||
|
||||
2. **Check GitHub CodeQL & Dependabot Alerts:**
|
||||
Navigate to the repository's **Security** tab on GitHub, or use the project's `vulnerability-scanner` skill to analyze active alerts. Ensure all static analysis findings (e.g., prototype pollution, insecure randomness, ReDoS, shell injections) are addressed and logically committed on a target branch.
|
||||
|
||||
---
|
||||
|
||||
## Phase 1: Pre-Merge
|
||||
|
||||
### 1. Create release branch
|
||||
|
||||
```bash
|
||||
git checkout -b release/v3.x.y
|
||||
```
|
||||
|
||||
### 2. Determine and sync version
|
||||
|
||||
Check current version in `package.json`:
|
||||
|
||||
```bash
|
||||
grep '"version"' package.json
|
||||
```
|
||||
|
||||
> **🔴 BRANCH-VERSION PARITY RULE**: The logical version in `package.json` MUST exactly match the release branch name. For example, if you are on `release/v3.7.0`, the version in `package.json` MUST be `3.7.0`.
|
||||
>
|
||||
> - If this is the FIRST time generating a release for a new minor/major branch (e.g., bumping from 3.6.9 to 3.7.0), you MUST ensure the version is bumped to match the new branch logic.
|
||||
> - If you are just bumping a patch on the current branch (e.g., 3.6.9 to 3.6.10), use:
|
||||
> `npm version patch --no-git-tag-version`
|
||||
|
||||
> **⚠️ ATOMIC COMMIT RULE — Version bump MUST happen before committing feature files.**
|
||||
>
|
||||
> **CORRECT order:**
|
||||
>
|
||||
> 1. `npm version patch --no-git-tag-version` ← bump first
|
||||
> 2. implement features / fix bugs
|
||||
> 3. `git add -A && git commit -m "chore(release): v3.x.y — all changes in ONE commit"`
|
||||
>
|
||||
> **OR if features are already staged:**
|
||||
>
|
||||
> 1. implement features (do NOT commit yet)
|
||||
> 2. `npm version patch --no-git-tag-version` ← bump before committing
|
||||
> 3. `git add -A && git commit -m "chore(release): v3.x.y — all changes in ONE commit"`
|
||||
>
|
||||
> **NEVER do this (creates version mismatch in git history):**
|
||||
>
|
||||
> - ~~commit features → then bump version → commit package.json separately~~
|
||||
>
|
||||
> This ensures that `git show v3.x.y` always contains both code changes and the version bump together.
|
||||
> The GitHub release tag will point to a commit that includes ALL changes for that version.
|
||||
|
||||
### 3. Regenerate lock file (REQUIRED after version bump)
|
||||
|
||||
**Mandatory** — skipping causes `@swc/helpers` lock mismatch and CI failures:
|
||||
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
### 4. Finalize CHANGELOG.md
|
||||
|
||||
> **🔴 NO MIXUPS RULE**: Ensure you do NOT mix the backlog of the previous version with the new one. The new version section must ONLY contain the features and fixes for the current release.
|
||||
|
||||
Replace the `[Unreleased]` header with the new version and date.
|
||||
Keep an empty `## [Unreleased]` section above it, separated by a horizontal rule (`---`).
|
||||
|
||||
```markdown
|
||||
## [Unreleased]
|
||||
|
||||
---
|
||||
|
||||
## [3.7.0] — 2026-04-19
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
- ...
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- ...
|
||||
|
||||
---
|
||||
|
||||
## [3.6.9] — 2026-04-19
|
||||
```
|
||||
|
||||
### 5. Update openapi.yaml version ⚠️ MANDATORY
|
||||
|
||||
> **CI will fail** if `docs/reference/openapi.yaml` version ≠ `package.json` version (`check:docs-sync` enforces this).
|
||||
|
||||
// turbo
|
||||
|
||||
```bash
|
||||
VERSION=$(node -p "require('./package.json').version")
|
||||
sed -i "s/ version: .*/ version: $VERSION/" docs/reference/openapi.yaml
|
||||
echo "✓ openapi.yaml → $VERSION"
|
||||
|
||||
for dir in electron open-sse; do
|
||||
if [ -d "$dir" ] && [ -f "$dir/package.json" ]; then
|
||||
(cd "$dir" && npm version "$VERSION" --no-git-tag-version --allow-same-version > /dev/null)
|
||||
echo "✓ $dir/package.json → $VERSION"
|
||||
fi
|
||||
done
|
||||
# Re-run install to assert the workspace lockfile is updated
|
||||
npm install
|
||||
```
|
||||
|
||||
### 6. Update README.md and i18n docs
|
||||
|
||||
Manually perform these documentation updates (there is no `/update-docs` slash command — it was deprecated in v3.8):
|
||||
|
||||
- Update feature table rows and "What's new in vX.Y.Z" section in `README.md`
|
||||
- Sync feature changes to all 40 language `docs/i18n/*/README.md` files (use the same row edits across each translated README)
|
||||
- Update the relevant `docs/<AREA>.md` if architecture or counts changed (e.g. `docs/frameworks/MCP-SERVER.md` when MCP tools change)
|
||||
- Re-run `npm run check:docs-sync` and `npm run check:docs-all` to catch drift
|
||||
|
||||
### 7. Run tests
|
||||
|
||||
// turbo
|
||||
|
||||
```bash
|
||||
npm test
|
||||
```
|
||||
|
||||
All tests must pass before creating the PR.
|
||||
|
||||
### 8. Stage, commit, and push
|
||||
|
||||
// turbo-all
|
||||
|
||||
```bash
|
||||
git add -A
|
||||
git commit -m "chore(release): v3.x.y — summary of changes"
|
||||
git push origin release/v3.x.y
|
||||
```
|
||||
|
||||
### 9. Open PR to main
|
||||
|
||||
### 9. Open PR to main
|
||||
|
||||
// turbo
|
||||
|
||||
```bash
|
||||
VERSION=$(node -p "require('./package.json').version")
|
||||
|
||||
# Extract the exact changelog entry for this version from the root CHANGELOG.md
|
||||
awk "/^## \\[$VERSION\\]/{flag=1; print; next} /^---/{if(flag) {flag=0; exit}} flag" CHANGELOG.md > /tmp/changelog_body.txt
|
||||
|
||||
# Append test status and next steps
|
||||
echo "" >> /tmp/changelog_body.txt
|
||||
echo "### Tests" >> /tmp/changelog_body.txt
|
||||
echo "- All tests pass" >> /tmp/changelog_body.txt
|
||||
echo "" >> /tmp/changelog_body.txt
|
||||
echo "### ⚠️ After merging: run Phase 2 steps to tag, publish, and deploy." >> /tmp/changelog_body.txt
|
||||
|
||||
gh pr create \
|
||||
--repo diegosouzapw/OmniRoute \
|
||||
--base main \
|
||||
--head release/v$VERSION \
|
||||
--title "Release v$VERSION" \
|
||||
--body-file /tmp/changelog_body.txt
|
||||
```
|
||||
|
||||
### 10. 🛑 STOP — Notify User & Await PR Confirmation
|
||||
|
||||
**This is a mandatory stop point.** Present the report in the final response and stop. Do not continue to the next phase until the user explicitly approves.
|
||||
|
||||
Inform the user:
|
||||
|
||||
- PR URL
|
||||
- Summary of changes
|
||||
- Test results
|
||||
- List of files changed
|
||||
|
||||
**DO NOT proceed to Phase 2 until the user confirms the PR looks good and merges it.**
|
||||
|
||||
---
|
||||
|
||||
## Phase 2: Post-Merge Validation (Local VPS)
|
||||
|
||||
> Run these steps only AFTER the user has merged the PR into `main` and all CI jobs have passed.
|
||||
|
||||
### 11. Deploy to Local VPS for Final Validation (MANDATORY)
|
||||
|
||||
Before cutting the official git tag and publishing to the world, deploy the `main` branch to the Local VPS for a final homologation test.
|
||||
|
||||
```bash
|
||||
git checkout main
|
||||
git pull origin main
|
||||
|
||||
# Build and pack locally
|
||||
cd /home/diegosouzapw/dev/proxys/OmniRoute && rm -f omniroute-*.tgz && rm -rf .next/cache app/.next/cache && npm run build:cli && rm -rf app/logs app/coverage app/.git app/.app-build-backup* && npm pack --ignore-scripts
|
||||
|
||||
# Deploy to LOCAL VPS (192.168.0.15)
|
||||
scp omniroute-*.tgz root@192.168.0.15:/tmp/
|
||||
ssh root@192.168.0.15 "npm install -g /tmp/omniroute-*.tgz --ignore-scripts && cd /usr/lib/node_modules/omniroute/app && npm rebuild better-sqlite3 && pm2 delete omniroute 2>/dev/null; pm2 start /root/.omniroute/ecosystem.config.cjs --update-env && pm2 save && echo '✅ Local done'"
|
||||
|
||||
# Verify
|
||||
curl -s -o /dev/null -w "LOCAL: HTTP %{http_code}\n" http://192.168.0.15:20128/
|
||||
```
|
||||
|
||||
### 12. 🛑 STOP — Notify User & Await Final OK
|
||||
|
||||
**This is a mandatory stop point.**
|
||||
Inform the user that the `main` branch is now running on the Local VPS.
|
||||
Wait for the user to manually test and give the **OK**.
|
||||
**DO NOT proceed to Phase 3 until the user confirms the local deploy is stable.**
|
||||
|
||||
---
|
||||
|
||||
## Phase 3: Official Launch
|
||||
|
||||
> Run these steps only AFTER the user gives the final OK from the Phase 2 local validation.
|
||||
|
||||
### 13. Create Git Tag and GitHub Release (MANDATORY)
|
||||
|
||||
// turbo
|
||||
|
||||
```bash
|
||||
git checkout main
|
||||
git pull origin main
|
||||
VERSION=$(node -p "require('./package.json').version")
|
||||
|
||||
# Extracts the changelog section for this version
|
||||
NOTES=$(awk "/^## \\[$VERSION\\]/{flag=1; next} /^---/{if(flag) {flag=0; exit}} flag" CHANGELOG.md | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')
|
||||
if [ -z "$NOTES" ]; then NOTES="OmniRoute v$VERSION Release"; fi
|
||||
|
||||
git tag -a "v$VERSION" -m "Release v$VERSION"
|
||||
git push origin "v$VERSION"
|
||||
gh release create "v$VERSION" --repo diegosouzapw/OmniRoute --title "v$VERSION" --notes "$NOTES" --target main || gh release edit "v$VERSION" --repo diegosouzapw/OmniRoute --title "v$VERSION" --notes "$NOTES"
|
||||
```
|
||||
|
||||
### 14. 🐳 Trigger Docker Hub build (MANDATORY — keep npm and Docker in sync)
|
||||
|
||||
> **CRITICAL**: Docker Hub and npm MUST always publish the same version.
|
||||
> The Docker image is built automatically via GitHub Actions when a new tag is pushed.
|
||||
> After pushing the tag in step 13, **verify the workflow runs**:
|
||||
|
||||
```bash
|
||||
# Verify the Docker workflow triggered
|
||||
gh run list --repo diegosouzapw/OmniRoute --workflow docker-publish.yml --limit 3
|
||||
|
||||
# Wait for the Docker build to complete (usually 5–10 min)
|
||||
gh run watch --repo diegosouzapw/OmniRoute
|
||||
```
|
||||
|
||||
### 15. Publish to NPM (Optional/Automated)
|
||||
|
||||
Normally handled by CI, but if manual publish is required:
|
||||
|
||||
```bash
|
||||
npm publish
|
||||
```
|
||||
|
||||
### 16. Deploy to AKAMAI VPS (Production)
|
||||
|
||||
Now that the release is officially cut, deploy it to the Akamai VPS.
|
||||
|
||||
```bash
|
||||
# Deploy to AKAMAI VPS (69.164.221.35)
|
||||
scp omniroute-*.tgz root@69.164.221.35:/tmp/
|
||||
ssh root@69.164.221.35 "npm install -g /tmp/omniroute-*.tgz --ignore-scripts && cd /usr/lib/node_modules/omniroute/app && npm rebuild better-sqlite3 && pm2 delete omniroute 2>/dev/null; pm2 start /root/.omniroute/ecosystem.config.cjs --update-env && pm2 save && echo '✅ Akamai done'"
|
||||
|
||||
# Verify
|
||||
curl -s -o /dev/null -w "AKAMAI: HTTP %{http_code}\n" http://69.164.221.35:20128/
|
||||
```
|
||||
|
||||
## Phase 4: Release Monitoring & Artifact Validation
|
||||
|
||||
> After triggering the official release, actively monitor the CI pipelines until all artifacts are successfully generated. If any pipeline fails, stop and apply the necessary corrections before continuing.
|
||||
|
||||
### 18. Monitor CI Pipelines
|
||||
|
||||
Wait for and verify the successful completion of the following automated jobs:
|
||||
|
||||
1. **Docker Hub Publish**
|
||||
2. **Electron Build**
|
||||
3. **NPM Registry Publish** (Check with `npm info omniroute version`)
|
||||
|
||||
```bash
|
||||
# Monitor Docker Hub workflow
|
||||
gh run list --repo diegosouzapw/OmniRoute --workflow docker-publish.yml --limit 1
|
||||
gh run watch <RUN_ID>
|
||||
|
||||
# Monitor Electron build
|
||||
gh run list --repo diegosouzapw/OmniRoute --workflow electron-release.yml --limit 1
|
||||
gh run watch <RUN_ID>
|
||||
|
||||
# Verify NPM version
|
||||
npm info omniroute version
|
||||
```
|
||||
|
||||
### 19. Handle Failures (If Any)
|
||||
|
||||
If a workflow fails:
|
||||
|
||||
- Use `gh run view <RUN_ID> --log-failed` to identify the error.
|
||||
- Apply the fix on the `main` branch.
|
||||
- If necessary, re-trigger the workflow using `gh workflow run <workflow_name.yml> --repo diegosouzapw/OmniRoute --ref v3.x.y`
|
||||
|
||||
### 20. Preserve release branch
|
||||
|
||||
```bash
|
||||
# Branch is kept for historical purposes. Do not delete.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Notes
|
||||
|
||||
- Ensure CHANGELOG, README and `docs/*` are current BEFORE this workflow — run `npm run check:docs-all` and `/version-bump` first (there is no `/update-docs` slash command anymore)
|
||||
- The `prepublishOnly` script runs `npm run build:cli` automatically during `npm publish`
|
||||
- After npm publish, verify with `npm info omniroute version`
|
||||
- Lock file sync errors are caused by skipping `npm install` after version bump
|
||||
- Use `gh auth switch -u diegosouzapw` if git push fails with wrong account
|
||||
|
||||
## Known CI Pitfalls
|
||||
|
||||
| CI failure | Cause | Fix |
|
||||
| ------------------------------------------------------------------------- | ------------------------------------------------------------------ | ---------------------------------------------------------------------- |
|
||||
| `[docs-sync] FAIL - OpenAPI version differs from package.json` | Skipped step 5 — `docs/reference/openapi.yaml` version not updated | Run step 5 (`sed -i ...`) and commit |
|
||||
| `[docs-sync] FAIL - CHANGELOG.md first section must be "## [Unreleased]"` | `## [Unreleased]` missing or not at top of CHANGELOG | Add `## [Unreleased]\n\n---\n` before the first versioned `## [x.y.z]` |
|
||||
| Electron Linux `.deb` build fails (`FpmTarget` error) | `fpm` Ruby gem not installed on `ubuntu-latest` runner | Already fixed in `electron-release.yml` (`gem install fpm` step) |
|
||||
| Docker Hub `502 error writing layer blob` | Transient Docker Hub network error during ARM64 push | Re-run the Docker publish workflow; no code change needed |
|
||||
706
.claude/commands/implement-features-cc.md
Normal file
706
.claude/commands/implement-features-cc.md
Normal file
@@ -0,0 +1,706 @@
|
||||
---
|
||||
description: Analyze open feature request issues, implement viable ones on dedicated branches, and respond to authors
|
||||
---
|
||||
|
||||
# /implement-features — Feature Request Harvest, Research & Implementation Workflow
|
||||
|
||||
## Overview
|
||||
|
||||
A **5-phase** workflow that systematically harvests feature requests from GitHub issues, creates structured idea files, researches solutions across the internet and Git repositories, presents a consolidated report for user approval, then generates detailed implementation plans and executes them.
|
||||
|
||||
**Output directory structure:**
|
||||
|
||||
```
|
||||
_ideia/
|
||||
├── viable/ # Features approved for implementation
|
||||
│ ├── need_details/ # ❓ Good idea but waiting for author clarification (issues stay OPEN)
|
||||
│ │ └── 1015-warp-terminal-mitm.md
|
||||
│ ├── 1046-native-playground.md # ✅ Ready — researched and planned
|
||||
│ └── 1046-native-playground.requirements.md
|
||||
├── defer/ # ⏭️ Good ideas deferred for future cycles (issues CLOSED)
|
||||
│ └── 1041-smart-auto-combos.md
|
||||
└── notfit/ # ❌ Out of scope / already exists (issues CLOSED)
|
||||
└── 945-telegram-integration.md
|
||||
|
||||
_tasks/features-vX.Y.Z/ # Implementation plans (per-release)
|
||||
└── 1046-native-playground.plan.md
|
||||
```
|
||||
|
||||
> **LIFECYCLE RULE:** `viable/` files are **DELETED** once the feature is implemented — they are not moved. Only unimplemented features live in `viable/` (or `viable/need_details/`). Files in `defer/` and `notfit/` remain as permanent reference.
|
||||
|
||||
> **BRANCH RULE**: All implementation work MUST happen on the current `release/vX.Y.Z` branch. Never create separate `feat/` branches. If no release branch exists yet, create one first using `/generate-release` Phase 1 steps 1–5.
|
||||
|
||||
---
|
||||
|
||||
## Phase 1 — Harvest: Collect & Catalog Feature Ideas
|
||||
|
||||
### 1.1 Identify the Repository
|
||||
|
||||
// turbo
|
||||
|
||||
- Run: `git -C <project_root> remote get-url origin` to extract owner/repo.
|
||||
|
||||
### 1.2 Ensure Release Branch Exists
|
||||
|
||||
// turbo
|
||||
|
||||
Before doing any work, ensure you are on the current release branch:
|
||||
|
||||
```bash
|
||||
# Check current branch
|
||||
git branch --show-current
|
||||
|
||||
# If on main, determine next version and create the release branch
|
||||
VERSION=$(node -p "require('./package.json').version")
|
||||
NEXT=$(node -p "const [a,b,c]=('$VERSION').split('.').map(Number); c>=9?a+'.'+(b+1)+'.0':a+'.'+b+'.'+(c+1)")
|
||||
git checkout -b release/v$NEXT
|
||||
npm version patch --no-git-tag-version
|
||||
npm install
|
||||
```
|
||||
|
||||
If already on a `release/vX.Y.Z` branch, continue working there.
|
||||
|
||||
### 1.3 Fetch ALL Open Feature Requests
|
||||
|
||||
// turbo-all
|
||||
|
||||
**⚠️ CRITICAL**: The JSON output of `gh issue list` can be truncated by the tool, silently hiding issues. You MUST use the two-step approach below.
|
||||
|
||||
**Step 1 — Get Issue numbers only** (small output, never truncated):
|
||||
|
||||
```bash
|
||||
# Fetch issues with feature/enhancement labels
|
||||
gh issue list --repo <owner>/<repo> --state open -l "enhancement" --limit 500 --json number --jq '.[].number'
|
||||
|
||||
# Also check for [Feature] in title (common pattern when no labels are set)
|
||||
gh issue list --repo <owner>/<repo> --state open --limit 500 --json number,title --jq '.[] | select(.title | test("\\[Feature\\]|\\[feature\\]|feature request"; "i")) | .number'
|
||||
```
|
||||
|
||||
- Merge both lists, deduplicate. Count and confirm the total.
|
||||
|
||||
**Step 2 — Fetch full metadata for each Issue** (one call per issue):
|
||||
|
||||
```bash
|
||||
gh issue view <NUMBER> --repo <owner>/<repo> --json number,title,labels,body,comments,createdAt,author,assignees
|
||||
```
|
||||
|
||||
- Read the **entire body** — including description, use cases, screenshots, mockups, and any embedded images.
|
||||
- Read **ALL comments** — community discussion, agreements, restrictions, owner responses, and linked PRs.
|
||||
- **Images**: If the body or comments contain image URLs (`` or `https://...png/jpg/gif`), note them — they may contain UI mockups, wireframes, or architecture diagrams that are essential to understanding the request.
|
||||
- You may batch these into parallel calls (up to 4 at a time).
|
||||
- Sort by oldest first (FIFO).
|
||||
|
||||
### 1.4 Create Idea Files (initially in `_ideia/` root)
|
||||
|
||||
For each feature request, create a structured idea file in `<project_root>/_ideia/`:
|
||||
|
||||
**Filename convention**: `<NUMBER>-<kebab-case-short-title>.md`
|
||||
Example: `1046-native-playground.md`, `1041-smart-auto-combos.md`
|
||||
|
||||
#### 1.4a — If the idea file does NOT exist yet, create it:
|
||||
|
||||
```markdown
|
||||
# Feature: <Title from Issue>
|
||||
|
||||
> GitHub Issue: #<NUMBER> — opened by @<author> on <date>
|
||||
> Status: 📋 Cataloged | Priority: TBD
|
||||
|
||||
## 📝 Original Request
|
||||
|
||||
<Paste the FULL issue body here, preserving all formatting, images, and code blocks>
|
||||
|
||||
## 💬 Community Discussion
|
||||
|
||||
<Summarize ALL comments chronologically, noting who said what and any decisions or objections raised>
|
||||
|
||||
### Participants
|
||||
|
||||
- @<author> — Original requester
|
||||
- @<commenter1> — <brief role/opinion>
|
||||
- ...
|
||||
|
||||
### Key Points
|
||||
|
||||
- <bullet list of the most important discussion points>
|
||||
- <agreements reached>
|
||||
- <objections raised>
|
||||
|
||||
## 🎯 Refined Feature Description
|
||||
|
||||
<YOUR interpretation and enrichment of the feature request. Expand on what was asked, fill in logical gaps, provide concrete examples of how it would work. This section should be MORE detailed and clearer than the original request.>
|
||||
|
||||
### What it solves
|
||||
|
||||
- <problem 1>
|
||||
- <problem 2>
|
||||
|
||||
### How it should work (high level)
|
||||
|
||||
1. <step 1>
|
||||
2. <step 2>
|
||||
3. ...
|
||||
|
||||
### Affected areas
|
||||
|
||||
- <list of codebase areas, modules, files likely affected>
|
||||
|
||||
## 📎 Attachments & References
|
||||
|
||||
- <any image URLs, mockup links, or external references from the issue>
|
||||
|
||||
## 🔗 Related Ideas
|
||||
|
||||
- <links to related \_ideia/ files if any overlap found>
|
||||
```
|
||||
|
||||
#### 1.4b — If the idea file ALREADY exists, update it:
|
||||
|
||||
- Append new comments from the issue to the **Community Discussion** section.
|
||||
- Update the **Refined Feature Description** if new information changes the understanding.
|
||||
- Add any new **Related Ideas** cross-references found.
|
||||
- **Do NOT overwrite** existing content — append and enrich it.
|
||||
|
||||
### 1.5 Cross-Reference & Deduplication
|
||||
|
||||
After processing all issues:
|
||||
|
||||
- Scan all `_ideia/*.md` files for overlapping features.
|
||||
- If two features are substantially the same, add `🔗 Related Ideas` cross-references to both.
|
||||
- If one is a strict subset of another, note it in the smaller file: `> ℹ️ This feature is a subset of #<OTHER_NUMBER>. Consider implementing together.`
|
||||
|
||||
---
|
||||
|
||||
## Phase 2 — Research: Find Solutions & Build Requirements
|
||||
|
||||
For each cataloged idea that is **viable** (aligns with the project's goals):
|
||||
|
||||
### 2.1 Viability Pre-Check
|
||||
|
||||
Before investing in research, quickly assess:
|
||||
|
||||
- [ ] Does this feature align with the project's goals and architecture?
|
||||
- [ ] Is it technically feasible with the current codebase?
|
||||
- [ ] Does it duplicate existing functionality?
|
||||
- [ ] Would it introduce breaking changes or security risks?
|
||||
- [ ] Is there enough detail to understand what's needed?
|
||||
|
||||
**Verdict options:**
|
||||
|
||||
| Verdict | When | Action |
|
||||
| --------------------- | ------------------------------------- | --------------------------- |
|
||||
| ✅ **VIABLE** | Good idea, enough context | Proceed to Research |
|
||||
| ❓ **NEEDS DETAIL** | Good idea, insufficient spec | Skip research, ask author |
|
||||
| ⏭️ **DEFER** | Good idea, too complex for this cycle | Catalog only, skip research |
|
||||
| ❌ **NOT FIT** | Doesn't fit the project | Explain why |
|
||||
| 🔁 **ALREADY EXISTS** | Feature already implemented | Point to existing feature |
|
||||
|
||||
### 2.2 Internet Research (for VIABLE features)
|
||||
|
||||
For each viable feature, perform systematic research:
|
||||
|
||||
**Step 1 — Web search for similar implementations:**
|
||||
|
||||
```
|
||||
WebSearch("how to implement <feature description> in <tech stack>")
|
||||
WebSearch("<feature keyword> implementation nextjs typescript 2025 2026")
|
||||
WebSearch("<feature keyword> open source library npm")
|
||||
```
|
||||
|
||||
**Step 2 — Find reference Git repositories:**
|
||||
|
||||
```
|
||||
WebSearch("site:github.com <feature keyword> <tech stack> stars:>100")
|
||||
WebSearch("github <feature keyword> implementation recently updated 2026")
|
||||
```
|
||||
|
||||
- Find **up to 10 relevant repositories**, sorted by most recently updated.
|
||||
- For each repository:
|
||||
- Note the repo URL, star count, last commit date
|
||||
- Read its README and relevant source files via `WebFetch`
|
||||
- Extract the architectural approach, patterns used, and key code snippets
|
||||
|
||||
**Step 3 — Read API docs and standards:**
|
||||
|
||||
If the feature involves an external API, protocol, or standard:
|
||||
|
||||
- Find and read the official documentation
|
||||
- Note version requirements, authentication patterns, rate limits
|
||||
|
||||
### 2.3 Create Requirements File
|
||||
|
||||
For each researched feature, create a requirements file alongside its idea file:
|
||||
|
||||
**Filename**: `<NUMBER>-<kebab-case-short-title>.requirements.md`
|
||||
|
||||
```markdown
|
||||
# Requirements: <Feature Title>
|
||||
|
||||
> Feature Idea: [#<NUMBER>](./<NUMBER>-<kebab-case-short-title>.md)
|
||||
> Research Date: <YYYY-MM-DD>
|
||||
> Verdict: ✅ VIABLE
|
||||
|
||||
## 🔍 Research Summary
|
||||
|
||||
<Brief summary of what was found during research>
|
||||
|
||||
## 📚 Reference Implementations
|
||||
|
||||
| # | Repository | Stars | Last Updated | Approach | Relevance |
|
||||
| --- | ---------------- | ----- | ------------ | -------- | ------------ |
|
||||
| 1 | [repo/name](url) | ⭐ N | YYYY-MM-DD | <brief> | High/Med/Low |
|
||||
| 2 | ... | | | | |
|
||||
|
||||
### Key Patterns Found
|
||||
|
||||
- <pattern 1 with code snippet or link>
|
||||
- <pattern 2>
|
||||
|
||||
## 📐 Proposed Solution Architecture
|
||||
|
||||
### Approach
|
||||
|
||||
<Describe the chosen approach based on research findings>
|
||||
|
||||
### New Files
|
||||
|
||||
| File | Purpose |
|
||||
| --------------------- | ------------- |
|
||||
| `path/to/new/file.ts` | <description> |
|
||||
|
||||
### Modified Files
|
||||
|
||||
| File | Changes |
|
||||
| -------------------------- | -------------- |
|
||||
| `path/to/existing/file.ts` | <what changes> |
|
||||
|
||||
### Database Changes
|
||||
|
||||
- <migrations needed, if any>
|
||||
|
||||
### API Changes
|
||||
|
||||
- <new/modified endpoints, if any>
|
||||
|
||||
### UI Changes
|
||||
|
||||
- <new/modified pages/components, if any>
|
||||
|
||||
## ⚙️ Implementation Effort
|
||||
|
||||
- **Estimated complexity**: Low / Medium / High / Very High
|
||||
- **Estimated files changed**: ~N
|
||||
- **Dependencies needed**: <new npm packages, if any>
|
||||
- **Breaking changes**: Yes/No — <details>
|
||||
- **i18n impact**: <number of new translation keys>
|
||||
- **Test coverage needed**: <brief description>
|
||||
|
||||
## ⚠️ Open Questions
|
||||
|
||||
- <question 1>
|
||||
- <question 2>
|
||||
|
||||
## 🔗 External References
|
||||
|
||||
- <documentation URLs>
|
||||
- <API references>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Phase 2.5 — Organize & Respond: Sort Files and Post GitHub Comments
|
||||
|
||||
### 2.5.1 Create Directory Structure
|
||||
|
||||
// turbo
|
||||
|
||||
```bash
|
||||
mkdir -p <project_root>/_ideia/viable
|
||||
mkdir -p <project_root>/_ideia/viable/need_details
|
||||
mkdir -p <project_root>/_ideia/defer
|
||||
mkdir -p <project_root>/_ideia/notfit
|
||||
```
|
||||
|
||||
### 2.5.2 Move Idea Files to Category Subdirectories
|
||||
|
||||
After classification, move EVERY idea file to its correct subdirectory:
|
||||
|
||||
```bash
|
||||
# ✅ VIABLE — move idea + requirements files
|
||||
mv _ideia/<NUMBER>-*.md _ideia/viable/
|
||||
mv _ideia/<NUMBER>-*.requirements.md _ideia/viable/
|
||||
|
||||
# ❓ NEEDS DETAIL — viable but waiting for author response
|
||||
mv _ideia/<NUMBER>-*.md _ideia/viable/need_details/
|
||||
|
||||
# ⏭️ DEFER — move idea files only
|
||||
mv _ideia/<NUMBER>-*.md _ideia/defer/
|
||||
|
||||
# ❌ NOT FIT & 🔁 ALREADY EXISTS — move idea files only
|
||||
mv _ideia/<NUMBER>-*.md _ideia/notfit/
|
||||
```
|
||||
|
||||
No files should remain in `_ideia/` root after this step (except subdirectories).
|
||||
|
||||
### 2.5.3 Post GitHub Comments by Category
|
||||
|
||||
**Each category has a specific comment template and action:**
|
||||
|
||||
---
|
||||
|
||||
#### For 🔁 ALREADY EXISTS — Comment + CLOSE issue
|
||||
|
||||
// turbo
|
||||
|
||||
The feature already exists in the system. Explain WHERE it is and HOW to use it.
|
||||
|
||||
```markdown
|
||||
Hi @<author>! Thanks for the suggestion! 🙏
|
||||
|
||||
Great news — this functionality **already exists** in OmniRoute:
|
||||
|
||||
**📍 Where to find it:** <exact dashboard path or settings location>
|
||||
|
||||
**🔧 How to use it:**
|
||||
|
||||
1. <step 1>
|
||||
2. <step 2>
|
||||
3. <step 3>
|
||||
|
||||
If you have any trouble finding or using it, feel free to ask in a Discussion. We're always happy to help!
|
||||
|
||||
Closing this as the feature is already available. 🎉
|
||||
```
|
||||
|
||||
```bash
|
||||
gh issue close <NUMBER> --repo <owner>/<repo> --comment "<comment above>"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### For ⏭️ DEFER — Comment + CLOSE issue
|
||||
|
||||
// turbo
|
||||
|
||||
Thank the user, explain the idea was cataloged, and that we'll study it before implementing.
|
||||
|
||||
```markdown
|
||||
Hi @<author>! Thanks for this thoughtful feature request! 🙏
|
||||
|
||||
We really appreciate the detailed proposal. We've **cataloged your idea** and it's now part of our improvement backlog.
|
||||
|
||||
Due to the **significant architectural impact** of this feature, we'll need to conduct thorough use-case studies and architectural analysis before we start development. This ensures we build it right and don't introduce regressions.
|
||||
|
||||
**What happens next:**
|
||||
|
||||
- Your idea is saved in our internal feature backlog
|
||||
- We'll conduct architecture studies when this area is prioritized
|
||||
- We'll notify you here when development begins
|
||||
|
||||
Thank you for contributing to OmniRoute's roadmap! Your input helps shape the product. 🚀
|
||||
```
|
||||
|
||||
```bash
|
||||
gh issue close <NUMBER> --repo <owner>/<repo> --comment "<comment above>"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### For ❌ NOT FIT — Comment + CLOSE issue
|
||||
|
||||
// turbo
|
||||
|
||||
Politely explain why the feature doesn't fit the project scope.
|
||||
|
||||
```markdown
|
||||
Hi @<author>! Thanks for the suggestion! 🙏
|
||||
|
||||
After careful analysis, we've determined that this feature **falls outside OmniRoute's core scope** as a proxy/router.
|
||||
|
||||
**Reason:** <explain why — e.g., "Telegram integration belongs in the application/orchestrator layer that consumes OmniRoute's API, not inside the router itself.">
|
||||
|
||||
**Alternative:** <suggest an alternative approach if possible>
|
||||
|
||||
We appreciate you thinking of ways to improve OmniRoute! If you'd like to discuss this further, feel free to open a Discussion. 🙏
|
||||
```
|
||||
|
||||
```bash
|
||||
gh issue close <NUMBER> --repo <owner>/<repo> --comment "<comment above>"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### For ❓ NEEDS DETAIL — Comment (keep OPEN)
|
||||
|
||||
// turbo
|
||||
|
||||
Ask for the specific missing details needed.
|
||||
|
||||
```markdown
|
||||
Hi @<author>! Thanks for the feature request — it's an interesting idea and we'd love to explore it further. 🙏
|
||||
|
||||
To move forward, we need a few more details:
|
||||
|
||||
1. <specific question 1>
|
||||
2. <specific question 2>
|
||||
3. <specific question 3>
|
||||
|
||||
If you know of any **open-source projects or repositories** that implement something similar, please share links — it would help us design the best solution.
|
||||
|
||||
Looking forward to your response! 🚀
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### For ✅ VIABLE — Comment (keep OPEN)
|
||||
|
||||
// turbo
|
||||
|
||||
Thank the user, confirm we've cataloged their idea, and explain it may be implemented in future versions.
|
||||
|
||||
```markdown
|
||||
Hi @<author>! Thanks for the great feature suggestion! 🙏
|
||||
|
||||
We've analyzed your request and it aligns well with OmniRoute's roadmap. We've **cataloged this feature** and it's in our implementation backlog.
|
||||
|
||||
**Status:** 📋 Cataloged for future implementation
|
||||
|
||||
This feature may be included in upcoming releases. We'll **respond to this issue and tag you** as soon as implementation begins so you can test it.
|
||||
|
||||
Thank you for helping improve OmniRoute! 🚀
|
||||
```
|
||||
|
||||
**⚠️ Do NOT close viable issues — they remain OPEN for tracking.**
|
||||
|
||||
---
|
||||
|
||||
## Phase 3 — Report: Present Findings to User
|
||||
|
||||
### 3.1 🛑 MANDATORY STOP — Present Consolidated Report
|
||||
|
||||
After completing Phase 1, Phase 2, and Phase 2.5, **STOP and present the following report** in the chat. Do NOT proceed to implementation.
|
||||
|
||||
Present a structured report containing:
|
||||
|
||||
#### 3.1a — Feature Summary Table
|
||||
|
||||
| # | Issue | Title | Verdict | Location | Action |
|
||||
| --- | ----- | ----- | --------------- | ----------------------------- | ----------------------------- |
|
||||
| 1 | #N | Title | ✅ VIABLE | `_ideia/viable/` | Issue OPEN, comment posted |
|
||||
| 2 | #N | Title | ⏭️ DEFER | `_ideia/defer/` | Issue CLOSED with explanation |
|
||||
| 3 | #N | Title | ❌ NOT FIT | `_ideia/notfit/` | Issue CLOSED with explanation |
|
||||
| 4 | #N | Title | 🔁 EXISTS | `_ideia/notfit/` | Issue CLOSED with guidance |
|
||||
| 5 | #N | Title | ❓ NEEDS DETAIL | `_ideia/viable/need_details/` | Issue OPEN, questions posted |
|
||||
|
||||
#### 3.1b — Viable Features Detail
|
||||
|
||||
For each VIABLE feature, provide a brief paragraph:
|
||||
|
||||
- What was found during research
|
||||
- The proposed approach
|
||||
- Key risks or unknowns
|
||||
- Which reference repositories were most useful
|
||||
|
||||
#### 3.1c — Issues Requiring Author Feedback
|
||||
|
||||
For features marked ❓ NEEDS DETAIL, list:
|
||||
|
||||
- What specific information is missing
|
||||
- What examples or repository references would help
|
||||
|
||||
#### 3.1d — Ask for User Confirmation
|
||||
|
||||
End the report with:
|
||||
|
||||
> **Ready to proceed with implementation?**
|
||||
>
|
||||
> - Reply **"sim"** or **"yes"** to generate full implementation plans for all VIABLE features.
|
||||
> - Reply with specific issue numbers to select only certain features.
|
||||
> - Reply **"não"** or **"no"** to stop here.
|
||||
|
||||
---
|
||||
|
||||
## Phase 4 — Plan: Generate Implementation Plans (after user says "yes")
|
||||
|
||||
> **⚠️ Do NOT enter this phase without explicit user approval from Phase 3.**
|
||||
|
||||
### 4.1 Create Task Directory
|
||||
|
||||
```bash
|
||||
mkdir -p <project_root>/_tasks/features-vX.Y.Z/
|
||||
```
|
||||
|
||||
### 4.2 Generate One Implementation Plan Per Feature
|
||||
|
||||
For each VIABLE feature approved by the user, create:
|
||||
|
||||
**Filename**: `_tasks/features-vX.Y.Z/<NUMBER>-<kebab-case-title>.plan.md`
|
||||
|
||||
```markdown
|
||||
# Implementation Plan: <Feature Title>
|
||||
|
||||
> Issue: #<NUMBER>
|
||||
> Idea: [\_ideia/viable/<NUMBER>-title.md](../../_ideia/viable/<NUMBER>-title.md)
|
||||
> Requirements: [\_ideia/viable/<NUMBER>-title.requirements.md](../../_ideia/viable/<NUMBER>-title.requirements.md)
|
||||
> Branch: `release/vX.Y.Z`
|
||||
|
||||
## Overview
|
||||
|
||||
<Brief description of what will be built>
|
||||
|
||||
## Pre-Implementation Checklist
|
||||
|
||||
- [ ] Read all related source files listed below
|
||||
- [ ] Confirm no conflicts with in-flight PRs
|
||||
- [ ] Verify database migration numbering
|
||||
|
||||
## Implementation Steps
|
||||
|
||||
### Step 1: <Title>
|
||||
|
||||
**Files:**
|
||||
|
||||
- `path/to/file.ts` — <what to change>
|
||||
|
||||
**Details:**
|
||||
<Detailed description of the change, including code patterns to follow, function signatures, etc.>
|
||||
|
||||
### Step 2: <Title>
|
||||
|
||||
...
|
||||
|
||||
### Step N: Tests
|
||||
|
||||
**New test files:**
|
||||
|
||||
- `tests/unit/<test-file>.test.mjs` — <what to test>
|
||||
|
||||
**Test cases:**
|
||||
|
||||
- [ ] <test case 1>
|
||||
- [ ] <test case 2>
|
||||
|
||||
### Step N+1: i18n
|
||||
|
||||
**Translation keys to add:**
|
||||
|
||||
- `<namespace>.<key>` — "<English value>"
|
||||
|
||||
### Step N+2: Documentation
|
||||
|
||||
- [ ] Update CHANGELOG.md
|
||||
- [ ] Update relevant docs/ files
|
||||
|
||||
## Verification Plan
|
||||
|
||||
1. Run `npm run build` — must pass
|
||||
2. Run `npm test` — all tests must pass
|
||||
3. Run `npm run lint` — no new errors
|
||||
4. <Manual verification steps>
|
||||
|
||||
## Commit Plan
|
||||
```
|
||||
|
||||
feat: <description> (#<NUMBER>)
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
|
||||
### 4.3 Present Plans for Final Approval
|
||||
|
||||
Present a summary of all generated plans:
|
||||
|
||||
> **Implementation plans generated:**
|
||||
>
|
||||
> | # | Feature | Plan File | Steps | Effort |
|
||||
> | --- | ------- | ---------------------------------------- | ------- | ------ |
|
||||
> | 1 | <title> | `_tasks/features-vX.Y.Z/N-title.plan.md` | N steps | Medium |
|
||||
>
|
||||
> Reply **"sim"** or **"yes"** to begin implementation of all features.
|
||||
> Reply with specific issue numbers to implement only certain ones.
|
||||
|
||||
---
|
||||
|
||||
## Phase 5 — Execute: Implement the Plans (after user says "yes")
|
||||
|
||||
> **⚠️ Do NOT enter this phase without explicit user approval from Phase 4.**
|
||||
|
||||
### 5.1 Implement Each Feature
|
||||
|
||||
For each approved plan, execute it step by step:
|
||||
|
||||
1. **Follow the plan** — implement exactly as specified in the `.plan.md` file
|
||||
2. **Build** — Run `npm run build` after each feature to verify compilation
|
||||
3. **Test** — Run `npm test` to ensure no regressions
|
||||
4. **Commit** — Commit with: `feat: <description> (#<NUMBER>)`
|
||||
5. **Update the plan** — Mark completed steps with `[x]` in the plan file
|
||||
6. **Continue** — Move to the next feature (do NOT switch branches)
|
||||
|
||||
### 5.2 Respond to Authors (Update Viable Issues)
|
||||
|
||||
For each implemented feature, **close the issue with a final comment**:
|
||||
|
||||
````markdown
|
||||
✅ **Implemented in `release/vX.Y.Z`!**
|
||||
|
||||
Hi @<author>! Great news — your feature request has been implemented! 🎉
|
||||
|
||||
**What was done:**
|
||||
|
||||
- <bullet list of what was built>
|
||||
|
||||
**How to try it:**
|
||||
|
||||
```bash
|
||||
git fetch origin && git checkout release/vX.Y.Z
|
||||
npm install && npm run dev
|
||||
```
|
||||
````
|
||||
|
||||
This will be included in the upcoming **vX.Y.Z** release. Feel free to reopen if you spot any issues! 🚀
|
||||
|
||||
````
|
||||
|
||||
```bash
|
||||
gh issue close <NUMBER> --repo <owner>/<repo> --comment "<comment above>"
|
||||
````
|
||||
|
||||
Then **DELETE the idea file** — it has served its purpose:
|
||||
|
||||
```bash
|
||||
# ✅ Implemented files are DELETED (not moved)
|
||||
rm _ideia/viable/<NUMBER>-<title>.md
|
||||
rm _ideia/viable/<NUMBER>-<title>.requirements.md # if exists
|
||||
```
|
||||
|
||||
> **Why delete?** `viable/` only holds features that still NEED to be done. Once implemented, the commit history and CHANGELOG are the source of truth. Keeping the file would be confusing.
|
||||
|
||||
### 5.3 Finalize & Push
|
||||
|
||||
After implementing all approved features:
|
||||
|
||||
1. **Update CHANGELOG.md** on the release branch with all new feature entries
|
||||
2. Push the release branch: `git push origin release/vX.Y.Z`
|
||||
3. Run `/generate-release` workflow Phase 1 steps 7–10 (tests → commit → push → open PR to main → wait for user)
|
||||
|
||||
### 5.4 Final Summary Report
|
||||
|
||||
Present a final summary report to the user:
|
||||
|
||||
| Issue | Title | Verdict | Action | Commit |
|
||||
| ----- | ----- | --------------- | -------------------------------------------------- | --------- |
|
||||
| #N | Title | ✅ Implemented | Issue closed, idea file deleted | `abc1234` |
|
||||
| #N | Title | ⏭️ Deferred | Issue closed + saved in `_ideia/defer/` | — |
|
||||
| #N | Title | ❌ Not Fit | Issue closed + saved in `_ideia/notfit/` | — |
|
||||
| #N | Title | 🔁 Exists | Issue closed + saved in `_ideia/notfit/` | — |
|
||||
| #N | Title | ❓ Needs Detail | Issue OPEN, moved to `_ideia/viable/need_details/` | — |
|
||||
|
||||
Include:
|
||||
|
||||
- Total features harvested
|
||||
- Total ideas cataloged (`viable/need_details/` + `defer/` + `notfit/`)
|
||||
- Total features implemented (idea files deleted, issues closed)
|
||||
- Total features deferred
|
||||
- Total issues closed
|
||||
- Total issues left open (needs detail only — viable are closed after implementation)
|
||||
- Test results (pass/fail count)
|
||||
50
.claude/commands/issue-triage-cc.md
Normal file
50
.claude/commands/issue-triage-cc.md
Normal file
@@ -0,0 +1,50 @@
|
||||
---
|
||||
description: How to respond to GitHub issues with insufficient information
|
||||
---
|
||||
|
||||
# Issue Triage Workflow
|
||||
|
||||
Respond to GitHub issues that need more information before they can be investigated.
|
||||
|
||||
## Steps
|
||||
|
||||
### 1. Identify issues needing triage
|
||||
|
||||
```bash
|
||||
gh issue list --state open --limit 20
|
||||
```
|
||||
|
||||
### 2. Evaluate each issue
|
||||
|
||||
Check if the issue has:
|
||||
|
||||
- Clear reproduction steps
|
||||
- Environment details (OS, Node.js version, OmniRoute version)
|
||||
- Error logs/screenshots
|
||||
- Expected vs actual behavior
|
||||
|
||||
### 3. Respond with triage template
|
||||
|
||||
For issues missing information:
|
||||
|
||||
```markdown
|
||||
Thank you for reporting this issue! To help us investigate, please provide:
|
||||
|
||||
1. **OmniRoute version**: (`omniroute --version`)
|
||||
2. **Node.js version**: (`node --version`)
|
||||
3. **Operating system**: (e.g., Ubuntu 24.04, macOS 15, Windows 11)
|
||||
4. **Installation method**: (npm, Docker, source)
|
||||
5. **Steps to reproduce**: (exact commands/actions that trigger the issue)
|
||||
6. **Error logs**: (paste relevant logs from the console)
|
||||
7. **Expected behavior**: (what should happen)
|
||||
|
||||
This will help us debug and resolve your issue faster. 🙏
|
||||
```
|
||||
|
||||
### 4. Label the issue
|
||||
|
||||
Add appropriate labels: `needs-info`, `bug`, `enhancement`, `question`, etc.
|
||||
|
||||
```bash
|
||||
gh issue edit <NUMBER> --add-label "needs-info"
|
||||
```
|
||||
166
.claude/commands/resolve-issues-cc.md
Normal file
166
.claude/commands/resolve-issues-cc.md
Normal file
@@ -0,0 +1,166 @@
|
||||
---
|
||||
description: Fetch all open GitHub issues, analyze bugs, resolve what's possible, triage the rest, wait for user validation, then commit and release
|
||||
---
|
||||
|
||||
# /resolve-issues — Automated Issue Resolution Workflow
|
||||
|
||||
## Overview
|
||||
|
||||
This workflow fetches all open issues from the project's GitHub repository, classifies them, analyzes bugs, proposes a resolution plan, waits for user validation, and ONLY THEN implements the fixes, commits, and closes the issues on the current release branch (`release/vX.Y.Z`). It does NOT merge or release automatically — the release branch is later merged via PR to main.
|
||||
|
||||
> **BRANCH RULE**: All work MUST happen on the current `release/vX.Y.Z` branch. Never create separate `fix/` branches. If no release branch exists yet, create one first using `/generate-release` Phase 1 steps 1–5.
|
||||
|
||||
> **⛔ PR PROHIBITION**: If a fix is associated with a contributor's PR, you MUST merge their PR — NEVER close it and re-implement the fix yourself. See `/review-prs` workflow for the full policy. The `gh pr close` command is FORBIDDEN unless the repository owner explicitly requests it.
|
||||
|
||||
## Steps
|
||||
|
||||
### 1. Identify the GitHub Repository
|
||||
|
||||
// turbo
|
||||
|
||||
- Run: `git -C <project_root> remote get-url origin` to extract the owner/repo
|
||||
- Parse the owner and repo name from the URL
|
||||
|
||||
### 2. Ensure Release Branch Exists
|
||||
|
||||
// turbo
|
||||
|
||||
Before doing any work, ensure you are on the current release branch:
|
||||
|
||||
```bash
|
||||
# Check current branch
|
||||
git branch --show-current
|
||||
|
||||
# If on main, determine next version and create the release branch
|
||||
VERSION=$(node -p "require('./package.json').version")
|
||||
NEXT=$(node -p "const [a,b,c]=('$VERSION').split('.').map(Number); c>=9?a+'.'+(b+1)+'.0':a+'.'+b+'.'+(c+1)")
|
||||
git checkout -b release/v$NEXT
|
||||
npm version patch --no-git-tag-version
|
||||
npm install
|
||||
```
|
||||
|
||||
If already on a `release/vX.Y.Z` branch, continue working there.
|
||||
|
||||
### 3. Fetch All Open Issues
|
||||
|
||||
// turbo-all
|
||||
|
||||
**⚠️ CRITICAL**: The JSON output of `gh issue list` can be truncated by the tool, silently hiding issues. You MUST use the two-step approach below to guarantee **all** issues are fetched.
|
||||
|
||||
**Step 3a — Get Issue numbers only** (small output, never truncated):
|
||||
|
||||
- Run: `gh issue list --repo <owner>/<repo> --state open --limit 500 --json number --jq '.[].number'`
|
||||
- This outputs one issue number per line. Count them and confirm total.
|
||||
|
||||
**Step 3b — Fetch full metadata for each Issue** (one call per issue):
|
||||
|
||||
- For each issue number from step 3a, run:
|
||||
`gh issue view <NUMBER> --repo <owner>/<repo> --json number,title,labels,body,comments,createdAt,author`
|
||||
- You may batch these into parallel calls (up to 4 at a time).
|
||||
- Sort by oldest first (FIFO).
|
||||
|
||||
### 4. Classify Each Issue
|
||||
|
||||
For each issue, determine its type:
|
||||
|
||||
- **Bug** — Has `bug` label, or body contains error messages, stack traces, "doesn't work", "broken", "crash", "error"
|
||||
- **Feature Request** — Has `enhancement`/`feature` label, or body describes new functionality
|
||||
- **Question** — Has `question` label, or is asking "how to" something
|
||||
- **Other** — Anything else
|
||||
|
||||
Focus ONLY on **Bugs** for resolution. Feature requests and questions should be skipped with a note in the final report.
|
||||
|
||||
### 5. Deep-Read Each Bug Issue (One-by-One Analysis)
|
||||
|
||||
**IMPORTANT**: Read each bug issue thoroughly, one at a time, before moving to the next. This is NOT a batch process — each issue needs focused attention.
|
||||
|
||||
#### 5a. Understand the Problem
|
||||
|
||||
For each bug issue, perform the full analysis:
|
||||
|
||||
1. **Read the entire body** — including Description, Steps to Reproduce, Expected/Actual Behavior, Error Logs, and Screenshots
|
||||
2. **Read ALL comments** — including bot triage comments (Kilo, etc.) and owner/community responses. Pay attention to:
|
||||
- Whether someone already responded with a fix
|
||||
- Whether a community member confirmed the issue is resolved
|
||||
- Whether the issue was marked as duplicate by a bot. **WARNING: DO NOT blindly trust bot duplicate labels (e.g., kilo-duplicate). Bots make mistakes. You MUST read the full conversation and do your own independent analysis to determine if it is truly a duplicate or a distinct bug.**
|
||||
3. **Identify the claimed error** — extract the exact error message, status code, and provider/model involved
|
||||
|
||||
#### 5b. Check Information Sufficiency
|
||||
|
||||
Verify the issue contains enough to act on:
|
||||
|
||||
- [ ] Clear description of the problem
|
||||
- [ ] Steps to reproduce OR error logs
|
||||
- [ ] Provider/model/version information
|
||||
- [ ] Expected vs actual behavior
|
||||
|
||||
#### 5c. Determine Issue Disposition
|
||||
|
||||
For each bug, classify into one of 5 actions:
|
||||
|
||||
| Disposition | When to Apply | Action |
|
||||
| ---------------------------- | ---------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------- |
|
||||
| **✅ CLOSE — Already Fixed** | Owner responded with fix + no user follow-up, OR community confirmed fix | Close with comment citing which version fixed it |
|
||||
| **✅ CLOSE — Duplicate** | You have independently verified the issue is a duplicate (do NOT rely solely on bot flags) + user provides no new info | Close referencing the original issue |
|
||||
| **✅ CLOSE — Stale** | We requested logs/info > 7 days ago with no reply | Close thanking the user, invite to reopen if needed |
|
||||
| **📝 RESPOND — Needs Info** | Issue is real but missing critical reproduction details | Comment asking for specifics per `/issue-triage` |
|
||||
| **📝 RESPOND — User Config** | Error is caused by unsupported env (Node version, wrong model path, missing API enablement) | Comment explaining the user-side fix |
|
||||
| **🔧 FIX — Code Change** | Root cause is confirmed in the codebase | Research, propose solution in report, wait for approval |
|
||||
|
||||
#### 5d. For "FIX — Code Change" Issues
|
||||
|
||||
Before coding, perform deep source analysis to formulate a plan:
|
||||
|
||||
1. **Search the codebase** — `grep_search` for error strings, relevant function names, affected files
|
||||
2. **Search the web** — for upstream API changes, SDK updates, or breaking changes that explain the bug
|
||||
3. **Read the full source file** — don't rely on grep snippets; understand the surrounding logic
|
||||
4. **Verify the root cause** — confirm the bug is reproducible based on the code, not just a user misconfiguration
|
||||
5. **Formulate a proposed solution** — detail the exact files and lines you will change and how you will solve it.
|
||||
6. **Create an Implementation Plan file** — write your proposed solution to `_tasks/features-vX.Y.Z/<ISSUE_NUMBER>-<short-description>.plan.md` (e.g. `_tasks/features-v3.7.6/1810-auto-restore-probe-failed-db.plan.md`) where `vX.Y.Z` is the current branch version. The plan should contain an Overview, Pre-Implementation Checklist, and detailed Implementation Steps (Files, Changes).
|
||||
7. **DO NOT modify the codebase yet** — wait for user approval on your report and plan first.
|
||||
|
||||
#### 5e. For "RESPOND" Issues
|
||||
|
||||
Post a substantive comment that:
|
||||
|
||||
- Acknowledges the specific error they reported
|
||||
- Explains the likely root cause
|
||||
- Provides concrete steps to resolve (version upgrade, env var fix, model path correction)
|
||||
- Asks for follow-up info if needed
|
||||
|
||||
**Do NOT post generic template responses.** Every comment should reference the user's specific error messages and environment.
|
||||
|
||||
### 6. Generate Report & Wait for Validation
|
||||
|
||||
Present a summary report to the user detailing your proposed actions. For any bugs that need fixing, explicitly explain your proposed solution (files to change and logic) and point out that it will be implemented on the release branch (`release/vX.Y.Z`) after approval.
|
||||
|
||||
| Issue | Title | Status | Proposed Action / Version |
|
||||
| ----- | ----- | ------------- | ----------------------------------------- |
|
||||
| #N | Title | ✅ Close | Already fixed / duplicate (explain why) |
|
||||
| #N | Title | 🔧 Propose | Explanation of the code fix to be applied |
|
||||
| #N | Title | 📝 Respond | Guidance comment to be posted |
|
||||
| #N | Title | ❓ Needs Info | Triage comment to be posted |
|
||||
| #N | Title | ⏭️ Skip | Feature request / not a bug |
|
||||
|
||||
> **⚠️ IMPORTANT**: Do NOT implement code changes, commit, push, or close issues at this step.
|
||||
> Wait for the user to review the proposed fixes and respond with **OK** before proceeding.
|
||||
|
||||
- If the user says **OK** or approves → Proceed to step 7
|
||||
- If the user requests changes → Adjust the proposed solution and present the report again
|
||||
- If the user rejects → Revert any accidental changes and stop
|
||||
|
||||
### 7. Implement Fixes, Run Tests & Commit (only after user approval)
|
||||
|
||||
After the user validates and gives the OK:
|
||||
|
||||
1. **Implement the fixes** — modify the codebase according to the approved plan.
|
||||
2. **Run tests** — `npm run test:all` (or the specific test file) to ensure 100% pass.
|
||||
3. **Update CHANGELOG.md** with all new bug fix entries.
|
||||
4. **Commit** each fix individually on the release branch with message format: `fix: <description> (#<issue_number>)`.
|
||||
5. **Push** the release branch: `git push origin release/vX.Y.Z`.
|
||||
6. **Close resolved issues immediately**. For each issue that was marked as Fixed, run:
|
||||
`gh issue close <NUMBER> --repo <owner>/<repo> --comment "Thank you for reporting! This issue has been fixed and will be included in the next release (vX.Y.Z)."`
|
||||
7. Likewise, close `Duplicate` issues referencing the original, close `Needs Info` if stale, and post the required comments.
|
||||
8. If the project runs automatic releases or needs a PR, proceed to run `/generate-release` workflow Phase 1 steps 7–10 (tests → commit → push → open PR to main → wait for user).
|
||||
|
||||
If NO fixes were committed, skip closing and source control steps and just conclude the workflow.
|
||||
120
.claude/commands/review-discussions-cc.md
Normal file
120
.claude/commands/review-discussions-cc.md
Normal file
@@ -0,0 +1,120 @@
|
||||
---
|
||||
description: Read all open GitHub Discussions, summarize them, respond to pending ones, and create issues from actionable feature requests
|
||||
---
|
||||
|
||||
# /review-discussions — GitHub Discussions Review & Response Workflow
|
||||
|
||||
## Overview
|
||||
|
||||
This workflow reads all open GitHub Discussions, generates a categorized summary, identifies which ones need a response, drafts and posts replies, and optionally creates issues from actionable feature requests. It follows the same flow used for Issues but adapted for the Discussions forum.
|
||||
|
||||
> **Tool mapping note (v3.8):** Where steps below say `browser_subagent` (an earlier-runtime tool), in Claude Code use the `gh` CLI via the `Bash` tool — `gh api graphql` for reading discussions and `gh api graphql -F query=...` mutations for posting comments. `WebFetch` is acceptable for read-only HTML scraping when GraphQL is overkill, but prefer `gh` for any write actions.
|
||||
|
||||
// turbo-all
|
||||
|
||||
## Steps
|
||||
|
||||
### 1. Identify the GitHub Repository
|
||||
|
||||
- Run: `git -C <project_root> remote get-url origin` to extract the owner/repo
|
||||
- Parse the owner and repo name from the URL
|
||||
|
||||
### 2. Fetch All Open Discussions
|
||||
|
||||
- Use `WebFetch` to fetch `https://github.com/<owner>/<repo>/discussions`
|
||||
- Parse the discussion list to get all discussion titles, IDs, authors, categories, and dates
|
||||
- For each discussion, fetch the individual page to read the full content and all comments/replies
|
||||
|
||||
### 3. Summarize All Discussions
|
||||
|
||||
For each discussion, extract:
|
||||
|
||||
- **Title** and **#Number**
|
||||
- **Author** (GitHub username)
|
||||
- **Category** (Announcements, General, Ideas, Q&A, Show and tell)
|
||||
- **Date** created
|
||||
- **Summary** of the original post (1-2 sentences)
|
||||
- **Comments count** and key participants
|
||||
- **Your previous response** (if any)
|
||||
- **Pending action** — whether a response or follow-up is needed
|
||||
|
||||
### 4. Present Summary Report to User
|
||||
|
||||
Present the full summary to the user organized by category, using a table:
|
||||
|
||||
| # | Category | Title | Author | Date | Status |
|
||||
| --- | -------- | ----- | ------ | ------ | ----------------- |
|
||||
| #N | Ideas | Title | @user | Mar 23 | ⚠️ Needs response |
|
||||
| #N | Q&A | Title | @user | Mar 9 | ✅ Answered |
|
||||
| #N | General | Title | @user | Mar 19 | ⚠️ Needs response |
|
||||
|
||||
Highlight:
|
||||
|
||||
- **⚠️ Needs response** — No reply from maintainer, or a follow-up comment was left unanswered
|
||||
- **✅ Answered** — Maintainer already responded
|
||||
- **🐛 Bug reported** — A bug was mentioned that needs tracking
|
||||
- **💡 Actionable** — Contains a concrete feature request that could become an issue
|
||||
|
||||
### 5. Draft & Post Responses
|
||||
|
||||
For each discussion that needs a response, draft a reply following these guidelines:
|
||||
|
||||
#### Response Style
|
||||
|
||||
- **Friendly and professional** — Start with "Hey @username!"
|
||||
- **Acknowledge the contribution** — Thank the user for their input
|
||||
- **Be specific** — Reference existing features, settings, or dashboard pages if the feature already exists
|
||||
- **Provide workarounds** — If the request isn't implemented yet, suggest current alternatives
|
||||
- **Commit to action** — If the request is valid, state that you'll open an issue or add it to the roadmap
|
||||
- **Keep it concise** — 3-5 paragraphs max
|
||||
|
||||
#### Posting via Browser
|
||||
|
||||
- Use `browser_subagent` to navigate to each discussion and post the comment
|
||||
- **IMPORTANT**: When typing text in GitHub comment boxes via the browser, use only plain ASCII characters:
|
||||
- Use regular hyphens `-` instead of em-dashes
|
||||
- Use `->` instead of arrow symbols
|
||||
- Do NOT use emoji Unicode characters (the browser keyboard may fail on them)
|
||||
- Use `**bold**` and `\`code\`` markdown formatting
|
||||
- Click the green "Comment" button (or "Reply" for threaded replies) after typing
|
||||
- Verify the comment was posted by checking the page shows the new comment
|
||||
|
||||
### 6. Create Issues from Actionable Feature Requests
|
||||
|
||||
For discussions that contain concrete, actionable feature requests:
|
||||
|
||||
1. Ask the user which ones should become issues
|
||||
2. For each approved request, create a GitHub issue via `browser_subagent`:
|
||||
- Navigate to `https://github.com/<owner>/<repo>/issues/new`
|
||||
- **Title**: `<Feature Name> - <Short description>`
|
||||
- **Body** should include:
|
||||
- `## Feature Request` header
|
||||
- `**Source:** Discussion #N by @author`
|
||||
- `## Problem` — What limitation the user hit
|
||||
- `## Proposed Solution` — How it could work
|
||||
- `### Implementation Ideas` — Technical approach
|
||||
- `### Current Workarounds` — What users can do today
|
||||
- `## Additional Context` — Links to related issues/discussions
|
||||
- Add `enhancement` label
|
||||
- Click "Submit new issue" / "Create"
|
||||
3. After creation, go back to the original discussion and post a comment linking to the new issue:
|
||||
- "I've opened Issue #N to track this feature request. Follow along there for updates!"
|
||||
|
||||
### 7. Final Report
|
||||
|
||||
Present a final summary to the user:
|
||||
|
||||
| Discussion | Action Taken |
|
||||
| ---------- | ---------------------------------- |
|
||||
| #N — Title | Responded with workarounds |
|
||||
| #N — Title | Responded + created Issue #N |
|
||||
| #N — Title | Already answered, no action needed |
|
||||
| #N — Title | Responded to follow-up comment |
|
||||
|
||||
## Notes
|
||||
|
||||
- This workflow is **interactive** — always present the summary and wait for user approval before posting responses or creating issues
|
||||
- If the user says "pode responder" (or similar approval), proceed with posting all drafted responses
|
||||
- For discussions in non-English languages, respond in the same language as the original post
|
||||
- Always reference specific dashboard paths, config options, or code files when explaining existing features
|
||||
- When a discussion reveals a bug, note it separately from feature requests
|
||||
@@ -79,7 +79,7 @@ If already on a `release/vX.Y.Z` branch, continue working there.
|
||||
|
||||
- For each PR number, run:
|
||||
`gh pr diff <NUMBER> --repo <owner>/<repo> > /tmp/pr<NUMBER>.diff`
|
||||
- Then read each diff file with `view_file`.
|
||||
- Then read each diff file with the `Read` tool.
|
||||
|
||||
- For each open PR, collect:
|
||||
- PR number, title, author, branch, number of commits, date
|
||||
@@ -174,7 +174,7 @@ Perform a **global impact assessment** to verify whether the PR changes are comp
|
||||
|
||||
### 6. Present to User
|
||||
|
||||
- Show the report via `notify_user` with `BlockedOnUser: true`
|
||||
- Show the report in the final response and stop. This is a mandatory checkpoint awaiting explicit user approval before continuing.
|
||||
- Wait for user decision:
|
||||
- **Approved** → Proceed to step 7
|
||||
- **Approved with changes** → Implement the fixes and corrections before merging
|
||||
@@ -199,10 +199,11 @@ Perform a **global impact assessment** to verify whether the PR changes are comp
|
||||
git push
|
||||
```
|
||||
|
||||
- **Fallback (For external forks without maintainer edit access or severe conflicts):**
|
||||
If `git push` fails because the PR comes from an external fork without write access, or there are extreme conflicts, you MUST NOT CLOSE THE PR.
|
||||
Instead, use `git cherry-pick`, or a reverse merge to bring their changes into the release branch, fix the issues locally, and commit them. Ensure you preserve the contributor's authorship (`git commit --author="Contributor Name <email>"` if creating new commits).
|
||||
Once you have integrated their work into the release branch, DO NOT close their PR. Instead, leave it open or try to merge it using the CLI if possible. Under NO CIRCUMSTANCES should you use `gh pr close`. Leave it open so the contributor retains credit.
|
||||
- **Fallback (ONLY for external forks without maintainer edit access):**
|
||||
Using `cherry-pick` instead of fixing the contributor's PR directly is a **LAST RESORT**. You MUST ALWAYS attempt to `git push` your fixes to their branch first.
|
||||
**ONLY if `git push` explicitly fails with a permission/access error** (meaning the contributor unchecked "Allow edits from maintainers" or it's a locked fork), you may use `git cherry-pick` to bring their changes into the release branch and fix the issues locally.
|
||||
Even then, ensure you preserve the contributor's authorship (`git commit --author="Contributor Name <email>"` if creating new commits).
|
||||
Once you have integrated their work into the release branch, **DO NOT close their PR**. Leave it open so the contributor retains credit. Under NO CIRCUMSTANCES should you use `gh pr close`.
|
||||
|
||||
- Run the project's test suite locally to verify nothing breaks:
|
||||
// turbo
|
||||
@@ -211,7 +212,7 @@ Perform a **global impact assessment** to verify whether the PR changes are comp
|
||||
### 8. Merge into Release Branch (NEVER CLOSE!)
|
||||
|
||||
> **⚠️ CRITICAL**: NEVER use `gh pr close` for a PR whose idea or code was accepted. Closing a PR in a contributor's face after taking their idea—or closing it just because it had conflicts—is unacceptable.
|
||||
> You MUST ALWAYS resolve conflicts and apply fixes on the author's PR branch, and then merge the PR using GitHub so the contributor gets the official "Merged" badge and proper credit on their profile.
|
||||
> You MUST ALWAYS resolve conflicts and apply fixes ON THE AUTHOR'S PR BRANCH (unless explicitly locked from edits), and then merge the PR using GitHub so the contributor gets the official "Merged" badge and proper credit on their profile. **Do not use cherry-pick just because it is "easier" than resolving conflicts on their branch.**
|
||||
|
||||
Even if the PR had severe conflicts or required significant architectural adjustments, you MUST:
|
||||
|
||||
@@ -229,6 +230,8 @@ In ALL cases:
|
||||
- Note it will be included in the upcoming release.
|
||||
- Be friendly, professional, and encouraging.
|
||||
|
||||
> **⚠️ MANDATORY CHANGELOG CREDIT**: When cherry-picking is used (because the PR branch couldn't be pushed to or `gh pr merge` failed), the contributor does NOT get the automatic GitHub "Merged" badge. In this case, you MUST compensate by adding an explicit entry to `CHANGELOG.md` in the `[Unreleased]` section with `(#PR_NUMBER — thanks @username)` format. This ensures the contributor gets public credit in the release notes even if GitHub doesn't auto-detect the cherry-pick. This is NOT optional — skipping it effectively erases the contributor's work from the release record.
|
||||
|
||||
### 9. Sync Local Release Branch
|
||||
|
||||
After merging PRs, sync the local release branch to include the new changes:
|
||||
@@ -244,7 +247,7 @@ After processing all approved PRs:
|
||||
|
||||
- If more PRs remain, go back to step 7
|
||||
- When all PRs are processed, **update CHANGELOG.md** on the release branch with all new entries
|
||||
- Run **test coverage** to verify all metrics stay above 85%:
|
||||
- Run **test coverage** to verify the gate (≥75% statements/lines/functions, ≥70% branches — measured ~82%):
|
||||
```bash
|
||||
npm run test:coverage
|
||||
```
|
||||
341
.claude/commands/version-bump-cc.md
Normal file
341
.claude/commands/version-bump-cc.md
Normal file
@@ -0,0 +1,341 @@
|
||||
---
|
||||
description: Bump version, auto-generate CHANGELOG from git commits, update all versioned files, and refresh root + docs/ documentation to reflect the current project state
|
||||
---
|
||||
|
||||
# Version Bump Workflow
|
||||
|
||||
Automatically bump the project version, generate CHANGELOG entries from git history since the last tag, update every file that references the version, and refresh project documentation to reflect the current state.
|
||||
|
||||
> **VERSION RULE: Always use PATCH bumps (3.x.y → 3.x.y+1)**
|
||||
> NEVER use `npm version minor` or `npm version major`.
|
||||
> Always use: `npm version patch --no-git-tag-version`
|
||||
> The threshold rule: when `y` reaches 10, bump to `3.(x+1).0` — e.g. `3.4.10` → `3.5.0`.
|
||||
|
||||
---
|
||||
|
||||
## Phase 1: Determine Version
|
||||
|
||||
### 1. Read current version and last tag
|
||||
|
||||
// turbo
|
||||
|
||||
```bash
|
||||
cd /home/diegosouzapw/dev/proxys/OmniRoute
|
||||
CURRENT_VERSION=$(node -p "require('./package.json').version")
|
||||
LAST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "")
|
||||
CURRENT_BRANCH=$(git branch --show-current)
|
||||
echo "Current version: $CURRENT_VERSION"
|
||||
echo "Last tag: $LAST_TAG"
|
||||
echo "Current branch: $CURRENT_BRANCH"
|
||||
```
|
||||
|
||||
### 2. Calculate new version
|
||||
|
||||
Apply the patch bump rule:
|
||||
|
||||
- If the current patch number is `9`, the new version is `3.(minor+1).0`
|
||||
- Otherwise, increment patch: `3.x.y` → `3.x.(y+1)`
|
||||
|
||||
If the version was ALREADY bumped (e.g. you are on a release branch and package.json already has the new version), **skip the npm version bump** and use the existing version.
|
||||
|
||||
### 3. Bump package.json (if needed)
|
||||
|
||||
// turbo
|
||||
|
||||
```bash
|
||||
# Only if version hasn't been bumped yet
|
||||
npm version patch --no-git-tag-version
|
||||
```
|
||||
|
||||
Or for threshold (y=10):
|
||||
|
||||
```bash
|
||||
# Manual threshold bump
|
||||
VERSION="3.X.0" # compute manually
|
||||
npm version "$VERSION" --no-git-tag-version
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Phase 2: Generate CHANGELOG from Git History
|
||||
|
||||
### 4. Collect commits since last tag
|
||||
|
||||
// turbo
|
||||
|
||||
```bash
|
||||
cd /home/diegosouzapw/dev/proxys/OmniRoute
|
||||
LAST_TAG=$(git describe --tags --abbrev=0 2>/dev/null)
|
||||
echo "=== Commits since $LAST_TAG ==="
|
||||
git log "$LAST_TAG"..HEAD --pretty=format:"%h %s" --no-merges | head -100
|
||||
echo ""
|
||||
echo "=== Merge commits ==="
|
||||
git log "$LAST_TAG"..HEAD --merges --pretty=format:"%h %s" | head -50
|
||||
```
|
||||
|
||||
### 5. Classify commits and generate CHANGELOG section
|
||||
|
||||
Analyze each commit message and classify into categories based on the conventional-commit prefix and content:
|
||||
|
||||
| Category | Patterns |
|
||||
| ------------------- | ------------------------------------------------ |
|
||||
| ✨ New Features | `feat:`, `feat(*):` |
|
||||
| 🐛 Bug Fixes | `fix:`, `fix(*):` |
|
||||
| ⚠️ Breaking Changes | `BREAKING CHANGE`, `!:` suffix |
|
||||
| 🛠️ Maintenance | `chore:`, `refactor:`, `perf:`, `build:` |
|
||||
| 🧪 Tests | `test:`, `tests:` |
|
||||
| 📝 Documentation | `docs:` |
|
||||
| 🔒 Security | `security:`, CVE references, vulnerability fixes |
|
||||
| 🌍 i18n | translation updates, locale changes |
|
||||
|
||||
For each category with entries, create a markdown section with descriptive bullet points. Use the commit messages but rewrite them to be human-readable and descriptive (not raw commit messages).
|
||||
|
||||
**If a commit references a PR number** (e.g. `#880`, `PR #885`), include it in the description.
|
||||
|
||||
### 6. Update CHANGELOG.md
|
||||
|
||||
Replace the `## [Unreleased]` section content with the generated entries, then add the new versioned section:
|
||||
|
||||
```markdown
|
||||
## [Unreleased]
|
||||
|
||||
---
|
||||
|
||||
## [NEW_VERSION] — YYYY-MM-DD
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
- **Feature name:** Description (#PR)
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- **Fix name:** Description (#PR)
|
||||
|
||||
### 🛠️ Maintenance
|
||||
|
||||
- **Item:** Description
|
||||
|
||||
---
|
||||
|
||||
## [PREVIOUS_VERSION] — YYYY-MM-DD
|
||||
|
||||
...
|
||||
```
|
||||
|
||||
The date must be today's date in `YYYY-MM-DD` format.
|
||||
|
||||
---
|
||||
|
||||
## Phase 3: Sync Version Across All Files
|
||||
|
||||
### 7. Update workspace package.json files and openapi.yaml
|
||||
|
||||
// turbo
|
||||
|
||||
```bash
|
||||
cd /home/diegosouzapw/dev/proxys/OmniRoute
|
||||
VERSION=$(node -p "require('./package.json').version")
|
||||
|
||||
# Update docs/reference/openapi.yaml version
|
||||
sed -i "s/ version: .*/ version: $VERSION/" docs/reference/openapi.yaml
|
||||
echo "✓ docs/reference/openapi.yaml → $VERSION"
|
||||
|
||||
# Update workspace packages (open-sse, electron)
|
||||
for dir in electron open-sse; do
|
||||
if [ -d "$dir" ] && [ -f "$dir/package.json" ]; then
|
||||
(cd "$dir" && npm version "$VERSION" --no-git-tag-version --allow-same-version > /dev/null)
|
||||
echo "✓ $dir/package.json → $VERSION"
|
||||
fi
|
||||
done
|
||||
|
||||
echo "✓ All workspace packages synced to $VERSION"
|
||||
```
|
||||
|
||||
### 8. Update llm.txt version references
|
||||
|
||||
// turbo
|
||||
|
||||
```bash
|
||||
cd /home/diegosouzapw/dev/proxys/OmniRoute
|
||||
VERSION=$(node -p "require('./package.json').version")
|
||||
OLD_VERSION_PATTERN='[0-9]\+\.[0-9]\+\.[0-9]\+'
|
||||
|
||||
# Update "Current version:" line
|
||||
sed -i "s/\*\*Current version:\*\* $OLD_VERSION_PATTERN/**Current version:** $VERSION/" llm.txt
|
||||
|
||||
# Update "Key Features (vX.Y.Z)" header
|
||||
sed -i "s/## Key Features (v$OLD_VERSION_PATTERN)/## Key Features (v$VERSION)/" llm.txt
|
||||
|
||||
echo "✓ llm.txt → $VERSION"
|
||||
```
|
||||
|
||||
### 9. Regenerate lock file
|
||||
|
||||
// turbo
|
||||
|
||||
```bash
|
||||
cd /home/diegosouzapw/dev/proxys/OmniRoute
|
||||
npm install
|
||||
echo "✓ Lock file regenerated"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Phase 4: Update Root Documentation
|
||||
|
||||
Based on the CHANGELOG entries generated in Phase 2, review and update these root-level files if relevant changes warrant updates:
|
||||
|
||||
### 10. Review and update root documentation files
|
||||
|
||||
For each file below, read the current content and determine if the CHANGELOG entries require any updates. Only modify files where substantive changes have occurred:
|
||||
|
||||
| File | When to update |
|
||||
| ----------------- | --------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `README.md` | New providers, major features, stats changes (test count, provider count), badges, installation instructions, feature table |
|
||||
| `AGENTS.md` | Architecture changes, new modules, new commands, new providers, new services/handlers/executors |
|
||||
| `CONTRIBUTING.md` | Dev workflow changes, new tooling, test infrastructure changes |
|
||||
| `SECURITY.md` | Security fixes, new auth mechanisms, vulnerability disclosures |
|
||||
| `llm.txt` | Provider count changes, new features, architecture changes |
|
||||
|
||||
**Update rules:**
|
||||
|
||||
- **README.md**: Update provider count, test count, feature highlights table, badges if any numbers changed. If a new provider was added, add it to the provider table. If a major feature was added, add it to the features section.
|
||||
- **AGENTS.md**: If new architecture components (handlers, executors, services, DB modules) were added, update the Architecture section. If new commands were added, update the Build/Test table.
|
||||
- **SECURITY.md**: Add new vulnerability fixes or security improvements to the relevant section.
|
||||
- **llm.txt**: Update provider count, feature list, version references.
|
||||
|
||||
### 11. Review and update docs/ files (excluding i18n/)
|
||||
|
||||
For each file in `docs/` (excluding `docs/i18n/`), review if CHANGELOG changes affect it:
|
||||
|
||||
| File | When to update |
|
||||
| --------------------------------------------- | ------------------------------------------------------------------ |
|
||||
| `docs/reference/API_REFERENCE.md` | New API endpoints, changed request/response formats |
|
||||
| `docs/architecture/ARCHITECTURE.md` | New modules, new services, changed data flow |
|
||||
| `docs/architecture/CODEBASE_DOCUMENTATION.md` | New files, architectural changes, module reorganization |
|
||||
| `docs/architecture/REPOSITORY_MAP.md` | New folders / files / one-line descriptions |
|
||||
| `docs/reference/CLI-TOOLS.md` | New CLI tool integrations, config format changes |
|
||||
| `docs/guides/USER_GUIDE.md` | UX changes, new dashboard pages, settings changes |
|
||||
| `docs/reference/PROVIDER_REFERENCE.md` | New providers (regenerate via `scripts/gen-provider-reference.ts`) |
|
||||
| `docs/frameworks/MCP-SERVER.md` | New MCP tools, changed tool signatures, scope changes |
|
||||
| `docs/frameworks/A2A-SERVER.md` | New A2A skills, protocol changes |
|
||||
| `docs/frameworks/AGENT_PROTOCOLS_GUIDE.md` | New external agent protocols supported |
|
||||
| `docs/frameworks/CLOUD_AGENT.md` | Cloud agent additions (codex-cloud, devin, jules) or API changes |
|
||||
| `docs/architecture/AUTHZ_GUIDE.md` | New route classifications, policy changes |
|
||||
| `docs/security/GUARDRAILS.md` | New guardrails registered, priority/order changes |
|
||||
| `docs/security/COMPLIANCE.md` | Audit log / retention / no-log policy changes |
|
||||
| `docs/frameworks/SKILLS.md` | Skill framework / registry / built-in skill changes |
|
||||
| `docs/frameworks/MEMORY.md` | Memory pipeline / extraction / injection / Qdrant changes |
|
||||
| `docs/frameworks/EVALS.md` | Evaluation framework changes, new evaluators |
|
||||
| `docs/frameworks/WEBHOOKS.md` | New webhook events, payload schema changes |
|
||||
| `docs/routing/REASONING_REPLAY.md` | Reasoning capture/replay pipeline changes |
|
||||
| `docs/routing/AUTO-COMBO.md` | Routing changes, new strategies, scoring weight changes |
|
||||
| `docs/architecture/RESILIENCE_GUIDE.md` | Circuit breaker / cooldown / lockout behavior changes |
|
||||
| `docs/security/STEALTH_GUIDE.md` | TLS / CLI fingerprint changes |
|
||||
| `docs/ops/TUNNELS_GUIDE.md` | Cloudflare tunnel feature changes |
|
||||
| `docs/guides/ELECTRON_GUIDE.md` | Electron build / signing / packaging changes |
|
||||
| `docs/guides/TROUBLESHOOTING.md` | New known issues, resolved problems |
|
||||
| `docs/ops/RELEASE_CHECKLIST.md` | Process changes |
|
||||
| `docs/ops/COVERAGE_PLAN.md` | Coverage gate adjustments, target metrics |
|
||||
| `docs/reference/openapi.yaml` | Already updated in step 7 |
|
||||
|
||||
**Only update files where the CHANGELOG entries directly affect the documented content.** Do NOT update files just to bump a version number — only when the documented behavior, features, or architecture has actually changed.
|
||||
|
||||
---
|
||||
|
||||
## Phase 5: Verify
|
||||
|
||||
### 12. Run lint check
|
||||
|
||||
// turbo
|
||||
|
||||
```bash
|
||||
cd /home/diegosouzapw/dev/proxys/OmniRoute
|
||||
npm run lint
|
||||
```
|
||||
|
||||
### 13. Run tests
|
||||
|
||||
// turbo
|
||||
|
||||
```bash
|
||||
cd /home/diegosouzapw/dev/proxys/OmniRoute
|
||||
npm test
|
||||
```
|
||||
|
||||
### 14. Verify version sync across all files
|
||||
|
||||
// turbo
|
||||
|
||||
```bash
|
||||
cd /home/diegosouzapw/dev/proxys/OmniRoute
|
||||
VERSION=$(node -p "require('./package.json').version")
|
||||
echo "Expected version: $VERSION"
|
||||
echo ""
|
||||
|
||||
echo "--- package.json ---"
|
||||
grep '"version"' package.json | head -1
|
||||
|
||||
echo "--- open-sse/package.json ---"
|
||||
grep '"version"' open-sse/package.json | head -1
|
||||
|
||||
echo "--- electron/package.json ---"
|
||||
[ -f electron/package.json ] && grep '"version"' electron/package.json | head -1
|
||||
|
||||
echo "--- docs/reference/openapi.yaml ---"
|
||||
grep " version:" docs/reference/openapi.yaml | head -1
|
||||
|
||||
echo "--- llm.txt ---"
|
||||
grep "Current version:" llm.txt
|
||||
|
||||
echo "--- CHANGELOG.md (first versioned entry) ---"
|
||||
grep "^## \[" CHANGELOG.md | head -2
|
||||
```
|
||||
|
||||
### 15. 🛑 STOP — Present Summary to User
|
||||
|
||||
**STOP** and present a summary to the user including:
|
||||
|
||||
- Old version → New version
|
||||
- CHANGELOG entries generated
|
||||
- Files modified
|
||||
- Test results
|
||||
- Any documentation updates made
|
||||
|
||||
**Wait for the user to confirm before committing.**
|
||||
|
||||
---
|
||||
|
||||
## Phase 6: Commit (only after user approval)
|
||||
|
||||
### 16. Stage and commit
|
||||
|
||||
// turbo-all
|
||||
|
||||
```bash
|
||||
cd /home/diegosouzapw/dev/proxys/OmniRoute
|
||||
git add -A
|
||||
VERSION=$(node -p "require('./package.json').version")
|
||||
git commit -m "chore(release): bump to v$VERSION — changelog, docs, version sync"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Notes
|
||||
|
||||
- This workflow does **NOT** create tags, releases, or deploy. Use `/generate-release` for the full release cycle after this.
|
||||
- This workflow does **NOT** update `docs/i18n/` translations. Translation updates are handled manually or via release tooling — the `/update-i18n` command does not currently exist as a Claude Code slash command.
|
||||
- The CHANGELOG generation is based on git commits since the last tag. If there are no new commits, the workflow should inform the user and stop.
|
||||
- Always verify the generated CHANGELOG entries make sense — raw commit messages may need rewriting for clarity.
|
||||
- If the version was already bumped (e.g. you're on a `release/vX.Y.Z` branch), skip the `npm version` step and use the existing version.
|
||||
|
||||
## Version Touchpoints Checklist
|
||||
|
||||
| File | Field/Pattern |
|
||||
| ----------------------------- | ----------------------------------------------------------- |
|
||||
| `package.json` | `"version": "X.Y.Z"` |
|
||||
| `open-sse/package.json` | `"version": "X.Y.Z"` |
|
||||
| `electron/package.json` | `"version": "X.Y.Z"` |
|
||||
| `docs/reference/openapi.yaml` | `version: X.Y.Z` |
|
||||
| `llm.txt` | `**Current version:** X.Y.Z` and `## Key Features (vX.Y.Z)` |
|
||||
| `CHANGELOG.md` | `## [X.Y.Z] — YYYY-MM-DD` |
|
||||
@@ -38,7 +38,9 @@ playwright-report
|
||||
blob-report
|
||||
|
||||
# Documentation (not needed in container)
|
||||
docs
|
||||
# Keep the docs directory itself in context so openapi.yaml can be re-included.
|
||||
docs/*
|
||||
!docs/openapi.yaml
|
||||
*.md
|
||||
!README.md
|
||||
|
||||
|
||||
345
.env.example
345
.env.example
@@ -91,6 +91,12 @@ OMNIROUTE_USE_TURBOPACK=1
|
||||
# Used by: src/lib/runtime/ports.ts — preserves canonical port in Electron.
|
||||
# OMNIROUTE_PORT=20128
|
||||
|
||||
# Hostname/bind address for the Next.js server.
|
||||
# Used by: scripts/run-next.mjs (HOST), Playwright runner (HOSTNAME).
|
||||
# Default: 0.0.0.0 (HOST) / 127.0.0.1 (HOSTNAME inside tests).
|
||||
#HOST=0.0.0.0
|
||||
#HOSTNAME=127.0.0.1
|
||||
|
||||
# Environment mode — affects Next.js behavior, logging verbosity, and caching.
|
||||
# Values: production | development | Default: production
|
||||
NODE_ENV=production
|
||||
@@ -147,6 +153,10 @@ ALLOW_API_KEY_REVEAL=false
|
||||
# Default: false (blocked) | Set true to enable local providers.
|
||||
# OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true
|
||||
|
||||
# Legacy alias toggling the SSRF guard. Used by: src/shared/network/outboundUrlGuard.ts
|
||||
# When unset, OmniRoute uses the per-feature defaults. Set to "false"/"0" to disable.
|
||||
# OUTBOUND_SSRF_GUARD_ENABLED=true
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# 5. INPUT SANITIZATION & PII PROTECTION (FASE-01)
|
||||
@@ -250,6 +260,20 @@ NEXT_PUBLIC_CLOUD_URL=
|
||||
# Legacy alias — fallback for NEXT_PUBLIC_BASE_URL in sync schedulers.
|
||||
# NEXT_PUBLIC_APP_URL=http://localhost:20128
|
||||
|
||||
# Public callback URL for asynchronous image/audio jobs (kie.ai, etc.).
|
||||
# Used by: open-sse/utils/kieTask.ts — overrides callbackUrlFromBaseUrl().
|
||||
# Honor order: KIE_CALLBACK_URL → OMNIROUTE_KIE_CALLBACK_URL → OMNIROUTE_PUBLIC_URL.
|
||||
#KIE_CALLBACK_URL=
|
||||
#OMNIROUTE_KIE_CALLBACK_URL=
|
||||
#OMNIROUTE_PUBLIC_URL=
|
||||
|
||||
# Upstream quota endpoints used by the Usage page. Override only for
|
||||
# debugging or when routing through a corporate mirror. Used by:
|
||||
# open-sse/services/usage.ts.
|
||||
#OMNIROUTE_CROF_USAGE_URL=https://crof.ai/usage_api/
|
||||
#OMNIROUTE_GEMINI_CLI_USAGE_URL=https://cloudcode-pa.googleapis.com/v1internal:loadCodeAssist
|
||||
#OMNIROUTE_CODEWHISPERER_BASE_URL=https://codewhisperer.us-east-1.amazonaws.com
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# 8. OUTBOUND PROXY (Upstream Provider Calls)
|
||||
@@ -332,6 +356,17 @@ NEXT_PUBLIC_ENABLE_SOCKS5_PROXY=true
|
||||
# Full list: admin, combos, health, models, routing, budget, metrics, pricing, memory, skills
|
||||
# OMNIROUTE_MCP_SCOPES=admin,combos,health
|
||||
|
||||
# Compress MCP tool descriptions before serializing the manifest.
|
||||
# Used by: open-sse/mcp-server/descriptionCompressor.ts — reduces token spend
|
||||
# for clients that read the full tool catalog.
|
||||
# Accepted disabling values: 0, false, off. Default: enabled.
|
||||
# OMNIROUTE_MCP_COMPRESS_DESCRIPTIONS=1
|
||||
|
||||
# Algorithm/profile used when description compression is enabled.
|
||||
# Used by: open-sse/mcp-server/descriptionCompressor.ts
|
||||
# Set to 0/false/off to skip compression entirely. Default: rtk
|
||||
# OMNIROUTE_MCP_DESCRIPTION_COMPRESSION=rtk
|
||||
|
||||
# Model catalog sync interval in hours.
|
||||
# Used by: src/shared/services/modelSyncScheduler.ts — periodic model refresh.
|
||||
# Default: 24
|
||||
@@ -347,6 +382,48 @@ PROVIDER_LIMITS_SYNC_INTERVAL_MINUTES=70
|
||||
# Useful for: CI builds, test environments, or resource-constrained containers.
|
||||
# OMNIROUTE_DISABLE_BACKGROUND_SERVICES=false
|
||||
|
||||
# Force runtime background tasks (healthchecks/sync) even under automated test
|
||||
# detection. Used by: src/lib/config/runtimeSettings.ts — overrides the test
|
||||
# heuristic in instrumentation-node.ts. Default: unset (tests skip background).
|
||||
#OMNIROUTE_ENABLE_RUNTIME_BACKGROUND_TASKS=1
|
||||
|
||||
# Background job interval for budget reset checks (ms). Default: 600000 (10m).
|
||||
# Used by: src/lib/jobs/budgetResetJob.ts. Floor: 10000.
|
||||
#OMNIROUTE_BUDGET_RESET_JOB_INTERVAL_MS=600000
|
||||
|
||||
# Reasoning cache cleanup cadence (ms). Default: 1800000 (30m). Floor: 60000.
|
||||
# Used by: src/lib/jobs/reasoningCacheCleanupJob.ts.
|
||||
#OMNIROUTE_REASONING_CACHE_CLEANUP_INTERVAL_MS=1800000
|
||||
|
||||
# Spend write batcher cadence (ms) and buffer size before forced flush.
|
||||
# Used by: src/lib/spend/batchWriter.ts. Defaults: 60000 ms / 1000 entries.
|
||||
#OMNIROUTE_SPEND_FLUSH_INTERVAL_MS=60000
|
||||
#OMNIROUTE_SPEND_MAX_BUFFER_SIZE=1000
|
||||
|
||||
# Config hot-reload polling interval (ms). Default: 5000.
|
||||
# Used by: src/lib/config/hotReload.ts. Lower than 1000ms is rejected.
|
||||
#OMNIROUTE_CONFIG_HOT_RELOAD_MS=5000
|
||||
|
||||
# Override the migrations directory used by src/lib/db/migrationRunner.ts.
|
||||
# Default: <repo>/src/lib/db/migrations.
|
||||
#OMNIROUTE_MIGRATIONS_DIR=
|
||||
|
||||
# Trust user-managed RTK project filter rules without strict signature checks.
|
||||
# Used by: open-sse/services/compression/engines/rtk/filterLoader.ts. Default: 0.
|
||||
#OMNIROUTE_RTK_TRUST_PROJECT_FILTERS=0
|
||||
|
||||
# Force a DB healthcheck regardless of cadence. Default: 0.
|
||||
# Used by: src/lib/db/core.ts::shouldRunDbHealthCheck().
|
||||
#OMNIROUTE_FORCE_DB_HEALTHCHECK=0
|
||||
|
||||
# DB healthcheck cadence override (ms). Default: 21600000 (6h).
|
||||
# Used by: src/lib/db/core.ts::getDbHealthCheckIntervalMs().
|
||||
#OMNIROUTE_DB_HEALTHCHECK_INTERVAL_MS=21600000
|
||||
|
||||
# Skip the Redis-backed auth cache used by API key lookups (forces DB reads).
|
||||
# Used by: src/lib/db/apiKeys.ts. Set to 1 to disable. Default: enabled.
|
||||
#OMNIROUTE_DISABLE_REDIS_AUTH_CACHE=0
|
||||
|
||||
# Flag set by bootstrap script after initial setup is complete.
|
||||
# Used by: src/app/(dashboard)/dashboard/page.tsx — shows setup wizard vs. dashboard.
|
||||
# OMNIROUTE_BOOTSTRAPPED=false
|
||||
@@ -355,6 +432,11 @@ PROVIDER_LIMITS_SYNC_INTERVAL_MINUTES=70
|
||||
# Used by: open-sse/executors/antigravity.ts — escape hatch for multi-project setups.
|
||||
# OMNIROUTE_ALLOW_BODY_PROJECT_OVERRIDE=0
|
||||
|
||||
# Adjust how Antigravity advertises remaining credits. Used by:
|
||||
# open-sse/services/antigravityCredits.ts — accepts forced override strings.
|
||||
# Default: empty (use upstream-reported credits).
|
||||
#ANTIGRAVITY_CREDITS=
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# 11. OAUTH PROVIDER CREDENTIALS
|
||||
@@ -393,12 +475,27 @@ ANTIGRAVITY_OAUTH_CLIENT_SECRET=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
|
||||
# ── GitHub Copilot ──
|
||||
GITHUB_OAUTH_CLIENT_ID=Iv1.b507a08c87ecfe98
|
||||
|
||||
# ── Windsurf / Devin CLI ──
|
||||
# Public Firebase Web API key used by Windsurf's Secure Token Service to refresh
|
||||
# short-lived browser-flow tokens. This is a client-side credential embedded in
|
||||
# the Windsurf app itself (not a secret). Long-lived import tokens skip this entirely.
|
||||
# Source: extracted from Devin CLI binary (devin.exe / model_configs_v2.bin).
|
||||
WINDSURF_FIREBASE_API_KEY=AIzaSyBpLTEGSt59AUPKxBb7lIWjSE2ZXQH7mgU
|
||||
|
||||
# ── GitLab Duo ──
|
||||
# Register an OAuth app at: https://gitlab.com/-/profile/applications
|
||||
# Set redirect URI to: http://localhost:20128/callback (or your NEXT_PUBLIC_BASE_URL + /callback)
|
||||
# Required scopes: api, read_user, openid, profile, email
|
||||
# GITLAB_DUO_OAUTH_CLIENT_ID=***
|
||||
# GITLAB_DUO_OAUTH_CLIENT_SECRET=*** # optional — PKCE flow does not require a secret
|
||||
#
|
||||
# Self-managed GitLab Duo instance overrides.
|
||||
# Used by: src/lib/oauth/gitlab.ts and src/lib/oauth/constants/oauth.ts —
|
||||
# fall back to these when the _DUO_ variants above are unset.
|
||||
#GITLAB_DUO_BASE_URL=https://gitlab.com
|
||||
#GITLAB_BASE_URL=https://gitlab.com
|
||||
#GITLAB_OAUTH_CLIENT_ID=
|
||||
#GITLAB_OAUTH_CLIENT_SECRET=
|
||||
|
||||
# ── Qoder ──
|
||||
QODER_OAUTH_CLIENT_SECRET=4Z3YjXycVsQvyGF1etiNlIBB4RsqSDtW
|
||||
@@ -470,6 +567,10 @@ QWEN_USER_AGENT="QwenCode/0.15.9 (linux; x64)"
|
||||
CURSOR_USER_AGENT="Cursor/3.3"
|
||||
GEMINI_CLI_USER_AGENT="google-api-nodejs-client/10.3.0"
|
||||
|
||||
# Override Codex client version sent in headers independently of the
|
||||
# CODEX_USER_AGENT string. Used by: open-sse/config/codexClient.ts.
|
||||
# CODEX_CLIENT_VERSION=0.130.0
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# 13. CLI FINGERPRINT COMPATIBILITY (Anti-Detection)
|
||||
@@ -484,7 +585,6 @@ GEMINI_CLI_USER_AGENT="google-api-nodejs-client/10.3.0"
|
||||
# CLI_COMPAT_CLAUDE=1
|
||||
# CLI_COMPAT_GITHUB=1
|
||||
# CLI_COMPAT_ANTIGRAVITY=1
|
||||
# CLI_COMPAT_KIRO=1
|
||||
# CLI_COMPAT_CURSOR=1
|
||||
# CLI_COMPAT_KIMI_CODING=1
|
||||
# CLI_COMPAT_KILOCODE=1
|
||||
@@ -494,6 +594,12 @@ GEMINI_CLI_USER_AGENT="google-api-nodejs-client/10.3.0"
|
||||
# Or enable for all providers at once:
|
||||
# CLI_COMPAT_ALL=1
|
||||
|
||||
# ── Kimi Coding CLI identity overrides ──
|
||||
# Used by: src/lib/oauth/providers/kimi-coding.ts — sent in OAuth + API headers.
|
||||
# Leave unset to use the captured defaults baked into the OmniRoute build.
|
||||
#KIMI_CLI_VERSION=1.36.0
|
||||
#KIMI_CODING_DEVICE_ID=
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# 14. API KEY PROVIDERS
|
||||
@@ -502,20 +608,20 @@ GEMINI_CLI_USER_AGENT="google-api-nodejs-client/10.3.0"
|
||||
# Preferred setup: Dashboard → Providers → Add API Key.
|
||||
# Setting here is an alternative for Docker/headless deployments.
|
||||
|
||||
# Static API keys for direct-authentication providers wired through the runtime.
|
||||
# OmniRoute loads provider credentials from the encrypted database or
|
||||
# data/provider-credentials.json. The variables below are documented escape
|
||||
# hatches that are referenced in code today.
|
||||
# DEEPSEEK_API_KEY=
|
||||
# GROQ_API_KEY=
|
||||
# XAI_API_KEY=
|
||||
# MISTRAL_API_KEY=
|
||||
# PERPLEXITY_API_KEY=
|
||||
# TOGETHER_API_KEY=
|
||||
# FIREWORKS_API_KEY=
|
||||
# CEREBRAS_API_KEY=
|
||||
# COHERE_API_KEY=
|
||||
# NVIDIA_API_KEY=
|
||||
|
||||
# Windsurf / Devin CLI direct API key.
|
||||
# Used by: open-sse/executors/devin-cli.ts — bypasses OAuth when set.
|
||||
# WINDSURF_API_KEY=
|
||||
|
||||
# Embedding Providers (optional — used by /v1/embeddings)
|
||||
# NEBIUS_API_KEY=
|
||||
# Provider keys above (OpenAI, Mistral, Together, Fireworks, NVIDIA) also work for embeddings.
|
||||
# OpenAI/Mistral/Together/Fireworks/NVIDIA configured via Dashboard → Providers
|
||||
# also work for embeddings.
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
@@ -539,6 +645,29 @@ GEMINI_CLI_USER_AGENT="google-api-nodejs-client/10.3.0"
|
||||
# FETCH_CONNECT_TIMEOUT_MS=30000 # TCP connection establishment (default: 30s)
|
||||
# FETCH_KEEPALIVE_TIMEOUT_MS=4000 # Keep-alive socket idle timeout (default: 4s)
|
||||
|
||||
# Default timeout (ms) for src/shared/utils/fetchTimeout.ts. Acts as the
|
||||
# fallback when FETCH_TIMEOUT_MS is unset. Default: 120000 (2 min).
|
||||
# OMNIROUTE_DEFAULT_FETCH_TIMEOUT_MS=120000
|
||||
|
||||
# ── ChatGPT TLS sidecar (Firefox-fingerprinted client) ──
|
||||
# Used by: open-sse/services/chatgptTlsClient.ts — wire-level timeout for
|
||||
# the bogdanfinn/tls-client koffi binding and the JS-side grace window
|
||||
# layered on top of it when the native library is wedged.
|
||||
# OMNIROUTE_CHATGPT_TLS_TIMEOUT_MS=60000
|
||||
# OMNIROUTE_CHATGPT_TLS_GRACE_MS=10000
|
||||
|
||||
# ── Circuit breaker thresholds and reset windows ──
|
||||
# Used by: open-sse/config/constants.ts → src/lib/resilience/settings.ts.
|
||||
# Defaults match historical PROVIDER_PROFILES values (post-scaling for
|
||||
# 500+ connections). Lower the threshold to react faster, raise it to
|
||||
# tolerate more transient failures before short-circuiting.
|
||||
# OMNIROUTE_CIRCUIT_BREAKER_OAUTH_THRESHOLD=8
|
||||
# OMNIROUTE_CIRCUIT_BREAKER_OAUTH_RESET_MS=60000
|
||||
# OMNIROUTE_CIRCUIT_BREAKER_API_KEY_THRESHOLD=12
|
||||
# OMNIROUTE_CIRCUIT_BREAKER_API_KEY_RESET_MS=30000
|
||||
# OMNIROUTE_CIRCUIT_BREAKER_LOCAL_THRESHOLD=2
|
||||
# OMNIROUTE_CIRCUIT_BREAKER_LOCAL_RESET_MS=15000
|
||||
|
||||
# ── Stream idle detection ──
|
||||
# STREAM_IDLE_TIMEOUT_MS=600000 # Max silence between SSE chunks (default: 600000)
|
||||
# # Extended-thinking models rarely pause >90s.
|
||||
@@ -621,6 +750,14 @@ APP_LOG_TO_FILE=true
|
||||
# Default: 512
|
||||
# CALL_LOG_PIPELINE_MAX_SIZE_KB=512
|
||||
|
||||
# Call log payload truncation limits — controls how much of request/response
|
||||
# bodies is retained in the database.
|
||||
# Used by: open-sse/handlers/chatCore.ts — cloneBoundedChatLogPayload()
|
||||
# CHAT_LOG_TEXT_LIMIT=65536 # Max string length before truncation (default: 64 KB)
|
||||
# CHAT_LOG_ARRAY_TAIL_ITEMS=24 # Number of array items retained from tail (default: 24)
|
||||
# CHAT_LOG_MAX_DEPTH=6 # Max nesting depth before truncation (default: 6)
|
||||
# CHAT_LOG_MAX_OBJECT_KEYS=80 # Max object keys retained (default: 80, 0 = no limit)
|
||||
|
||||
# Maximum rows in the proxy_logs SQLite table.
|
||||
# Default: 100000
|
||||
# PROXY_LOGS_TABLE_MAX_ROWS=100000
|
||||
@@ -703,6 +840,13 @@ APP_LOG_TO_FILE=true
|
||||
# NANOBANANA_POLL_TIMEOUT_MS=120000 # Max wait for job completion (default: 120s)
|
||||
# NANOBANANA_POLL_INTERVAL_MS=2500 # Poll frequency (default: 2.5s)
|
||||
|
||||
# ── AWS Bedrock (Kiro / Audio) ──
|
||||
# Region used to construct AWS Bedrock endpoints. Used by:
|
||||
# src/lib/providers/validation.ts and open-sse/handlers/audioSpeech.ts.
|
||||
# AWS_REGION takes precedence over AWS_DEFAULT_REGION when both are set.
|
||||
# AWS_REGION=us-east-1
|
||||
# AWS_DEFAULT_REGION=us-east-1
|
||||
|
||||
# ── Cloudflare Workers AI ──
|
||||
# Account ID override for Cloudflare Workers AI executor.
|
||||
# Used by: open-sse/executors/cloudflare-ai.ts
|
||||
@@ -762,21 +906,55 @@ APP_LOG_TO_FILE=true
|
||||
# Used by: open-sse/services/rateLimitManager.ts
|
||||
# RATE_LIMIT_MAX_WAIT_MS=120000
|
||||
|
||||
# Force the auto-enable rate limit safety net on/off regardless of the persisted
|
||||
# Dashboard setting. Used by: open-sse/services/rateLimitManager.ts.
|
||||
# Accepted values: true|1|on (force on), false|0|off (force off), unset (use Dashboard).
|
||||
# RATE_LIMIT_AUTO_ENABLE=
|
||||
|
||||
# Stagger interval (ms) between provider token healthchecks at startup.
|
||||
# Used by: src/lib/tokenHealthCheck.ts. Default: 3000.
|
||||
# HEALTHCHECK_STAGGER_MS=3000
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# 22. DEBUGGING
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# These variables enable verbose debugging output. NEVER enable in production.
|
||||
|
||||
# Dump Cursor protobuf decode/encode details to console.
|
||||
# CURSOR_PROTOBUF_DEBUG=1
|
||||
|
||||
# Dump raw Cursor SSE stream data to console.
|
||||
# Cursor executor verbose debug (decoded SSE chunks, etc.).
|
||||
# CURSOR_STREAM_DEBUG is kept as a backward-compatible alias.
|
||||
# Used by: open-sse/executors/cursor.ts
|
||||
# CURSOR_DEBUG=1
|
||||
# CURSOR_STREAM_DEBUG=1
|
||||
|
||||
# When CURSOR_DEBUG=1, also append raw decoded chunks to this file path.
|
||||
# CURSOR_DUMP_FILE=/tmp/cursor-stream.log
|
||||
|
||||
# Cursor stream idle timeout (ms). Default: 300000 (5 min).
|
||||
# Used by: open-sse/executors/cursor.ts.
|
||||
# CURSOR_STREAM_TIMEOUT_MS=300000
|
||||
|
||||
# Cursor state DB path override (for cursor version detection).
|
||||
# Used by: open-sse/utils/cursorVersionDetector.ts. Default: probed automatically.
|
||||
# CURSOR_STATE_DB_PATH=
|
||||
|
||||
# Direct Cursor bearer token used by scripts/cursor-tap.cjs (developer tooling).
|
||||
# CURSOR_TOKEN=
|
||||
|
||||
# Log Responses API SSE-to-JSON translation details.
|
||||
# DEBUG_RESPONSES_SSE_TO_JSON=true
|
||||
|
||||
# Log request shape (content-type + content-length) for large chat payloads.
|
||||
# Used by: src/app/api/v1/chat/completions/route.ts. Set to "0" to silence.
|
||||
# Default: enabled.
|
||||
# OMNIROUTE_LOG_REQUEST_SHAPE=1
|
||||
|
||||
# Write raw (untruncated) request/response JSON in call log artifacts.
|
||||
# When enabled, serializeArtifactForStorage skips size-based truncation.
|
||||
# Also enabled automatically when APP_LOG_LEVEL=debug.
|
||||
# WARNING: produces large files — use only for temporary debugging.
|
||||
# CHAT_DEBUG_FILE=true
|
||||
|
||||
# Enable E2E test mode — relaxes auth and enables test harness hooks.
|
||||
# NEXT_PUBLIC_OMNIROUTE_E2E_MODE=true
|
||||
|
||||
@@ -792,3 +970,140 @@ APP_LOG_TO_FILE=true
|
||||
|
||||
# GitHub Personal Access Token with issues:write scope.
|
||||
# GITHUB_ISSUES_TOKEN=ghp_xxxx
|
||||
|
||||
# Generic GitHub access token consumed by issue triage / agent helpers.
|
||||
# Used by: src/app/api/v1/issues/* and src/lib/cloudAgent/* — falls back to
|
||||
# GITHUB_ISSUES_TOKEN when unset.
|
||||
# GITHUB_TOKEN=
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# 24. PROVIDER QUOTAS, TUNNELS & SANDBOXED SKILLS
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# Provider quota endpoints, network tunnels (Tailscale, Ngrok, MITM debug
|
||||
# proxy), 1Proxy egress pool, skills sandbox runtime, and miscellaneous CLI
|
||||
# binaries referenced by the executor layer or the dashboard runtime.
|
||||
|
||||
# ── Alibaba (Bailian) coding plan quota ──
|
||||
# Host/full URL override used by: open-sse/services/bailianQuotaFetcher.ts.
|
||||
# When unset the fetcher uses the production Alibaba endpoints.
|
||||
# ALIBABA_CODING_PLAN_HOST=
|
||||
# ALIBABA_CODING_PLAN_QUOTA_URL=
|
||||
|
||||
# ── Context window tuning ──
|
||||
# Tokens reserved for completion output when computing prompt budgets.
|
||||
# Used by: open-sse/services/contextManager.ts. Default: 1024.
|
||||
# CONTEXT_RESERVE_TOKENS=1024
|
||||
|
||||
# ── Model alias rewriting (legacy compatibility) ──
|
||||
# Toggle the legacy model-alias compatibility layer used by older clients.
|
||||
# Used by: open-sse/services/model.ts. Default: enabled.
|
||||
# MODEL_ALIAS_COMPAT_ENABLED=true
|
||||
|
||||
# ── Devin CLI binary path ──
|
||||
# Used by: open-sse/executors/devin-cli.ts. Default: looked up via PATH.
|
||||
# CLI_DEVIN_BIN=devin
|
||||
|
||||
# ── Command Code (custom CLI) callback ──
|
||||
# Local port used for OAuth-style callbacks from the Command Code CLI helper.
|
||||
# Used by: src/app/api/providers/command-code/auth/shared.ts.
|
||||
# COMMAND_CODE_CALLBACK_PORT=
|
||||
|
||||
# ── MITM debug proxy (development only) ──
|
||||
# Used by: src/mitm/server.cjs — captures upstream traffic for inspection.
|
||||
# MITM_LOCAL_PORT=443
|
||||
# MITM_DISABLE_TLS_VERIFY=0
|
||||
|
||||
# ── 1Proxy egress pool ──
|
||||
# Used by: src/lib/oneproxySync.ts — fetches proxy nodes from the OmniRoute
|
||||
# CrofAI 1Proxy service. Disable, override URL, or tune the import quality.
|
||||
# ONEPROXY_ENABLED=true
|
||||
# ONEPROXY_API_URL=https://1proxy-api.aitradepulse.com
|
||||
# ONEPROXY_MAX_PROXIES=500
|
||||
# ONEPROXY_MIN_QUALITY_THRESHOLD=50
|
||||
|
||||
# ── Tailscale tunnel binaries ──
|
||||
# Optional explicit paths to tailscale/tailscaled binaries used by the
|
||||
# dashboard's tunnel manager. Used by: src/lib/tailscaleTunnel.ts.
|
||||
# TAILSCALE_BIN=/usr/local/bin/tailscale
|
||||
# TAILSCALED_BIN=/usr/local/bin/tailscaled
|
||||
|
||||
# ── Ngrok tunnel ──
|
||||
# Used by: src/lib/ngrokTunnel.ts — authenticates outbound tunnels.
|
||||
# NGROK_AUTHTOKEN=
|
||||
|
||||
# ── Database backups ──
|
||||
# Used by: src/lib/db/backup.ts.
|
||||
# DB_BACKUP_MAX_FILES=20
|
||||
# DB_BACKUP_RETENTION_DAYS=0
|
||||
|
||||
# ── TLS sidecar override ──
|
||||
# Used by: open-sse/services/chatgptTlsClient.ts tests. Production deployments
|
||||
# should leave this unset; the sidecar is auto-managed.
|
||||
# OMNIROUTE_TLS_PROXY_URL=
|
||||
|
||||
# ── Skills sandbox (experimental) ──
|
||||
# Used by: src/lib/skills/builtins.ts. All values support comma lists where
|
||||
# noted in the source.
|
||||
# SKILLS_MAX_FILE_BYTES=1048576
|
||||
# SKILLS_MAX_HTTP_RESPONSE_BYTES=256000
|
||||
# SKILLS_MAX_SANDBOX_OUTPUT_CHARS=100000
|
||||
# SKILLS_SANDBOX_TIMEOUT_MS=10000
|
||||
# SKILLS_SANDBOX_NETWORK_ENABLED=0
|
||||
# SKILLS_ALLOWED_SANDBOX_IMAGES=
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# 25. TEST & E2E
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# Used by scripts/run-next-playwright.mjs, scripts/smoke-electron-packaged.mjs,
|
||||
# scripts/run-ecosystem-tests.mjs and scripts/uninstall.mjs.
|
||||
# Production deployments should leave every value below unset.
|
||||
|
||||
# E2E bootstrap mode for the Playwright runner. Accepted: auth | fresh | reuse.
|
||||
# Default (when unset): auth.
|
||||
# OMNIROUTE_E2E_BOOTSTRAP_MODE=auth
|
||||
|
||||
# Admin password injected into the Playwright test environment.
|
||||
# Falls back to INITIAL_PASSWORD when unset.
|
||||
# OMNIROUTE_E2E_PASSWORD=
|
||||
|
||||
# Disable the local healthcheck poll during Playwright runs (default: true).
|
||||
# OMNIROUTE_DISABLE_LOCAL_HEALTHCHECK=true
|
||||
|
||||
# Disable the OAuth token healthcheck loop during tests (default: true).
|
||||
# OMNIROUTE_DISABLE_TOKEN_HEALTHCHECK=true
|
||||
|
||||
# Silence healthcheck noise in Playwright stdout (default: true).
|
||||
# OMNIROUTE_HIDE_HEALTHCHECK_LOGS=true
|
||||
|
||||
# Skip the Next.js production build before Playwright starts (CI optimization).
|
||||
# OMNIROUTE_PLAYWRIGHT_SKIP_BUILD=0
|
||||
|
||||
# Skip the OmniRoute uninstall hook (used by CI to keep node_modules intact).
|
||||
# OMNIROUTE_SKIP_UNINSTALL_HOOK=0
|
||||
|
||||
# Ecosystem/protocol test orchestrators wait this long (ms) for the server to
|
||||
# become healthy. Default: 180000.
|
||||
# ECOSYSTEM_SERVER_WAIT_MS=180000
|
||||
|
||||
# Docs translation pipeline (used by scripts/i18n/run-translation.mjs).
|
||||
# OpenAI-compatible base URL, e.g. https://cloud.omniroute.online/v1
|
||||
# OMNIROUTE_TRANSLATION_API_URL=
|
||||
# Bearer token for the translation backend (NEVER commit a real key here).
|
||||
# OMNIROUTE_TRANSLATION_API_KEY=
|
||||
# Model id, e.g. gpt-4o-mini or cx/gpt-5.4-mini.
|
||||
# OMNIROUTE_TRANSLATION_MODEL=gpt-4o-mini
|
||||
# Per-request timeout in milliseconds (default 60000).
|
||||
# OMNIROUTE_TRANSLATION_TIMEOUT_MS=60000
|
||||
# Number of parallel translation requests (default 4).
|
||||
# OMNIROUTE_TRANSLATION_CONCURRENCY=4
|
||||
|
||||
# Electron smoke harness (used by scripts/smoke-electron-packaged.mjs).
|
||||
# ELECTRON_SMOKE_URL=http://127.0.0.1:20128/login
|
||||
# ELECTRON_SMOKE_TIMEOUT_MS=45000
|
||||
# ELECTRON_SMOKE_SETTLE_MS=2000
|
||||
# ELECTRON_SMOKE_APP_EXECUTABLE=
|
||||
# ELECTRON_SMOKE_DATA_DIR=
|
||||
# ELECTRON_SMOKE_KEEP_DATA=0
|
||||
# ELECTRON_SMOKE_STREAM_LOGS=0
|
||||
|
||||
38
.github/workflows/ci.yml
vendored
38
.github/workflows/ci.yml
vendored
@@ -41,6 +41,32 @@ jobs:
|
||||
- run: npm run typecheck:core
|
||||
- run: npm run typecheck:noimplicit:core
|
||||
|
||||
docs-sync-strict:
|
||||
name: Docs Sync (Strict)
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: ${{ env.CI_NODE_VERSION }}
|
||||
cache: npm
|
||||
- run: npm ci
|
||||
- run: npm run check:docs-all
|
||||
- name: i18n translation drift (strict)
|
||||
run: node scripts/i18n/check-translation-drift.mjs
|
||||
|
||||
i18n-ui-coverage:
|
||||
name: i18n UI Coverage
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: ${{ env.CI_NODE_VERSION }}
|
||||
cache: npm
|
||||
- run: npm ci
|
||||
- run: node scripts/i18n/check-ui-keys-coverage.mjs --threshold=80
|
||||
|
||||
i18n-matrix:
|
||||
name: Build language matrix
|
||||
runs-on: ubuntu-latest
|
||||
@@ -71,7 +97,7 @@ jobs:
|
||||
|
||||
- name: Validate ${{ matrix.lang }}
|
||||
run: |
|
||||
python3 scripts/validate_translation.py quick -l '${{ matrix.lang }}' > result.txt
|
||||
python3 scripts/i18n/validate_translation.py quick -l '${{ matrix.lang }}' > result.txt
|
||||
|
||||
- name: Upload result
|
||||
if: always()
|
||||
@@ -94,7 +120,7 @@ jobs:
|
||||
- name: Fetch base branch
|
||||
run: git fetch --no-tags origin "${GITHUB_BASE_REF}" --depth=1
|
||||
- name: Validate source changes include tests
|
||||
run: node scripts/check-pr-test-policy.mjs --summary-file .artifacts/pr-test-policy.md
|
||||
run: node scripts/check/check-pr-test-policy.mjs --summary-file .artifacts/pr-test-policy.md
|
||||
- name: Publish PR test policy summary
|
||||
if: always()
|
||||
run: |
|
||||
@@ -255,7 +281,7 @@ jobs:
|
||||
run: |
|
||||
mkdir -p coverage
|
||||
if [ -f coverage/coverage-summary.json ]; then
|
||||
node scripts/test-report-summary.mjs \
|
||||
node scripts/check/test-report-summary.mjs \
|
||||
--input coverage/coverage-summary.json \
|
||||
--output coverage/coverage-report.md \
|
||||
--threshold 60
|
||||
@@ -436,7 +462,7 @@ jobs:
|
||||
cache: npm
|
||||
- run: npm ci
|
||||
- run: npm run check:node-runtime
|
||||
- run: node --import tsx/esm --test --test-shard=${{ matrix.shard }}/2 tests/integration/*.test.ts
|
||||
- run: node --import tsx/esm --test --test-force-exit --test-concurrency=1 --test-shard=${{ matrix.shard }}/2 tests/integration/*.test.ts
|
||||
|
||||
test-security:
|
||||
name: Security Tests
|
||||
@@ -462,6 +488,8 @@ jobs:
|
||||
if: always()
|
||||
needs:
|
||||
- lint
|
||||
- docs-sync-strict
|
||||
- i18n-ui-coverage
|
||||
- i18n
|
||||
- pr-test-policy
|
||||
|
||||
@@ -507,6 +535,8 @@ jobs:
|
||||
echo "| Job | Status |" >> "$GITHUB_STEP_SUMMARY"
|
||||
echo "|-----|--------|" >> "$GITHUB_STEP_SUMMARY"
|
||||
echo "| Lint | $(status '${{ needs.lint.result }}') |" >> "$GITHUB_STEP_SUMMARY"
|
||||
echo "| Docs Sync (Strict) | $(status '${{ needs.docs-sync-strict.result }}') |" >> "$GITHUB_STEP_SUMMARY"
|
||||
echo "| i18n UI Coverage | $(status '${{ needs.i18n-ui-coverage.result }}') |" >> "$GITHUB_STEP_SUMMARY"
|
||||
echo "| PR Test Policy | $(status '${{ needs.pr-test-policy.result }}') |" >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
echo "| SonarQube | $(status '${{ needs.sonarqube.result }}') |" >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
7
.github/workflows/claude-code-review.yml
vendored
7
.github/workflows/claude-code-review.yml
vendored
@@ -36,9 +36,8 @@ jobs:
|
||||
uses: anthropics/claude-code-action@v1
|
||||
with:
|
||||
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
||||
plugin_marketplaces: 'https://github.com/anthropics/claude-code.git'
|
||||
plugins: 'code-review@claude-code-plugins'
|
||||
prompt: '/code-review:code-review ${{ github.repository }}/pull/${{ github.event.pull_request.number }}'
|
||||
plugin_marketplaces: "https://github.com/anthropics/claude-code.git"
|
||||
plugins: "code-review@claude-code-plugins"
|
||||
prompt: "/code-review:code-review ${{ github.repository }}/pull/${{ github.event.pull_request.number }}"
|
||||
# See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md
|
||||
# or https://code.claude.com/docs/en/cli-reference for available options
|
||||
|
||||
|
||||
1
.github/workflows/claude.yml
vendored
1
.github/workflows/claude.yml
vendored
@@ -47,4 +47,3 @@ jobs:
|
||||
# See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md
|
||||
# or https://code.claude.com/docs/en/cli-reference for available options
|
||||
# claude_args: '--allowed-tools Bash(gh pr *)'
|
||||
|
||||
|
||||
@@ -6,5 +6,16 @@ if ! command -v npx >/dev/null 2>&1; then
|
||||
fi
|
||||
|
||||
npx lint-staged
|
||||
node scripts/check-docs-sync.mjs
|
||||
node scripts/check/check-docs-sync.mjs
|
||||
npm run check:any-budget:t11
|
||||
|
||||
# Strict env-doc sync (FASE 2)
|
||||
node scripts/check/check-env-doc-sync.mjs
|
||||
|
||||
# i18n docs drift advisory (FASE 5) — warn-only on pre-commit; CI enforces strict.
|
||||
node scripts/i18n/check-translation-drift.mjs --warn || \
|
||||
echo "⚠️ i18n drift detected. Run 'npm run i18n:run' to update locale mirrors."
|
||||
|
||||
# i18n UI coverage advisory (FASE 6) — pre-commit warns; CI enforces strict.
|
||||
node scripts/i18n/check-ui-keys-coverage.mjs --threshold=80 || \
|
||||
echo "⚠️ UI i18n coverage below 80% for at least one locale."
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#!/usr/bin/env sh
|
||||
if ! command -v npm >/dev/null 2>&1; then
|
||||
echo "⚠️ npm not found in PATH — skipping pre-push hooks"
|
||||
echo " Run 'npm test' manually before pushing."
|
||||
exit 0
|
||||
fi
|
||||
#if ! command -v npm >/dev/null 2>&1; then
|
||||
# echo "⚠️ npm not found in PATH — skipping pre-push hooks"
|
||||
# echo " Run 'npm test' manually before pushing."
|
||||
# exit 0
|
||||
#fi
|
||||
|
||||
npm run test:unit
|
||||
#npm run test:unit
|
||||
|
||||
24
.i18n-state.json
Normal file
24
.i18n-state.json
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"sources": {
|
||||
"CLAUDE.md": {
|
||||
"source_hash": "ee7af1716a6e22feb93bb160f8b2810fa7dce8f56075b218ced51b04863f1786",
|
||||
"locales": {
|
||||
"pt-BR": {
|
||||
"source_hash": "ee7af1716a6e22feb93bb160f8b2810fa7dce8f56075b218ced51b04863f1786",
|
||||
"target_hash": "7a85c5795d376e8572598f9d963e32ec62f925b952b61b60c78fa42785836014",
|
||||
"updated_at": "2026-05-13T20:02:23.088Z"
|
||||
}
|
||||
}
|
||||
},
|
||||
"docs/architecture/ARCHITECTURE.md": {
|
||||
"source_hash": "7e691870a4f6f25a535a023206cff5ef88aed619c9734851ebdacdd3a3bafcc8",
|
||||
"locales": {
|
||||
"pt-BR": {
|
||||
"source_hash": "7e691870a4f6f25a535a023206cff5ef88aed619c9734851ebdacdd3a3bafcc8",
|
||||
"target_hash": "3e2a58f341f20b29cf245330b094bec40ffeede43526b4d416c660c37abca8db",
|
||||
"updated_at": "2026-05-13T22:58:05.984Z"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,328 +0,0 @@
|
||||
# Feature Proposal: Batch Delete Provider Accounts
|
||||
|
||||
## Summary
|
||||
|
||||
Add **batch delete** functionality for provider accounts (connections) in the provider detail page (`/dashboard/providers/[id]`). Users select multiple accounts via checkboxes and delete them in a single action, replacing the current one-by-one delete workflow.
|
||||
|
||||
## Problem Statement
|
||||
|
||||
Users managing multiple provider accounts (e.g., 20+ API keys or OAuth connections) have to delete accounts individually. Each deletion requires:
|
||||
|
||||
1. Finding the account
|
||||
2. Clicking the delete button
|
||||
3. Confirming via browser `confirm()` dialog
|
||||
4. Waiting for the API call
|
||||
5. Repeating for every account
|
||||
|
||||
This is:
|
||||
- **Time-consuming**: O(n) confirm dialogs and API calls for n accounts
|
||||
- **Error-prone**: Easy to accidentally click the wrong account
|
||||
- **Tedious**: No way to quickly clean up stale or duplicate accounts
|
||||
|
||||
## Solution
|
||||
|
||||
Add a checkbox-based selection UI to the provider connections list:
|
||||
|
||||
```
|
||||
┌────────────────────────────────────────────────────────────────┐
|
||||
│ [x] Account #1 (kiro-prod) [Delete Selected (3)] │
|
||||
│ [x] Account #2 (kiro-staging) │
|
||||
│ [ ] Account #3 (kiro-backup) │
|
||||
└────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Detailed PR Specification
|
||||
|
||||
### Files to Modify
|
||||
|
||||
| File | Change |
|
||||
|------|--------|
|
||||
| `src/app/(dashboard)/dashboard/providers/[id]/page.tsx` | Add batch delete state, select-all + per-row checkboxes, batch delete handler |
|
||||
| `src/app/api/providers/route.ts` | Add `DELETE /api/providers` with `POST` body `ids: string[]` for batch delete |
|
||||
| `src/lib/db/providers.ts` | Add `deleteProviderConnections(ids: string[])` batch DB function |
|
||||
| `src/i18n/messages/en.json` | Add i18n keys: `batchDeleteSelected`, `batchDeleteConfirm`, `batchDeleteSuccess` |
|
||||
| `src/i18n/messages/*.json` | Add i18n keys to all locale files |
|
||||
| `tests/unit/db-providers-crud.test.ts` | Add unit tests for batch delete DB function |
|
||||
| `tests/integration/api-routes-critical.test.ts` | Add integration test for batch delete API endpoint |
|
||||
|
||||
### 1. DB Layer (`src/lib/db/providers.ts`)
|
||||
|
||||
```typescript
|
||||
export async function deleteProviderConnections(ids: string[]): Promise<number> {
|
||||
const db = getDbInstance() as unknown as DbLike;
|
||||
if (ids.length === 0) return 0;
|
||||
|
||||
// Delete quota snapshots for each connection first
|
||||
const deleteSnapshots = db.prepare("DELETE FROM quota_snapshots WHERE connection_id = ?");
|
||||
for (const id of ids) {
|
||||
deleteSnapshots.run(id);
|
||||
}
|
||||
|
||||
// Batch delete connections
|
||||
const placeholders = ids.map(() => "?").join(",");
|
||||
const result = db.prepare(
|
||||
`DELETE FROM provider_connections WHERE id IN (${placeholders})`
|
||||
).run(...ids);
|
||||
|
||||
backupDbFile("pre-write");
|
||||
invalidateDbCache("connections");
|
||||
return result.changes ?? 0;
|
||||
}
|
||||
```
|
||||
|
||||
### 2. API Route (`src/app/api/providers/route.ts`)
|
||||
|
||||
Add a new route handler for batch delete. The existing `/api/providers/[id]` only handles single-id operations. The main providers route file (`/api/providers/route.ts`) should be extended:
|
||||
|
||||
```typescript
|
||||
// DELETE /api/providers — Batch delete connections
|
||||
// Body: { ids: string[] }
|
||||
export async function DELETE(request: Request) {
|
||||
const authError = await requireManagementAuth(request);
|
||||
if (authError) return authError;
|
||||
|
||||
let body: { ids?: string[] };
|
||||
try {
|
||||
body = await request.json();
|
||||
} catch {
|
||||
return NextResponse.json({ error: "Invalid JSON body" }, { status: 400 });
|
||||
}
|
||||
|
||||
if (!Array.isArray(body.ids) || body.ids.length === 0) {
|
||||
return NextResponse.json(
|
||||
{ error: "ids must be a non-empty array of connection IDs" },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
if (body.ids.length > 100) {
|
||||
return NextResponse.json(
|
||||
{ error: "Cannot delete more than 100 connections at once" },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
const deleted = await deleteProviderConnections(body.ids);
|
||||
await syncToCloudIfEnabled();
|
||||
|
||||
logAuditEvent({
|
||||
action: "provider.credentials.batch_revoked",
|
||||
actor: "admin",
|
||||
resourceType: "provider_credentials",
|
||||
status: "success",
|
||||
metadata: { count: deleted, ids: body.ids },
|
||||
});
|
||||
|
||||
return NextResponse.json({ message: `Deleted ${deleted} connection(s)`, deleted });
|
||||
} catch (error) {
|
||||
console.log("Error batch deleting connections:", error);
|
||||
return NextResponse.json({ error: "Failed to batch delete connections" }, { status: 500 });
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. UI Layer (`src/app/(dashboard)/dashboard/providers/[id]/page.tsx`)
|
||||
|
||||
#### New State Variables
|
||||
|
||||
```typescript
|
||||
// Batch selection state
|
||||
const [selectedIds, setSelectedIds] = useState<Set<string>>(new Set());
|
||||
const [batchDeleting, setBatchDeleting] = useState(false);
|
||||
```
|
||||
|
||||
#### New Functions
|
||||
|
||||
```typescript
|
||||
const handleToggleSelectAll = useCallback(() => {
|
||||
setSelectedIds((prev) =>
|
||||
prev.size === connections.length ? new Set() : new Set(connections.map((c) => c.id))
|
||||
);
|
||||
}, [connections]);
|
||||
|
||||
const handleToggleSelectOne = useCallback((id: string) => {
|
||||
setSelectedIds((prev) => {
|
||||
const next = new Set(prev);
|
||||
if (next.has(id)) next.delete(id);
|
||||
else next.add(id);
|
||||
return next;
|
||||
});
|
||||
}, []);
|
||||
|
||||
const handleBatchDelete = async () => {
|
||||
if (selectedIds.size === 0) return;
|
||||
if (!confirm(t("batchDeleteConfirm", { count: selectedIds.size }))) return;
|
||||
|
||||
setBatchDeleting(true);
|
||||
try {
|
||||
const res = await fetch("/api/providers", {
|
||||
method: "DELETE",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ ids: Array.from(selectedIds) }),
|
||||
});
|
||||
|
||||
if (res.ok) {
|
||||
setSelectedIds(new Set());
|
||||
await fetchConnections();
|
||||
notify.success(t("batchDeleteSuccess", { count: selectedIds.size }));
|
||||
} else {
|
||||
const data = await res.json();
|
||||
notify.error(data.error || "Batch delete failed");
|
||||
}
|
||||
} catch (error) {
|
||||
notify.error("Network error during batch delete");
|
||||
} finally {
|
||||
setBatchDeleting(false);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
#### Per-Row Checkbox (inside `ConnectionRow`)
|
||||
|
||||
Add a checkbox as the first element of each row:
|
||||
|
||||
```tsx
|
||||
// In ConnectionRow interface, add:
|
||||
interface ConnectionRowProps {
|
||||
isSelected?: boolean;
|
||||
onToggleSelect?: () => void;
|
||||
// ... existing props
|
||||
}
|
||||
|
||||
// In ConnectionRow render, before priority arrows:
|
||||
<div className="flex items-center gap-3 flex-1 min-w-0">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={isSelected}
|
||||
onChange={onToggleSelect}
|
||||
className="w-4 h-4 rounded border-border text-primary focus:ring-primary/30 cursor-pointer"
|
||||
/>
|
||||
{/* Priority arrows */}
|
||||
...
|
||||
```
|
||||
|
||||
#### Header Row (above connections list)
|
||||
|
||||
```tsx
|
||||
<div className="flex items-center justify-between px-3 py-2 bg-muted/50 rounded-t-lg border border-b-0 border-border">
|
||||
<label className="flex items-center gap-2 cursor-pointer select-none">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={selectedIds.size === connections.length && connections.length > 0}
|
||||
ref={(el) => { if (el) el.indeterminate = selectedIds.size > 0 && selectedIds.size < connections.length; }}
|
||||
onChange={handleToggleSelectAll}
|
||||
className="w-4 h-4 rounded border-border text-primary focus:ring-primary/30"
|
||||
/>
|
||||
<span className="text-sm font-medium text-text-muted">
|
||||
{selectedIds.size > 0
|
||||
? `${selectedIds.size} selected`
|
||||
: `${connections.length} accounts`}
|
||||
</span>
|
||||
</label>
|
||||
|
||||
{selectedIds.size > 0 && (
|
||||
<Button
|
||||
variant="destructive"
|
||||
size="sm"
|
||||
icon="delete"
|
||||
loading={batchDeleting}
|
||||
onClick={handleBatchDelete}
|
||||
>
|
||||
{t("batchDeleteSelected", { count: selectedIds.size })}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
```
|
||||
|
||||
### 4. i18n Keys (to add to all locale files)
|
||||
|
||||
```json
|
||||
{
|
||||
"batchDeleteSelected": "Delete Selected ({count})",
|
||||
"batchDeleteConfirm": "Delete {count} connection(s)? This action cannot be undone.",
|
||||
"batchDeleteSuccess": "Deleted {count} connection(s)"
|
||||
}
|
||||
```
|
||||
|
||||
### 5. Testing
|
||||
|
||||
#### Unit Test (`tests/unit/db-providers-crud.test.ts`)
|
||||
|
||||
```typescript
|
||||
test("deleteProviderConnections deletes multiple connections", async () => {
|
||||
const ids = [
|
||||
(await createProviderConnection({ provider: "openai", name: "test-1", authType: "apikey" })).id!,
|
||||
(await createProviderConnection({ provider: "openai", name: "test-2", authType: "apikey" })).id!,
|
||||
];
|
||||
|
||||
const deleted = await deleteProviderConnections(ids);
|
||||
expect(deleted).toBe(2);
|
||||
|
||||
for (const id of ids) {
|
||||
const conn = await getProviderConnectionById(id);
|
||||
expect(conn).toBeNull();
|
||||
}
|
||||
});
|
||||
|
||||
test("deleteProviderConnections with empty array returns 0", async () => {
|
||||
const deleted = await deleteProviderConnections([]);
|
||||
expect(deleted).toBe(0);
|
||||
});
|
||||
```
|
||||
|
||||
#### Integration Test (`tests/integration/api-routes-critical.test.ts`)
|
||||
|
||||
```typescript
|
||||
test("DELETE /api/providers — batch delete", async () => {
|
||||
const ids = [conn1.id, conn2.id];
|
||||
const res = await fetch("http://localhost:20128/api/providers", {
|
||||
method: "DELETE",
|
||||
headers: { "Content-Type": "application/json", "Authorization": `Bearer ${apiKey}` },
|
||||
body: JSON.stringify({ ids }),
|
||||
});
|
||||
|
||||
expect(res.status).toBe(200);
|
||||
const data = await res.json();
|
||||
expect(data.deleted).toBe(2);
|
||||
});
|
||||
```
|
||||
|
||||
### UX Details
|
||||
|
||||
1. **Indeterminate select-all**: When some (but not all) rows are selected, the select-all checkbox shows as indeterminate (dash)
|
||||
2. **Confirmation**: Shows `confirm()` with count ("Delete 3 connections?")
|
||||
3. **Optimistic update**: Immediately clears selected IDs and removes deleted connections from list on success
|
||||
4. **Error handling**: Shows error notification; connections remain in list if delete fails
|
||||
5. **Loading state**: Button shows spinner during delete; row checkboxes disabled
|
||||
6. **Empty state**: No "Delete Selected" button when nothing selected
|
||||
7. **Audit logging**: Each batch delete logged as `provider.credentials.batch_revoked`
|
||||
|
||||
### Non-Goals
|
||||
|
||||
- Bulk enable/disable (separate feature)
|
||||
- Moving selected accounts (separate feature)
|
||||
- Batch rename/edit (separate feature)
|
||||
- Deleting across different providers (each provider page operates independently)
|
||||
|
||||
### Risks & Mitigations
|
||||
|
||||
| Risk | Mitigation |
|
||||
|------|------------|
|
||||
| User accidentally deletes wrong accounts | Require confirmation dialog with count |
|
||||
| Too many connections selected | Cap at 100 per batch; show error if exceeded |
|
||||
| Partial failure on batch delete | DB runs in transaction; all-or-nothing semantics |
|
||||
| Performance with large selections | Batch SQL with `IN (...)` clause is efficient up to 100 |
|
||||
|
||||
### Coverage
|
||||
|
||||
Per repository rules, this change affects production code in `src/` → automated tests required:
|
||||
- Unit test for `deleteProviderConnections()` in `tests/unit/db-providers-crud.test.ts`
|
||||
- Integration test for `DELETE /api/providers` batch endpoint in `tests/integration/api-routes-critical.test.ts`
|
||||
- Run `npm run test:coverage` — all 4 metrics must meet 60% minimum
|
||||
|
||||
---
|
||||
|
||||
## Related Issues
|
||||
|
||||
- Closes this issue on merge
|
||||
@@ -52,8 +52,8 @@ AGENTS.md
|
||||
bun.lock
|
||||
|
||||
# Build artifacts (pre-built goes inside app/)
|
||||
.next/
|
||||
node_modules/
|
||||
/.next/
|
||||
/node_modules/
|
||||
|
||||
# Ignore large binary files and other build directories
|
||||
*.tgz
|
||||
|
||||
45
@omniroute/opencode-provider/README.md
Normal file
45
@omniroute/opencode-provider/README.md
Normal file
@@ -0,0 +1,45 @@
|
||||
# @omniroute/opencode-provider
|
||||
|
||||
Provider plugin for connecting [OpenCode](https://github.com/anomalyco/opencode) to [OmniRoute](https://github.com/diegosouzapw/OmniRoute).
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
npm install @omniroute/opencode-provider
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```javascript
|
||||
import { createOmniRouteProvider } from "@omniroute/opencode-provider";
|
||||
|
||||
const provider = createOmniRouteProvider({
|
||||
baseURL: "http://localhost:20128/v1",
|
||||
apiKey: "your-omniroute-api-key",
|
||||
});
|
||||
```
|
||||
|
||||
Then configure OpenCode to use the provider:
|
||||
|
||||
```jsonc
|
||||
// OpenCode settings
|
||||
{
|
||||
"provider": provider
|
||||
}
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
### `createOmniRouteProvider(options)`
|
||||
|
||||
Creates an OpenCode-compatible provider object that routes requests through OmniRoute.
|
||||
|
||||
**Options:**
|
||||
|
||||
| Option | Type | Required | Description |
|
||||
| --------- | -------- | -------- | ---------------------------------------------------------- |
|
||||
| `baseURL` | `string` | Yes | OmniRoute API base URL (e.g., `http://localhost:20128/v1`) |
|
||||
| `apiKey` | `string` | Yes | OmniRoute API key |
|
||||
| `model` | `string` | No | Model identifier (default: `"opencode"`) |
|
||||
|
||||
**Returns:** An OpenCode-compatible provider object with `id`, `name`, `npm`, `options`, and `auth` fields.
|
||||
3
@omniroute/opencode-provider/index.d.ts
vendored
Normal file
3
@omniroute/opencode-provider/index.d.ts
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
import OmniRouteProvider from "./index.js";
|
||||
export { OmniRouteProvider };
|
||||
export default OmniRouteProvider;
|
||||
1
@omniroute/opencode-provider/index.js
Normal file
1
@omniroute/opencode-provider/index.js
Normal file
@@ -0,0 +1 @@
|
||||
export { createOmniRouteProvider, default as default } from "./index.ts";
|
||||
54
@omniroute/opencode-provider/index.ts
Normal file
54
@omniroute/opencode-provider/index.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
/**
|
||||
* OpenCode provider plugin for OmniRoute AI Gateway
|
||||
*
|
||||
* Usage:
|
||||
* import { createOmniRouteProvider } from "@omniroute/opencode-provider";
|
||||
* const provider = createOmniRouteProvider({
|
||||
* baseURL: "http://localhost:20128/v1",
|
||||
* apiKey: "your-api-key",
|
||||
* });
|
||||
*
|
||||
* Then add to OpenCode settings:
|
||||
* { "provider": provider }
|
||||
*/
|
||||
|
||||
export interface OmniRouteProviderOptions {
|
||||
baseURL: string;
|
||||
apiKey: string;
|
||||
model?: string;
|
||||
}
|
||||
|
||||
export interface OmniRouteProvider {
|
||||
id: string;
|
||||
name: string;
|
||||
npm: string;
|
||||
options: Record<string, unknown>;
|
||||
auth: { type: string; apiKey: string };
|
||||
}
|
||||
|
||||
export function createOmniRouteProvider(options: OmniRouteProviderOptions): OmniRouteProvider {
|
||||
if (!options.baseURL) {
|
||||
throw new Error("baseURL is required");
|
||||
}
|
||||
if (!options.apiKey) {
|
||||
throw new Error("apiKey is required");
|
||||
}
|
||||
|
||||
const baseURL = options.baseURL.replace(/\/+$/, "");
|
||||
|
||||
return {
|
||||
id: "omniroute",
|
||||
name: "OmniRoute AI Gateway",
|
||||
npm: "@omniroute/opencode-provider",
|
||||
options: {
|
||||
baseURL: `${baseURL}/v1`,
|
||||
model: options.model || "opencode",
|
||||
},
|
||||
auth: {
|
||||
type: "apiKey",
|
||||
apiKey: options.apiKey,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export default createOmniRouteProvider;
|
||||
20
@omniroute/opencode-provider/package.json
Normal file
20
@omniroute/opencode-provider/package.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"name": "@omniroute/opencode-provider",
|
||||
"version": "1.0.0",
|
||||
"description": "OpenCode provider plugin for OmniRoute AI Gateway",
|
||||
"type": "module",
|
||||
"main": "index.js",
|
||||
"types": "index.d.ts",
|
||||
"files": [
|
||||
"index.js",
|
||||
"index.d.ts",
|
||||
"README.md"
|
||||
],
|
||||
"keywords": [
|
||||
"omniroute",
|
||||
"opencode",
|
||||
"provider"
|
||||
],
|
||||
"license": "MIT",
|
||||
"peerDependencies": {}
|
||||
}
|
||||
89
AGENTS.md
89
AGENTS.md
@@ -127,13 +127,18 @@ Always run `prettier --write` on changed files.
|
||||
|
||||
### Data Layer (`src/lib/db/`)
|
||||
|
||||
All persistence uses SQLite through domain-specific modules:
|
||||
`core.ts`, `providers.ts`, `models.ts`, `combos.ts`, `apiKeys.ts`, `settings.ts`,
|
||||
`backup.ts`, `proxies.ts`, `prompts.ts`, `webhooks.ts`, `detailedLogs.ts`,
|
||||
`domainState.ts`, `registeredKeys.ts`, `quotaSnapshots.ts`, `modelComboMappings.ts`,
|
||||
`cliToolState.ts`, `encryption.ts`, `readCache.ts`, `secrets.ts`, `stateReset.ts`,
|
||||
`contextHandoffs.ts`, `compression.ts`.
|
||||
Schema migrations live in `db/migrations/` and run via `migrationRunner.ts`.
|
||||
All persistence uses SQLite through **45+ domain-specific modules** in `src/lib/db/`. Top modules:
|
||||
|
||||
- Core: `core.ts`, `migrationRunner.ts`, `encryption.ts`, `stateReset.ts`
|
||||
- Providers / catalog: `providers.ts`, `models.ts`, `providerLimits.ts`, `compressionAnalytics.ts`
|
||||
- Routing: `combos.ts`, `modelComboMappings.ts`, `domainState.ts`, `commandCodeAuth.ts`
|
||||
- Auth: `apiKeys.ts`, `secrets.ts`, `registeredKeys.ts`, `sessionAccountAffinity.ts`
|
||||
- Usage / billing: `quotaSnapshots.ts`, `creditBalance.ts`, `usage*.ts`, `compressionCacheStats.ts`
|
||||
- Storage: `backup.ts`, `cleanup.ts`, `jsonMigration.ts`, `healthCheck.ts`, `databaseSettings.ts`
|
||||
- Extension modules: `evals.ts`, `webhooks.ts`, `reasoningCache.ts`, `readCache.ts`, `tierConfig.ts`, `compressionCombos.ts`, `compressionScheduler.ts`, `batches.ts`, `files.ts`, `syncTokens.ts`, `proxies.ts`, `oneproxy.ts`, `upstreamProxy.ts`, `versionManager.ts`, `cliToolState.ts`, `prompts.ts`, `detailedLogs.ts`, `contextHandoffs.ts`, `compression.ts`, `stats.ts`
|
||||
|
||||
Live count: `ls src/lib/db/*.ts | wc -l` (currently 45).
|
||||
Schema migrations live in `db/migrations/` (55 files) and run via `migrationRunner.ts`.
|
||||
`src/lib/localDb.ts` is a **re-export layer only** — never add logic there.
|
||||
|
||||
#### DB Internals
|
||||
@@ -142,8 +147,8 @@ Schema migrations live in `db/migrations/` and run via `migrationRunner.ts`.
|
||||
journaling. `SCHEMA_SQL` defines 15 base tables. Helpers: `rowToCamel`, `encryptConnectionFields`.
|
||||
- **`migrationRunner.ts`**: Applies versioned SQL files from `db/migrations/` inside transactions.
|
||||
Tracks applied migrations in `_omniroute_migrations` table.
|
||||
- **Migrations**: 22 files (`001_initial_schema.sql` → `022_compression_settings.sql`).
|
||||
Each migration is idempotent and runs in a transaction.
|
||||
- **Migrations**: 55 files (`001_initial_schema.sql` → `055_command_code_auth_sessions.sql`).
|
||||
Each migration is idempotent and runs in a transaction. Live count: `ls src/lib/db/migrations/*.sql | wc -l`.
|
||||
- **Domain modules** import `getDbInstance()` from `core.ts` for all CRUD operations.
|
||||
Each module owns a specific table/set of tables (e.g., `providers.ts` → `provider_connections`,
|
||||
`combos.ts` → `combos`). Encryption helpers protect sensitive fields at rest.
|
||||
@@ -212,7 +217,7 @@ Zod schemas, and unit tests aligned when editing.
|
||||
### Provider Categories
|
||||
|
||||
- **Free** (4): Qoder AI, Qwen Code, Gemini CLI (deprecated), Kiro AI
|
||||
- **OAuth** (8): Claude Code, Antigravity, Codex, GitHub Copilot, Cursor, Kimi Coding, Kilo Code, Cline
|
||||
- **OAuth** (14): Claude Code, Antigravity, Codex, GitHub Copilot, Cursor, Kimi Coding, Kilo Code, Cline, Qwen (⚠️ free tier discontinued 2026-04-15), Kiro, Qoder, Gemini, Windsurf (v3.8), GitLab Duo (v3.8)
|
||||
- **API Key** (120+): OpenAI, Anthropic, Gemini, DeepSeek, Groq, xAI, Mistral, Perplexity,
|
||||
Together, Fireworks, Cerebras, Cohere, NVIDIA, Nebius, SiliconFlow, Hyperbolic,
|
||||
HuggingFace, OpenRouter, Vertex AI, Cloudflare AI, Scaleway, AI/ML API, Pollinations,
|
||||
@@ -321,7 +326,7 @@ Modular prompt compression that runs proactively before the existing reactive co
|
||||
and iterates through targets in order until one succeeds or all fail.
|
||||
- **`resolveComboTargets()`**: Expands a combo configuration into an ordered array of
|
||||
`ResolvedComboTarget[]`, each specifying provider + model + account + credentials.
|
||||
- **Strategies** (13): priority, weighted, fill-first, round-robin, P2C, random, least-used,
|
||||
- **Strategies** (14): priority, weighted, fill-first, round-robin, P2C, random, least-used, reset-aware (v3.8),
|
||||
cost-optimized, strict-random, auto, lkgp, context-optimized, context-relay.
|
||||
- Each target calls **`handleSingleModel()`** which wraps `handleChatCore()` with
|
||||
per-target error handling and circuit breaker checks.
|
||||
@@ -334,7 +339,7 @@ Policy engine modules: `policyEngine.ts`, `comboResolver.ts`, `costRules.ts`,
|
||||
|
||||
### MCP Server (`open-sse/mcp-server/`)
|
||||
|
||||
37 tools, 3 transports (stdio / SSE / Streamable HTTP). Scoped auth (10 scopes), Zod schemas.
|
||||
37 tools (30 base + 3 memory + 4 skills), 3 transports (stdio / SSE / Streamable HTTP). Scoped auth (~13 scopes), Zod schemas. See [`docs/frameworks/MCP-SERVER.md`](docs/frameworks/MCP-SERVER.md).
|
||||
|
||||
**Core tools** (20): get_health, list_combos, get_combo_metrics, switch_combo, check_quota,
|
||||
route_request, cost_report, list_models_catalog, web_search, simulate_route, set_budget_guard,
|
||||
@@ -369,7 +374,7 @@ handler: async (args) => {...} }`. Zod validates inputs before the handler fires
|
||||
|
||||
JSON-RPC 2.0, SSE streaming, Task Manager with TTL cleanup.
|
||||
Agent Card at `/.well-known/agent.json`.
|
||||
Skills: `quotaManagement.ts`, `smartRouting.ts`.
|
||||
Skills (5): `smartRouting.ts`, `quotaManagement.ts`, `providerDiscovery.ts`, `costAnalysis.ts`, `healthReport.ts`.
|
||||
|
||||
#### A2A Internals
|
||||
|
||||
@@ -422,6 +427,34 @@ MITM proxy capability with certificate management, DNS handling, and target rout
|
||||
|
||||
Request middleware including `promptInjectionGuard.ts`.
|
||||
|
||||
### Guardrails (`src/lib/guardrails/`)
|
||||
|
||||
Hot-reloadable guardrails framework (3 built-in: pii-masker, prompt-injection, vision-bridge). Fail-open; per-request opt-out via header. See [`docs/security/GUARDRAILS.md`](docs/security/GUARDRAILS.md).
|
||||
|
||||
### Cloud Agents (`src/lib/cloudAgent/`)
|
||||
|
||||
`CloudAgentBase` abstract class + 3 agents (codex-cloud, devin, jules). Tasks persisted in `cloud_agent_tasks`; management auth required. See [`docs/frameworks/CLOUD_AGENT.md`](docs/frameworks/CLOUD_AGENT.md).
|
||||
|
||||
### Evals (`src/lib/evals/`)
|
||||
|
||||
Generic eval framework: `evalRunner.ts`, `runtime.ts`. Targets: combo / model / suite-default. See [`docs/frameworks/EVALS.md`](docs/frameworks/EVALS.md).
|
||||
|
||||
### Webhooks (`src/lib/webhookDispatcher.ts`)
|
||||
|
||||
HMAC-signed delivery, exponential backoff, auto-disable after 10 failures. 7 event types. See [`docs/frameworks/WEBHOOKS.md`](docs/frameworks/WEBHOOKS.md).
|
||||
|
||||
### Authorization Pipeline (`src/server/authz/`)
|
||||
|
||||
`classify → policies → enforce`. 3 route classes (PUBLIC / CLIENT_API / MANAGEMENT). See [`docs/architecture/AUTHZ_GUIDE.md`](docs/architecture/AUTHZ_GUIDE.md).
|
||||
|
||||
### Reasoning Replay (`src/lib/db/reasoningCache.ts` + `open-sse/services/reasoningCache.ts`)
|
||||
|
||||
Hybrid in-memory + SQLite cache for `reasoning_content`. Re-injects on multi-turn for strict providers (DeepSeek V4, Kimi K2, Qwen-Thinking, GLM, xiaomi-mimo). See [`docs/routing/REASONING_REPLAY.md`](docs/routing/REASONING_REPLAY.md).
|
||||
|
||||
### Tunnels (`src/lib/{cloudflaredTunnel,ngrokTunnel}.ts` + `src/app/api/tunnels/`)
|
||||
|
||||
Cloudflare Quick/Named, ngrok, Tailscale Funnel. See [`docs/ops/TUNNELS_GUIDE.md`](docs/ops/TUNNELS_GUIDE.md).
|
||||
|
||||
### Adding a New Provider
|
||||
|
||||
1. Register in `src/shared/constants/providers.ts`
|
||||
@@ -438,6 +471,36 @@ Request middleware including `promptInjectionGuard.ts`.
|
||||
- **[`src/lib/db/AGENTS.md`](src/lib/db/AGENTS.md)** — SQLite persistence, domain modules, migrations
|
||||
- **[`open-sse/services/AGENTS.md`](open-sse/services/AGENTS.md)** — Routing engine, combo resolution, strategy selection
|
||||
|
||||
## Reference Documentation (docs/)
|
||||
|
||||
For any non-trivial change, read the matching deep-dive first:
|
||||
|
||||
| Area | Doc |
|
||||
| ------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| Repo navigation | [`docs/architecture/REPOSITORY_MAP.md`](docs/architecture/REPOSITORY_MAP.md) |
|
||||
| Architecture | [`docs/architecture/ARCHITECTURE.md`](docs/architecture/ARCHITECTURE.md) |
|
||||
| Engineering reference | [`docs/architecture/CODEBASE_DOCUMENTATION.md`](docs/architecture/CODEBASE_DOCUMENTATION.md) |
|
||||
| Auto-Combo (9-factor, 14 strategies) | [`docs/routing/AUTO-COMBO.md`](docs/routing/AUTO-COMBO.md) |
|
||||
| Resilience (3 layers) | [`docs/architecture/RESILIENCE_GUIDE.md`](docs/architecture/RESILIENCE_GUIDE.md) |
|
||||
| Skills | [`docs/frameworks/SKILLS.md`](docs/frameworks/SKILLS.md) |
|
||||
| Memory | [`docs/frameworks/MEMORY.md`](docs/frameworks/MEMORY.md) |
|
||||
| Cloud agents | [`docs/frameworks/CLOUD_AGENT.md`](docs/frameworks/CLOUD_AGENT.md) |
|
||||
| Guardrails | [`docs/security/GUARDRAILS.md`](docs/security/GUARDRAILS.md) |
|
||||
| Evals | [`docs/frameworks/EVALS.md`](docs/frameworks/EVALS.md) |
|
||||
| Compliance | [`docs/security/COMPLIANCE.md`](docs/security/COMPLIANCE.md) |
|
||||
| Webhooks | [`docs/frameworks/WEBHOOKS.md`](docs/frameworks/WEBHOOKS.md) |
|
||||
| Authz | [`docs/architecture/AUTHZ_GUIDE.md`](docs/architecture/AUTHZ_GUIDE.md) |
|
||||
| Stealth | [`docs/security/STEALTH_GUIDE.md`](docs/security/STEALTH_GUIDE.md) |
|
||||
| Reasoning replay | [`docs/routing/REASONING_REPLAY.md`](docs/routing/REASONING_REPLAY.md) |
|
||||
| Agent protocols (A2A / ACP / Cloud) | [`docs/frameworks/AGENT_PROTOCOLS_GUIDE.md`](docs/frameworks/AGENT_PROTOCOLS_GUIDE.md) |
|
||||
| MCP server | [`docs/frameworks/MCP-SERVER.md`](docs/frameworks/MCP-SERVER.md) |
|
||||
| A2A server | [`docs/frameworks/A2A-SERVER.md`](docs/frameworks/A2A-SERVER.md) |
|
||||
| API reference | [`docs/reference/API_REFERENCE.md`](docs/reference/API_REFERENCE.md) + [`docs/reference/openapi.yaml`](docs/reference/openapi.yaml) |
|
||||
| Provider catalog (auto-generated) | [`docs/reference/PROVIDER_REFERENCE.md`](docs/reference/PROVIDER_REFERENCE.md) |
|
||||
| Tunnels | [`docs/ops/TUNNELS_GUIDE.md`](docs/ops/TUNNELS_GUIDE.md) |
|
||||
| Electron desktop | [`docs/guides/ELECTRON_GUIDE.md`](docs/guides/ELECTRON_GUIDE.md) |
|
||||
| Release flow | [`docs/ops/RELEASE_CHECKLIST.md`](docs/ops/RELEASE_CHECKLIST.md) |
|
||||
|
||||
---
|
||||
|
||||
## Review Focus
|
||||
|
||||
225
CHANGELOG.md
225
CHANGELOG.md
@@ -2,37 +2,258 @@
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Changed
|
||||
|
||||
- **BREAKING**: dropped Node 20.x support. Minimum Node version is now 22.22.2 (or 24.0.0+). Required because http-proxy-middleware 4.x requires `node >=22.15.0`. Users on Node 20 must upgrade — see [`package.json` engines field](package.json) and the README Node badge.
|
||||
- **Platform overhaul (FASES 1-9):** scripts cleanup, `.env` audit, `/docs` restructure, diagrams folder, i18n pipelines (docs + UI), `/src/app/docs` sync, CI gates.
|
||||
- **Scripts:** `scripts/` reorganized into 6 subfolders (`build/`, `dev/`, `check/`, `docs/`, `i18n/`, `ad-hoc/`); 23 one-shot scripts archived to the `archive/scripts-scratch-pre-3.8` branch.
|
||||
- **Environment:** `.env.example` cleaned (-11 orphan vars, +63 missing vars, 11 hardcoded URLs/timeouts promoted to env); new strict `scripts/check/check-env-doc-sync.mjs` validates code ↔ `.env.example` ↔ `docs/reference/ENVIRONMENT.md` parity.
|
||||
- **Docs:** `/docs` restructured into 8 functional subfolders (`architecture/`, `guides/`, `reference/`, `frameworks/`, `routing/`, `security/`, `compression/`, `ops/`); ~899 cross-references rewritten.
|
||||
- **Diagrams:** new `docs/diagrams/` with 8 canonical Mermaid sources + SVG exports, linked into 9 docs.
|
||||
- **i18n (docs):** hash-based incremental pipeline (`config/i18n.json`, `scripts/i18n/run-translation.mjs`, `.i18n-state.json`); old `i18n_autotranslate.py` and `generate-multilang.mjs` deprecated.
|
||||
- **i18n (UI):** `scripts/i18n/sync-ui-keys.mjs` propagates `en.json` keys to all 40 locales (no missing keys; coverage ≥ 85.8%); cosmetic `DocsI18n.tsx` removed (locale handling unified via `next-intl`).
|
||||
- **`/src/app/docs`:** drift fixes (179 → 177 providers, 13 → 14 strategies, 36 → 37 MCP tools); YAML frontmatter added to all docs; `ApiExplorer` consumes `docs/reference/openapi.yaml` (19 endpoints); `content.ts` updated (37 MCP tool groups, 7 internal deployment guide hrefs).
|
||||
- **CI gates:** strict env-doc-sync in pre-commit; `check:doc-links` validates internal markdown refs; new `docs-sync-strict` and `i18n-ui-coverage` jobs in `.github/workflows/ci.yml`.
|
||||
|
||||
### Fixed
|
||||
|
||||
- **Docs:** 270 broken internal markdown links repaired (consequence of `/docs` subfolder restructure not relativizing all paths). Categories: 241 `i18n-relative`, 14 `parent-relative`, 9 `screenshots`, 2 deleted-RFC, 4 misc. Now `npm run check:doc-links` PASS with 0 broken links.
|
||||
|
||||
## [3.8.0] — 2026-05-06
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
- **feat(antigravity):** integrate Antigravity provider with dynamic `maxOutputTokens` calculation (bumping to `thinkingBudget + 1`) and standard Cloud Code envelope payload sanitization (#2055, #2063)
|
||||
- **feat(providers):** add Command Code provider (#2199 — thanks @ddarkr)
|
||||
- **feat(providers):** add ModelScope provider-specific 429 handling and retry logic (#2202 — thanks @InkshadeWoods)
|
||||
- **feat(providers):** update Gemini CLI provider models catalog (#2196 — thanks @nickwizard)
|
||||
- **feat(antigravity):** integrate Antigravity provider with dynamic `maxOutputTokens` calculation, identity fingerprinting overhaul, and Cloud Code envelope payload sanitization (#2055, #2063)
|
||||
- **feat(gemini-cli):** add custom projectId support for Gemini CLI transport (UI, DB, executor) (#1991)
|
||||
- **feat(providers):** add KIE media provider support with dynamic polling, text models, and expanded video models catalog
|
||||
- **feat(providers):** add Z.AI provider support with GLM quota handling and new quota labels
|
||||
- **feat(providers):** add 9 new free AI providers — LLM7, Lepton, Kluster, UncloseAI, BazaarLink, Completions, Enally, FreeTheAi (#2096)
|
||||
- **feat(providers):** batch delete provider connections via checkbox multi-select (#2094)
|
||||
- **feat(cursor):** full OpenAI parity — tool calls, streaming, and session management (#2082)
|
||||
- **feat(cursor):** surface Cursor Pro plan usage on provider-limits dashboard (#2128 — thanks @payne0420)
|
||||
- **feat(cli):** comprehensive CLI enhancement suite with 20+ new commands including `omniroute providers`, `omniroute combos`, `omniroute doctor` (#2074)
|
||||
- **feat(cli):** add modular CLI setup and provider management commands (#2046)
|
||||
- **feat(mcp):** add DeepSeek quota and limit monitoring feature (#2089)
|
||||
- **feat(circuit-breaker):** classify 429 errors and apply per-kind cooldowns (#2116)
|
||||
- **feat(multi):** manifest-aware tier routing — W1-W4 complete (#2014)
|
||||
- **feat(combos):** add reset-aware routing strategy for quota-based providers
|
||||
- **feat(combo):** add context_length input field to combo edit form (#2047)
|
||||
- **feat(combo):** add `fallbackDelayMs` to combo configuration and related settings
|
||||
- **feat(chat):** dynamic tool limit detection with proactive truncation (#2061)
|
||||
- **feat(chat):** add `STREAM_READINESS_TIMEOUT_MS` and integrate into chat handling
|
||||
- **feat(chat):** enhance error handling for semaphore capacity with fallback logic
|
||||
- **feat(sse):** refresh Claude OAuth wire image to claude-cli/2.1.131 (#2011)
|
||||
- **feat(github):** add `targetFormat: openai-responses` to all GitHub models (#2122)
|
||||
- **feat(api):** allow configuration via API calls — open management routes to Bearer keys with manage scope (#2103)
|
||||
- **feat(api):** update API bridge proxy timeout to 600,000ms (#2019)
|
||||
- **feat(api):** aggregate combo model metadata in catalog endpoint — `buildComboCatalogMetadata()` inlines contextLength, strategy, and target count for combo entries (#2166 — thanks @faisalill)
|
||||
- **feat(usage):** add service tier breakdown, codex fast service tier analytics, and account for fast tier
|
||||
- **feat(qdrant):** embedding model discovery (#2086)
|
||||
- **feat(auth):** per-session sticky routing for Codex (#1887)
|
||||
- **feat(oauth):** complete Windsurf and Devin CLI OAuth + API-token flows — WindsurfExecutor (gRPC-web/protobuf), DevinCliExecutor (ACP JSON-RPC 2.0 over stdio), model alias map, OAuth provider config (#2168 — thanks @Zhaba1337228)
|
||||
- **feat(inworld):** enhance Inworld TTS support (#2123)
|
||||
- **feat(kiro):** headless auth via kiro-cli SQLite, image support, tool overflow handling, and model list sync (#2129 — thanks @christlau)
|
||||
- **feat(auto):** zero-config auto-routing with `auto/` prefix — dynamic virtual combo from connected providers with 6 variant profiles (coding, fast, cheap, offline, smart, lkgp), analytics tab, and settings UI (#2131 — thanks @oyi77)
|
||||
- **feat(resilience):** add model cooldowns dashboard card with real-time list, individual/bulk re-enable, and auto-refresh (#2146 — thanks @rafacpti23)
|
||||
- **feat(resilience):** `useUpstream429BreakerHints` toggle — per-provider default policy for upstream 429 hint trust at the circuit-breaker cooldown layer with tri-state PATCH semantics (#2133 — thanks @eleata)
|
||||
- **feat(search):** add Ollama Search as a web search provider with registry integration and validation (#2176 — thanks @andrewmunsell)
|
||||
- **feat(search):** add Z.AI Coding Plan Search via MCP protocol integration (#2238 — thanks @andrewmunsell)
|
||||
- **feat(debug):** configurable chat log truncation limits via environment variables (`CHAT_LOG_TEXT_LIMIT`, `CHAT_LOG_ARRAY_TAIL_ITEMS`, `CHAT_LOG_MAX_DEPTH`, `CHAT_LOG_MAX_OBJECT_KEYS`) and `CHAT_DEBUG_FILE` mode for untruncated JSON payloads (#2156 — thanks @bypanghu)
|
||||
- **feat(responses):** degrade `background: true` to synchronous execution with a warning instead of throwing `unsupportedFeature` (#2164 — thanks @Yosee11)
|
||||
- **feat(mitm):** dynamic Linux certificate path detection for multi-distro MITM cert trust (Debian, Arch/CachyOS, Fedora/RHEL, openSUSE) with NSS browser database injection (#2134 — thanks @flyingmongoose)
|
||||
- **feat(1proxy):** add dedicated settings tab with proxy rotation support (#2135 — thanks @oyi77)
|
||||
- **feat(antigravity):** support custom Google Cloud project ID for Antigravity provider (#2227 — thanks @nickwizard)
|
||||
- **feat(cli):** CLI Integration Suite — 5 new management commands (`config`, `status`, `logs`, `update`, `provider`), 3 API endpoints, config generators for 6 tools (Claude, Cline, Codex, Continue, KiloCode, OpenCode), zero-config `auto/` routing, and `@omniroute/opencode-provider` npm package (#2240 — thanks @oyi77)
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- **fix(pricing):** make `getPricingForModel` fully case-insensitive to ensure custom prices correctly reflect in new incoming requests cost calculations
|
||||
- **fix(gemini):** prevent `functionDeclarations` from being dropped by the sanitizer when `googleSearch` tool is present (#2077)
|
||||
- **fix(pollinations):** add `jsonMode: true` flag in the request transformation to enforce correct JSON structure from Pollinations API (#2109)
|
||||
- **fix(docker):** update Dockerfile to copy `/docs` directory during build ensuring API catalog availability at runtime (#2083)
|
||||
- **fix(docker):** include OpenAPI spec in runtime image (#2007)
|
||||
- **fix(providers):** strip OpenAI-specific fields in Kiro translator to prevent 400 errors (#2037)
|
||||
- **fix(kiro):** normalize tool-use payloads to prevent 400 errors from agents (#2104)
|
||||
- **fix(kiro):** merge adjacent user history turns after role normalization (#2105)
|
||||
- **fix(ui):** resolve text contrast issues for zero-config warning banner in light mode (#2050)
|
||||
- **fix(core):** inject global system prompt correctly into downstream chat completions pipeline (#2080)
|
||||
- **fix(core):** restore Claude Code adaptive thinking defaults and resolve audio transcription CORS regression
|
||||
- **fix(routing):** add missing v1beta rewrites to next.config to resolve 404 on Gemini models endpoint (#2102)
|
||||
- **fix(routing):** fix bare GPT-5.5 routing for Codex-only installations (#2054)
|
||||
- **fix(routing):** add fuzzy auto-combo routing for `auto/*` model prefix (#2010)
|
||||
- **fix(cache):** optimize cache_control preservation logic and explicitly align tool schema with upstream Claude Code expectations
|
||||
- **fix(db):** preserve legacy SQLite database path on Windows to prevent data loss (#1973)
|
||||
- **fix(db):** reduce hot-path persistence overhead (#2039)
|
||||
- **fix(db):** resolve migration conflict by renumbering overlapping migration entries (#2041)
|
||||
- **fix(settings):** resolve model alias persistence double stringification preventing UI updates (#2018)
|
||||
- **fix(routing):** dynamically filter bare model auto-resolution by active provider connections to prevent dead-routing (#2029)
|
||||
- **fix(embeddings):** add Google Gemini embeddings compatibility via OpenAI-compatible endpoint mapping (#2006)
|
||||
- **fix(sse):** prevent Claude OAuth multi-account correlation via metadata.user_id (#2053)
|
||||
- **fix(sse):** prevent Claude Code identity cloak overrides and fix fallback resilience (#2053)
|
||||
- **fix(sse):** classify hour quota errors as QUOTA_EXHAUSTED (#2119)
|
||||
- **fix(sse):** fix CC-compatible streaming bridge (#2118)
|
||||
- **fix(antigravity):** sanitize Claude Cloud Code payloads (#2090)
|
||||
- **fix(antigravity):** add duplex half for streaming bodies
|
||||
- **fix(antigravity):** align identity protocol and behavior with official AM
|
||||
- **fix(chatgpt-web):** plumb proxy through to native tls-client (#2022, #2023)
|
||||
- **fix(codex):** expose native model IDs in catalog (#2012)
|
||||
- **fix(glm):** add dedicated coding transport (#2087)
|
||||
- **fix(compression):** support Responses input and expand Spanish compression rules (#2028)
|
||||
- **fix(catalog):** auto-calculate combo context_length from target model limits (#2030)
|
||||
- **fix(api):** fix usage analytics and API key identity (#2008, #2092)
|
||||
- **fix(api-key):** allow Unicode letters in API key name validation (#1996)
|
||||
- **fix(auth):** allow bootstrap without password (#2048)
|
||||
- **fix(proxy):** clean up proxy page redundancy and fix 1proxy sync empty body error (#2052)
|
||||
- **fix(dashboard):** resolve Unknown plan display in Provider Limits
|
||||
- **fix(usage):** add extensible CURRENCY_SYMBOLS mapping for deepseek currencies
|
||||
- **fix(runtime):** harden timer handling and model pricing fallback
|
||||
- **fix(i18n):** complete Simplified Chinese translations (#2115)
|
||||
- **fix(mitm):** add Linux cert install and skip sudo password when root (#1999)
|
||||
- **fix(mitm):** prevent stub from loading at runtime via bypass module
|
||||
- **fix:** remove Anthropic-Beta header from non-Anthropic providers to fix identity contamination (#1989)
|
||||
- **fix(cli):** resolve .env loading failure for global npm installations
|
||||
- **fix(authz):** classify `/dashboard/onboarding` as PUBLIC to unblock setup wizard (#2127)
|
||||
- **fix(chatcore):** stop leaking provider credentials in response headers
|
||||
- **fix(analytics):** precise SQL matching for `auto/` prefix models
|
||||
- **fix(export):** exclude telemetry/usage-history tables from JSON config backups by default to prevent unbounded file growth (#2125)
|
||||
- **fix(translator):** preserve `body.system` in openai→claude translator when Claude Code sends native Anthropic system array through /chat/completions — fixes v3.7.9 regression where system prompt was silently dropped, triggering Anthropic 429 (#2130)
|
||||
- **fix(sanitizer):** preserve `reasoning_content` on assistant messages with `tool_calls` or `function_call` — fixes Kimi and other thinking-enabled providers returning 400 errors when reasoning_content was incorrectly stripped (#2140 — thanks @DavyMassoneto)
|
||||
- **fix(catalog):** ensure individual (non-combo) models expose `context_length` via `getTokenLimit()` fallback chain — prevents OpenCode and other clients from falling back to conservative ~4000 token limit (#2136 — thanks @herjarsa)
|
||||
- **fix(docker):** remove docs directory from `.dockerignore` so API catalog documentation is available at runtime inside containers (#2137, #2120 — thanks @hartmark)
|
||||
- **fix(types):** systematic `any` type elimination across 8 core files — `antigravity.ts`, `accountFallback.ts`, `usage.ts`, `geminiHelper.ts`, `error.ts`, `apiKeys.ts`, `settings.ts`, `logger.ts` (#2137 — thanks @hartmark)
|
||||
- **fix(providers):** restore cloud agent provider exports and logger import (#2138 — thanks @backryun)
|
||||
- **fix(providers):** remove duplicate `CLOUD_AGENT_PROVIDERS` declaration, move Kiro dash→dot Claude model aliases to `PROVIDER_MODEL_ALIASES`, and trim deprecated Kiro registry entries (#2141 — thanks @backryun)
|
||||
- **fix:** Follow OpenAI specification, handle throttling in batch and fix UI (#2045)
|
||||
- **fix(cliproxyapi):** probe `/v1/models` for health when CPA 6.x has no `/health` endpoint (#2189 — thanks @Brkic-Nikola)
|
||||
- **fix(cliproxyapi):** detect Anthropic-shaped request bodies and route to `/v1/messages`, strip Capy extras, and round-trip `mcp_*` tool name rewrites to `Mcp_*` (#2165 — thanks @Brkic-Nikola)
|
||||
- **fix(cliproxyapi):** detect Anthropic shape on minimal Capy bodies (#2192 — thanks @Brkic-Nikola)
|
||||
- **fix(stream):** skip `[DONE]` terminator for Claude SSE clients (#2190 — thanks @Brkic-Nikola)
|
||||
- **fix(claudeHelper):** emit `data` field on `redacted_thinking`, drop bogus signature (#2191 — thanks @Brkic-Nikola)
|
||||
- **fix(modelSpecs):** cap thinking budget for Claude Opus 4.6 / 4.7 / Sonnet 4.6 (#2197 — thanks @Brkic-Nikola)
|
||||
- **fix(reasoning-cache):** include xiaomi-mimo in replay provider/model detection (#2198 — thanks @Brkic-Nikola)
|
||||
- **fix(kiro):** synthesize minimal tools schema when `body.tools` is omitted but message history contains `tool_calls`, preventing 400 errors from Claude Code and OpenCode (#2149 — thanks @Gioxaa)
|
||||
- **fix(kiro):** avoid treating high-traffic 429s as quota exhaustion — use `classify429FromError` to prevent premature account deactivation (#2153 — thanks @Gioxaa)
|
||||
- **fix(responses):** propagate `include` array (e.g. `reasoning.encrypted_content`) during Chat→Responses API translation, fixing broken thinking panel in Codex/OpenCode (#2154 — thanks @Gioxaa)
|
||||
- **fix(responses):** emit reasoning summary as `delta.reasoning_content` (flat) instead of `delta.reasoning.summary` (nested) for Chat Completions client compatibility (#2159 — thanks @Gioxaa)
|
||||
- **fix(cloudflare):** add state file write serialization lock to prevent race conditions in `cloudflaredTunnel.ts` (#2156 — thanks @bypanghu)
|
||||
- **fix(providers):** allow optional-key providers to pass connection test (#2169 — thanks @andrewmunsell)
|
||||
- **fix(providers):** correct pollinations requests and provider dashboard state
|
||||
- **fix(providers):** fix Azure AI Foundry provider connection handling (#2236 — thanks @one-vs)
|
||||
- **fix(providers/command-code):** fix validation request format for Command Code API (#2243 — thanks @ddarkr)
|
||||
- **fix(antigravity):** strip `generationConfig.thinkingConfig` for Claude models routed through Antigravity to prevent upstream errors (#2217 — thanks @NomenAK)
|
||||
- **fix(antigravity):** bootstrap project via `loadCodeAssist` + `fetchAvailableModels` fallback for robust startup (#2219 — thanks @NomenAK)
|
||||
- **fix(rateLimit):** never `.stop()` during runtime reset, evict cache instead to prevent stale rate-limit state (#2218 — thanks @NomenAK)
|
||||
- **fix(ModelSync):** shared loopback readiness gate + IPv4 force to prevent model sync failures on dual-stack hosts (#2221 — thanks @NomenAK)
|
||||
- **fix(proxyFetch):** retry once on undici dispatcher failure before native fallback (#2222 — thanks @NomenAK)
|
||||
- **fix(model):** local aliases override cross-proxy provider inference to prevent incorrect model resolution (#2223 — thanks @NomenAK)
|
||||
- **fix(claudeHelper):** preserve latest assistant thinking blocks verbatim to prevent Anthropic HTTP 400 errors (#2224 — thanks @NomenAK)
|
||||
- **fix(deepseek):** preserve `reasoning_content` through full pipeline for DeepSeek V4 models — prevents reasoning context loss on multi-turn conversations (#2231 — thanks @kang-heewon)
|
||||
- **fix(sse-heartbeat):** shape-aware keepalives keep streams alive through stricter proxies (#2233 — thanks @NomenAK)
|
||||
- **fix(translator):** coerce `submit_pr_review` `functionalChanges`/`findings` to arrays to prevent upstream schema errors (#2242 — thanks @NomenAK)
|
||||
- **fix(api):** validate model cooldown delete payload
|
||||
- **fix(ci):** run coverage gate serially, align resilience and thinking checks, align cloud code thinking and model catalog tests
|
||||
|
||||
### 🔒 Security
|
||||
|
||||
- **fix(security):** remediate CodeQL vulnerabilities (ReDoS, cryptographic bias, stack trace exposure, and weak password hashing) (#216, #215, #211, #208, #206, #210)
|
||||
- **fix(security):** sanitize error messages in API routes to prevent stack trace exposure (CodeQL js/stack-trace-exposure) (#2209)
|
||||
- **fix(security):** remediate regex validation backtracking path in core compression cleanup (#1990)
|
||||
- **fix(core):** harden input handling and stabilization for prompt compression edge cases
|
||||
|
||||
### 📝 Documentation
|
||||
|
||||
- **docs:** add competitive marketing tables and SEO/AEO optimizations to README (#2091)
|
||||
- **docs:** refresh providers, model catalogs, and docs for v3.8.0 (#2088)
|
||||
- **docs:** update Claude MD and update GLM-CN max context to 200k (#2027)
|
||||
- **docs(env):** add `GITLAB_DUO_OAUTH_CLIENT_ID` to `.env.example` (#2031)
|
||||
- **docs:** add Brazilian WhatsApp group link to README (#2201 — thanks @rafacpti23)
|
||||
|
||||
### 🔧 Improvements
|
||||
|
||||
- **refactor(executor):** `sanitizeReasoningEffortForProvider()` hook in `BaseExecutor.execute()` — downgrades `xhigh`→`high` for unsupporting providers, strips effort for mistral/devstral and github claude models (#2162 — thanks @hachimed)
|
||||
- **refactor(translator):** remove redundant provider guard from Claude thinking placeholder injection — applies to all `targetFormat === FORMATS.CLAUDE` bodies (#2161 — thanks @JohnDoe-oss)
|
||||
- **refactor(catalog):** remove 11 `.ts` extension imports, eliminate all `as any` casts, add `CustomModelEntry` interface and `ComboModelStep` type predicate, normalize alias resolution with `resolveCanonicalProviderId()` (#2152 — thanks @herjarsa)
|
||||
- **feat(resilience):** `useUpstream429BreakerHints` tri-state PATCH field — `true`/`false` persists, `null` resets to undefined (omitted from JSON) (#2146 tests — thanks @rafacpti23)
|
||||
|
||||
### 🧹 Chores & Maintenance
|
||||
|
||||
- **chore(providers):** prune redundant local provider icon assets in favor of `@lobehub/icons` web fonts (#1992)
|
||||
- **chore(providers):** remove deprecated models (#2033)
|
||||
- **chore(providers):** improve BazaarLink and Completions.me support (#2177 — thanks @backryun)
|
||||
- **chore(registry):** refresh `contextLength` and `maxOutputTokens` for claude, kiro, github, kimi-coding, xiaomi-mimo, codex/gpt-5.5 models (#2163 — thanks @brucevoin)
|
||||
- **chore(models):** tidy up Alibaba Coding Plan base URL, reorganize Cursor model list by family, fix `gpt-4o` model ID, update OpenCode Zen model (#2150 — thanks @backryun)
|
||||
- **chore(deps):** resolve npm audit moderate vulnerability (hono)
|
||||
- **chore(deps):** move `gray-matter` from devDependencies to dependencies (runtime requirement) (#2156 — thanks @bypanghu)
|
||||
- **deps:** bump `fast-uri` from 3.1.0 to 3.1.2 (#2078)
|
||||
- **deps:** bump `hono` from 4.12.14 to 4.12.18 (#2065, #2079)
|
||||
- **deps:** bump the development group with 6 updates (#2184)
|
||||
- **deps:** bump `electron-builder` from 26.9.1 to 26.10.0 (#2183)
|
||||
- **ci:** update build-fork workflow to build from main branch (#2055)
|
||||
- **ci:** skip SonarCloud scan on main pushes to optimize CI time
|
||||
- **test:** stabilize cooldown abort coverage case in integration testing
|
||||
- **build(deps):** regenerate `package-lock.json` to match `http-proxy-middleware` 4.x bump (#2228 — thanks @NomenAK)
|
||||
- **fix(requestLogger):** exempt tools field from array truncation for full debug visibility (#2234 — thanks @NomenAK)
|
||||
|
||||
### 🏆 v3.8.0 Community Contributors
|
||||
|
||||
Thank you to all **55+ community contributors** who made v3.8.0 possible! 🎉
|
||||
|
||||
| Contributor | PRs | Contributions |
|
||||
| :--------------------------------------------------------- | :-: | :--------------------------------------------------------------------------------- |
|
||||
| [@NomenAK](https://github.com/NomenAK) | 12 | #2217, #2218, #2219, #2221, #2222, #2223, #2224, #2228, #2233, #2234, #2242, #2192 |
|
||||
| [@oyi77](https://github.com/oyi77) | 12 | #2010, #2014, #2041, #2052, #2061, #2074, #2091, #2094, #2096, #2131, #2135, #2240 |
|
||||
| [@backryun](https://github.com/backryun) | 8 | #1992, #2033, #2088, #2123, #2138, #2141, #2150, #2177 |
|
||||
| [@Brkic-Nikola](https://github.com/Brkic-Nikola) | 6 | #2165, #2189, #2190, #2191, #2192, #2197 |
|
||||
| [@Gioxaa](https://github.com/Gioxaa) | 5 | #2105, #2149, #2153, #2154, #2159 |
|
||||
| [@dhaern](https://github.com/dhaern) | 4 | #2028, #2039, #2087, #2090 |
|
||||
| [@andrewmunsell](https://github.com/andrewmunsell) | 3 | #2169, #2176, #2238 |
|
||||
| [@ddarkr](https://github.com/ddarkr) | 3 | #2047, #2199, #2243 |
|
||||
| [@nickwizard](https://github.com/nickwizard) | 3 | #1991, #2196, #2227 |
|
||||
| [@herjarsa](https://github.com/herjarsa) | 3 | #2030, #2136, #2152 |
|
||||
| [@rafacpti23](https://github.com/rafacpti23) | 3 | #2086, #2146, #2201 |
|
||||
| [@Tentoxa](https://github.com/Tentoxa) | 2 | #2011, #2053 |
|
||||
| [@wauputr4](https://github.com/wauputr4) | 2 | #2009, #2046 |
|
||||
| [@hartmark](https://github.com/hartmark) | 2 | #2045, #2137 |
|
||||
| [@payne0420](https://github.com/payne0420) | 2 | #2082, #2128 |
|
||||
| [@bypanghu](https://github.com/bypanghu) | 2 | #2027, #2156 |
|
||||
| [@eleata](https://github.com/eleata) | 2 | #2116, #2133 |
|
||||
| [@Tr0sT](https://github.com/Tr0sT) | 1 | #2012 |
|
||||
| [@AveryanAlex](https://github.com/AveryanAlex) | 1 | #2008 |
|
||||
| [@rodrigogbbr-stack](https://github.com/rodrigogbbr-stack) | 1 | #1996 |
|
||||
| [@NekoMonci12](https://github.com/NekoMonci12) | 1 | #1999 |
|
||||
| [@congvc-dev](https://github.com/congvc-dev) | 1 | #2004 |
|
||||
| [@tatsster](https://github.com/tatsster) | 1 | #2007 |
|
||||
| [@xssdem](https://github.com/xssdem) | 1 | #2023 |
|
||||
| [@wucm667](https://github.com/wucm667) | 1 | #2031 |
|
||||
| [@tces1](https://github.com/tces1) | 1 | #2048 |
|
||||
| [@guanbear](https://github.com/guanbear) | 1 | #2054 |
|
||||
| [@Gi99lin](https://github.com/Gi99lin) | 1 | #2055 |
|
||||
| [@ivan-mezentsev](https://github.com/ivan-mezentsev) | 1 | #2063 |
|
||||
| [@JxnLexn](https://github.com/JxnLexn) | 1 | #2019 |
|
||||
| [@yoviarpauzi](https://github.com/yoviarpauzi) | 1 | #2092 |
|
||||
| [@gleber](https://github.com/gleber) | 1 | #2103 |
|
||||
| [@rilham97](https://github.com/rilham97) | 1 | #2104 |
|
||||
| [@boa-z](https://github.com/boa-z) | 1 | #2115 |
|
||||
| [@rdself](https://github.com/rdself) | 1 | #2118 |
|
||||
| [@clousky2020](https://github.com/clousky2020) | 1 | #2119 |
|
||||
| [@abhinavjnu](https://github.com/abhinavjnu) | 1 | #2122 |
|
||||
| [@HoaPham98](https://github.com/HoaPham98) | 1 | #2089 |
|
||||
| [@christlau](https://github.com/christlau) | 1 | #2129 |
|
||||
| [@flyingmongoose](https://github.com/flyingmongoose) | 1 | #2134 |
|
||||
| [@05dunski](https://github.com/05dunski) | 1 | #1978 (cherry-picked) |
|
||||
| [@DavyMassoneto](https://github.com/DavyMassoneto) | 1 | #2140 |
|
||||
| [@Zhaba1337228](https://github.com/Zhaba1337228) | 1 | #2168 |
|
||||
| [@faisalill](https://github.com/faisalill) | 1 | #2166 |
|
||||
| [@Yosee11](https://github.com/Yosee11) | 1 | #2164 |
|
||||
| [@hachimed](https://github.com/hachimed) | 1 | #2162 |
|
||||
| [@JohnDoe-oss](https://github.com/JohnDoe-oss) | 1 | #2161 |
|
||||
| [@brucevoin](https://github.com/brucevoin) | 1 | #2163 |
|
||||
| [@InkshadeWoods](https://github.com/InkshadeWoods) | 1 | #2202 |
|
||||
| [@kang-heewon](https://github.com/kang-heewon) | 1 | #2231 |
|
||||
| [@one-vs](https://github.com/one-vs) | 1 | #2236 |
|
||||
|
||||
## [3.7.9] — 2026-05-03
|
||||
|
||||
@@ -1192,7 +1413,7 @@ We identified that **155 community PRs** across the entire project history (from
|
||||
- **33 New API Key Providers:** Massive provider expansion adding DeepInfra, Vercel AI Gateway, Lambda AI, SambaNova, nScale, OVHcloud AI, Baseten, PublicAI, Moonshot AI, Meta Llama API, v0 (Vercel), Morph, Featherless AI, FriendliAI, LlamaGate, Galadriel, Weights & Biases Inference, Volcengine, AI21 Labs, Venice.ai, Codestral, Upstage, Maritalk, Xiaomi MiMo, Inference.net, NanoGPT, Predibase, Bytez, Heroku AI, Databricks, Snowflake Cortex, and GigaChat (Sber). OmniRoute now supports **100+ providers** (4 Free + 8 OAuth + 91 API Key + Custom compatible)
|
||||
- **Global Email Privacy Toggle:** Added a persistent eye-icon toggle button across all dashboard pages (Providers, Usage Limits, Playground) that reveals or hides masked email addresses. Toggle state is stored in localStorage and synced globally via Zustand store
|
||||
- **Documentation Refresh:** Updated README, ARCHITECTURE, FEATURES, AGENTS.md, and API_REFERENCE for v3.6.2 with accurate provider counts (100+), new executor list, and system API documentation
|
||||
- **Uninstall Guide:** Created comprehensive `docs/UNINSTALL.md` covering clean uninstallation for all deployment methods (npm, Docker, Electron, source)
|
||||
- **Uninstall Guide:** Created comprehensive `docs/guides/UNINSTALL.md` covering clean uninstallation for all deployment methods (npm, Docker, Electron, source)
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
|
||||
115
CLAUDE.md
115
CLAUDE.md
@@ -11,7 +11,7 @@ npm run build # Production build (Next.js 16 standalone)
|
||||
npm run lint # ESLint (0 errors expected; warnings are pre-existing)
|
||||
npm run typecheck:core # TypeScript check (should be clean)
|
||||
npm run typecheck:noimplicit:core # Strict check (no implicit any)
|
||||
npm run test:coverage # Unit tests + coverage gate (60% min)
|
||||
npm run test:coverage # Unit tests + coverage gate (75/75/75/70 — statements/lines/functions/branches)
|
||||
npm run check # lint + test combined
|
||||
npm run check:cycles # Detect circular dependencies
|
||||
```
|
||||
@@ -37,20 +37,20 @@ For full test matrix, see `CONTRIBUTING.md` → "Running Tests". For deep archit
|
||||
|
||||
**OmniRoute** — unified AI proxy/router. One endpoint, 160+ LLM providers, auto-fallback.
|
||||
|
||||
| Layer | Location | Purpose |
|
||||
| ------------- | ----------------------- | ------------------------------------------ |
|
||||
| API Routes | `src/app/api/v1/` | Next.js App Router — entry points |
|
||||
| Handlers | `open-sse/handlers/` | Request processing (chat, embeddings, etc) |
|
||||
| Executors | `open-sse/executors/` | Provider-specific HTTP dispatch |
|
||||
| Translators | `open-sse/translator/` | Format conversion (OpenAI↔Claude↔Gemini) |
|
||||
| Transformer | `open-sse/transformer/` | Responses API ↔ Chat Completions |
|
||||
| Services | `open-sse/services/` | Combo routing, rate limits, caching, etc |
|
||||
| Database | `src/lib/db/` | SQLite domain modules (22 files) |
|
||||
| Domain/Policy | `src/domain/` | Policy engine, cost rules, fallback logic |
|
||||
| MCP Server | `open-sse/mcp-server/` | 29 tools, 3 transports, 10 scopes |
|
||||
| A2A Server | `src/lib/a2a/` | JSON-RPC 2.0 agent protocol |
|
||||
| Skills | `src/lib/skills/` | Extensible skill framework |
|
||||
| Memory | `src/lib/memory/` | Persistent conversational memory |
|
||||
| Layer | Location | Purpose |
|
||||
| ------------- | ----------------------- | ------------------------------------------------------------------ |
|
||||
| API Routes | `src/app/api/v1/` | Next.js App Router — entry points |
|
||||
| Handlers | `open-sse/handlers/` | Request processing (chat, embeddings, etc) |
|
||||
| Executors | `open-sse/executors/` | Provider-specific HTTP dispatch |
|
||||
| Translators | `open-sse/translator/` | Format conversion (OpenAI↔Claude↔Gemini) |
|
||||
| Transformer | `open-sse/transformer/` | Responses API ↔ Chat Completions |
|
||||
| Services | `open-sse/services/` | Combo routing, rate limits, caching, etc |
|
||||
| Database | `src/lib/db/` | SQLite domain modules (45+ files, 55 migrations) |
|
||||
| Domain/Policy | `src/domain/` | Policy engine, cost rules, fallback logic |
|
||||
| MCP Server | `open-sse/mcp-server/` | 37 tools (30 base + 3 memory + 4 skills), 3 transports, ~13 scopes |
|
||||
| A2A Server | `src/lib/a2a/` | JSON-RPC 2.0 agent protocol |
|
||||
| Skills | `src/lib/skills/` | Extensible skill framework |
|
||||
| Memory | `src/lib/memory/` | Persistent conversational memory |
|
||||
|
||||
Monorepo: `src/` (Next.js 16 app), `open-sse/` (streaming engine workspace), `electron/` (desktop app), `tests/`, `bin/` (CLI entry point).
|
||||
|
||||
@@ -72,14 +72,17 @@ Client → /v1/chat/completions (Next.js route)
|
||||
|
||||
API routes follow a consistent pattern: `Route → CORS preflight → Zod body validation → Optional auth (extractApiKey/isValidApiKey) → API key policy enforcement → Handler delegation (open-sse)`. No global Next.js middleware — interception is route-specific.
|
||||
|
||||
**Combo routing** (`open-sse/services/combo.ts`): 13 strategies (priority, weighted, fill-first, round-robin, P2C, random, least-used, cost-optimized, strict-random, auto, lkgp, context-optimized, context-relay). Each target calls `handleSingleModel()` which wraps `handleChatCore()` with per-target error handling and circuit breaker checks.
|
||||
**Combo routing** (`open-sse/services/combo.ts`): 14 strategies (priority, weighted, fill-first, round-robin, P2C, random, least-used, cost-optimized, reset-aware, strict-random, auto, lkgp, context-optimized, context-relay). Each target calls `handleSingleModel()` which wraps `handleChatCore()` with per-target error handling and circuit breaker checks. See `docs/routing/AUTO-COMBO.md` for the 9-factor Auto-Combo scoring and `docs/architecture/RESILIENCE_GUIDE.md` for the 3 resilience layers.
|
||||
|
||||
---
|
||||
|
||||
## Resilience Runtime State
|
||||
|
||||
OmniRoute has three related but distinct temporary-failure mechanisms. Keep their
|
||||
scope separate when debugging routing behavior.
|
||||
scope separate when debugging routing behavior. See the
|
||||
[3-layer resilience diagram](./docs/diagrams/exported/resilience-3layers.svg)
|
||||
(source: [docs/diagrams/resilience-3layers.mmd](./docs/diagrams/resilience-3layers.mmd))
|
||||
for an at-a-glance map.
|
||||
|
||||
### Provider Circuit Breaker
|
||||
|
||||
@@ -280,31 +283,78 @@ connection continue serving other models.
|
||||
|
||||
### Adding a New A2A Skill
|
||||
|
||||
1. Create skill in `src/lib/a2a/skills/`
|
||||
1. Create skill in `src/lib/a2a/skills/` (5 already exist: smart-routing, quota-management, provider-discovery, cost-analysis, health-report)
|
||||
2. Skill receives task context (messages, metadata) → returns structured result
|
||||
3. Register in the DB-backed skill registry
|
||||
4. Write tests
|
||||
3. Register in `A2A_SKILL_HANDLERS` in `src/lib/a2a/taskExecution.ts`
|
||||
4. Expose in `src/app/.well-known/agent.json/route.ts` (Agent Card)
|
||||
5. Write tests in `tests/unit/`
|
||||
6. Document in `docs/frameworks/A2A-SERVER.md` skill table
|
||||
|
||||
### Adding a New Cloud Agent
|
||||
|
||||
1. Create agent class in `src/lib/cloudAgent/agents/` extending `CloudAgentBase` (3 already exist: codex-cloud, devin, jules)
|
||||
2. Implement `createTask`, `getStatus`, `approvePlan`, `sendMessage`, `listSources`
|
||||
3. Register in `src/lib/cloudAgent/registry.ts`
|
||||
4. Add OAuth/credentials handling if needed (`src/lib/oauth/providers/`)
|
||||
5. Tests + document in `docs/frameworks/CLOUD_AGENT.md`
|
||||
|
||||
### Adding a New Guardrail / Eval / Skill / Webhook event
|
||||
|
||||
- Guardrail: `src/lib/guardrails/` → docs: `docs/security/GUARDRAILS.md`
|
||||
- Eval suite: `src/lib/evals/` → docs: `docs/frameworks/EVALS.md`
|
||||
- Skill (sandbox): `src/lib/skills/` → docs: `docs/frameworks/SKILLS.md`
|
||||
- Webhook event: `src/lib/webhookDispatcher.ts` → docs: `docs/frameworks/WEBHOOKS.md`
|
||||
|
||||
---
|
||||
|
||||
## Reference Documentation
|
||||
|
||||
For any non-trivial change, read the matching deep-dive first:
|
||||
|
||||
| Area | Doc |
|
||||
| -------------------------------------------- | ----------------------------------------------------------------- |
|
||||
| Repo navigation | `docs/architecture/REPOSITORY_MAP.md` |
|
||||
| Architecture | `docs/architecture/ARCHITECTURE.md` |
|
||||
| Engineering reference | `docs/architecture/CODEBASE_DOCUMENTATION.md` |
|
||||
| Auto-Combo (9-factor scoring, 14 strategies) | `docs/routing/AUTO-COMBO.md` |
|
||||
| Resilience (3 mechanisms) | `docs/architecture/RESILIENCE_GUIDE.md` |
|
||||
| Reasoning replay | `docs/routing/REASONING_REPLAY.md` |
|
||||
| Skills framework | `docs/frameworks/SKILLS.md` |
|
||||
| Memory system (FTS5 + Qdrant) | `docs/frameworks/MEMORY.md` |
|
||||
| Cloud agents | `docs/frameworks/CLOUD_AGENT.md` |
|
||||
| Guardrails (PII / injection / vision) | `docs/security/GUARDRAILS.md` |
|
||||
| Evals | `docs/frameworks/EVALS.md` |
|
||||
| Compliance / audit | `docs/security/COMPLIANCE.md` |
|
||||
| Webhooks | `docs/frameworks/WEBHOOKS.md` |
|
||||
| Authorization pipeline | `docs/architecture/AUTHZ_GUIDE.md` |
|
||||
| Stealth (TLS / fingerprint) | `docs/security/STEALTH_GUIDE.md` |
|
||||
| Agent protocols (A2A / ACP / Cloud) | `docs/frameworks/AGENT_PROTOCOLS_GUIDE.md` |
|
||||
| MCP server | `docs/frameworks/MCP-SERVER.md` |
|
||||
| A2A server | `docs/frameworks/A2A-SERVER.md` |
|
||||
| API reference + OpenAPI | `docs/reference/API_REFERENCE.md` + `docs/reference/openapi.yaml` |
|
||||
| Provider catalog (auto-generated) | `docs/reference/PROVIDER_REFERENCE.md` |
|
||||
| Release flow | `docs/ops/RELEASE_CHECKLIST.md` |
|
||||
|
||||
---
|
||||
|
||||
## Testing
|
||||
|
||||
| What | Command |
|
||||
| ----------------------- | ------------------------------------------------------ |
|
||||
| Unit tests | `npm run test:unit` |
|
||||
| Single file | `node --import tsx/esm --test tests/unit/file.test.ts` |
|
||||
| Vitest (MCP, autoCombo) | `npm run test:vitest` |
|
||||
| E2E (Playwright) | `npm run test:e2e` |
|
||||
| Protocol E2E (MCP+A2A) | `npm run test:protocols:e2e` |
|
||||
| Ecosystem | `npm run test:ecosystem` |
|
||||
| Coverage gate | `npm run test:coverage` (60% min all metrics) |
|
||||
| Coverage report | `npm run coverage:report` |
|
||||
| What | Command |
|
||||
| ----------------------- | --------------------------------------------------------------------------- |
|
||||
| Unit tests | `npm run test:unit` |
|
||||
| Single file | `node --import tsx/esm --test tests/unit/file.test.ts` |
|
||||
| Vitest (MCP, autoCombo) | `npm run test:vitest` |
|
||||
| E2E (Playwright) | `npm run test:e2e` |
|
||||
| Protocol E2E (MCP+A2A) | `npm run test:protocols:e2e` |
|
||||
| Ecosystem | `npm run test:ecosystem` |
|
||||
| Coverage gate | `npm run test:coverage` (75/75/75/70 — statements/lines/functions/branches) |
|
||||
| Coverage report | `npm run coverage:report` |
|
||||
|
||||
**PR rule**: If you change production code in `src/`, `open-sse/`, `electron/`, or `bin/`, you must include or update tests in the same PR.
|
||||
|
||||
**Test layer preference**: unit first → integration (multi-module or DB state) → e2e (UI/workflow only). Encode bug reproductions as automated tests before or alongside the fix.
|
||||
|
||||
**Copilot coverage policy**: When a PR changes production code and coverage is below 60%, do not just report — add or update tests, rerun the coverage gate, then ask for confirmation. Include commands run, changed test files, and final coverage result in the PR report.
|
||||
**Copilot coverage policy**: When a PR changes production code and coverage is below 75% (statements/lines/functions) or 70% (branches), do not just report — add or update tests, rerun the coverage gate, then ask for confirmation. Include commands run, changed test files, and final coverage result in the PR report.
|
||||
|
||||
---
|
||||
|
||||
@@ -350,4 +400,5 @@ git push -u origin feat/your-feature
|
||||
6. Never silently swallow errors in SSE streams
|
||||
7. Always validate inputs with Zod schemas
|
||||
8. Always include tests when changing production code
|
||||
9. Coverage must stay ≥60% (statements, lines, functions, branches)
|
||||
9. Coverage must stay ≥75% (statements, lines, functions) / ≥70% (branches). Current measured: ~82%.
|
||||
10. Never bypass Husky hooks (`--no-verify`, `--no-gpg-sign`) without explicit operator approval.
|
||||
|
||||
@@ -17,23 +17,23 @@ diverse, inclusive, and healthy community.
|
||||
Examples of behavior that contributes to a positive environment for our
|
||||
community include:
|
||||
|
||||
* Demonstrating empathy and kindness toward other people
|
||||
* Being respectful of differing opinions, viewpoints, and experiences
|
||||
* Giving and gracefully accepting constructive feedback
|
||||
* Accepting responsibility and apologizing to those affected by our mistakes,
|
||||
- Demonstrating empathy and kindness toward other people
|
||||
- Being respectful of differing opinions, viewpoints, and experiences
|
||||
- Giving and gracefully accepting constructive feedback
|
||||
- Accepting responsibility and apologizing to those affected by our mistakes,
|
||||
and learning from the experience
|
||||
* Focusing on what is best not just for us as individuals, but for the
|
||||
- Focusing on what is best not just for us as individuals, but for the
|
||||
overall community
|
||||
|
||||
Examples of unacceptable behavior include:
|
||||
|
||||
* The use of sexualized language or imagery, and sexual attention or
|
||||
- The use of sexualized language or imagery, and sexual attention or
|
||||
advances of any kind
|
||||
* Trolling, insulting or derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or email
|
||||
- Trolling, insulting or derogatory comments, and personal or political attacks
|
||||
- Public or private harassment
|
||||
- Publishing others' private information, such as a physical or email
|
||||
address, without their explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
- Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Enforcement Responsibilities
|
||||
@@ -59,8 +59,11 @@ representative at an online or offline event.
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported to the community leaders responsible for enforcement at
|
||||
.
|
||||
reported to the community leaders responsible for enforcement by opening a
|
||||
private security advisory at
|
||||
<https://github.com/diegosouzapw/OmniRoute/security/advisories/new>
|
||||
or by emailing the maintainer at diegosouza.pw@outlook.com.
|
||||
For security-sensitive incidents, see [`SECURITY.md`](SECURITY.md).
|
||||
All complaints will be reviewed and investigated promptly and fairly.
|
||||
|
||||
All community leaders are obligated to respect the privacy and security of the
|
||||
@@ -106,7 +109,7 @@ Violating these terms may lead to a permanent ban.
|
||||
### 4. Permanent Ban
|
||||
|
||||
**Community Impact**: Demonstrating a pattern of violation of community
|
||||
standards, including sustained inappropriate behavior, harassment of an
|
||||
standards, including sustained inappropriate behavior, harassment of an
|
||||
individual, or aggression toward or disparagement of classes of individuals.
|
||||
|
||||
**Consequence**: A permanent ban from any sort of public interaction within
|
||||
@@ -115,8 +118,8 @@ the community.
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
||||
version 2.0, available at
|
||||
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
|
||||
version 2.1, available at
|
||||
https://www.contributor-covenant.org/version/2/1/code_of_conduct.html.
|
||||
|
||||
Community Impact Guidelines were inspired by [Mozilla's code of conduct
|
||||
enforcement ladder](https://github.com/mozilla/diversity).
|
||||
|
||||
@@ -108,7 +108,7 @@ test: add observability unit tests
|
||||
refactor(db): consolidate rate limit tables
|
||||
```
|
||||
|
||||
Scopes: `db`, `sse`, `oauth`, `dashboard`, `api`, `cli`, `docker`, `ci`, `mcp`, `a2a`, `memory`, `skills`.
|
||||
Scopes (v3.8): `db`, `sse`, `oauth`, `dashboard`, `api`, `cli`, `docker`, `ci`, `mcp`, `a2a`, `memory`, `skills`, `cloud-agent`, `guardrails`, `compression`, `auto-combo`, `resilience`, `providers`, `executors`, `translator`, `domain`, `authz`.
|
||||
|
||||
---
|
||||
|
||||
@@ -149,7 +149,7 @@ Coverage notes:
|
||||
- If a PR changes production code in `src/`, `open-sse/`, `electron/`, or `bin/`, it must add or update automated tests in the same PR
|
||||
- `npm run coverage:report` prints the detailed file-by-file report from the latest coverage run
|
||||
- `npm run test:coverage:legacy` preserves the older metric for historical comparison
|
||||
- See `docs/COVERAGE_PLAN.md` for the phased coverage improvement roadmap
|
||||
- See `docs/ops/COVERAGE_PLAN.md` for the phased coverage improvement roadmap
|
||||
|
||||
### Pull Request Requirements
|
||||
|
||||
@@ -208,7 +208,7 @@ src/ # TypeScript (.ts / .tsx)
|
||||
├── mitm/ # MITM proxy (cert, DNS, target routing)
|
||||
├── shared/
|
||||
│ ├── components/ # React components (.tsx)
|
||||
│ ├── constants/ # Provider definitions (60+), MCP scopes, routing strategies
|
||||
│ ├── constants/ # Provider definitions (177), MCP scopes, 14 routing strategies
|
||||
│ ├── utils/ # Circuit breaker, sanitizer, auth helpers
|
||||
│ └── validation/ # Zod v4 schemas
|
||||
└── sse/ # SSE proxy pipeline
|
||||
@@ -301,7 +301,7 @@ Releases are managed via the `/generate-release` workflow. When a new GitHub Rel
|
||||
|
||||
## Getting Help
|
||||
|
||||
- **Architecture**: See [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md)
|
||||
- **API Reference**: See [`docs/API_REFERENCE.md`](docs/API_REFERENCE.md)
|
||||
- **Architecture**: See [`docs/architecture/ARCHITECTURE.md`](docs/architecture/ARCHITECTURE.md)
|
||||
- **API Reference**: See [`docs/reference/API_REFERENCE.md`](docs/reference/API_REFERENCE.md)
|
||||
- **Issues**: [github.com/diegosouzapw/OmniRoute/issues](https://github.com/diegosouzapw/OmniRoute/issues)
|
||||
- **ADRs**: See `docs/adr/` for architectural decision records
|
||||
|
||||
26
Dockerfile
26
Dockerfile
@@ -1,4 +1,4 @@
|
||||
FROM node:24.15.0-trixie-slim AS builder
|
||||
FROM node:26.1.0-trixie-slim AS builder
|
||||
WORKDIR /app
|
||||
|
||||
RUN apt-get update \
|
||||
@@ -6,9 +6,9 @@ RUN apt-get update \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
COPY package*.json ./
|
||||
COPY scripts/postinstall.mjs ./scripts/postinstall.mjs
|
||||
COPY scripts/postinstallSupport.mjs ./scripts/postinstallSupport.mjs
|
||||
COPY scripts/native-binary-compat.mjs ./scripts/native-binary-compat.mjs
|
||||
COPY scripts/build/postinstall.mjs ./scripts/build/postinstall.mjs
|
||||
COPY scripts/build/postinstallSupport.mjs ./scripts/build/postinstallSupport.mjs
|
||||
COPY scripts/build/native-binary-compat.mjs ./scripts/build/native-binary-compat.mjs
|
||||
ENV NPM_CONFIG_LEGACY_PEER_DEPS=true
|
||||
RUN if [ -f package-lock.json ]; then \
|
||||
npm ci --no-audit --no-fund --legacy-peer-deps; \
|
||||
@@ -19,7 +19,7 @@ RUN if [ -f package-lock.json ]; then \
|
||||
COPY . ./
|
||||
RUN mkdir -p /app/data && npm run build -- --webpack
|
||||
|
||||
FROM node:24.15.0-trixie-slim AS runner-base
|
||||
FROM node:26.1.0-trixie-slim AS runner-base
|
||||
WORKDIR /app
|
||||
|
||||
LABEL org.opencontainers.image.title="omniroute" \
|
||||
@@ -56,21 +56,21 @@ COPY --from=builder /app/src/lib/db/migrations ./migrations
|
||||
ENV OMNIROUTE_MIGRATIONS_DIR=/app/migrations
|
||||
# MITM server.cjs is spawned at runtime via child_process — not traced by nft
|
||||
COPY --from=builder /app/src/mitm/server.cjs ./src/mitm/server.cjs
|
||||
# OpenAPI spec is read from disk by /api/openapi/spec at runtime for the
|
||||
# Endpoints dashboard. Next.js standalone tracing does not include it.
|
||||
COPY --from=builder /app/docs/openapi.yaml ./docs/openapi.yaml
|
||||
# Documentation files and OpenAPI spec are read from disk at runtime.
|
||||
# Next.js standalone tracing does not include them.
|
||||
COPY --from=builder /app/docs ./docs
|
||||
|
||||
COPY --from=builder /app/scripts/run-standalone.mjs ./run-standalone.mjs
|
||||
COPY --from=builder /app/scripts/runtime-env.mjs ./runtime-env.mjs
|
||||
COPY --from=builder /app/scripts/bootstrap-env.mjs ./bootstrap-env.mjs
|
||||
COPY --from=builder /app/scripts/healthcheck.mjs ./healthcheck.mjs
|
||||
COPY --from=builder /app/scripts/dev/run-standalone.mjs ./dev/run-standalone.mjs
|
||||
COPY --from=builder /app/scripts/build/runtime-env.mjs ./build/runtime-env.mjs
|
||||
COPY --from=builder /app/scripts/build/bootstrap-env.mjs ./build/bootstrap-env.mjs
|
||||
COPY --from=builder /app/scripts/dev/healthcheck.mjs ./healthcheck.mjs
|
||||
|
||||
EXPOSE 20128
|
||||
|
||||
HEALTHCHECK --interval=30s --timeout=5s --start-period=15s --retries=3 \
|
||||
CMD ["node", "healthcheck.mjs"]
|
||||
|
||||
CMD ["node", "run-standalone.mjs"]
|
||||
CMD ["node", "dev/run-standalone.mjs"]
|
||||
|
||||
FROM runner-base AS runner-cli
|
||||
|
||||
|
||||
47
GEMINI.md
47
GEMINI.md
@@ -1,21 +1,50 @@
|
||||
# Security and Cleanliness Rules for AI Assistants
|
||||
|
||||
> **Scope:** rules for Gemini-based agents. For Claude Code, see `CLAUDE.md`. For other AI assistants, see `AGENTS.md`.
|
||||
|
||||
## 1. File Placement & Organization
|
||||
|
||||
- **Test Files**: ALL unit tests, integration tests, ecosystem tests, or Vitest files MUST strictly be placed within the `tests/` directory (e.g., `tests/unit/`, `tests/integration/`). NEVER create test files in the project root (`/`).
|
||||
- **Scripts and Utilities**: ALL maintenance, debugging, generation, or experimental scripts (`.cjs`, `.mjs`, `.js`, `.ts`) MUST be placed strictly inside the `scripts/` directory or `scripts/scratch/` for temporary one-offs. NEVER dump loose scripts in the project root (`/`).
|
||||
- **Scripts and Utilities**: ALL maintenance, debugging, generation, or experimental scripts (`.cjs`, `.mjs`, `.js`, `.ts`) MUST be placed strictly inside one of the `scripts/` subfolders (`build/`, `dev/`, `check/`, `docs/`, `i18n/`, `ad-hoc/`). One-shot or experimental code goes under `scripts/ad-hoc/`. NEVER dump loose scripts in the project root (`/`) or the top-level `scripts/` folder.
|
||||
|
||||
**The Project Root MUST ONLY CONTAIN:**
|
||||
|
||||
- Configuration files (`vitest.config.ts`, `next.config.mjs`, `eslint.config.mjs`, etc.)
|
||||
- Configuration files (`vitest.config.ts`, `next.config.mjs`, `eslint.config.mjs`, `tsconfig*.json`, `playwright.config.ts`, `prettier.config.mjs`, `postcss.config.mjs`, `sonar-project.properties`, `fly.toml`, `docker-compose*.yml`, `Dockerfile`)
|
||||
- Dependency files (`package.json`, `package-lock.json`)
|
||||
- Documentation files (`README.md`, `CHANGELOG.md`, `AGENTS.md`)
|
||||
- CI/CD files and ignore definitions (`.gitignore`, `.dockerignore`)
|
||||
- Documentation files (`README.md`, `CHANGELOG.md`, `LICENSE`, `AGENTS.md`, `CLAUDE.md`, `GEMINI.md`, `CONTRIBUTING.md`, `SECURITY.md`, `CODE_OF_CONDUCT.md`, `llm.txt`, `Tuto_Qdrant.md`)
|
||||
- CI/CD files and ignore definitions (`.gitignore`, `.dockerignore`, `.npmignore`, `.npmrc`, `.node-version`, `.nvmrc`, `.env.example`)
|
||||
|
||||
When creating _any_ validation tests or one-off logic scripts, default to using `scripts/scratch/` or the `tests/unit/` directories according to your goals. Do not pollute the `/` root context.
|
||||
When creating _any_ validation tests or one-off logic scripts, default to using `scripts/ad-hoc/` or the `tests/unit/` directories according to your goals. Do not pollute the `/` root context.
|
||||
|
||||
## 2. VPS Dashboard Credentials
|
||||
## 2. Hard Rules (mirror of `CLAUDE.md`)
|
||||
|
||||
| Environment | URL | Password |
|
||||
| ----------- | ------------------------- | -------- |
|
||||
| Local VPS | http://192.168.0.15:20128 | 123456 |
|
||||
1. **Never commit secrets or credentials.** Use `.env` (auto-generated from `.env.example`) or a vault. Passwords, OAuth secrets, API keys, and Cookie values must never appear in committed files.
|
||||
2. **Never add logic to `src/lib/localDb.ts`.** It is a re-export barrel only.
|
||||
3. **Never use `eval()`, `new Function()`, or any implied eval.** ESLint enforces this.
|
||||
4. **Never commit directly to `main`.** Use `feat/`, `fix/`, `refactor/`, `docs/`, `test/`, or `chore/` branches.
|
||||
5. **Never write raw SQL in routes** — always go through `src/lib/db/` domain modules.
|
||||
6. **Never silently swallow errors in SSE streams** — propagate them or abort the stream cleanly.
|
||||
7. **Never bypass Husky hooks** (`--no-verify`, `--no-gpg-sign`) without explicit operator approval.
|
||||
8. **Always validate inputs with Zod schemas** from `src/shared/validation/schemas.ts`.
|
||||
9. **Always include tests when changing production code** (`src/`, `open-sse/`, `electron/`, `bin/`).
|
||||
10. **Coverage must stay** ≥ 75 % statements / 75 % lines / 75 % functions / 70 % branches (real measured: ~82 %).
|
||||
|
||||
## 3. Codebase navigation
|
||||
|
||||
| Task | Read this first |
|
||||
| ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| Understand the codebase | `docs/architecture/REPOSITORY_MAP.md` |
|
||||
| Architecture overview | `docs/architecture/ARCHITECTURE.md` |
|
||||
| Engineering reference | `docs/architecture/CODEBASE_DOCUMENTATION.md` |
|
||||
| Add a feature | `CONTRIBUTING.md` + the matching `docs/<area>.md` |
|
||||
| Per-area deep dives | `docs/frameworks/SKILLS.md`, `docs/frameworks/MEMORY.md`, `docs/frameworks/EVALS.md`, `docs/security/GUARDRAILS.md`, `docs/security/COMPLIANCE.md`, `docs/frameworks/CLOUD_AGENT.md`, `docs/frameworks/MCP-SERVER.md`, `docs/frameworks/A2A-SERVER.md`, `docs/architecture/AUTHZ_GUIDE.md`, `docs/architecture/RESILIENCE_GUIDE.md`, `docs/routing/AUTO-COMBO.md`, `docs/frameworks/WEBHOOKS.md`, `docs/routing/REASONING_REPLAY.md`, `docs/security/STEALTH_GUIDE.md`, `docs/ops/TUNNELS_GUIDE.md`, `docs/guides/ELECTRON_GUIDE.md`, `docs/reference/PROVIDER_REFERENCE.md` |
|
||||
| Release flow | `docs/ops/RELEASE_CHECKLIST.md` |
|
||||
|
||||
## 4. Local development access
|
||||
|
||||
The dashboard is reachable at the operator's chosen URL/port (default `http://localhost:20128`). Credentials are operator-specific:
|
||||
|
||||
- **Initial admin password** is read from the `INITIAL_PASSWORD` env var on first install (defaults to `CHANGEME` in `.env.example`; rotate immediately after first login).
|
||||
- **Local VPS / shared dev environments**: ask the operator for the URL and current credentials — they live in their personal vault, NOT in this repo.
|
||||
|
||||
> Any credential observed in a previous version of this file was a non-production demo value; treat it as compromised and do not reuse it.
|
||||
|
||||
42
README.md
42
README.md
@@ -6,7 +6,11 @@
|
||||
|
||||
_The most complete open-source AI proxy — **one endpoint**, **160+ providers**, **13 routing strategies**, zero downtime. Multi-platform: **Web**, **Desktop (Electron)**, **Mobile (PWA + Termux)**. Fully extensible via **MCP Server (37 tools)**, **A2A Protocol**, and **Memory/Skills** systems. Available in **40+ languages**._
|
||||
|
||||
**Chat Completions • Responses API • Embeddings • Image Generation • Video • Music • Audio Speech/Transcription • Reranking • Moderations • Web Search • MCP Server • A2A Protocol • 4,600+ Tests • 100% TypeScript**
|
||||
[](https://www.npmjs.com/package/omniroute)
|
||||
[](LICENSE)
|
||||
[](package.json)
|
||||
[](https://github.com/diegosouzapw/OmniRoute)
|
||||
[](https://trendshift.io/repositories/23589)
|
||||
|
||||
<br/>
|
||||
|
||||
@@ -1031,6 +1035,34 @@ Combo: "my-coding-stack" Format Translation:
|
||||
|
||||
## 🎯 Use Cases — Ready-Made Combo Playbooks
|
||||
|
||||
### Case 0: "I want zero-config, auto-routing NOW"
|
||||
|
||||
**Problem:** Don't want to create combos manually. Just want AI routing to work immediately.
|
||||
|
||||
```bash
|
||||
# No combo creation needed! Use auto/ prefix directly:
|
||||
model: "auto" # Default LKGP routing across all connected providers
|
||||
model: "auto/coding" # Quality-first weights for code generation
|
||||
model: "auto/fast" # Low-latency routing (fastest provider first)
|
||||
model: "auto/cheap" # Cost-optimized (cheapest per token)
|
||||
model: "auto/offline" # High availability (most quota available)
|
||||
model: "auto/smart" # Best discovery (10% exploration rate)
|
||||
```
|
||||
|
||||
**How it works:**
|
||||
|
||||
1. Add providers in Dashboard → Providers (OAuth or API key)
|
||||
2. Use `auto/` prefix in any AI tool — **no combo creation needed**
|
||||
3. OmniRoute dynamically builds a virtual combo from your active connections
|
||||
4. Routes using LKGP (Last Known Good Provider) + 6-factor scoring
|
||||
5. Session stickiness ensures consistent provider selection
|
||||
|
||||
**Dashboard indicator:** A blue banner at the top shows "Auto-Routing Active" with a link to `/dashboard/combos` for configuration.
|
||||
|
||||
**Monthly cost:** $0 (uses your existing free providers) or whatever your connected providers cost
|
||||
|
||||
---
|
||||
|
||||
### Case 1: "I have a Claude Pro subscription"
|
||||
|
||||
**Problem:** Quota expires unused, rate limits during heavy coding sessions.
|
||||
@@ -1514,8 +1546,10 @@ MIT License - see [LICENSE](LICENSE) for details.
|
||||
---
|
||||
|
||||
<div align="center">
|
||||
<sub>Built with ❤️ for developers who code 24/7</sub>
|
||||
<br/>
|
||||
<sub><a href="https://omniroute.online">omniroute.online</a></sub>
|
||||
|
||||
**[⬆ Back to top](#-omniroute)** · Built with ❤️ for the open-source AI community.
|
||||
|
||||
<sub>OmniRoute v3.8.0 · Node ≥22.22.2 · MIT License · <a href="https://omniroute.online">omniroute.online</a></sub>
|
||||
|
||||
</div>
|
||||
<!-- GitHub Discussions enabled for community Q&A -->
|
||||
|
||||
69
SECURITY.md
69
SECURITY.md
@@ -20,9 +20,9 @@ If you discover a security vulnerability in OmniRoute, please report it responsi
|
||||
|
||||
| Version | Support Status |
|
||||
| ------- | -------------- |
|
||||
| 3.6.x | ✅ Active |
|
||||
| 3.5.x | ✅ Security |
|
||||
| < 3.5.0 | ❌ Unsupported |
|
||||
| 3.8.x | ✅ Active |
|
||||
| 3.7.x | ✅ Security |
|
||||
| < 3.7.0 | ❌ Unsupported |
|
||||
|
||||
---
|
||||
|
||||
@@ -31,19 +31,22 @@ If you discover a security vulnerability in OmniRoute, please report it responsi
|
||||
OmniRoute implements a multi-layered security model:
|
||||
|
||||
```
|
||||
Request → CORS → API Key Auth → Prompt Injection Guard → Input Sanitizer → Rate Limiter → Circuit Breaker → Provider
|
||||
Request → CORS → Authz pipeline (classify → policies → enforce)
|
||||
→ Guardrails (PII masker, prompt injection, vision bridge)
|
||||
→ Rate Limiter → Circuit Breaker → Cooldown → Model Lockout → Provider
|
||||
```
|
||||
|
||||
### 🔐 Authentication & Authorization
|
||||
|
||||
| Feature | Implementation |
|
||||
| -------------------- | ---------------------------------------------------------- |
|
||||
| **Dashboard Login** | Password-based auth with JWT tokens (HttpOnly cookies) |
|
||||
| **API Key Auth** | HMAC-signed keys with CRC validation |
|
||||
| **OAuth 2.0 + PKCE** | Secure provider auth (Claude, Codex, Gemini, Cursor, etc.) |
|
||||
| **Token Refresh** | Automatic OAuth token refresh before expiry |
|
||||
| **Secure Cookies** | `AUTH_COOKIE_SECURE=true` for HTTPS environments |
|
||||
| **MCP Scopes** | 10 granular scopes for MCP tool access control |
|
||||
| Feature | Implementation |
|
||||
| -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **Dashboard Login** | Password-based auth with JWT tokens (HttpOnly cookies) |
|
||||
| **API Key Auth** | HMAC-signed keys with CRC validation |
|
||||
| **OAuth 2.0 + PKCE** | 14 providers (Claude, Codex, GitHub, Cursor, Antigravity, Gemini, Kimi Coding, Kilo Code, Cline, Qwen, Kiro, Qoder, Windsurf, GitLab Duo) |
|
||||
| **Token Refresh** | Automatic OAuth token refresh before expiry |
|
||||
| **Secure Cookies** | `AUTH_COOKIE_SECURE=true` for HTTPS environments |
|
||||
| **Authz Pipeline** | Route classification (PUBLIC / CLIENT_API / MANAGEMENT) — see `docs/architecture/AUTHZ_GUIDE.md` |
|
||||
| **MCP Scopes** | ~13 granular scopes (read:health, write:combos, execute:completions, etc.) — see `docs/frameworks/MCP-SERVER.md` |
|
||||
|
||||
### 🛡️ Encryption at Rest
|
||||
|
||||
@@ -58,6 +61,18 @@ All sensitive data stored in SQLite is encrypted using **AES-256-GCM** with scry
|
||||
STORAGE_ENCRYPTION_KEY=$(openssl rand -hex 32)
|
||||
```
|
||||
|
||||
### 🛡️ Guardrails Framework
|
||||
|
||||
OmniRoute ships a hot-reloadable **guardrails registry** (`src/lib/guardrails/`) with 3 built-in guardrails ordered by priority:
|
||||
|
||||
| Guardrail | Priority | Purpose |
|
||||
| ------------------ | -------- | --------------------------------------------------------------------------------------- |
|
||||
| `vision-bridge` | 5 | Bridges non-vision models with image-aware descriptions; SSRF protection for image URLs |
|
||||
| `pii-masker` | 10 | Pre+post call PII redaction (emails, phone, CPF, CNPJ, credit cards, SSN) |
|
||||
| `prompt-injection` | 20 | Detects override/role-hijack/jailbreak/leak patterns |
|
||||
|
||||
Custom guardrails register via `registerGuardrail(new MyGuardrail())`. The model is fail-open (exceptions never block traffic). Per-request opt-out via `x-omniroute-disabled-guardrails` header. → See [`docs/security/GUARDRAILS.md`](docs/security/GUARDRAILS.md).
|
||||
|
||||
### 🧠 Prompt Injection Guard
|
||||
|
||||
Middleware that detects and blocks prompt injection attacks in LLM requests:
|
||||
@@ -168,8 +183,30 @@ docker run -d \
|
||||
|
||||
## Dependencies
|
||||
|
||||
- Run `npm audit` regularly
|
||||
- Run `npm audit` regularly (`npm run audit:deps` covers main + electron)
|
||||
- Keep dependencies updated
|
||||
- The project uses `husky` + `lint-staged` for pre-commit checks
|
||||
- CI pipeline runs ESLint security rules on every push
|
||||
- Provider constants validated at module load via Zod (`src/shared/validation/providerSchema.ts`)
|
||||
- The project uses `husky` + `lint-staged` for pre-commit checks (lint-staged + check-docs-sync + check:any-budget:t11)
|
||||
- CI pipeline runs ESLint security rules on every push (`no-eval`, `no-implied-eval`, `no-new-func` = error)
|
||||
- Provider constants validated at module load via Zod (`src/shared/validation/schemas.ts`)
|
||||
- Secure-by-default libraries used: `dompurify` / `isomorphic-dompurify` (XSS), `jose` (JWT), `better-sqlite3` (no SQLi risk via parameterized queries), `bcryptjs` (password hashing)
|
||||
|
||||
## Hard Security Rules
|
||||
|
||||
These rules are enforced by tooling and reviewers:
|
||||
|
||||
1. **Never commit secrets** — `.env` is gitignored; `.env.example` is the template
|
||||
2. **Never use `eval()`, `new Function()`, or implied eval** — ESLint enforces
|
||||
3. **Never bypass Husky hooks** (`--no-verify`, `--no-gpg-sign`) without explicit operator approval
|
||||
4. **Never write raw SQL in routes** — always go through `src/lib/db/` (parameterized)
|
||||
5. **Always validate inputs with Zod** — `src/shared/validation/schemas.ts`
|
||||
6. **Always sanitize upstream headers** — denylist in `src/shared/constants/upstreamHeaders.ts`
|
||||
7. **Encrypt credentials at rest** — AES-256-GCM via `src/lib/db/encryption.ts`
|
||||
|
||||
## References
|
||||
|
||||
- [`docs/architecture/AUTHZ_GUIDE.md`](docs/architecture/AUTHZ_GUIDE.md) — authorization pipeline
|
||||
- [`docs/security/GUARDRAILS.md`](docs/security/GUARDRAILS.md) — guardrails framework
|
||||
- [`docs/security/COMPLIANCE.md`](docs/security/COMPLIANCE.md) — audit log and retention
|
||||
- [`docs/architecture/RESILIENCE_GUIDE.md`](docs/architecture/RESILIENCE_GUIDE.md) — circuit breaker + cooldown + lockout
|
||||
- [`docs/security/STEALTH_GUIDE.md`](docs/security/STEALTH_GUIDE.md) — TLS fingerprinting (legal/ethical notice)
|
||||
- [`CLAUDE.md`](CLAUDE.md) — hard rules for AI agents
|
||||
|
||||
@@ -1,9 +1,19 @@
|
||||
# Tutorial Qdrant no OmniRoute (Guia para vídeo)
|
||||
|
||||
> ⚠️ **Status (v3.8.0):** Integração Qdrant está **dormente** no pipeline. As funções de upsert/search/delete existem em `src/lib/memory/qdrant.ts` e a UI de configuração está pronta (`MemorySkillsTab.tsx` + endpoint `/api/settings/qdrant/embedding-models`), mas:
|
||||
>
|
||||
> - `upsertSemanticMemoryPoint`, `searchSemanticMemory` e `deleteSemanticMemoryPoint` **não são chamadas** pelo pipeline de chat — busca semântica corrente usa apenas o store local em SQLite (ver `docs/frameworks/MEMORY.md`).
|
||||
> - As rotas `/api/settings/qdrant/health`, `/api/settings/qdrant/search` e `/api/settings/qdrant/cleanup` mencionadas neste tutorial **ainda não foram implementadas**.
|
||||
> - Os botões "Testar conexão" e "Teste de busca" no painel exigem que essas rotas existam; até lá, são placeholders.
|
||||
>
|
||||
> Este documento descreve a UX/configuração planejada. Para o sistema de memória ativo hoje, consulte [`docs/frameworks/MEMORY.md`](docs/frameworks/MEMORY.md). Acompanhe o status da ativação em issues marcadas com `area:qdrant`.
|
||||
|
||||
## 1) O que é o Qdrant no OmniRoute
|
||||
|
||||
O Qdrant é o banco vetorial usado para memória semântica.
|
||||
|
||||
No OmniRoute, ele ajuda a:
|
||||
|
||||
- Encontrar contexto por significado (não só palavra exata).
|
||||
- Reaproveitar memórias antigas com mais precisão.
|
||||
- Melhorar respostas com base em histórico relevante.
|
||||
@@ -12,38 +22,48 @@ No OmniRoute, ele ajuda a:
|
||||
---
|
||||
|
||||
## 2) Quando o OmniRoute envia dados para o Qdrant
|
||||
|
||||
Com Qdrant habilitado e modelo de embedding configurado, o sistema envia vetores quando:
|
||||
|
||||
- Memórias são salvas (upsert de memória).
|
||||
- Fluxos de chat recuperam contexto semântico/híbrido.
|
||||
- Testes de busca no painel geram embedding e consultam a coleção.
|
||||
|
||||
Resumo prático:
|
||||
|
||||
- Sem Qdrant: busca mais limitada (texto/chave).
|
||||
- Com Qdrant: busca por similaridade semântica (mais inteligente).
|
||||
|
||||
---
|
||||
|
||||
## 3) Pré-requisitos
|
||||
|
||||
Você precisa de:
|
||||
|
||||
- Instância Qdrant acessível (porta 6333).
|
||||
- Coleção criada (ex.: `omniroute_memory`).
|
||||
- Modelo de embedding válido (ex.: OpenRouter).
|
||||
- Credencial do provider do embedding configurada no OmniRoute.
|
||||
|
||||
Exemplo de modelo OpenRouter:
|
||||
|
||||
- `openrouter/nvidia/llama-nemotron-embed-v1-1b-v2:free`
|
||||
|
||||
Importante:
|
||||
|
||||
- O texto do modelo deve estar em formato `provider/model`.
|
||||
- Se usar modelo com dimensão diferente da coleção, a busca falha.
|
||||
|
||||
---
|
||||
|
||||
## 4) Como configurar no painel do OmniRoute
|
||||
|
||||
No menu:
|
||||
|
||||
- `Admin > Settings > Qdrant (Memória vetorial)`
|
||||
|
||||
Preencha:
|
||||
|
||||
- `Ativar Qdrant`: ligado.
|
||||
- `Host`: IP ou URL do servidor Qdrant (sem porta no campo Host).
|
||||
- `Porta`: `6333`.
|
||||
@@ -52,6 +72,7 @@ Preencha:
|
||||
- `API Key`: opcional (preencha se seu Qdrant exigir).
|
||||
|
||||
Depois:
|
||||
|
||||
1. Clique em `Salvar`.
|
||||
2. Clique em `Testar conexão`.
|
||||
3. No `Teste de busca`, digite um texto e clique em `Buscar`.
|
||||
@@ -59,7 +80,9 @@ Depois:
|
||||
---
|
||||
|
||||
## 5) Como criar a coleção no Dashboard do Qdrant (sem comando)
|
||||
|
||||
No Qdrant Dashboard:
|
||||
|
||||
1. Clique em `Create collection`.
|
||||
2. Escolha `Global search`.
|
||||
3. Em tipo de busca, use `Custom`.
|
||||
@@ -70,21 +93,26 @@ No Qdrant Dashboard:
|
||||
5. Salve a coleção com nome `omniroute_memory`.
|
||||
|
||||
Se já tinha coleção com dimensão errada:
|
||||
|
||||
- Recrie a coleção com dimensão correta.
|
||||
|
||||
---
|
||||
|
||||
## 6) Como validar se está funcionando
|
||||
|
||||
Checklist rápido:
|
||||
|
||||
1. `Testar conexão` no OmniRoute retorna OK.
|
||||
2. Busca no painel retorna resultados (não “Sem resultados”).
|
||||
3. No Qdrant Dashboard, aparecem pontos na coleção (payload + vector).
|
||||
4. Resultados de chat passam a recuperar contexto mais relevante.
|
||||
|
||||
Sinal clássico de problema:
|
||||
|
||||
- Dados entram no Qdrant, mas busca do painel não retorna nada.
|
||||
|
||||
Causas comuns:
|
||||
|
||||
- Dimensão do vetor incompatível.
|
||||
- Nome do vetor diferente do esperado (`omniao`).
|
||||
- Modelo inválido/incompleto no campo de embedding.
|
||||
@@ -93,7 +121,9 @@ Causas comuns:
|
||||
---
|
||||
|
||||
## 7) O que melhorou com esta atualização
|
||||
|
||||
Nesta melhoria do OmniRoute:
|
||||
|
||||
- Suporte a embeddings de qualquer provider compatível (não só OpenAI fixo).
|
||||
- Endpoint para carregar modelos de embedding na tela de configurações.
|
||||
- Campo manual para modelo custom quando não aparecer na lista.
|
||||
@@ -102,7 +132,9 @@ Nesta melhoria do OmniRoute:
|
||||
---
|
||||
|
||||
## 8) Roteiro curto para seu vídeo
|
||||
|
||||
Sugestão de demo (3-5 minutos):
|
||||
|
||||
1. Mostrar problema sem Qdrant (busca simples).
|
||||
2. Abrir Settings e habilitar Qdrant.
|
||||
3. Configurar host/porta/collection/modelo.
|
||||
@@ -112,25 +144,33 @@ Sugestão de demo (3-5 minutos):
|
||||
7. Rodar um chat e mostrar melhoria de recuperação semântica.
|
||||
|
||||
Mensagem final para a galera:
|
||||
|
||||
- "Qdrant no OmniRoute transforma memória de palavra-chave em memória por significado."
|
||||
|
||||
---
|
||||
|
||||
## 9) Referências de código (para equipe técnica)
|
||||
- UI de configuração Qdrant:
|
||||
- `src/app/(dashboard)/dashboard/settings/components/MemorySkillsTab.tsx`
|
||||
- Endpoint de modelos de embedding para Qdrant:
|
||||
- `src/app/api/settings/qdrant/embedding-models/route.ts`
|
||||
- Integração backend com Qdrant (health, upsert, search, cleanup):
|
||||
- `src/lib/memory/qdrant.ts`
|
||||
- Recuperação de memórias no fluxo de chat:
|
||||
- `src/lib/memory/retrieval.ts`
|
||||
- `open-sse/handlers/chatCore.ts`
|
||||
|
||||
**Implementado:**
|
||||
|
||||
- UI de configuração Qdrant: `src/app/(dashboard)/dashboard/settings/components/MemorySkillsTab.tsx`
|
||||
- Endpoint de modelos de embedding: `src/app/api/settings/qdrant/embedding-models/route.ts`
|
||||
- Funções backend (definidas mas dormentes): `src/lib/memory/qdrant.ts` exporta `upsertSemanticMemoryPoint`, `searchSemanticMemory`, `deleteSemanticMemoryPoint`
|
||||
|
||||
**Pendente para ativar a integração:**
|
||||
|
||||
- Rotas API: `/api/settings/qdrant/health`, `/api/settings/qdrant/search`, `/api/settings/qdrant/cleanup`
|
||||
- Wire-up no fluxo de chat: `src/lib/memory/retrieval.ts` e `open-sse/handlers/chatCore.ts` precisam chamar `searchSemanticMemory` quando Qdrant estiver habilitado nas settings
|
||||
- Wire-up no save de memória: `src/lib/memory/extraction.ts` (ou camada equivalente) precisa chamar `upsertSemanticMemoryPoint` após persistir cada memória
|
||||
|
||||
Para o sistema de memória ativo hoje (SQLite-only), ver [`docs/frameworks/MEMORY.md`](docs/frameworks/MEMORY.md).
|
||||
|
||||
---
|
||||
|
||||
## 10) Observação importante de segurança
|
||||
|
||||
Nunca exponha em vídeo:
|
||||
|
||||
- API key completa do OpenRouter.
|
||||
- Tokens reais de produção.
|
||||
- Endpoints internos sem proteção.
|
||||
@@ -1,48 +0,0 @@
|
||||
{
|
||||
"auditReportVersion": 2,
|
||||
"vulnerabilities": {
|
||||
"follow-redirects": {
|
||||
"name": "follow-redirects",
|
||||
"severity": "moderate",
|
||||
"isDirect": false,
|
||||
"via": [
|
||||
{
|
||||
"source": 1116560,
|
||||
"name": "follow-redirects",
|
||||
"dependency": "follow-redirects",
|
||||
"title": "follow-redirects leaks Custom Authentication Headers to Cross-Domain Redirect Targets",
|
||||
"url": "https://github.com/advisories/GHSA-r4q5-vmmm-2653",
|
||||
"severity": "moderate",
|
||||
"cwe": ["CWE-200"],
|
||||
"cvss": {
|
||||
"score": 0,
|
||||
"vectorString": null
|
||||
},
|
||||
"range": "<=1.15.11"
|
||||
}
|
||||
],
|
||||
"effects": [],
|
||||
"range": "<=1.15.11",
|
||||
"nodes": ["node_modules/follow-redirects"],
|
||||
"fixAvailable": true
|
||||
}
|
||||
},
|
||||
"metadata": {
|
||||
"vulnerabilities": {
|
||||
"info": 0,
|
||||
"low": 0,
|
||||
"moderate": 1,
|
||||
"high": 0,
|
||||
"critical": 0,
|
||||
"total": 1
|
||||
},
|
||||
"dependencies": {
|
||||
"prod": 421,
|
||||
"dev": 480,
|
||||
"optional": 158,
|
||||
"peer": 480,
|
||||
"peerOptional": 0,
|
||||
"total": 1462
|
||||
}
|
||||
}
|
||||
}
|
||||
2853
bin/cli-commands.mjs
Normal file
2853
bin/cli-commands.mjs
Normal file
File diff suppressed because it is too large
Load Diff
182
bin/cli/commands/config.mjs
Normal file
182
bin/cli/commands/config.mjs
Normal file
@@ -0,0 +1,182 @@
|
||||
import { parseArgs, getStringFlag, hasFlag } from "../args.mjs";
|
||||
import { printHeading, printInfo, printSuccess, printError } from "../io.mjs";
|
||||
import { resolveDataDir } from "../data-dir.mjs";
|
||||
import path from "node:path";
|
||||
import fs from "node:fs";
|
||||
|
||||
function printConfigHelp() {
|
||||
console.log(`
|
||||
Usage:
|
||||
omniroute config list List all CLI tools and config status
|
||||
omniroute config get <tool> Show current config for a tool
|
||||
omniroute config set <tool> [options] Write config for a tool
|
||||
omniroute config validate <tool> Validate config format without writing
|
||||
|
||||
Options:
|
||||
--base-url <url> OmniRoute API base URL (default: http://localhost:20128/v1)
|
||||
--api-key <key> API key for the tool
|
||||
--model <model> Model identifier (where applicable)
|
||||
--json Output as JSON
|
||||
--non-interactive Do not prompt for confirmation
|
||||
--yes Skip confirmation prompt
|
||||
--help Show this help
|
||||
|
||||
Tools: claude, codex, opencode, cline, kilocode, continue
|
||||
`);
|
||||
}
|
||||
|
||||
function ensureBackup(configPath) {
|
||||
if (!fs.existsSync(configPath)) return;
|
||||
const backupDir = path.join(path.dirname(configPath), ".omniroute.bak");
|
||||
if (!fs.existsSync(backupDir)) fs.mkdirSync(backupDir, { recursive: true });
|
||||
const backupPath = path.join(backupDir, path.basename(configPath) + ".bak");
|
||||
fs.copyFileSync(configPath, backupPath);
|
||||
return backupPath;
|
||||
}
|
||||
|
||||
export async function runConfigCommand(argv) {
|
||||
const { flags, positionals } = parseArgs(argv);
|
||||
|
||||
if (hasFlag(flags, "help") || hasFlag(flags, "h") || positionals.length === 0) {
|
||||
printConfigHelp();
|
||||
return 0;
|
||||
}
|
||||
|
||||
const subcommand = positionals[0];
|
||||
const toolId = positionals[1];
|
||||
|
||||
if (subcommand === "list") {
|
||||
const { detectAllTools } = await import("../../../src/lib/cli-helper/tool-detector.js");
|
||||
const tools = await detectAllTools();
|
||||
|
||||
if (hasFlag(flags, "json")) {
|
||||
console.log(JSON.stringify(tools, null, 2));
|
||||
} else {
|
||||
printHeading("CLI Tool Configuration Status");
|
||||
for (const t of tools) {
|
||||
const status = t.configured
|
||||
? "✓ Configured"
|
||||
: t.installed
|
||||
? "✗ Not configured"
|
||||
: "✗ Not installed";
|
||||
console.log(` ${t.name.padEnd(14)} ${status}`);
|
||||
if (t.version) console.log(` version: ${t.version}`);
|
||||
console.log(` config: ${t.configPath}`);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (subcommand === "get") {
|
||||
if (!toolId) {
|
||||
printError("Tool ID required. Usage: omniroute config get <tool>");
|
||||
return 1;
|
||||
}
|
||||
const { detectTool } = await import("../../../src/lib/cli-helper/tool-detector.js");
|
||||
const tool = await detectTool(toolId);
|
||||
if (!tool) {
|
||||
printError(`Unknown tool: ${toolId}`);
|
||||
return 1;
|
||||
}
|
||||
if (hasFlag(flags, "json")) {
|
||||
console.log(JSON.stringify(tool, null, 2));
|
||||
} else {
|
||||
printHeading(`${tool.name} Configuration`);
|
||||
console.log(` Installed: ${tool.installed ? "Yes" : "No"}`);
|
||||
console.log(` Configured: ${tool.configured ? "Yes" : "No"}`);
|
||||
console.log(` Config: ${tool.configPath}`);
|
||||
if (tool.version) console.log(` Version: ${tool.version}`);
|
||||
if (tool.configContents) {
|
||||
console.log(`\n Contents:`);
|
||||
console.log(tool.configContents);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (subcommand === "set") {
|
||||
if (!toolId) {
|
||||
printError("Tool ID required. Usage: omniroute config set <tool> [options]");
|
||||
return 1;
|
||||
}
|
||||
|
||||
const baseUrl =
|
||||
getStringFlag(flags, "base-url", "OMNIROUTE_BASE_URL") || "http://localhost:20128/v1";
|
||||
const apiKey = getStringFlag(flags, "api-key", "OMNIROUTE_API_KEY");
|
||||
const model = getStringFlag(flags, "model");
|
||||
|
||||
if (!apiKey) {
|
||||
printError("API key required. Use --api-key or set OMNIROUTE_API_KEY.");
|
||||
return 1;
|
||||
}
|
||||
|
||||
const { generateConfig } =
|
||||
await import("../../../src/lib/cli-helper/config-generator/index.js");
|
||||
const result = await generateConfig(toolId, { baseUrl, apiKey, model });
|
||||
|
||||
if (!result.success) {
|
||||
printError(result.error || "Failed to generate config");
|
||||
return 1;
|
||||
}
|
||||
|
||||
const nonInteractive = hasFlag(flags, "non-interactive") || hasFlag(flags, "yes");
|
||||
|
||||
if (!nonInteractive) {
|
||||
console.log(`\n About to write config to: ${result.configPath}`);
|
||||
console.log(` Content preview:\n`);
|
||||
console.log(result.content);
|
||||
console.log("");
|
||||
|
||||
const readline = await import("node:readline");
|
||||
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
||||
const answer = await new Promise((resolve) => rl.question("Proceed? [y/N] ", resolve));
|
||||
rl.close();
|
||||
|
||||
if (!/^y(es)?$/i.test(answer)) {
|
||||
console.log("Aborted.");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
const dir = path.dirname(result.configPath);
|
||||
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
||||
|
||||
const backupPath = ensureBackup(result.configPath);
|
||||
if (backupPath) printInfo(`Backup saved to: ${backupPath}`);
|
||||
|
||||
fs.writeFileSync(result.configPath, result.content, "utf-8");
|
||||
printSuccess(`Config written to ${result.configPath}`);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (subcommand === "validate") {
|
||||
if (!toolId) {
|
||||
printError("Tool ID required. Usage: omniroute config validate <tool>");
|
||||
return 1;
|
||||
}
|
||||
|
||||
const baseUrl =
|
||||
getStringFlag(flags, "base-url", "OMNIROUTE_BASE_URL") || "http://localhost:20128/v1";
|
||||
const apiKey = getStringFlag(flags, "api-key", "OMNIROUTE_API_KEY") || "test-key";
|
||||
const model = getStringFlag(flags, "model");
|
||||
|
||||
const { generateConfig } =
|
||||
await import("../../../src/lib/cli-helper/config-generator/index.js");
|
||||
const result = await generateConfig(toolId, { baseUrl, apiKey, model });
|
||||
|
||||
if (!result.success) {
|
||||
printError(`Validation failed: ${result.error}`);
|
||||
return 1;
|
||||
}
|
||||
|
||||
printSuccess(`Config for ${toolId} is valid`);
|
||||
if (hasFlag(flags, "json")) {
|
||||
console.log(JSON.stringify({ valid: true, content: result.content }, null, 2));
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
printError(`Unknown subcommand: ${subcommand}`);
|
||||
printConfigHelp();
|
||||
return 1;
|
||||
}
|
||||
@@ -320,14 +320,24 @@ async function checkPorts() {
|
||||
}
|
||||
|
||||
async function checkNodeRuntime(rootDir) {
|
||||
const { getNodeRuntimeSupport } = await import(
|
||||
pathToFileURL(path.join(rootDir, "bin", "nodeRuntimeSupport.mjs")).href
|
||||
);
|
||||
const support = getNodeRuntimeSupport();
|
||||
if (!support.nodeCompatible) {
|
||||
return fail("Node runtime", `${support.nodeVersion} is outside supported policy`, support);
|
||||
try {
|
||||
const { getNodeRuntimeSupport } = await import(
|
||||
pathToFileURL(path.join(rootDir, "bin", "nodeRuntimeSupport.mjs")).href
|
||||
);
|
||||
const support = getNodeRuntimeSupport();
|
||||
if (!support.nodeCompatible) {
|
||||
return fail("Node runtime", `${support.nodeVersion} is outside supported policy`, support);
|
||||
}
|
||||
return ok("Node runtime", `${support.nodeVersion} is supported`, support);
|
||||
} catch {
|
||||
// nodeRuntimeSupport.mjs is only available in full source installs, not in Docker images
|
||||
const version = process.version;
|
||||
return warn(
|
||||
"Node runtime",
|
||||
`${version} (runtime support module unavailable in this environment)`,
|
||||
{ nodeVersion: version }
|
||||
);
|
||||
}
|
||||
return ok("Node runtime", `${support.nodeVersion} is supported`, support);
|
||||
}
|
||||
|
||||
async function checkNativeBinary(rootDir) {
|
||||
@@ -348,14 +358,21 @@ async function checkNativeBinary(rootDir) {
|
||||
return warn("Native binary", "better-sqlite3 native binary was not found", { candidates });
|
||||
}
|
||||
|
||||
const { isNativeBinaryCompatible } = await import(
|
||||
pathToFileURL(path.join(rootDir, "scripts", "native-binary-compat.mjs")).href
|
||||
);
|
||||
const compatible = isNativeBinaryCompatible(binaryPath);
|
||||
if (!compatible) {
|
||||
return fail("Native binary", "better-sqlite3 native binary is incompatible", { binaryPath });
|
||||
try {
|
||||
const { isNativeBinaryCompatible } = await import(
|
||||
pathToFileURL(path.join(rootDir, "scripts", "native-binary-compat.mjs")).href
|
||||
);
|
||||
const compatible = isNativeBinaryCompatible(binaryPath);
|
||||
if (!compatible) {
|
||||
return fail("Native binary", "better-sqlite3 native binary is incompatible", { binaryPath });
|
||||
}
|
||||
return ok("Native binary", "better-sqlite3 native binary is compatible", { binaryPath });
|
||||
} catch {
|
||||
// native-binary-compat.mjs is only available in full source installs, not in Docker images
|
||||
return warn("Native binary", "Compatibility check unavailable in this environment", {
|
||||
binaryPath,
|
||||
});
|
||||
}
|
||||
return ok("Native binary", "better-sqlite3 native binary is compatible", { binaryPath });
|
||||
}
|
||||
|
||||
function checkMemory() {
|
||||
@@ -449,6 +466,15 @@ export async function collectDoctorChecks(context = {}, options = {}) {
|
||||
checks.push(await checkServerLiveness(options));
|
||||
}
|
||||
|
||||
// CLI tool health checks
|
||||
try {
|
||||
const { collectCliToolChecks } = await import("../../../src/lib/cli-helper/doctor/checks.js");
|
||||
const cliChecks = await collectCliToolChecks();
|
||||
checks.push(...cliChecks);
|
||||
} catch (err) {
|
||||
checks.push(warn("CLI Tools", `Could not run CLI tool checks: ${err.message}`));
|
||||
}
|
||||
|
||||
return {
|
||||
dataDir,
|
||||
dbPath,
|
||||
@@ -476,7 +502,7 @@ Options:
|
||||
--liveness-url <url> Full health endpoint URL override
|
||||
|
||||
Checks:
|
||||
config, database, storage/encryption, ports, Node runtime, native binary, memory, server liveness
|
||||
config, database, storage/encryption, ports, Node runtime, native binary, memory, server liveness, CLI tools
|
||||
`);
|
||||
}
|
||||
|
||||
|
||||
83
bin/cli/commands/logs.mjs
Normal file
83
bin/cli/commands/logs.mjs
Normal file
@@ -0,0 +1,83 @@
|
||||
import { parseArgs, getStringFlag, hasFlag } from "../args.mjs";
|
||||
import { printHeading, printInfo, printError } from "../io.mjs";
|
||||
|
||||
function printLogsHelp() {
|
||||
console.log(`
|
||||
Usage:
|
||||
omniroute logs [options]
|
||||
|
||||
Options:
|
||||
--follow Stream logs in real-time
|
||||
--filter <level> Filter by level (error, warn, info) — comma-separated
|
||||
--lines <n> Number of lines to fetch (default: 100)
|
||||
--timeout <ms> Connection timeout in ms (default: 30000)
|
||||
--base-url <url> OmniRoute API base URL (default: http://localhost:20128)
|
||||
--json Output as JSON
|
||||
--help Show this help
|
||||
`);
|
||||
}
|
||||
|
||||
export async function runLogsCommand(argv) {
|
||||
const { flags } = parseArgs(argv);
|
||||
|
||||
if (hasFlag(flags, "help") || hasFlag(flags, "h")) {
|
||||
printLogsHelp();
|
||||
return 0;
|
||||
}
|
||||
|
||||
const baseUrl = getStringFlag(flags, "base-url") || "http://localhost:20128";
|
||||
const follow = hasFlag(flags, "follow");
|
||||
const filter = getStringFlag(flags, "filter");
|
||||
const lines = getStringFlag(flags, "lines") || "100";
|
||||
const timeout = parseInt(getStringFlag(flags, "timeout") || "30000", 10);
|
||||
|
||||
const filters = filter ? filter.split(",").map((f) => f.trim()) : [];
|
||||
|
||||
const { createLogStream } = await import("../../../src/lib/cli-helper/log-streamer.js");
|
||||
const { stream, stop } = createLogStream({ baseUrl, filters, follow, timeout });
|
||||
|
||||
const reader = stream.getReader();
|
||||
const decoder = new TextDecoder();
|
||||
let buffer = "";
|
||||
|
||||
const processLine = (line) => {
|
||||
if (!line.trim()) return;
|
||||
if (hasFlag(flags, "json")) {
|
||||
console.log(line);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const parsed = JSON.parse(line);
|
||||
const level = parsed.level || "info";
|
||||
const ts = parsed.timestamp || new Date().toISOString();
|
||||
const msg = parsed.message || JSON.stringify(parsed);
|
||||
const prefix =
|
||||
{ error: "\x1b[31m[ERR]", warn: "\x1b[33m[WRN]", info: "\x1b[36m[INF]" }[level] || "[INF]";
|
||||
console.log(`${prefix}\x1b[0m ${ts} ${msg}`);
|
||||
} catch {
|
||||
console.log(line);
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
while (true) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) break;
|
||||
buffer += decoder.decode(value, { stream: true });
|
||||
const lines = buffer.split("\n");
|
||||
buffer = lines.pop() || "";
|
||||
for (const line of lines) processLine(line);
|
||||
}
|
||||
if (buffer) processLine(buffer);
|
||||
} catch (err) {
|
||||
if (err.name === "AbortError") {
|
||||
printInfo("Log stream stopped.");
|
||||
} else {
|
||||
printError(`Log stream error: ${err.message}`);
|
||||
}
|
||||
} finally {
|
||||
stop();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
278
bin/cli/commands/provider-cmd.mjs
Normal file
278
bin/cli/commands/provider-cmd.mjs
Normal file
@@ -0,0 +1,278 @@
|
||||
import { parseArgs, getStringFlag, hasFlag } from "../args.mjs";
|
||||
import { printHeading, printInfo, printSuccess, printError } from "../io.mjs";
|
||||
import { resolveDataDir, resolveStoragePath } from "../data-dir.mjs";
|
||||
import path from "node:path";
|
||||
import fs from "node:fs";
|
||||
|
||||
function printProviderHelp() {
|
||||
console.log(`
|
||||
Usage:
|
||||
omniroute provider add <name> [options] Add a provider connection
|
||||
omniroute provider list List configured providers
|
||||
omniroute provider remove <name|id> Remove a provider connection
|
||||
omniroute provider test <name|id> Test a provider connection
|
||||
omniroute provider default <name|id> Set default provider
|
||||
|
||||
Options:
|
||||
--provider <id> Provider id (e.g., openai, anthropic, omniroute)
|
||||
--api-key <key> API key for the provider
|
||||
--provider-name <name> Display name for the connection
|
||||
--default-model <model> Default model to use
|
||||
--base-url <url> Custom base URL override
|
||||
--json Output as JSON
|
||||
--yes Skip confirmation
|
||||
--help Show this help
|
||||
`);
|
||||
}
|
||||
|
||||
export async function runProviderCommand(argv) {
|
||||
const { flags, positionals } = parseArgs(argv);
|
||||
|
||||
if (hasFlag(flags, "help") || hasFlag(flags, "h") || positionals.length === 0) {
|
||||
printProviderHelp();
|
||||
return 0;
|
||||
}
|
||||
|
||||
const subcommand = positionals[0];
|
||||
|
||||
if (subcommand === "add") {
|
||||
const providerName = positionals[1] || getStringFlag(flags, "provider");
|
||||
const apiKey = getStringFlag(flags, "api-key");
|
||||
const displayName = getStringFlag(flags, "provider-name");
|
||||
const defaultModel = getStringFlag(flags, "default-model");
|
||||
const baseUrl = getStringFlag(flags, "base-url");
|
||||
|
||||
if (!providerName) {
|
||||
printError("Provider name required. Usage: omniroute provider add <name>");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (providerName === "omniroute") {
|
||||
// Special case: add OmniRoute as a provider in OpenCode config
|
||||
const opencodePath = path.join(
|
||||
process.env.HOME || os.homedir(),
|
||||
".config",
|
||||
"opencode",
|
||||
"opencode.json"
|
||||
);
|
||||
const { generateConfig } =
|
||||
await import("../../../src/lib/cli-helper/config-generator/index.js");
|
||||
const result = await generateConfig("opencode", {
|
||||
baseUrl: baseUrl || "http://localhost:20128/v1",
|
||||
apiKey: apiKey || "",
|
||||
});
|
||||
|
||||
if (!result.success) {
|
||||
printError(result.error || "Failed to generate config");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!hasFlag(flags, "yes")) {
|
||||
console.log(`\n About to write OpenCode config to: ${opencodePath}`);
|
||||
console.log(` Content:\n`);
|
||||
console.log(result.content);
|
||||
console.log("");
|
||||
const readline = await import("node:readline");
|
||||
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
||||
const answer = await new Promise((resolve) => rl.question("Proceed? [y/N] ", resolve));
|
||||
rl.close();
|
||||
if (!/^y(es)?$/i.test(answer)) {
|
||||
printInfo("Aborted.");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
const dir = path.dirname(opencodePath);
|
||||
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
||||
fs.writeFileSync(opencodePath, result.content, "utf-8");
|
||||
printSuccess(`OpenCode config written to ${opencodePath}`);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Generic provider addition via SQLite
|
||||
const dbPath = resolveStoragePath(resolveDataDir());
|
||||
if (!fs.existsSync(dbPath)) {
|
||||
printError("Database not found. Run `omniroute setup` first.");
|
||||
return 1;
|
||||
}
|
||||
|
||||
const { default: Database } = await import("better-sqlite3");
|
||||
const db = new Database(dbPath);
|
||||
|
||||
try {
|
||||
const stmt = db.prepare(`
|
||||
INSERT INTO provider_connections (provider, name, api_key, default_model, provider_specific_data)
|
||||
VALUES (?, ?, ?, ?, ?)
|
||||
`);
|
||||
const specificData = baseUrl ? JSON.stringify({ baseUrl }) : null;
|
||||
stmt.run(
|
||||
providerName,
|
||||
displayName || providerName,
|
||||
apiKey || "",
|
||||
defaultModel || null,
|
||||
specificData
|
||||
);
|
||||
printSuccess(`Provider "${displayName || providerName}" added`);
|
||||
} finally {
|
||||
db.close();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (subcommand === "list") {
|
||||
const dbPath = resolveStoragePath(resolveDataDir());
|
||||
if (!fs.existsSync(dbPath)) {
|
||||
if (isJson()) console.log(JSON.stringify([]));
|
||||
else printInfo("No database found. Run `omniroute setup` first.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
const { default: Database } = await import("better-sqlite3");
|
||||
const db = new Database(dbPath);
|
||||
try {
|
||||
const rows = db
|
||||
.prepare("SELECT id, provider, name, default_model FROM provider_connections")
|
||||
.all();
|
||||
if (isJson()) {
|
||||
console.log(JSON.stringify(rows, null, 2));
|
||||
} else {
|
||||
printHeading("Configured Providers");
|
||||
for (const r of rows) {
|
||||
console.log(
|
||||
` [${r.id}] ${r.name} (${r.provider})${r.default_model ? ` — model: ${r.default_model}` : ""}`
|
||||
);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
db.close();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (subcommand === "remove") {
|
||||
const target = positionals[1];
|
||||
if (!target) {
|
||||
printError("Provider name or ID required. Usage: omniroute provider remove <name|id>");
|
||||
return 1;
|
||||
}
|
||||
|
||||
const dbPath = resolveStoragePath(resolveDataDir());
|
||||
if (!fs.existsSync(dbPath)) {
|
||||
printError("Database not found.");
|
||||
return 1;
|
||||
}
|
||||
|
||||
const { default: Database } = await import("better-sqlite3");
|
||||
const db = new Database(dbPath);
|
||||
try {
|
||||
const isId = /^\d+$/.test(target);
|
||||
const stmt = isId
|
||||
? db.prepare("DELETE FROM provider_connections WHERE id = ?")
|
||||
: db.prepare("DELETE FROM provider_connections WHERE name = ? OR provider = ?");
|
||||
const result = stmt.run(isId ? parseInt(target, 10) : target);
|
||||
if (result.changes > 0) {
|
||||
printSuccess(`Removed ${result.changes} provider(s)`);
|
||||
} else {
|
||||
printError("Provider not found");
|
||||
}
|
||||
} finally {
|
||||
db.close();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (subcommand === "test") {
|
||||
const target = positionals[1];
|
||||
if (!target) {
|
||||
printError("Provider name or ID required. Usage: omniroute provider test <name|id>");
|
||||
return 1;
|
||||
}
|
||||
|
||||
const dbPath = resolveStoragePath(resolveDataDir());
|
||||
if (!fs.existsSync(dbPath)) {
|
||||
printError("Database not found.");
|
||||
return 1;
|
||||
}
|
||||
|
||||
const { default: Database } = await import("better-sqlite3");
|
||||
const db = new Database(dbPath);
|
||||
try {
|
||||
const isId = /^\d+$/.test(target);
|
||||
const row = isId
|
||||
? db.prepare("SELECT * FROM provider_connections WHERE id = ?").get(parseInt(target, 10))
|
||||
: db
|
||||
.prepare("SELECT * FROM provider_connections WHERE name = ? OR provider = ?")
|
||||
.get(target);
|
||||
|
||||
if (!row) {
|
||||
printError("Provider not found");
|
||||
return 1;
|
||||
}
|
||||
|
||||
const { testProviderApiKey } = await import("../provider-test.mjs");
|
||||
const result = await testProviderApiKey({
|
||||
provider: row.provider,
|
||||
apiKey: row.api_key,
|
||||
defaultModel: row.default_model,
|
||||
baseUrl: row.provider_specific_data ? JSON.parse(row.provider_specific_data).baseUrl : null,
|
||||
});
|
||||
|
||||
if (isJson()) {
|
||||
console.log(JSON.stringify(result, null, 2));
|
||||
} else if (result.valid) {
|
||||
printSuccess(`Provider "${row.name}" is reachable`);
|
||||
} else {
|
||||
printError(`Provider test failed: ${result.error || "unknown error"}`);
|
||||
}
|
||||
} finally {
|
||||
db.close();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (subcommand === "default") {
|
||||
const target = positionals[1];
|
||||
if (!target) {
|
||||
printError("Provider name or ID required. Usage: omniroute provider default <name|id>");
|
||||
return 1;
|
||||
}
|
||||
|
||||
const dbPath = resolveStoragePath(resolveDataDir());
|
||||
if (!fs.existsSync(dbPath)) {
|
||||
printError("Database not found.");
|
||||
return 1;
|
||||
}
|
||||
|
||||
const { default: Database } = await import("better-sqlite3");
|
||||
const db = new Database(dbPath);
|
||||
try {
|
||||
const isId = /^\d+$/.test(target);
|
||||
const row = isId
|
||||
? db.prepare("SELECT * FROM provider_connections WHERE id = ?").get(parseInt(target, 10))
|
||||
: db
|
||||
.prepare("SELECT * FROM provider_connections WHERE name = ? OR provider = ?")
|
||||
.get(target);
|
||||
|
||||
if (!row) {
|
||||
printError("Provider not found");
|
||||
return 1;
|
||||
}
|
||||
|
||||
db.prepare("UPDATE provider_connections SET is_default = 0").run();
|
||||
db.prepare("UPDATE provider_connections SET is_default = 1 WHERE id = ?").run(row.id);
|
||||
printSuccess(`Default provider set to "${row.name}"`);
|
||||
} finally {
|
||||
db.close();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
printError(`Unknown subcommand: ${subcommand}`);
|
||||
printProviderHelp();
|
||||
return 1;
|
||||
}
|
||||
|
||||
function isJson() {
|
||||
return process.argv.includes("--json");
|
||||
}
|
||||
@@ -26,8 +26,8 @@ async function resolvePassword(flags, prompt, nonInteractive) {
|
||||
const answer = await prompt.ask("Set an admin password now? [y/N]", "N");
|
||||
if (!/^y(es)?$/i.test(answer)) return "";
|
||||
|
||||
const password = await prompt.ask("Admin password");
|
||||
const confirm = await prompt.ask("Confirm password");
|
||||
const password = await prompt.askSecret("Admin password");
|
||||
const confirm = await prompt.askSecret("Confirm password");
|
||||
if (password !== confirm) {
|
||||
throw new Error("Passwords do not match.");
|
||||
}
|
||||
|
||||
84
bin/cli/commands/status.mjs
Normal file
84
bin/cli/commands/status.mjs
Normal file
@@ -0,0 +1,84 @@
|
||||
import { parseArgs, getStringFlag, hasFlag } from "../args.mjs";
|
||||
import { printHeading, printInfo, printSuccess } from "../io.mjs";
|
||||
import { resolveDataDir, resolveStoragePath } from "../data-dir.mjs";
|
||||
import path from "node:path";
|
||||
import fs from "node:fs";
|
||||
import os from "node:os";
|
||||
|
||||
function getPackageVersion() {
|
||||
try {
|
||||
const pkg = JSON.parse(fs.readFileSync(path.join(process.cwd(), "package.json"), "utf-8"));
|
||||
return pkg.version || "unknown";
|
||||
} catch {
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
function formatBytes(bytes) {
|
||||
if (bytes < 1024) return `${bytes} B`;
|
||||
if (bytes < 1048576) return `${(bytes / 1024).toFixed(1)} KB`;
|
||||
return `${(bytes / 1048576).toFixed(1)} MB`;
|
||||
}
|
||||
|
||||
export async function runStatusCommand(argv) {
|
||||
const { flags } = parseArgs(argv);
|
||||
const isJson = hasFlag(flags, "json");
|
||||
const isVerbose = hasFlag(flags, "verbose");
|
||||
|
||||
const dataDir = resolveDataDir();
|
||||
const dbPath = resolveStoragePath(dataDir);
|
||||
const version = getPackageVersion();
|
||||
|
||||
const status = {
|
||||
version,
|
||||
dataDir,
|
||||
database: {
|
||||
exists: fs.existsSync(dbPath),
|
||||
path: dbPath,
|
||||
size: fs.existsSync(dbPath) ? formatBytes(fs.statSync(dbPath).size) : null,
|
||||
},
|
||||
configDir: path.join(dataDir, "config"),
|
||||
configExists: fs.existsSync(path.join(dataDir, "config")),
|
||||
};
|
||||
|
||||
if (isVerbose || !isJson) {
|
||||
try {
|
||||
const { detectAllTools } = await import("../../../src/lib/cli-helper/tool-detector.js");
|
||||
const tools = await detectAllTools();
|
||||
status.tools = tools.map((t) => ({
|
||||
id: t.id,
|
||||
name: t.name,
|
||||
installed: t.installed,
|
||||
configured: t.configured,
|
||||
version: t.version || null,
|
||||
}));
|
||||
} catch {
|
||||
status.tools = "unavailable";
|
||||
}
|
||||
}
|
||||
|
||||
if (isJson) {
|
||||
console.log(JSON.stringify(status, null, 2));
|
||||
return 0;
|
||||
}
|
||||
|
||||
printHeading("OmniRoute Status");
|
||||
console.log(` Version: ${status.version}`);
|
||||
console.log(` Data Dir: ${status.dataDir}`);
|
||||
console.log(
|
||||
` Database: ${status.database.exists ? "Found" : "Not found"} (${status.database.size || "N/A"})`
|
||||
);
|
||||
console.log(` Config Dir: ${status.configExists ? "Exists" : "Not found"}`);
|
||||
|
||||
if (status.tools) {
|
||||
console.log("\n CLI Tools:");
|
||||
for (const t of status.tools) {
|
||||
const icon = t.configured ? "✓" : t.installed ? "~" : "✗";
|
||||
console.log(
|
||||
` ${icon} ${t.name.padEnd(14)} ${t.installed ? "installed" : "not installed"}${t.version ? ` (${t.version})` : ""}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
166
bin/cli/commands/update.mjs
Normal file
166
bin/cli/commands/update.mjs
Normal file
@@ -0,0 +1,166 @@
|
||||
import { parseArgs, getStringFlag, hasFlag } from "../args.mjs";
|
||||
import { printHeading, printInfo, printSuccess, printError } from "../io.mjs";
|
||||
import { homedir } from "node:os";
|
||||
import path from "node:path";
|
||||
import { execFile } from "node:child_process";
|
||||
import { promisify } from "node:util";
|
||||
|
||||
const execFileAsync = promisify(execFile);
|
||||
|
||||
function printUpdateHelp() {
|
||||
console.log(`
|
||||
Usage:
|
||||
omniroute update [options]
|
||||
|
||||
Options:
|
||||
--check Check for available update without applying
|
||||
--dry-run Show what would be updated without applying
|
||||
--backup Create backup before updating (default: true)
|
||||
--no-backup Skip backup creation
|
||||
--help Show this help
|
||||
|
||||
Environment:
|
||||
OMNIRoute_AUTO_UPDATE Set to "true" to enable auto-update check on startup
|
||||
`);
|
||||
}
|
||||
|
||||
async function getCurrentVersion() {
|
||||
try {
|
||||
const { readFileSync } = await import("node:fs");
|
||||
const pkg = JSON.parse(readFileSync(path.join(process.cwd(), "package.json"), "utf-8"));
|
||||
return pkg.version;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function getLatestVersion() {
|
||||
try {
|
||||
const { stdout } = await execFileAsync("npm", ["view", "omniroute", "version"], {
|
||||
timeout: 15000,
|
||||
});
|
||||
return stdout.trim();
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function compareVersions(a, b) {
|
||||
const pa = a.split(".").map(Number);
|
||||
const pb = b.split(".").map(Number);
|
||||
for (let i = 0; i < 3; i++) {
|
||||
if ((pa[i] || 0) > (pb[i] || 0)) return 1;
|
||||
if ((pa[i] || 0) < (pb[i] || 0)) return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
async function createBackup() {
|
||||
const binPath = path.join(process.cwd(), "bin");
|
||||
const backupDir = path.join(homedir(), ".omniroute", "backups", `omniroute-${Date.now()}`);
|
||||
|
||||
try {
|
||||
const { mkdirSync, copyFileSync, existsSync } = await import("node:fs");
|
||||
if (!existsSync(binPath)) return null;
|
||||
|
||||
mkdirSync(backupDir, { recursive: true });
|
||||
const files = ["omniroute.mjs", "cli", "nodeRuntimeSupport.mjs", "mcp-server.mjs"];
|
||||
for (const f of files) {
|
||||
const src = path.join(binPath, f);
|
||||
if (existsSync(src)) {
|
||||
copyFileSync(src, path.join(backupDir, f));
|
||||
}
|
||||
}
|
||||
return backupDir;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export async function runUpdateCommand(argv) {
|
||||
const { flags } = parseArgs(argv);
|
||||
|
||||
if (hasFlag(flags, "help") || hasFlag(flags, "h")) {
|
||||
printUpdateHelp();
|
||||
return 0;
|
||||
}
|
||||
|
||||
const checkOnly = hasFlag(flags, "check");
|
||||
const dryRun = hasFlag(flags, "dry-run");
|
||||
const skipBackup = hasFlag(flags, "no-backup");
|
||||
|
||||
const current = await getCurrentVersion();
|
||||
const latest = await getLatestVersion();
|
||||
|
||||
if (!current) {
|
||||
printError("Could not determine current version");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!latest) {
|
||||
printError("Could not check latest version. Is npm available?");
|
||||
return 1;
|
||||
}
|
||||
|
||||
printHeading("OmniRoute Update");
|
||||
console.log(` Current version: ${current}`);
|
||||
console.log(` Latest version: ${latest}`);
|
||||
|
||||
const cmp = compareVersions(current, latest);
|
||||
if (cmp >= 0) {
|
||||
printSuccess("You are running the latest version!");
|
||||
return 0;
|
||||
}
|
||||
|
||||
console.log(`\n Update available: ${current} → ${latest}`);
|
||||
|
||||
if (checkOnly) {
|
||||
console.log("\n Run `omniroute update` to apply the update.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (dryRun) {
|
||||
console.log("\n [DRY RUN] Would run: npm install -g omniroute@latest");
|
||||
if (!skipBackup) console.log(" [DRY RUN] Would create backup in ~/.omniroute/backups/");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!skipBackup) {
|
||||
printInfo("Creating backup...");
|
||||
const backupPath = await createBackup();
|
||||
if (backupPath) {
|
||||
printSuccess(`Backup created: ${backupPath}`);
|
||||
} else {
|
||||
printError("Failed to create backup. Aborting update.");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasFlag(flags, "yes")) {
|
||||
const readline = await import("node:readline");
|
||||
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
||||
const answer = await new Promise((resolve) =>
|
||||
rl.question(`Proceed with update to ${latest}? [y/N] `, resolve)
|
||||
);
|
||||
rl.close();
|
||||
if (!/^y(es)?$/i.test(answer)) {
|
||||
printInfo("Update aborted.");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
printInfo("Updating OmniRoute...");
|
||||
try {
|
||||
const { execSync } = await import("child_process");
|
||||
execSync("npm install -g omniroute@latest", { stdio: "inherit" });
|
||||
printSuccess(`Updated to version ${latest}`);
|
||||
printInfo("Run `omniroute --version` to verify.");
|
||||
return 0;
|
||||
} catch (err) {
|
||||
printError(`Update failed: ${err.message}`);
|
||||
printInfo("Restore from backup:");
|
||||
const backupDir = path.join(homedir(), ".omniroute", "backups");
|
||||
printInfo(` ls ${backupDir}`);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,11 @@
|
||||
import { runDoctorCommand } from "./commands/doctor.mjs";
|
||||
import { runProvidersCommand } from "./commands/providers.mjs";
|
||||
import { runSetupCommand } from "./commands/setup.mjs";
|
||||
import { runConfigCommand } from "./commands/config.mjs";
|
||||
import { runStatusCommand } from "./commands/status.mjs";
|
||||
import { runLogsCommand } from "./commands/logs.mjs";
|
||||
import { runUpdateCommand } from "./commands/update.mjs";
|
||||
import { runProviderCommand } from "./commands/provider-cmd.mjs";
|
||||
|
||||
export async function runCliCommand(command, argv, context = {}) {
|
||||
if (command === "doctor") {
|
||||
@@ -15,5 +20,25 @@ export async function runCliCommand(command, argv, context = {}) {
|
||||
return runSetupCommand(argv, context);
|
||||
}
|
||||
|
||||
if (command === "config") {
|
||||
return runConfigCommand(argv);
|
||||
}
|
||||
|
||||
if (command === "status") {
|
||||
return runStatusCommand(argv);
|
||||
}
|
||||
|
||||
if (command === "logs") {
|
||||
return runLogsCommand(argv);
|
||||
}
|
||||
|
||||
if (command === "update") {
|
||||
return runUpdateCommand(argv);
|
||||
}
|
||||
|
||||
if (command === "provider") {
|
||||
return runProviderCommand(argv);
|
||||
}
|
||||
|
||||
throw new Error(`Unknown CLI command: ${command}`);
|
||||
}
|
||||
|
||||
@@ -16,11 +16,31 @@ export function createPrompt() {
|
||||
});
|
||||
}
|
||||
|
||||
function askSecret(question) {
|
||||
return new Promise((resolve) => {
|
||||
let prompted = false;
|
||||
const saved = rl._writeToOutput.bind(rl);
|
||||
rl._writeToOutput = function (str) {
|
||||
if (!prompted) {
|
||||
rl.output.write(str);
|
||||
if (str.endsWith(": ")) prompted = true;
|
||||
return;
|
||||
}
|
||||
// Suppress character echo; allow only newlines through
|
||||
if (str === "\r\n" || str === "\n" || str === "\r") rl.output.write("\n");
|
||||
};
|
||||
rl.question(`${question}: `, (answer) => {
|
||||
rl._writeToOutput = saved;
|
||||
resolve(answer.trim());
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function close() {
|
||||
rl.close();
|
||||
}
|
||||
|
||||
return { ask, close };
|
||||
return { ask, askSecret, close };
|
||||
}
|
||||
|
||||
export function printHeading(title) {
|
||||
@@ -34,3 +54,7 @@ export function printSuccess(message) {
|
||||
export function printInfo(message) {
|
||||
console.log(`\x1b[2m${message}\x1b[0m`);
|
||||
}
|
||||
|
||||
export function printError(message) {
|
||||
console.log(`\x1b[31m✖ ${message}\x1b[0m`);
|
||||
}
|
||||
|
||||
@@ -79,7 +79,16 @@ loadEnvFile();
|
||||
|
||||
const args = process.argv.slice(2);
|
||||
const command = args[0];
|
||||
const CLI_COMMANDS = new Set(["doctor", "providers", "setup"]);
|
||||
const CLI_COMMANDS = new Set([
|
||||
"doctor",
|
||||
"providers",
|
||||
"setup",
|
||||
"config",
|
||||
"status",
|
||||
"logs",
|
||||
"update",
|
||||
"provider",
|
||||
]);
|
||||
|
||||
if (CLI_COMMANDS.has(command)) {
|
||||
try {
|
||||
@@ -106,6 +115,67 @@ if (args.includes("--help") || args.includes("-h")) {
|
||||
omniroute --no-open Don't open browser automatically
|
||||
omniroute --mcp Start MCP server (stdio transport for IDEs)
|
||||
omniroute reset-encrypted-columns Reset encrypted credentials (recovery)
|
||||
|
||||
\x1b[1mServer Management:\x1b[0m
|
||||
omniroute serve Start the OmniRoute server
|
||||
omniroute stop Stop the running server
|
||||
omniroute restart Restart the server
|
||||
omniroute dashboard Open dashboard in browser
|
||||
omniroute open Alias for dashboard (same as dashboard)
|
||||
|
||||
\x1b[1mCLI Integration Suite:\x1b[0m
|
||||
omniroute setup Interactive wizard to configure CLI tools
|
||||
omniroute doctor Run health diagnostics
|
||||
omniroute status Show comprehensive status
|
||||
omniroute logs Stream request logs (--json, --search, --follow)
|
||||
omniroute config show Display current configuration
|
||||
|
||||
\x1b[1mProvider & Keys:\x1b[0m
|
||||
omniroute provider list List available providers
|
||||
omniroute provider add Add OmniRoute as provider
|
||||
omniroute keys add Add API key for provider
|
||||
omniroute keys list List configured API keys
|
||||
omniroute keys remove Remove API key
|
||||
|
||||
\x1b[1mModels & Combos:\x1b[0m
|
||||
omniroute models List available models (--json, --search)
|
||||
omniroute models <prov> Filter models by provider
|
||||
omniroute combo list List routing combos
|
||||
omniroute combo switch Switch active combo
|
||||
omniroute combo create Create new combo
|
||||
omniroute combo delete Delete a combo
|
||||
|
||||
\x1b[1mBackup & Restore:\x1b[0m
|
||||
omniroute backup Create backup of config & DB
|
||||
omniroute restore Restore from backup (list or specify timestamp)
|
||||
|
||||
\x1b[1mMonitoring:\x1b[0m
|
||||
omniroute health Detailed health (breakers, cache, memory)
|
||||
omniroute quota Show provider quota usage
|
||||
omniroute cache Show cache status
|
||||
omniroute cache clear Clear semantic/signature cache
|
||||
|
||||
\x1b[1mProtocols:\x1b[0m
|
||||
omniroute mcp status MCP server status
|
||||
omniroute mcp restart Restart MCP server
|
||||
omniroute a2a status A2A server status
|
||||
omniroute a2a card Show A2A agent card
|
||||
|
||||
\x1b[1mTunnels & Network:\x1b[0m
|
||||
omniroute tunnel list List active tunnels
|
||||
omniroute tunnel create Create tunnel (cloudflare/tailscale/ngrok)
|
||||
omniroute tunnel stop Stop a tunnel
|
||||
|
||||
\x1b[1mEnvironment:\x1b[0m
|
||||
omniroute env show Show environment variables
|
||||
omniroute env get <key> Get specific env var
|
||||
omniroute env set <k> <v> Set env var (temporary)
|
||||
|
||||
\x1b[1mTools & Utils:\x1b[0m
|
||||
omniroute test Test provider connectivity
|
||||
omniroute update Check for updates
|
||||
omniroute completion Generate shell completion
|
||||
|
||||
omniroute --help Show this help
|
||||
omniroute --version Show version
|
||||
|
||||
@@ -139,6 +209,18 @@ if (args.includes("--help") || args.includes("-h")) {
|
||||
omniroute providers test-all
|
||||
omniroute providers validate
|
||||
|
||||
\x1b[1mCLI Tools:\x1b[0m
|
||||
omniroute config list List CLI tool configuration status
|
||||
omniroute config get <tool> Show config for a specific tool
|
||||
omniroute config set <tool> Write config for a tool
|
||||
omniroute config validate <tool> Validate config without writing
|
||||
omniroute status Offline status dashboard
|
||||
omniroute logs [--follow] [--filter] Stream usage logs
|
||||
omniroute update [--check] [--dry-run] Check or apply OmniRoute update
|
||||
omniroute provider add <name> Add a provider connection
|
||||
omniroute provider list List configured providers
|
||||
omniroute provider test <name|id> Test a provider connection
|
||||
|
||||
\x1b[1mAfter starting:\x1b[0m
|
||||
Dashboard: http://localhost:<dashboard-port>
|
||||
API: http://localhost:<api-port>/v1
|
||||
@@ -160,6 +242,24 @@ if (args.includes("--version") || args.includes("-v")) {
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
// ── CLI Integration Suite subcommands ───────────────────────────────────────
|
||||
const subcommands = [
|
||||
"setup", "doctor", "status", "logs", "provider", "config", "test", "update",
|
||||
"serve", "stop", "restart",
|
||||
"keys", "models", "combo",
|
||||
"completion", "dashboard",
|
||||
"backup", "restore", "quota", "health",
|
||||
"cache", "mcp", "a2a", "tunnel",
|
||||
"env", "open"
|
||||
];
|
||||
const subcommand = args[0];
|
||||
|
||||
if (subcommands.includes(subcommand)) {
|
||||
const { runSubcommand } = await import("./cli-commands.mjs");
|
||||
await runSubcommand(subcommand, args.slice(1));
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
// ── reset-encrypted-columns subcommand ──────────────────────────────────────
|
||||
// Recovery tool for users who lost STORAGE_ENCRYPTION_KEY after upgrade (#1622)
|
||||
if (args.includes("reset-encrypted-columns")) {
|
||||
|
||||
65
config/i18n-schema.json
Normal file
65
config/i18n-schema.json
Normal file
@@ -0,0 +1,65 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "OmniRoute i18n config",
|
||||
"description": "Canonical list of locales used by both the UI (next-intl) and the docs translation pipeline.",
|
||||
"type": "object",
|
||||
"required": ["default", "rtl", "locales"],
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"$schema": { "type": "string" },
|
||||
"default": {
|
||||
"type": "string",
|
||||
"description": "Default fallback locale code (must exist in `locales`)."
|
||||
},
|
||||
"rtl": {
|
||||
"type": "array",
|
||||
"description": "Locale codes that should be rendered right-to-left.",
|
||||
"items": { "type": "string" }
|
||||
},
|
||||
"uiOnly": {
|
||||
"type": "array",
|
||||
"description": "Locale codes shipped in the UI but not produced as a docs translation target.",
|
||||
"items": { "type": "string" }
|
||||
},
|
||||
"docsExcluded": {
|
||||
"type": "array",
|
||||
"description": "Locale codes that should NOT receive docs translations (e.g. the source language).",
|
||||
"items": { "type": "string" }
|
||||
},
|
||||
"locales": {
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": ["code", "label", "name", "flag"],
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"code": {
|
||||
"type": "string",
|
||||
"description": "Locale code (ISO-639-1, optionally with region — e.g. `pt-BR`)."
|
||||
},
|
||||
"label": {
|
||||
"type": "string",
|
||||
"description": "Short uppercase label for compact UI badges."
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "Display name in the locale's native script (kept for compatibility with existing LanguageSelector)."
|
||||
},
|
||||
"native": {
|
||||
"type": "string",
|
||||
"description": "Same as `name` — present as an alias so new code can rely on a stable field name."
|
||||
},
|
||||
"english": {
|
||||
"type": "string",
|
||||
"description": "English name of the language (used by the docs translator system prompt)."
|
||||
},
|
||||
"flag": {
|
||||
"type": "string",
|
||||
"description": "Flag emoji shown next to the locale."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
337
config/i18n.json
Normal file
337
config/i18n.json
Normal file
@@ -0,0 +1,337 @@
|
||||
{
|
||||
"$schema": "./i18n-schema.json",
|
||||
"default": "en",
|
||||
"rtl": ["ar", "fa", "he", "ur"],
|
||||
"uiOnly": ["en"],
|
||||
"docsExcluded": ["en"],
|
||||
"locales": [
|
||||
{
|
||||
"code": "ar",
|
||||
"label": "AR",
|
||||
"name": "العربية",
|
||||
"native": "العربية",
|
||||
"english": "Arabic",
|
||||
"flag": "🇸🇦"
|
||||
},
|
||||
{
|
||||
"code": "bg",
|
||||
"label": "BG",
|
||||
"name": "Български",
|
||||
"native": "Български",
|
||||
"english": "Bulgarian",
|
||||
"flag": "🇧🇬"
|
||||
},
|
||||
{
|
||||
"code": "bn",
|
||||
"label": "BN",
|
||||
"name": "বাংলা",
|
||||
"native": "বাংলা",
|
||||
"english": "Bengali",
|
||||
"flag": "🇧🇩"
|
||||
},
|
||||
{
|
||||
"code": "cs",
|
||||
"label": "CS",
|
||||
"name": "Čeština",
|
||||
"native": "Čeština",
|
||||
"english": "Czech",
|
||||
"flag": "🇨🇿"
|
||||
},
|
||||
{
|
||||
"code": "da",
|
||||
"label": "DA",
|
||||
"name": "Dansk",
|
||||
"native": "Dansk",
|
||||
"english": "Danish",
|
||||
"flag": "🇩🇰"
|
||||
},
|
||||
{
|
||||
"code": "de",
|
||||
"label": "DE",
|
||||
"name": "Deutsch",
|
||||
"native": "Deutsch",
|
||||
"english": "German",
|
||||
"flag": "🇩🇪"
|
||||
},
|
||||
{
|
||||
"code": "en",
|
||||
"label": "EN",
|
||||
"name": "English",
|
||||
"native": "English",
|
||||
"english": "English",
|
||||
"flag": "🇺🇸"
|
||||
},
|
||||
{
|
||||
"code": "es",
|
||||
"label": "ES",
|
||||
"name": "Español",
|
||||
"native": "Español",
|
||||
"english": "Spanish",
|
||||
"flag": "🇪🇸"
|
||||
},
|
||||
{
|
||||
"code": "fa",
|
||||
"label": "FA",
|
||||
"name": "فارسی",
|
||||
"native": "فارسی",
|
||||
"english": "Persian",
|
||||
"flag": "🇮🇷"
|
||||
},
|
||||
{
|
||||
"code": "fi",
|
||||
"label": "FI",
|
||||
"name": "Suomi",
|
||||
"native": "Suomi",
|
||||
"english": "Finnish",
|
||||
"flag": "🇫🇮"
|
||||
},
|
||||
{
|
||||
"code": "fr",
|
||||
"label": "FR",
|
||||
"name": "Français",
|
||||
"native": "Français",
|
||||
"english": "French",
|
||||
"flag": "🇫🇷"
|
||||
},
|
||||
{
|
||||
"code": "gu",
|
||||
"label": "GU",
|
||||
"name": "ગુજરાતી",
|
||||
"native": "ગુજરાતી",
|
||||
"english": "Gujarati",
|
||||
"flag": "🇮🇳"
|
||||
},
|
||||
{
|
||||
"code": "he",
|
||||
"label": "HE",
|
||||
"name": "עברית",
|
||||
"native": "עברית",
|
||||
"english": "Hebrew",
|
||||
"flag": "🇮🇱"
|
||||
},
|
||||
{
|
||||
"code": "hi",
|
||||
"label": "HI",
|
||||
"name": "हिन्दी",
|
||||
"native": "हिन्दी",
|
||||
"english": "Hindi",
|
||||
"flag": "🇮🇳"
|
||||
},
|
||||
{
|
||||
"code": "hu",
|
||||
"label": "HU",
|
||||
"name": "Magyar",
|
||||
"native": "Magyar",
|
||||
"english": "Hungarian",
|
||||
"flag": "🇭🇺"
|
||||
},
|
||||
{
|
||||
"code": "id",
|
||||
"label": "ID",
|
||||
"name": "Bahasa Indonesia",
|
||||
"native": "Bahasa Indonesia",
|
||||
"english": "Indonesian",
|
||||
"flag": "🇮🇩"
|
||||
},
|
||||
{
|
||||
"code": "in",
|
||||
"label": "IN",
|
||||
"name": "Bahasa Indonesia (Alt)",
|
||||
"native": "Bahasa Indonesia (Alt)",
|
||||
"english": "Indonesian (Legacy)",
|
||||
"flag": "🇮🇩"
|
||||
},
|
||||
{
|
||||
"code": "it",
|
||||
"label": "IT",
|
||||
"name": "Italiano",
|
||||
"native": "Italiano",
|
||||
"english": "Italian",
|
||||
"flag": "🇮🇹"
|
||||
},
|
||||
{
|
||||
"code": "ja",
|
||||
"label": "JA",
|
||||
"name": "日本語",
|
||||
"native": "日本語",
|
||||
"english": "Japanese",
|
||||
"flag": "🇯🇵"
|
||||
},
|
||||
{
|
||||
"code": "ko",
|
||||
"label": "KO",
|
||||
"name": "한국어",
|
||||
"native": "한국어",
|
||||
"english": "Korean",
|
||||
"flag": "🇰🇷"
|
||||
},
|
||||
{
|
||||
"code": "mr",
|
||||
"label": "MR",
|
||||
"name": "मराठी",
|
||||
"native": "मराठी",
|
||||
"english": "Marathi",
|
||||
"flag": "🇮🇳"
|
||||
},
|
||||
{
|
||||
"code": "ms",
|
||||
"label": "MS",
|
||||
"name": "Bahasa Melayu",
|
||||
"native": "Bahasa Melayu",
|
||||
"english": "Malay",
|
||||
"flag": "🇲🇾"
|
||||
},
|
||||
{
|
||||
"code": "nl",
|
||||
"label": "NL",
|
||||
"name": "Nederlands",
|
||||
"native": "Nederlands",
|
||||
"english": "Dutch",
|
||||
"flag": "🇳🇱"
|
||||
},
|
||||
{
|
||||
"code": "no",
|
||||
"label": "NO",
|
||||
"name": "Norsk",
|
||||
"native": "Norsk",
|
||||
"english": "Norwegian",
|
||||
"flag": "🇳🇴"
|
||||
},
|
||||
{
|
||||
"code": "phi",
|
||||
"label": "PHI",
|
||||
"name": "Filipino",
|
||||
"native": "Filipino",
|
||||
"english": "Filipino",
|
||||
"flag": "🇵🇭"
|
||||
},
|
||||
{
|
||||
"code": "pl",
|
||||
"label": "PL",
|
||||
"name": "Polski",
|
||||
"native": "Polski",
|
||||
"english": "Polish",
|
||||
"flag": "🇵🇱"
|
||||
},
|
||||
{
|
||||
"code": "pt",
|
||||
"label": "PT",
|
||||
"name": "Português (Portugal)",
|
||||
"native": "Português (Portugal)",
|
||||
"english": "Portuguese (Portugal)",
|
||||
"flag": "🇵🇹"
|
||||
},
|
||||
{
|
||||
"code": "pt-BR",
|
||||
"label": "PT-BR",
|
||||
"name": "Português (Brasil)",
|
||||
"native": "Português (Brasil)",
|
||||
"english": "Portuguese (Brazil)",
|
||||
"flag": "🇧🇷"
|
||||
},
|
||||
{
|
||||
"code": "ro",
|
||||
"label": "RO",
|
||||
"name": "Română",
|
||||
"native": "Română",
|
||||
"english": "Romanian",
|
||||
"flag": "🇷🇴"
|
||||
},
|
||||
{
|
||||
"code": "ru",
|
||||
"label": "RU",
|
||||
"name": "Русский",
|
||||
"native": "Русский",
|
||||
"english": "Russian",
|
||||
"flag": "🇷🇺"
|
||||
},
|
||||
{
|
||||
"code": "sk",
|
||||
"label": "SK",
|
||||
"name": "Slovenčina",
|
||||
"native": "Slovenčina",
|
||||
"english": "Slovak",
|
||||
"flag": "🇸🇰"
|
||||
},
|
||||
{
|
||||
"code": "sv",
|
||||
"label": "SV",
|
||||
"name": "Svenska",
|
||||
"native": "Svenska",
|
||||
"english": "Swedish",
|
||||
"flag": "🇸🇪"
|
||||
},
|
||||
{
|
||||
"code": "sw",
|
||||
"label": "SW",
|
||||
"name": "Kiswahili",
|
||||
"native": "Kiswahili",
|
||||
"english": "Swahili",
|
||||
"flag": "🇰🇪"
|
||||
},
|
||||
{
|
||||
"code": "ta",
|
||||
"label": "TA",
|
||||
"name": "தமிழ்",
|
||||
"native": "தமிழ்",
|
||||
"english": "Tamil",
|
||||
"flag": "🇮🇳"
|
||||
},
|
||||
{
|
||||
"code": "te",
|
||||
"label": "TE",
|
||||
"name": "తెలుగు",
|
||||
"native": "తెలుగు",
|
||||
"english": "Telugu",
|
||||
"flag": "🇮🇳"
|
||||
},
|
||||
{
|
||||
"code": "th",
|
||||
"label": "TH",
|
||||
"name": "ไทย",
|
||||
"native": "ไทย",
|
||||
"english": "Thai",
|
||||
"flag": "🇹🇭"
|
||||
},
|
||||
{
|
||||
"code": "tr",
|
||||
"label": "TR",
|
||||
"name": "Türkçe",
|
||||
"native": "Türkçe",
|
||||
"english": "Turkish",
|
||||
"flag": "🇹🇷"
|
||||
},
|
||||
{
|
||||
"code": "uk-UA",
|
||||
"label": "UK-UA",
|
||||
"name": "Українська",
|
||||
"native": "Українська",
|
||||
"english": "Ukrainian",
|
||||
"flag": "🇺🇦"
|
||||
},
|
||||
{
|
||||
"code": "ur",
|
||||
"label": "UR",
|
||||
"name": "اردو",
|
||||
"native": "اردو",
|
||||
"english": "Urdu",
|
||||
"flag": "🇵🇰"
|
||||
},
|
||||
{
|
||||
"code": "vi",
|
||||
"label": "VI",
|
||||
"name": "Tiếng Việt",
|
||||
"native": "Tiếng Việt",
|
||||
"english": "Vietnamese",
|
||||
"flag": "🇻🇳"
|
||||
},
|
||||
{
|
||||
"code": "zh-CN",
|
||||
"label": "ZH-CN",
|
||||
"name": "中文 (简体)",
|
||||
"native": "中文 (简体)",
|
||||
"english": "Chinese (Simplified)",
|
||||
"flag": "🇨🇳"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -41,7 +41,7 @@ x-common: &common
|
||||
services:
|
||||
# ── Redis (Rate Limiter Backend) ──────────────────────────────────
|
||||
redis:
|
||||
image: redis:8.6.2
|
||||
image: redis:7-alpine
|
||||
container_name: omniroute-redis
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
|
||||
@@ -1,488 +0,0 @@
|
||||
# API Reference
|
||||
|
||||
🌐 **Languages:** 🇺🇸 [English](API_REFERENCE.md) | 🇧🇷 [Português (Brasil)](i18n/pt-BR/API_REFERENCE.md) | 🇪🇸 [Español](i18n/es/API_REFERENCE.md) | 🇫🇷 [Français](i18n/fr/API_REFERENCE.md) | 🇮🇹 [Italiano](i18n/it/API_REFERENCE.md) | 🇷🇺 [Русский](i18n/ru/API_REFERENCE.md) | 🇨🇳 [中文 (简体)](i18n/zh-CN/API_REFERENCE.md) | 🇩🇪 [Deutsch](i18n/de/API_REFERENCE.md) | 🇮🇳 [हिन्दी](i18n/in/API_REFERENCE.md) | 🇹🇭 [ไทย](i18n/th/API_REFERENCE.md) | 🇺🇦 [Українська](i18n/uk-UA/API_REFERENCE.md) | 🇸🇦 [العربية](i18n/ar/API_REFERENCE.md) | 🇯🇵 [日本語](i18n/ja/API_REFERENCE.md) | 🇻🇳 [Tiếng Việt](i18n/vi/API_REFERENCE.md) | 🇧🇬 [Български](i18n/bg/API_REFERENCE.md) | 🇩🇰 [Dansk](i18n/da/API_REFERENCE.md) | 🇫🇮 [Suomi](i18n/fi/API_REFERENCE.md) | 🇮🇱 [עברית](i18n/he/API_REFERENCE.md) | 🇭🇺 [Magyar](i18n/hu/API_REFERENCE.md) | 🇮🇩 [Bahasa Indonesia](i18n/id/API_REFERENCE.md) | 🇰🇷 [한국어](i18n/ko/API_REFERENCE.md) | 🇲🇾 [Bahasa Melayu](i18n/ms/API_REFERENCE.md) | 🇳🇱 [Nederlands](i18n/nl/API_REFERENCE.md) | 🇳🇴 [Norsk](i18n/no/API_REFERENCE.md) | 🇵🇹 [Português (Portugal)](i18n/pt/API_REFERENCE.md) | 🇷🇴 [Română](i18n/ro/API_REFERENCE.md) | 🇵🇱 [Polski](i18n/pl/API_REFERENCE.md) | 🇸🇰 [Slovenčina](i18n/sk/API_REFERENCE.md) | 🇸🇪 [Svenska](i18n/sv/API_REFERENCE.md) | 🇵🇭 [Filipino](i18n/phi/API_REFERENCE.md) | 🇨🇿 [Čeština](i18n/cs/API_REFERENCE.md)
|
||||
|
||||
Complete reference for all OmniRoute API endpoints.
|
||||
|
||||
---
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Chat Completions](#chat-completions)
|
||||
- [Embeddings](#embeddings)
|
||||
- [Image Generation](#image-generation)
|
||||
- [List Models](#list-models)
|
||||
- [Compatibility Endpoints](#compatibility-endpoints)
|
||||
- [Semantic Cache](#semantic-cache)
|
||||
- [Dashboard & Management](#dashboard--management)
|
||||
- [Request Processing](#request-processing)
|
||||
- [Authentication](#authentication)
|
||||
|
||||
---
|
||||
|
||||
## Chat Completions
|
||||
|
||||
```bash
|
||||
POST /v1/chat/completions
|
||||
Authorization: Bearer your-api-key
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"model": "cc/claude-opus-4-6",
|
||||
"messages": [
|
||||
{"role": "user", "content": "Write a function to..."}
|
||||
],
|
||||
"stream": true
|
||||
}
|
||||
```
|
||||
|
||||
### Custom Headers
|
||||
|
||||
| Header | Direction | Description |
|
||||
| ------------------------ | --------- | ------------------------------------------------ |
|
||||
| `X-OmniRoute-No-Cache` | Request | Set to `true` to bypass cache |
|
||||
| `X-OmniRoute-Progress` | Request | Set to `true` for progress events |
|
||||
| `X-Session-Id` | Request | Sticky session key for external session affinity |
|
||||
| `x_session_id` | Request | Underscore variant also accepted (direct HTTP) |
|
||||
| `Idempotency-Key` | Request | Dedup key (5s window) |
|
||||
| `X-Request-Id` | Request | Alternative dedup key |
|
||||
| `X-OmniRoute-Cache` | Response | `HIT` or `MISS` (non-streaming) |
|
||||
| `X-OmniRoute-Idempotent` | Response | `true` if deduplicated |
|
||||
| `X-OmniRoute-Progress` | Response | `enabled` if progress tracking on |
|
||||
| `X-OmniRoute-Session-Id` | Response | Effective session ID used by OmniRoute |
|
||||
|
||||
> Nginx note: if you rely on underscore headers (for example `x_session_id`), enable `underscores_in_headers on;`.
|
||||
|
||||
---
|
||||
|
||||
## Embeddings
|
||||
|
||||
```bash
|
||||
POST /v1/embeddings
|
||||
Authorization: Bearer your-api-key
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"model": "nebius/Qwen/Qwen3-Embedding-8B",
|
||||
"input": "The food was delicious"
|
||||
}
|
||||
```
|
||||
|
||||
Available providers: Nebius, OpenAI, Mistral, Together AI, Fireworks, NVIDIA, **OpenRouter**, **GitHub Models**.
|
||||
|
||||
```bash
|
||||
# List all embedding models
|
||||
GET /v1/embeddings
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Image Generation
|
||||
|
||||
```bash
|
||||
POST /v1/images/generations
|
||||
Authorization: Bearer your-api-key
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"model": "openai/gpt-image-2",
|
||||
"prompt": "A beautiful sunset over mountains",
|
||||
"size": "1024x1024"
|
||||
}
|
||||
```
|
||||
|
||||
Available providers: OpenAI (GPT Image 2), xAI (Grok Image), Together AI (FLUX), Fireworks AI, Nebius (FLUX), Hyperbolic, NanoBanana, **OpenRouter**, SD WebUI (local), ComfyUI (local).
|
||||
|
||||
```bash
|
||||
# List all image models
|
||||
GET /v1/images/generations
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## List Models
|
||||
|
||||
```bash
|
||||
GET /v1/models
|
||||
Authorization: Bearer your-api-key
|
||||
|
||||
→ Returns all chat, embedding, and image models + combos in OpenAI format
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Compatibility Endpoints
|
||||
|
||||
| Method | Path | Format |
|
||||
| ------ | --------------------------- | ---------------------- |
|
||||
| POST | `/v1/chat/completions` | OpenAI |
|
||||
| POST | `/v1/messages` | Anthropic |
|
||||
| POST | `/v1/responses` | OpenAI Responses |
|
||||
| POST | `/v1/embeddings` | OpenAI |
|
||||
| POST | `/v1/images/generations` | OpenAI |
|
||||
| GET | `/v1/models` | OpenAI |
|
||||
| POST | `/v1/messages/count_tokens` | Anthropic |
|
||||
| GET | `/v1beta/models` | Gemini |
|
||||
| POST | `/v1beta/models/{...path}` | Gemini generateContent |
|
||||
| POST | `/v1/api/chat` | Ollama |
|
||||
|
||||
### Dedicated Provider Routes
|
||||
|
||||
```bash
|
||||
POST /v1/providers/{provider}/chat/completions
|
||||
POST /v1/providers/{provider}/embeddings
|
||||
POST /v1/providers/{provider}/images/generations
|
||||
```
|
||||
|
||||
The provider prefix is auto-added if missing. Mismatched models return `400`.
|
||||
|
||||
---
|
||||
|
||||
## Semantic Cache
|
||||
|
||||
```bash
|
||||
# Get cache stats
|
||||
GET /api/cache/stats
|
||||
|
||||
# Clear all caches
|
||||
DELETE /api/cache/stats
|
||||
```
|
||||
|
||||
Response example:
|
||||
|
||||
```json
|
||||
{
|
||||
"semanticCache": {
|
||||
"memorySize": 42,
|
||||
"memoryMaxSize": 500,
|
||||
"dbSize": 128,
|
||||
"hitRate": 0.65
|
||||
},
|
||||
"idempotency": {
|
||||
"activeKeys": 3,
|
||||
"windowMs": 5000
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Dashboard & Management
|
||||
|
||||
### Authentication
|
||||
|
||||
| Endpoint | Method | Description |
|
||||
| ----------------------------- | ------- | --------------------- |
|
||||
| `/api/auth/login` | POST | Login |
|
||||
| `/api/auth/logout` | POST | Logout |
|
||||
| `/api/settings/require-login` | GET/PUT | Toggle login required |
|
||||
|
||||
### Provider Management
|
||||
|
||||
| Endpoint | Method | Description |
|
||||
| ---------------------------- | --------------------- | ---------------------------------------------- |
|
||||
| `/api/providers` | GET/POST | List / create providers |
|
||||
| `/api/providers/[id]` | GET/PUT/DELETE | Manage a provider |
|
||||
| `/api/providers/[id]/test` | POST | Test provider connection |
|
||||
| `/api/providers/[id]/models` | GET | List provider models |
|
||||
| `/api/providers/validate` | POST | Validate provider config |
|
||||
| `/api/provider-nodes*` | Various | Provider node management |
|
||||
| `/api/provider-models` | GET/POST/PATCH/DELETE | Custom models (add, update, hide/show, delete) |
|
||||
|
||||
### OAuth Flows
|
||||
|
||||
| Endpoint | Method | Description |
|
||||
| -------------------------------- | ------- | ----------------------- |
|
||||
| `/api/oauth/[provider]/[action]` | Various | Provider-specific OAuth |
|
||||
|
||||
### Routing & Config
|
||||
|
||||
| Endpoint | Method | Description |
|
||||
| --------------------- | -------- | ----------------------------- |
|
||||
| `/api/models/alias` | GET/POST | Model aliases |
|
||||
| `/api/models/catalog` | GET | All models by provider + type |
|
||||
| `/api/combos*` | Various | Combo management |
|
||||
| `/api/keys*` | Various | API key management |
|
||||
| `/api/pricing` | GET | Model pricing |
|
||||
|
||||
### Usage & Analytics
|
||||
|
||||
| Endpoint | Method | Description |
|
||||
| --------------------------- | ------ | -------------------- |
|
||||
| `/api/usage/history` | GET | Usage history |
|
||||
| `/api/usage/logs` | GET | Usage logs |
|
||||
| `/api/usage/request-logs` | GET | Request-level logs |
|
||||
| `/api/usage/[connectionId]` | GET | Per-connection usage |
|
||||
|
||||
### Settings
|
||||
|
||||
| Endpoint | Method | Description |
|
||||
| ------------------------------- | ------------- | ------------------------- |
|
||||
| `/api/settings` | GET/PUT/PATCH | General settings |
|
||||
| `/api/settings/proxy` | GET/PUT | Network proxy config |
|
||||
| `/api/settings/proxy/test` | POST | Test proxy connection |
|
||||
| `/api/settings/ip-filter` | GET/PUT | IP allowlist/blocklist |
|
||||
| `/api/settings/thinking-budget` | GET/PUT | Reasoning token budget |
|
||||
| `/api/settings/system-prompt` | GET/PUT | Global system prompt |
|
||||
| `/api/settings/compression` | GET/PUT | Global compression config |
|
||||
|
||||
### Context & Compression
|
||||
|
||||
| Endpoint | Method | Description |
|
||||
| -------------------------------------- | -------------- | ------------------------------------------------------------------------ |
|
||||
| `/api/compression/preview` | POST | Preview off/lite/standard/aggressive/ultra/RTK/stacked compression |
|
||||
| `/api/compression/language-packs` | GET | List available Caveman language packs |
|
||||
| `/api/compression/rules` | GET | List Caveman rule metadata |
|
||||
| `/api/context/caveman/config` | GET/PUT | Caveman-specific settings alias |
|
||||
| `/api/context/rtk/config` | GET/PUT | RTK-specific settings, including custom filters and raw-output retention |
|
||||
| `/api/context/rtk/filters` | GET | RTK filter catalog and custom-filter diagnostics |
|
||||
| `/api/context/rtk/test` | POST | Run RTK preview/test against a text payload |
|
||||
| `/api/context/rtk/raw-output/[id]` | GET | Read retained redacted raw output by pointer id |
|
||||
| `/api/context/combos` | GET/POST | Compression combo list/create |
|
||||
| `/api/context/combos/[id]` | GET/PUT/DELETE | Compression combo detail/update/delete |
|
||||
| `/api/context/combos/[id]/assignments` | GET/PUT | Assign compression combos to routing combos |
|
||||
| `/api/context/analytics` | GET | Compression analytics alias |
|
||||
|
||||
### Monitoring
|
||||
|
||||
| Endpoint | Method | Description |
|
||||
| ------------------------ | ---------- | ---------------------------------------------------------------------------------------------------- |
|
||||
| `/api/sessions` | GET | Active session tracking |
|
||||
| `/api/rate-limits` | GET | Per-account rate limits |
|
||||
| `/api/monitoring/health` | GET | Health check + provider summary (`catalogCount`, `configuredCount`, `activeCount`, `monitoredCount`) |
|
||||
| `/api/cache/stats` | GET/DELETE | Cache stats / clear |
|
||||
|
||||
### Backup & Export/Import
|
||||
|
||||
| Endpoint | Method | Description |
|
||||
| --------------------------- | ------ | --------------------------------------- |
|
||||
| `/api/db-backups` | GET | List available backups |
|
||||
| `/api/db-backups` | PUT | Create a manual backup |
|
||||
| `/api/db-backups` | POST | Restore from a specific backup |
|
||||
| `/api/db-backups/export` | GET | Download database as .sqlite file |
|
||||
| `/api/db-backups/import` | POST | Upload .sqlite file to replace database |
|
||||
| `/api/db-backups/exportAll` | GET | Download full backup as .tar.gz archive |
|
||||
|
||||
### Cloud Sync
|
||||
|
||||
| Endpoint | Method | Description |
|
||||
| ---------------------- | ------- | --------------------- |
|
||||
| `/api/sync/cloud` | Various | Cloud sync operations |
|
||||
| `/api/sync/initialize` | POST | Initialize sync |
|
||||
| `/api/cloud/*` | Various | Cloud management |
|
||||
|
||||
### Tunnels
|
||||
|
||||
| Endpoint | Method | Description |
|
||||
| -------------------------- | ------ | ----------------------------------------------------------------------- |
|
||||
| `/api/tunnels/cloudflared` | GET | Read Cloudflare Quick Tunnel install/runtime status for the dashboard |
|
||||
| `/api/tunnels/cloudflared` | POST | Enable or disable the Cloudflare Quick Tunnel (`action=enable/disable`) |
|
||||
| `/api/tunnels/ngrok` | GET | Read ngrok Tunnel runtime status for the dashboard |
|
||||
| `/api/tunnels/ngrok` | POST | Enable or disable the ngrok Tunnel (`action=enable/disable`) |
|
||||
|
||||
### CLI Tools
|
||||
|
||||
| Endpoint | Method | Description |
|
||||
| ---------------------------------- | ------ | ------------------- |
|
||||
| `/api/cli-tools/claude-settings` | GET | Claude CLI status |
|
||||
| `/api/cli-tools/codex-settings` | GET | Codex CLI status |
|
||||
| `/api/cli-tools/droid-settings` | GET | Droid CLI status |
|
||||
| `/api/cli-tools/openclaw-settings` | GET | OpenClaw CLI status |
|
||||
| `/api/cli-tools/runtime/[toolId]` | GET | Generic CLI runtime |
|
||||
|
||||
CLI responses include: `installed`, `runnable`, `command`, `commandPath`, `runtimeMode`, `reason`.
|
||||
|
||||
### ACP Agents
|
||||
|
||||
| Endpoint | Method | Description |
|
||||
| ----------------- | ------ | -------------------------------------------------------- |
|
||||
| `/api/acp/agents` | GET | List all detected agents (built-in + custom) with status |
|
||||
| `/api/acp/agents` | POST | Add custom agent or refresh detection cache |
|
||||
| `/api/acp/agents` | DELETE | Remove a custom agent by `id` query param |
|
||||
|
||||
GET response includes `agents[]` (id, name, binary, version, installed, protocol, isCustom) and `summary` (total, installed, notFound, builtIn, custom).
|
||||
|
||||
### Resilience & Rate Limits
|
||||
|
||||
| Endpoint | Method | Description |
|
||||
| ----------------------- | --------- | ---------------------------------------------------------------------------------- |
|
||||
| `/api/resilience` | GET/PATCH | Get/update request queue, connection cooldown, provider breaker, and wait settings |
|
||||
| `/api/resilience/reset` | POST | Reset provider circuit breakers |
|
||||
| `/api/rate-limits` | GET | Per-account rate limit status |
|
||||
| `/api/rate-limit` | GET | Global rate limit configuration |
|
||||
|
||||
### Evals
|
||||
|
||||
| Endpoint | Method | Description |
|
||||
| ------------ | -------- | --------------------------------- |
|
||||
| `/api/evals` | GET/POST | List eval suites / run evaluation |
|
||||
|
||||
### Policies
|
||||
|
||||
| Endpoint | Method | Description |
|
||||
| --------------- | --------------- | ----------------------- |
|
||||
| `/api/policies` | GET/POST/DELETE | Manage routing policies |
|
||||
|
||||
### Compliance
|
||||
|
||||
| Endpoint | Method | Description |
|
||||
| --------------------------- | ------ | ----------------------------- |
|
||||
| `/api/compliance/audit-log` | GET | Compliance audit log (last N) |
|
||||
|
||||
### v1beta (Gemini-Compatible)
|
||||
|
||||
| Endpoint | Method | Description |
|
||||
| -------------------------- | ------ | --------------------------------- |
|
||||
| `/v1beta/models` | GET | List models in Gemini format |
|
||||
| `/v1beta/models/{...path}` | POST | Gemini `generateContent` endpoint |
|
||||
|
||||
These endpoints mirror Gemini's API format for clients that expect native Gemini SDK compatibility.
|
||||
|
||||
### Internal / System APIs
|
||||
|
||||
| Endpoint | Method | Description |
|
||||
| ------------------------ | ------ | ---------------------------------------------------- |
|
||||
| `/api/init` | GET | Application initialization check (used on first run) |
|
||||
| `/api/tags` | GET | Ollama-compatible model tags (for Ollama clients) |
|
||||
| `/api/restart` | POST | Trigger graceful server restart |
|
||||
| `/api/shutdown` | POST | Trigger graceful server shutdown |
|
||||
| `/api/system/env/repair` | POST | Repair OAuth provider environment variables |
|
||||
| `/api/system-info` | GET | Generate system diagnostics report |
|
||||
|
||||
> **Note:** These endpoints are used internally by the system or for Ollama client compatibility. They are not typically called by end users.
|
||||
|
||||
### OAuth Environment Repair _(v3.6.1+)_
|
||||
|
||||
```bash
|
||||
POST /api/system/env/repair
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"provider": "claude-code"
|
||||
}
|
||||
```
|
||||
|
||||
Repairs missing or corrupted OAuth environment variables for a specific provider. Returns:
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"repaired": ["CLAUDE_CODE_OAUTH_CLIENT_ID", "CLAUDE_CODE_OAUTH_CLIENT_SECRET"],
|
||||
"backupPath": "/home/user/.omniroute/backups/env-repair-2026-04-11.bak"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Audio Transcription
|
||||
|
||||
```bash
|
||||
POST /v1/audio/transcriptions
|
||||
Authorization: Bearer your-api-key
|
||||
Content-Type: multipart/form-data
|
||||
```
|
||||
|
||||
Transcribe audio files using Deepgram or AssemblyAI.
|
||||
|
||||
**Request:**
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:20128/v1/audio/transcriptions \
|
||||
-H "Authorization: Bearer your-api-key" \
|
||||
-F "file=@recording.mp3" \
|
||||
-F "model=deepgram/nova-3"
|
||||
```
|
||||
|
||||
**Response:**
|
||||
|
||||
```json
|
||||
{
|
||||
"text": "Hello, this is the transcribed audio content.",
|
||||
"task": "transcribe",
|
||||
"language": "en",
|
||||
"duration": 12.5
|
||||
}
|
||||
```
|
||||
|
||||
**Supported providers:** `deepgram/nova-3`, `assemblyai/best`.
|
||||
|
||||
**Supported formats:** `mp3`, `wav`, `m4a`, `flac`, `ogg`, `webm`.
|
||||
|
||||
---
|
||||
|
||||
## Ollama Compatibility
|
||||
|
||||
For clients that use Ollama's API format:
|
||||
|
||||
```bash
|
||||
# Chat endpoint (Ollama format)
|
||||
POST /v1/api/chat
|
||||
|
||||
# Model listing (Ollama format)
|
||||
GET /api/tags
|
||||
```
|
||||
|
||||
Requests are automatically translated between Ollama and internal formats.
|
||||
|
||||
---
|
||||
|
||||
## Telemetry
|
||||
|
||||
```bash
|
||||
# Get latency telemetry summary (p50/p95/p99 per provider)
|
||||
GET /api/telemetry/summary
|
||||
```
|
||||
|
||||
**Response:**
|
||||
|
||||
```json
|
||||
{
|
||||
"providers": {
|
||||
"claudeCode": { "p50": 245, "p95": 890, "p99": 1200, "count": 150 },
|
||||
"github": { "p50": 180, "p95": 620, "p99": 950, "count": 320 }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Budget
|
||||
|
||||
```bash
|
||||
# Get budget status for all API keys
|
||||
GET /api/usage/budget
|
||||
|
||||
# Set or update a budget
|
||||
POST /api/usage/budget
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"keyId": "key-123",
|
||||
"limit": 50.00,
|
||||
"period": "monthly"
|
||||
}
|
||||
```
|
||||
|
||||
## Request Processing
|
||||
|
||||
1. Client sends request to `/v1/*`
|
||||
2. Route handler calls `handleChat`, `handleEmbedding`, `handleAudioTranscription`, or `handleImageGeneration`
|
||||
3. Model is resolved (direct provider/model or alias/combo)
|
||||
4. Credentials selected from local DB with account availability filtering
|
||||
5. For chat: `handleChatCore` checks semantic/signature cache and resolves combo compression settings
|
||||
6. Proactive compression runs before provider translation when enabled (`lite`, Caveman, RTK, or stacked)
|
||||
7. Provider executor sends upstream request
|
||||
8. Response translated back to client format (chat) or returned as-is (embeddings/images/audio)
|
||||
9. Usage, compression analytics, and request logs are recorded
|
||||
10. Fallback applies on errors according to combo rules
|
||||
|
||||
Full architecture reference: [`ARCHITECTURE.md`](ARCHITECTURE.md)
|
||||
|
||||
---
|
||||
|
||||
## Authentication
|
||||
|
||||
- Dashboard routes (`/dashboard/*`) use `auth_token` cookie
|
||||
- Login uses saved password hash; fallback to `INITIAL_PASSWORD`
|
||||
- `requireLogin` toggleable via `/api/settings/require-login`
|
||||
- `/v1/*` routes optionally require Bearer API key when `REQUIRE_API_KEY=true`
|
||||
@@ -1,8 +1,79 @@
|
||||
# OmniRoute Auto-Combo Engine
|
||||
|
||||
> Self-managing model chains with adaptive scoring
|
||||
> Self-managing model chains with adaptive scoring + zero-config auto-routing
|
||||
|
||||
## How It Works
|
||||
## Zero-Config Auto-Routing (`auto/` prefix)
|
||||
|
||||
> **NEW:** No combo creation required. Use `auto/` prefix directly in any client.
|
||||
|
||||
### Quick Examples
|
||||
|
||||
| Model ID | Variant | Behavior |
|
||||
| -------------- | ------- | ------------------------------------------------------------------------ |
|
||||
| `auto` | default | All connected providers, LKGP strategy, balanced weights |
|
||||
| `auto/coding` | coding | Quality-first weights, suitable for code generation |
|
||||
| `auto/fast` | fast | Low-latency weighted selection |
|
||||
| `auto/cheap` | cheap | Cost-optimized routing (lowest cost first) |
|
||||
| `auto/offline` | offline | Favors providers with highest quota availability |
|
||||
| `auto/smart` | smart | Quality-first + higher exploration rate (10%) for better model discovery |
|
||||
| `auto/lkgp` | lkgp | Explicit LKGP (same as default `auto`) |
|
||||
|
||||
**How to use:**
|
||||
|
||||
```bash
|
||||
# Any IDE or CLI tool that supports OpenAI format
|
||||
Base URL: http://localhost:20128/v1
|
||||
API Key: <your-endpoint-key>
|
||||
|
||||
# In your code/config, set model to:
|
||||
model: "auto" # balanced default
|
||||
model: "auto/coding" # best for coding tasks
|
||||
model: "auto/fast" # fastest available
|
||||
model: "auto/cheap" # cheapest per token
|
||||
```
|
||||
|
||||
**What happens:**
|
||||
|
||||
1. OmniRoute detects `auto/` prefix in `src/sse/handlers/chat.ts`
|
||||
2. Queries all **active provider connections** from the database
|
||||
3. Filters to those with valid credentials (API key or OAuth token)
|
||||
4. Determines the model per connection (`connection.defaultModel` or provider's first model)
|
||||
5. Builds a **virtual combo** in-memory (not stored in DB)
|
||||
6. Routes using the selected variant's weight profile + LKGP strategy
|
||||
|
||||
**Key properties:**
|
||||
|
||||
- ✅ **Always-on:** No toggle, no combo creation, no configuration needed
|
||||
- ✅ **Dynamic:** Reflects current connected providers automatically
|
||||
- ✅ **Session stickiness:** LKGP ensures last successful provider is prioritized
|
||||
- ✅ **Multi-account aware:** Each provider connection becomes a separate candidate
|
||||
- ✅ **No DB writes:** Virtual combo exists only for the request, zero persistence overhead
|
||||
|
||||
**Behind the scenes:**
|
||||
|
||||
```txt
|
||||
Request: { model: "auto/coding" }
|
||||
↓
|
||||
src/sse/handlers/chat.ts detects prefix
|
||||
↓
|
||||
createVirtualAutoCombo('coding') → candidatePool from active connections
|
||||
↓
|
||||
handleComboChat (same engine as persisted combos)
|
||||
↓
|
||||
Auto-scoring selects best provider/model per request
|
||||
```
|
||||
|
||||
**Implementation files:**
|
||||
|
||||
| File | Purpose |
|
||||
| --------------------------------------------------------- | ----------------------------------------- |
|
||||
| `open-sse/services/autoCombo/autoPrefix.ts` | Prefix parser (`parseAutoPrefix`) |
|
||||
| `open-sse/services/autoCombo/virtualFactory.ts` | Creates virtual `AutoComboConfig` objects |
|
||||
| `open-sse/services/autoCombo/providerRegistryAccessor.ts` | Test hook for mocking provider registry |
|
||||
| `src/sse/handlers/chat.ts` | Integration: auto prefix short-circuit |
|
||||
| `src/shared/constants/providers.ts` | `SYSTEM_PROVIDERS.auto` system entry |
|
||||
|
||||
## How It Works (Persisted Auto-Combos)
|
||||
|
||||
The Auto-Combo Engine dynamically selects the best provider/model for each request using a **6-factor scoring function**:
|
||||
|
||||
|
||||
@@ -351,23 +351,119 @@ They run as internal routes and use OmniRoute's model routing automatically.
|
||||
| `/v1/audio/speech` | Text-to-speech | ElevenLabs, OpenAI TTS |
|
||||
| `/v1/audio/transcriptions` | Speech-to-text | Deepgram, AssemblyAI |
|
||||
|
||||
### CLI Tools API (New in v3.8)
|
||||
|
||||
| Endpoint | Method | Description |
|
||||
| ------------------------------- | ------ | ------------------------------------------------ |
|
||||
| `/api/cli-tools/detect` | GET | Detect all installed CLI tools and config status |
|
||||
| `/api/cli-tools/detect?tool=ID` | GET | Detect a specific tool by ID |
|
||||
| `/api/cli-tools/config` | GET | List generated configs for all tools |
|
||||
| `/api/cli-tools/config` | POST | Generate config for a specific tool |
|
||||
| `/api/cli-tools/apply` | POST | Apply config to a tool (with backup) |
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
## CLI Commands Reference (New in v3.8)
|
||||
|
||||
| Error | Cause | Fix |
|
||||
| ------------------------- | ----------------------- | ------------------------------------------ |
|
||||
| `Connection refused` | OmniRoute not running | `pm2 start omniroute` |
|
||||
| `401 Unauthorized` | Wrong API key | Check in `/dashboard/api-manager` |
|
||||
| `No combo configured` | No active routing combo | Set up in `/dashboard/combos` |
|
||||
| `invalid model` | Model not in catalog | Use `auto` or check `/dashboard/providers` |
|
||||
| CLI shows "not installed" | Binary not in PATH | Check `which <command>` |
|
||||
| `kiro-cli: not found` | Not in PATH | `export PATH="$HOME/.local/bin:$PATH"` |
|
||||
### `omniroute config`
|
||||
|
||||
Manage CLI tool configurations directly from the terminal.
|
||||
|
||||
```bash
|
||||
omniroute config list # List all tools and config status
|
||||
omniroute config get <tool> # Show config for a specific tool
|
||||
omniroute config set <tool> \ # Generate and write config
|
||||
--api-key sk-your-key \
|
||||
[--base-url http://localhost:20128/v1] \
|
||||
[--model auto]
|
||||
omniroute config validate <tool> # Validate config without writing
|
||||
```
|
||||
|
||||
**Options:** `--base-url`, `--api-key`, `--model`, `--json`, `--non-interactive`, `--yes`, `--help`
|
||||
|
||||
### `omniroute status`
|
||||
|
||||
Show offline status dashboard with version, database, and tool info.
|
||||
|
||||
```bash
|
||||
omniroute status # Human-readable status
|
||||
omniroute status --json # JSON output
|
||||
omniroute status --verbose # Include tool detection details
|
||||
```
|
||||
|
||||
### `omniroute logs`
|
||||
|
||||
Stream usage logs from the API endpoint.
|
||||
|
||||
```bash
|
||||
omniroute logs # Fetch last 100 log lines
|
||||
omniroute logs --follow # Stream in real-time
|
||||
omniroute logs --filter error,warn # Filter by level
|
||||
omniroute logs --lines 500 # Fetch more lines
|
||||
omniroute logs --base-url http://localhost:20128
|
||||
```
|
||||
|
||||
**Options:** `--follow`, `--filter`, `--lines`, `--timeout`, `--base-url`, `--json`, `--help`
|
||||
|
||||
### `omniroute update`
|
||||
|
||||
Check for or apply OmniRoute updates.
|
||||
|
||||
```bash
|
||||
omniroute update --check # Check for updates only
|
||||
omniroute update --dry-run # Preview update without applying
|
||||
omniroute update --yes # Apply update without prompt
|
||||
omniroute update --no-backup # Skip backup creation
|
||||
```
|
||||
|
||||
**Options:** `--check`, `--dry-run`, `--backup`, `--no-backup`, `--yes`, `--help`
|
||||
|
||||
### `omniroute provider`
|
||||
|
||||
Manage provider connections from the CLI.
|
||||
|
||||
```bash
|
||||
omniroute provider add openai --api-key sk-xxx # Add a provider
|
||||
omniroute provider list # List all providers
|
||||
omniroute provider remove <name|id> # Remove a provider
|
||||
omniroute provider test <name|id> # Test connectivity
|
||||
omniroute provider default <name|id> # Set default provider
|
||||
```
|
||||
|
||||
**Options:** `--provider`, `--api-key`, `--provider-name`, `--default-model`, `--base-url`, `--json`, `--yes`, `--help`
|
||||
|
||||
---
|
||||
|
||||
## Quick Setup Script (One Command)
|
||||
|
||||
Set up all CLI tools and configure for OmniRoute:
|
||||
|
||||
```bash
|
||||
OMNIROUTE_URL="http://localhost:20128/v1"
|
||||
OMNIROUTE_ANTHROPIC_URL="http://localhost:20128"
|
||||
OMNIROUTE_KEY="sk-your-omniroute-key"
|
||||
|
||||
npm install -g @anthropic-ai/claude-code @openai/codex opencode-ai cline kilocode @qwen-code/qwen-code
|
||||
|
||||
# Kiro CLI
|
||||
apt-get install -y unzip 2>/dev/null; curl -fsSL https://cli.kiro.dev/install | bash
|
||||
|
||||
# Write configs
|
||||
mkdir -p ~/.claude ~/.codex ~/.config/opencode ~/.continue
|
||||
|
||||
cat > ~/.claude/settings.json <<< "{\"env\":{\"ANTHROPIC_BASE_URL\":\"$OMNIROUTE_ANTHROPIC_URL\",\"ANTHROPIC_AUTH_TOKEN\":\"$OMNIROUTE_KEY\"}}"
|
||||
cat > ~/.codex/config.yaml <<< "model: auto\napiKey: $OMNIROUTE_KEY\napiBaseUrl: $OMNIROUTE_URL"
|
||||
cat >> ~/.bashrc << EOF
|
||||
export OPENAI_BASE_URL="$OMNIROUTE_URL"
|
||||
export OPENAI_API_KEY="$OMNIROUTE_KEY"
|
||||
export ANTHROPIC_BASE_URL="$OMNIROUTE_ANTHROPIC_URL"
|
||||
export ANTHROPIC_AUTH_TOKEN="$OMNIROUTE_KEY"
|
||||
EOF
|
||||
|
||||
source ~/.bashrc
|
||||
echo "✅ All CLIs installed and configured for OmniRoute"
|
||||
```
|
||||
|
||||
```bash
|
||||
# Install all CLIs and configure for OmniRoute (replace with your key and server URL)
|
||||
OMNIROUTE_URL="http://localhost:20128/v1"
|
||||
|
||||
@@ -1,589 +0,0 @@
|
||||
# omniroute — Codebase Documentation
|
||||
|
||||
🌐 **Languages:** 🇺🇸 [English](CODEBASE_DOCUMENTATION.md) | 🇧🇷 [Português (Brasil)](i18n/pt-BR/CODEBASE_DOCUMENTATION.md) | 🇪🇸 [Español](i18n/es/CODEBASE_DOCUMENTATION.md) | 🇫🇷 [Français](i18n/fr/CODEBASE_DOCUMENTATION.md) | 🇮🇹 [Italiano](i18n/it/CODEBASE_DOCUMENTATION.md) | 🇷🇺 [Русский](i18n/ru/CODEBASE_DOCUMENTATION.md) | 🇨🇳 [中文 (简体)](i18n/zh-CN/CODEBASE_DOCUMENTATION.md) | 🇩🇪 [Deutsch](i18n/de/CODEBASE_DOCUMENTATION.md) | 🇮🇳 [हिन्दी](i18n/in/CODEBASE_DOCUMENTATION.md) | 🇹🇭 [ไทย](i18n/th/CODEBASE_DOCUMENTATION.md) | 🇺🇦 [Українська](i18n/uk-UA/CODEBASE_DOCUMENTATION.md) | 🇸🇦 [العربية](i18n/ar/CODEBASE_DOCUMENTATION.md) | 🇯🇵 [日本語](i18n/ja/CODEBASE_DOCUMENTATION.md) | 🇻🇳 [Tiếng Việt](i18n/vi/CODEBASE_DOCUMENTATION.md) | 🇧🇬 [Български](i18n/bg/CODEBASE_DOCUMENTATION.md) | 🇩🇰 [Dansk](i18n/da/CODEBASE_DOCUMENTATION.md) | 🇫🇮 [Suomi](i18n/fi/CODEBASE_DOCUMENTATION.md) | 🇮🇱 [עברית](i18n/he/CODEBASE_DOCUMENTATION.md) | 🇭🇺 [Magyar](i18n/hu/CODEBASE_DOCUMENTATION.md) | 🇮🇩 [Bahasa Indonesia](i18n/id/CODEBASE_DOCUMENTATION.md) | 🇰🇷 [한국어](i18n/ko/CODEBASE_DOCUMENTATION.md) | 🇲🇾 [Bahasa Melayu](i18n/ms/CODEBASE_DOCUMENTATION.md) | 🇳🇱 [Nederlands](i18n/nl/CODEBASE_DOCUMENTATION.md) | 🇳🇴 [Norsk](i18n/no/CODEBASE_DOCUMENTATION.md) | 🇵🇹 [Português (Portugal)](i18n/pt/CODEBASE_DOCUMENTATION.md) | 🇷🇴 [Română](i18n/ro/CODEBASE_DOCUMENTATION.md) | 🇵🇱 [Polski](i18n/pl/CODEBASE_DOCUMENTATION.md) | 🇸🇰 [Slovenčina](i18n/sk/CODEBASE_DOCUMENTATION.md) | 🇸🇪 [Svenska](i18n/sv/CODEBASE_DOCUMENTATION.md) | 🇵🇭 [Filipino](i18n/phi/CODEBASE_DOCUMENTATION.md) | 🇨🇿 [Čeština](i18n/cs/CODEBASE_DOCUMENTATION.md)
|
||||
|
||||
> A comprehensive, beginner-friendly guide to the **omniroute** multi-provider AI proxy router.
|
||||
|
||||
---
|
||||
|
||||
## 1. What Is omniroute?
|
||||
|
||||
omniroute is a **proxy router** that sits between AI clients (Claude CLI, Codex, Cursor IDE, etc.) and AI providers (Anthropic, Google, OpenAI, AWS, GitHub, etc.). It solves one big problem:
|
||||
|
||||
> **Different AI clients speak different "languages" (API formats), and different AI providers expect different "languages" too.** omniroute translates between them automatically.
|
||||
|
||||
Think of it like a universal translator at the United Nations — any delegate can speak any language, and the translator converts it for any other delegate.
|
||||
|
||||
---
|
||||
|
||||
## 2. Architecture Overview
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
subgraph Clients
|
||||
A[Claude CLI]
|
||||
B[Codex]
|
||||
C[Cursor IDE]
|
||||
D[OpenAI-compatible]
|
||||
end
|
||||
|
||||
subgraph omniroute
|
||||
E[Handler Layer]
|
||||
F[Translator Layer]
|
||||
G[Executor Layer]
|
||||
H[Services Layer]
|
||||
end
|
||||
|
||||
subgraph Providers
|
||||
I[Anthropic Claude]
|
||||
J[Google Gemini]
|
||||
K[OpenAI / Codex]
|
||||
L[GitHub Copilot]
|
||||
M[AWS Kiro]
|
||||
N[Antigravity]
|
||||
O[Cursor API]
|
||||
end
|
||||
|
||||
A --> E
|
||||
B --> E
|
||||
C --> E
|
||||
D --> E
|
||||
E --> F
|
||||
F --> G
|
||||
G --> I
|
||||
G --> J
|
||||
G --> K
|
||||
G --> L
|
||||
G --> M
|
||||
G --> N
|
||||
G --> O
|
||||
H -.-> E
|
||||
H -.-> G
|
||||
```
|
||||
|
||||
### Core Principle: Hub-and-Spoke Translation
|
||||
|
||||
All format translation passes through **OpenAI format as the hub**:
|
||||
|
||||
```
|
||||
Client Format → [OpenAI Hub] → Provider Format (request)
|
||||
Provider Format → [OpenAI Hub] → Client Format (response)
|
||||
```
|
||||
|
||||
This means you only need **N translators** (one per format) instead of **N²** (every pair).
|
||||
|
||||
---
|
||||
|
||||
## 3. Project Structure
|
||||
|
||||
```
|
||||
omniroute/
|
||||
├── open-sse/ ← Core proxy library (portable, framework-agnostic)
|
||||
│ ├── index.js ← Main entry point, exports everything
|
||||
│ ├── config/ ← Configuration & constants
|
||||
│ ├── executors/ ← Provider-specific request execution
|
||||
│ ├── handlers/ ← Request handling orchestration
|
||||
│ ├── services/ ← Business logic (auth, models, fallback, usage)
|
||||
│ ├── translator/ ← Format translation engine
|
||||
│ │ ├── request/ ← Request translators (8 files)
|
||||
│ │ ├── response/ ← Response translators (7 files)
|
||||
│ │ └── helpers/ ← Shared translation utilities (6 files)
|
||||
│ └── utils/ ← Utility functions
|
||||
├── src/ ← Application layer (Express/Worker runtime)
|
||||
│ ├── app/ ← Web UI, API routes, middleware
|
||||
│ ├── lib/ ← Database, auth, and shared library code
|
||||
│ ├── mitm/ ← Man-in-the-middle proxy utilities
|
||||
│ ├── models/ ← Database models
|
||||
│ ├── shared/ ← Shared utilities (wrappers around open-sse)
|
||||
│ ├── sse/ ← SSE endpoint handlers
|
||||
│ └── store/ ← State management
|
||||
├── data/ ← Runtime data (credentials, logs)
|
||||
│ └── provider-credentials.json (external credentials override, gitignored)
|
||||
└── tester/ ← Test utilities
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Module-by-Module Breakdown
|
||||
|
||||
### 4.1 Config (`open-sse/config/`)
|
||||
|
||||
The **single source of truth** for all provider configuration.
|
||||
|
||||
| File | Purpose |
|
||||
| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `constants.ts` | `PROVIDERS` object with base URLs, OAuth credentials (defaults), headers, and default system prompts for every provider. Also defines `HTTP_STATUS`, `ERROR_TYPES`, `COOLDOWN_MS`, `BACKOFF_CONFIG`, and `SKIP_PATTERNS`. |
|
||||
| `credentialLoader.ts` | Loads external credentials from `data/provider-credentials.json` and merges them over the hardcoded defaults in `PROVIDERS`. Keeps secrets out of source control while maintaining backwards compatibility. |
|
||||
| `providerModels.ts` | Central model registry: maps provider aliases → model IDs. Functions like `getModels()`, `getProviderByAlias()`. |
|
||||
| `codexInstructions.ts` | System instructions injected into Codex requests (editing constraints, sandbox rules, approval policies). |
|
||||
| `defaultThinkingSignature.ts` | Default "thinking" signatures for Claude and Gemini models. |
|
||||
| `ollamaModels.ts` | Schema definition for local Ollama models (name, size, family, quantization). |
|
||||
|
||||
#### Credential Loading Flow
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A["App starts"] --> B["constants.ts defines PROVIDERS\nwith hardcoded defaults"]
|
||||
B --> C{"data/provider-credentials.json\nexists?"}
|
||||
C -->|Yes| D["credentialLoader reads JSON"]
|
||||
C -->|No| E["Use hardcoded defaults"]
|
||||
D --> F{"For each provider in JSON"}
|
||||
F --> G{"Provider exists\nin PROVIDERS?"}
|
||||
G -->|No| H["Log warning, skip"]
|
||||
G -->|Yes| I{"Value is object?"}
|
||||
I -->|No| J["Log warning, skip"]
|
||||
I -->|Yes| K["Merge clientId, clientSecret,\ntokenUrl, authUrl, refreshUrl"]
|
||||
K --> F
|
||||
H --> F
|
||||
J --> F
|
||||
F -->|Done| L["PROVIDERS ready with\nmerged credentials"]
|
||||
E --> L
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 4.2 Executors (`open-sse/executors/`)
|
||||
|
||||
Executors encapsulate **provider-specific logic** using the **Strategy Pattern**. Each executor overrides base methods as needed.
|
||||
|
||||
```mermaid
|
||||
classDiagram
|
||||
class BaseExecutor {
|
||||
+buildUrl(model, stream, options)
|
||||
+buildHeaders(credentials, stream, body)
|
||||
+transformRequest(body, model, stream, credentials)
|
||||
+execute(url, options)
|
||||
+shouldRetry(status, error)
|
||||
+refreshCredentials(credentials, log)
|
||||
}
|
||||
|
||||
class DefaultExecutor {
|
||||
+refreshCredentials()
|
||||
}
|
||||
|
||||
class AntigravityExecutor {
|
||||
+buildUrl()
|
||||
+buildHeaders()
|
||||
+transformRequest()
|
||||
+shouldRetry()
|
||||
+refreshCredentials()
|
||||
}
|
||||
|
||||
class CursorExecutor {
|
||||
+buildUrl()
|
||||
+buildHeaders()
|
||||
+transformRequest()
|
||||
+parseResponse()
|
||||
+generateChecksum()
|
||||
}
|
||||
|
||||
class KiroExecutor {
|
||||
+buildUrl()
|
||||
+buildHeaders()
|
||||
+transformRequest()
|
||||
+parseEventStream()
|
||||
+refreshCredentials()
|
||||
}
|
||||
|
||||
BaseExecutor <|-- DefaultExecutor
|
||||
BaseExecutor <|-- AntigravityExecutor
|
||||
BaseExecutor <|-- CursorExecutor
|
||||
BaseExecutor <|-- KiroExecutor
|
||||
BaseExecutor <|-- CodexExecutor
|
||||
BaseExecutor <|-- GeminiCLIExecutor
|
||||
BaseExecutor <|-- GithubExecutor
|
||||
```
|
||||
|
||||
| Executor | Provider | Key Specializations |
|
||||
| ---------------- | ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------- |
|
||||
| `base.ts` | — | Abstract base: URL building, headers, retry logic, credential refresh |
|
||||
| `default.ts` | Claude, Gemini, OpenAI, GLM, Kimi, MiniMax | Generic OAuth token refresh for standard providers |
|
||||
| `antigravity.ts` | Google Cloud Code | Project/session ID generation, multi-URL fallback, custom retry parsing from error messages ("reset after 2h7m23s") |
|
||||
| `cursor.ts` | Cursor IDE | **Most complex**: SHA-256 checksum auth, Protobuf request encoding, binary EventStream → SSE response parsing |
|
||||
| `codex.ts` | OpenAI Codex | Injects system instructions, manages thinking levels, removes unsupported parameters |
|
||||
| `gemini-cli.ts` | Google Gemini CLI | Custom URL building (`streamGenerateContent`), Google OAuth token refresh |
|
||||
| `github.ts` | GitHub Copilot | Dual token system (GitHub OAuth + Copilot token), VSCode header mimicking |
|
||||
| `kiro.ts` | AWS CodeWhisperer | AWS EventStream binary parsing, AMZN event frames, token estimation |
|
||||
| `index.ts` | — | Factory: maps provider name → executor class, with default fallback |
|
||||
|
||||
---
|
||||
|
||||
### 4.3 Handlers (`open-sse/handlers/`)
|
||||
|
||||
The **orchestration layer** — coordinates translation, execution, streaming, and error handling.
|
||||
|
||||
| File | Purpose |
|
||||
| --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `chatCore.ts` | **Central orchestrator** (~600 lines). Handles the complete request lifecycle: format detection → translation → executor dispatch → streaming/non-streaming response → token refresh → error handling → usage logging. |
|
||||
| `responsesHandler.ts` | Adapter for OpenAI's Responses API: converts Responses format → Chat Completions → sends to `chatCore` → converts SSE back to Responses format. |
|
||||
| `embeddings.ts` | Embedding generation handler: resolves embedding model → provider, dispatches to provider API, returns OpenAI-compatible embedding response. Supports 6+ providers. |
|
||||
| `imageGeneration.ts` | Image generation handler: resolves image model → provider, supports OpenAI-compatible, Gemini-image (Antigravity), and fallback (Nebius) modes. Returns base64 or URL images. |
|
||||
|
||||
#### Request Lifecycle (chatCore.ts)
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Client
|
||||
participant chatCore
|
||||
participant Translator
|
||||
participant Executor
|
||||
participant Provider
|
||||
|
||||
Client->>chatCore: Request (any format)
|
||||
chatCore->>chatCore: Detect source format
|
||||
chatCore->>chatCore: Check bypass patterns
|
||||
chatCore->>chatCore: Resolve model & provider
|
||||
chatCore->>Translator: Translate request (source → OpenAI → target)
|
||||
chatCore->>Executor: Get executor for provider
|
||||
Executor->>Executor: Build URL, headers, transform request
|
||||
Executor->>Executor: Refresh credentials if needed
|
||||
Executor->>Provider: HTTP fetch (streaming or non-streaming)
|
||||
|
||||
alt Streaming
|
||||
Provider-->>chatCore: SSE stream
|
||||
chatCore->>chatCore: Pipe through SSE transform stream
|
||||
Note over chatCore: Transform stream translates<br/>each chunk: target → OpenAI → source
|
||||
chatCore-->>Client: Translated SSE stream
|
||||
else Non-streaming
|
||||
Provider-->>chatCore: JSON response
|
||||
chatCore->>Translator: Translate response
|
||||
chatCore-->>Client: Translated JSON
|
||||
end
|
||||
|
||||
alt Error (401, 429, 500...)
|
||||
chatCore->>Executor: Retry with credential refresh
|
||||
chatCore->>chatCore: Account fallback logic
|
||||
end
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 4.4 Services (`open-sse/services/`)
|
||||
|
||||
Business logic that supports the handlers and executors.
|
||||
|
||||
| File | Purpose |
|
||||
| -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `provider.ts` | **Format detection** (`detectFormat`): analyzes request body structure to identify Claude/OpenAI/Gemini/Antigravity/Responses formats (includes `max_tokens` heuristic for Claude). Also: URL building, header building, thinking config normalization. Supports `openai-compatible-*` and `anthropic-compatible-*` dynamic providers. |
|
||||
| `model.ts` | Model string parsing (`claude/model-name` → `{provider: "claude", model: "model-name"}`), alias resolution with collision detection, input sanitization (rejects path traversal/control chars), and model info resolution with async alias getter support. |
|
||||
| `accountFallback.ts` | Rate-limit handling: exponential backoff (1s → 2s → 4s → max 2min), account cooldown management, error classification (which errors trigger fallback vs. not). |
|
||||
| `tokenRefresh.ts` | OAuth token refresh for **every provider**: Google (Gemini, Antigravity), Claude, Codex, Qwen, Qoder, GitHub (OAuth + Copilot dual-token), Kiro (AWS SSO OIDC + Social Auth). Includes in-flight promise deduplication cache and retry with exponential backoff. |
|
||||
| `combo.ts` | **Combo models**: chains of fallback models. If model A fails with a fallback-eligible error, try model B, then C, etc. Returns actual upstream status codes. |
|
||||
| `usage.ts` | Fetches quota/usage data from provider APIs (GitHub Copilot quotas, Antigravity model quotas, Codex rate limits, Kiro usage breakdowns, Claude settings). |
|
||||
| `accountSelector.ts` | Smart account selection with scoring algorithm: considers priority, health status, round-robin position, and cooldown state to pick the optimal account for each request. |
|
||||
| `contextManager.ts` | Request context lifecycle management: creates and tracks per-request context objects with metadata (request ID, timestamps, provider info) for debugging and logging. |
|
||||
| `ipFilter.ts` | IP-based access control: supports allowlist and blocklist modes. Validates client IP against configured rules before processing API requests. |
|
||||
| `sessionManager.ts` | Session tracking with client fingerprinting: tracks active sessions using hashed client identifiers, monitors request counts, and provides session metrics. |
|
||||
| `signatureCache.ts` | Request signature-based deduplication cache: prevents duplicate requests by caching recent request signatures and returning cached responses for identical requests within a time window. |
|
||||
| `systemPrompt.ts` | Global system prompt injection: prepends or appends a configurable system prompt to all requests, with per-provider compatibility handling. |
|
||||
| `thinkingBudget.ts` | Reasoning token budget management: supports passthrough, auto (strip thinking config), custom (fixed budget), and adaptive (complexity-scaled) modes for controlling thinking/reasoning tokens. |
|
||||
| `wildcardRouter.ts` | Wildcard model pattern routing: resolves wildcard patterns (e.g., `*/claude-*`) to concrete provider/model pairs based on availability and priority. |
|
||||
|
||||
#### Token Refresh Deduplication
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant R1 as Request 1
|
||||
participant R2 as Request 2
|
||||
participant Cache as refreshPromiseCache
|
||||
participant OAuth as OAuth Provider
|
||||
|
||||
R1->>Cache: getAccessToken("gemini", token)
|
||||
Cache->>Cache: No in-flight promise
|
||||
Cache->>OAuth: Start refresh
|
||||
R2->>Cache: getAccessToken("gemini", token)
|
||||
Cache->>Cache: Found in-flight promise
|
||||
Cache-->>R2: Return existing promise
|
||||
OAuth-->>Cache: New access token
|
||||
Cache-->>R1: New access token
|
||||
Cache-->>R2: Same access token (shared)
|
||||
Cache->>Cache: Delete cache entry
|
||||
```
|
||||
|
||||
#### Account Fallback State Machine
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
[*] --> Active
|
||||
Active --> Error: Request fails (401/429/500)
|
||||
Error --> Cooldown: Apply backoff
|
||||
Cooldown --> Active: Cooldown expires
|
||||
Active --> Active: Request succeeds (reset backoff)
|
||||
|
||||
state Error {
|
||||
[*] --> ClassifyError
|
||||
ClassifyError --> ShouldFallback: Rate limit / Auth / Transient
|
||||
ClassifyError --> NoFallback: 400 Bad Request
|
||||
}
|
||||
|
||||
state Cooldown {
|
||||
[*] --> ExponentialBackoff
|
||||
ExponentialBackoff: Level 0 = 1s
|
||||
ExponentialBackoff: Level 1 = 2s
|
||||
ExponentialBackoff: Level 2 = 4s
|
||||
ExponentialBackoff: Max = 2min
|
||||
}
|
||||
```
|
||||
|
||||
#### Combo Model Chain
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A["Request with\ncombo model"] --> B["Model A"]
|
||||
B -->|"2xx Success"| C["Return response"]
|
||||
B -->|"429/401/500"| D{"Fallback\neligible?"}
|
||||
D -->|Yes| E["Model B"]
|
||||
D -->|No| F["Return error"]
|
||||
E -->|"2xx Success"| C
|
||||
E -->|"429/401/500"| G{"Fallback\neligible?"}
|
||||
G -->|Yes| H["Model C"]
|
||||
G -->|No| F
|
||||
H -->|"2xx Success"| C
|
||||
H -->|"Fail"| I["All failed →\nReturn last status"]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 4.5 Translator (`open-sse/translator/`)
|
||||
|
||||
The **format translation engine** using a self-registering plugin system.
|
||||
|
||||
#### Architecture
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
subgraph "Request Translation"
|
||||
A["Claude → OpenAI"]
|
||||
B["Gemini → OpenAI"]
|
||||
C["Antigravity → OpenAI"]
|
||||
D["OpenAI Responses → OpenAI"]
|
||||
E["OpenAI → Claude"]
|
||||
F["OpenAI → Gemini"]
|
||||
G["OpenAI → Kiro"]
|
||||
H["OpenAI → Cursor"]
|
||||
end
|
||||
|
||||
subgraph "Response Translation"
|
||||
I["Claude → OpenAI"]
|
||||
J["Gemini → OpenAI"]
|
||||
K["Kiro → OpenAI"]
|
||||
L["Cursor → OpenAI"]
|
||||
M["OpenAI → Claude"]
|
||||
N["OpenAI → Antigravity"]
|
||||
O["OpenAI → Responses"]
|
||||
end
|
||||
```
|
||||
|
||||
| Directory | Files | Description |
|
||||
| ------------ | ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `request/` | 8 translators | Convert request bodies between formats. Each file self-registers via `register(from, to, fn)` on import. |
|
||||
| `response/` | 7 translators | Convert streaming response chunks between formats. Handles SSE event types, thinking blocks, tool calls. |
|
||||
| `helpers/` | 6 helpers | Shared utilities: `claudeHelper` (system prompt extraction, thinking config), `geminiHelper` (parts/contents mapping), `openaiHelper` (format filtering), `toolCallHelper` (ID generation, missing response injection), `maxTokensHelper`, `responsesApiHelper`. |
|
||||
| `index.ts` | — | Translation engine: `translateRequest()`, `translateResponse()`, state management, registry. |
|
||||
| `formats.ts` | — | Format constants: `OPENAI`, `CLAUDE`, `GEMINI`, `ANTIGRAVITY`, `KIRO`, `CURSOR`, `OPENAI_RESPONSES`. |
|
||||
|
||||
#### Key Design: Self-Registering Plugins
|
||||
|
||||
```javascript
|
||||
// Each translator file calls register() on import:
|
||||
import { register } from "../index.js";
|
||||
register("claude", "openai", translateClaudeToOpenAI);
|
||||
|
||||
// The index.js imports all translator files, triggering registration:
|
||||
import "./request/claude-to-openai.js"; // ← self-registers
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 4.6 Utils (`open-sse/utils/`)
|
||||
|
||||
| File | Purpose |
|
||||
| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| `error.ts` | Error response building (OpenAI-compatible format), upstream error parsing, Antigravity retry-time extraction from error messages, SSE error streaming. |
|
||||
| `stream.ts` | **SSE Transform Stream** — the core streaming pipeline. Two modes: `TRANSLATE` (full format translation) and `PASSTHROUGH` (normalize + extract usage). Handles chunk buffering, usage estimation, content length tracking. Per-stream encoder/decoder instances avoid shared state. |
|
||||
| `streamHelpers.ts` | Low-level SSE utilities: `parseSSELine` (whitespace-tolerant), `hasValuableContent` (filters empty chunks for OpenAI/Claude/Gemini), `fixInvalidId`, `formatSSE` (format-aware SSE serialization with `perf_metrics` cleanup). |
|
||||
| `usageTracking.ts` | Token usage extraction from any format (Claude/OpenAI/Gemini/Responses), estimation with separate tool/message char-per-token ratios, buffer addition (2000 tokens safety margin), format-specific field filtering, console logging with ANSI colors. |
|
||||
| `requestLogger.ts` | Legacy file-based request logging helper kept for compatibility. Current deployments should prefer `APP_LOG_TO_FILE` for application logs and the call log pipeline for persisted request artifacts. |
|
||||
| `bypassHandler.ts` | Intercepts specific patterns from Claude CLI (title extraction, warmup, count) and returns fake responses without calling any provider. Supports both streaming and non-streaming. Intentionally limited to Claude CLI scope. |
|
||||
| `networkProxy.ts` | Resolves outbound proxy URL for a given provider with precedence: provider-specific config → global config → environment variables (`HTTPS_PROXY`/`HTTP_PROXY`/`ALL_PROXY`). Supports `NO_PROXY` exclusions. Caches config for 30s. |
|
||||
|
||||
#### SSE Streaming Pipeline
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A["Provider SSE stream"] --> B["TextDecoder\n(per-stream instance)"]
|
||||
B --> C["Buffer lines\n(split on newline)"]
|
||||
C --> D["parseSSELine()\n(trim whitespace, parse JSON)"]
|
||||
D --> E{"Mode?"}
|
||||
E -->|TRANSLATE| F["translateResponse()\ntarget → OpenAI → source"]
|
||||
E -->|PASSTHROUGH| G["fixInvalidId()\nnormalize chunk"]
|
||||
F --> H["hasValuableContent()\nfilter empty chunks"]
|
||||
G --> H
|
||||
H -->|"Has content"| I["extractUsage()\ntrack token counts"]
|
||||
H -->|"Empty"| J["Skip chunk"]
|
||||
I --> K["formatSSE()\nserialize + clean perf_metrics"]
|
||||
K --> L["TextEncoder\n(per-stream instance)"]
|
||||
L --> M["Enqueue to\nclient stream"]
|
||||
|
||||
style A fill:#f9f,stroke:#333
|
||||
style M fill:#9f9,stroke:#333
|
||||
```
|
||||
|
||||
#### Request Logger Session Structure
|
||||
|
||||
```
|
||||
logs/
|
||||
└── claude_gemini_claude-sonnet_20260208_143045/
|
||||
├── 1_req_client.json ← Raw client request
|
||||
├── 2_req_source.json ← After initial conversion
|
||||
├── 3_req_openai.json ← OpenAI intermediate format
|
||||
├── 4_req_target.json ← Final target format
|
||||
├── 5_res_provider.txt ← Provider SSE chunks (streaming)
|
||||
├── 5_res_provider.json ← Provider response (non-streaming)
|
||||
├── 6_res_openai.txt ← OpenAI intermediate chunks
|
||||
├── 7_res_client.txt ← Client-facing SSE chunks
|
||||
└── 6_error.json ← Error details (if any)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 4.7 Application Layer (`src/`)
|
||||
|
||||
| Directory | Purpose |
|
||||
| ------------- | ---------------------------------------------------------------------- |
|
||||
| `src/app/` | Web UI, API routes, Express middleware, OAuth callback handlers |
|
||||
| `src/lib/` | Database access (`localDb.ts`, `usageDb.ts`), authentication, shared |
|
||||
| `src/mitm/` | Man-in-the-middle proxy utilities for intercepting provider traffic |
|
||||
| `src/models/` | Database model definitions |
|
||||
| `src/shared/` | Wrappers around open-sse functions (provider, stream, error, etc.) |
|
||||
| `src/sse/` | SSE endpoint handlers that wire the open-sse library to Express routes |
|
||||
| `src/store/` | Application state management |
|
||||
|
||||
#### Notable API Routes
|
||||
|
||||
| Route | Methods | Purpose |
|
||||
| --------------------------------------------- | --------------- | ------------------------------------------------------------------------------------- |
|
||||
| `/api/provider-models` | GET/POST/DELETE | CRUD for custom models per provider |
|
||||
| `/api/models/catalog` | GET | Aggregated catalog of all models (chat, embedding, image, custom) grouped by provider |
|
||||
| `/api/settings/proxy` | GET/PUT/DELETE | Hierarchical outbound proxy configuration (`global/providers/combos/keys`) |
|
||||
| `/api/settings/proxy/test` | POST | Validates proxy connectivity and returns public IP/latency |
|
||||
| `/v1/providers/[provider]/chat/completions` | POST | Dedicated per-provider chat completions with model validation |
|
||||
| `/v1/providers/[provider]/embeddings` | POST | Dedicated per-provider embeddings with model validation |
|
||||
| `/v1/providers/[provider]/images/generations` | POST | Dedicated per-provider image generation with model validation |
|
||||
| `/api/settings/ip-filter` | GET/PUT | IP allowlist/blocklist management |
|
||||
| `/api/settings/thinking-budget` | GET/PUT | Reasoning token budget configuration (passthrough/auto/custom/adaptive) |
|
||||
| `/api/settings/system-prompt` | GET/PUT | Global system prompt injection for all requests |
|
||||
| `/api/sessions` | GET | Active session tracking and metrics |
|
||||
| `/api/rate-limits` | GET | Per-account rate limit status |
|
||||
|
||||
---
|
||||
|
||||
## 5. Key Design Patterns
|
||||
|
||||
### 5.1 Hub-and-Spoke Translation
|
||||
|
||||
All formats translate through **OpenAI format as the hub**. Adding a new provider only requires writing **one pair** of translators (to/from OpenAI), not N pairs.
|
||||
|
||||
### 5.2 Executor Strategy Pattern
|
||||
|
||||
Each provider has a dedicated executor class inheriting from `BaseExecutor`. The factory in `executors/index.ts` selects the right one at runtime.
|
||||
|
||||
### 5.3 Self-Registering Plugin System
|
||||
|
||||
Translator modules register themselves on import via `register()`. Adding a new translator is just creating a file and importing it.
|
||||
|
||||
### 5.4 Account Fallback with Exponential Backoff
|
||||
|
||||
When a provider returns 429/401/500, the system can switch to the next account, applying exponential cooldowns (1s → 2s → 4s → max 2min).
|
||||
|
||||
### 5.5 Combo Model Chains
|
||||
|
||||
A "combo" groups multiple `provider/model` strings. If the first fails, fallback to the next automatically.
|
||||
|
||||
### 5.6 Stateful Streaming Translation
|
||||
|
||||
Response translation maintains state across SSE chunks (thinking block tracking, tool call accumulation, content block indexing) via the `initState()` mechanism.
|
||||
|
||||
### 5.7 Usage Safety Buffer
|
||||
|
||||
A 2000-token buffer is added to reported usage to prevent clients from hitting context window limits due to overhead from system prompts and format translation.
|
||||
|
||||
---
|
||||
|
||||
## 6. Supported Formats
|
||||
|
||||
| Format | Direction | Identifier |
|
||||
| ----------------------- | --------------- | ------------------ |
|
||||
| OpenAI Chat Completions | source + target | `openai` |
|
||||
| OpenAI Responses API | source + target | `openai-responses` |
|
||||
| Anthropic Claude | source + target | `claude` |
|
||||
| Google Gemini | source + target | `gemini` |
|
||||
| Google Gemini CLI | target only | `gemini-cli` |
|
||||
| Antigravity | source + target | `antigravity` |
|
||||
| AWS Kiro | target only | `kiro` |
|
||||
| Cursor | target only | `cursor` |
|
||||
|
||||
---
|
||||
|
||||
## 7. Supported Providers
|
||||
|
||||
| Provider | Auth Method | Executor | Key Notes |
|
||||
| ------------------------ | ---------------------- | ----------- | --------------------------------------------- |
|
||||
| Anthropic Claude | API key or OAuth | Default | Uses `x-api-key` header |
|
||||
| Google Gemini | API key or OAuth | Default | Uses `x-goog-api-key` header |
|
||||
| Google Gemini CLI | OAuth | GeminiCLI | Uses `streamGenerateContent` endpoint |
|
||||
| Antigravity | OAuth | Antigravity | Multi-URL fallback, custom retry parsing |
|
||||
| OpenAI | API key | Default | Standard Bearer auth |
|
||||
| Codex | OAuth | Codex | Injects system instructions, manages thinking |
|
||||
| GitHub Copilot | OAuth + Copilot token | Github | Dual token, VSCode header mimicking |
|
||||
| Kiro (AWS) | AWS SSO OIDC or Social | Kiro | Binary EventStream parsing |
|
||||
| Cursor IDE | Checksum auth | Cursor | Protobuf encoding, SHA-256 checksums |
|
||||
| Qwen | OAuth | Default | Standard auth |
|
||||
| Qoder | OAuth (Basic + Bearer) | Default | Dual auth header |
|
||||
| OpenRouter | API key | Default | Standard Bearer auth |
|
||||
| GLM, Kimi, MiniMax | API key | Default | Claude-compatible, use `x-api-key` |
|
||||
| `openai-compatible-*` | API key | Default | Dynamic: any OpenAI-compatible endpoint |
|
||||
| `anthropic-compatible-*` | API key | Default | Dynamic: any Claude-compatible endpoint |
|
||||
|
||||
---
|
||||
|
||||
## 8. Data Flow Summary
|
||||
|
||||
### Streaming Request
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A["Client"] --> B["detectFormat()"]
|
||||
B --> C["translateRequest()\nsource → OpenAI → target"]
|
||||
C --> D["Executor\nbuildUrl + buildHeaders"]
|
||||
D --> E["fetch(providerURL)"]
|
||||
E --> F["createSSEStream()\nTRANSLATE mode"]
|
||||
F --> G["parseSSELine()"]
|
||||
G --> H["translateResponse()\ntarget → OpenAI → source"]
|
||||
H --> I["extractUsage()\n+ addBuffer"]
|
||||
I --> J["formatSSE()"]
|
||||
J --> K["Client receives\ntranslated SSE"]
|
||||
K --> L["logUsage()\nsaveRequestUsage()"]
|
||||
```
|
||||
|
||||
### Non-Streaming Request
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A["Client"] --> B["detectFormat()"]
|
||||
B --> C["translateRequest()\nsource → OpenAI → target"]
|
||||
C --> D["Executor.execute()"]
|
||||
D --> E["translateResponse()\ntarget → OpenAI → source"]
|
||||
E --> F["Return JSON\nresponse"]
|
||||
```
|
||||
|
||||
### Bypass Flow (Claude CLI)
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A["Claude CLI request"] --> B{"Match bypass\npattern?"}
|
||||
B -->|"Title/Warmup/Count"| C["Generate fake\nOpenAI response"]
|
||||
B -->|"No match"| D["Normal flow"]
|
||||
C --> E["Translate to\nsource format"]
|
||||
E --> F["Return without\ncalling provider"]
|
||||
```
|
||||
@@ -1,119 +0,0 @@
|
||||
# 🐳 Docker Guide — OmniRoute
|
||||
|
||||
> Complete Docker deployment reference. For a quick start, see the [README Docker section](../README.md#-docker).
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Quick Run](#quick-run)
|
||||
- [With Environment File](#with-environment-file)
|
||||
- [Docker Compose](#docker-compose)
|
||||
- [Docker Compose with Caddy (HTTPS)](#docker-compose-with-caddy-https-auto-tls)
|
||||
- [Cloudflare Quick Tunnel](#cloudflare-quick-tunnel)
|
||||
- [Image Tags](#image-tags)
|
||||
- [Important Notes](#important-notes)
|
||||
|
||||
---
|
||||
|
||||
## Quick Run
|
||||
|
||||
```bash
|
||||
docker run -d \
|
||||
--name omniroute \
|
||||
--restart unless-stopped \
|
||||
--stop-timeout 40 \
|
||||
-p 20128:20128 \
|
||||
-v omniroute-data:/app/data \
|
||||
diegosouzapw/omniroute:latest
|
||||
```
|
||||
|
||||
## With Environment File
|
||||
|
||||
```bash
|
||||
# Copy and edit .env first
|
||||
cp .env.example .env
|
||||
|
||||
docker run -d \
|
||||
--name omniroute \
|
||||
--restart unless-stopped \
|
||||
--stop-timeout 40 \
|
||||
--env-file .env \
|
||||
-p 20128:20128 \
|
||||
-v omniroute-data:/app/data \
|
||||
diegosouzapw/omniroute:latest
|
||||
```
|
||||
|
||||
## Docker Compose
|
||||
|
||||
```bash
|
||||
# Base profile (no CLI tools)
|
||||
docker compose --profile base up -d
|
||||
|
||||
# CLI profile (Claude Code, Codex, OpenClaw built-in)
|
||||
docker compose --profile cli up -d
|
||||
```
|
||||
|
||||
## Docker Compose with Caddy (HTTPS Auto-TLS)
|
||||
|
||||
OmniRoute can be securely exposed using Caddy's automatic SSL provisioning. Ensure your domain's DNS A record points to your server's IP.
|
||||
|
||||
```yaml
|
||||
services:
|
||||
omniroute:
|
||||
image: diegosouzapw/omniroute:latest
|
||||
container_name: omniroute
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- omniroute-data:/app/data
|
||||
environment:
|
||||
- PORT=20128
|
||||
- NEXT_PUBLIC_BASE_URL=https://your-domain.com
|
||||
|
||||
caddy:
|
||||
image: caddy:latest
|
||||
container_name: caddy
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
command: caddy reverse-proxy --from https://your-domain.com --to http://omniroute:20128
|
||||
|
||||
volumes:
|
||||
omniroute-data:
|
||||
```
|
||||
|
||||
## Cloudflare Quick Tunnel
|
||||
|
||||
Dashboard support for Docker deployments includes a one-click **Cloudflare Quick Tunnel** on `Dashboard → Endpoints`. The first enable downloads `cloudflared` only when needed, starts a temporary tunnel to your current `/v1` endpoint, and shows the generated `https://*.trycloudflare.com/v1` URL directly below your normal public URL.
|
||||
|
||||
Endpoint tunnel panels (Cloudflare, Tailscale, ngrok) can be shown or hidden from `Settings → Appearance` without changing active tunnel state.
|
||||
|
||||
### Tunnel Notes
|
||||
|
||||
- Quick Tunnel URLs are temporary and change after every restart.
|
||||
- Quick Tunnels are not auto-restored after an OmniRoute or container restart. Re-enable them from the dashboard when needed.
|
||||
- Managed install currently supports Linux, macOS, and Windows on `x64` / `arm64`.
|
||||
- Managed Quick Tunnels default to HTTP/2 transport to avoid noisy QUIC UDP buffer warnings in constrained container environments. Set `CLOUDFLARED_PROTOCOL=quic` or `auto` if you want a different transport.
|
||||
- Docker images bundle system CA roots and pass them to managed `cloudflared`, which avoids TLS trust failures when the tunnel bootstraps inside the container.
|
||||
- Set `CLOUDFLARED_BIN=/absolute/path/to/cloudflared` if you want OmniRoute to use an existing binary instead of downloading one.
|
||||
|
||||
## Image Tags
|
||||
|
||||
| Image | Tag | Size | Description |
|
||||
| ------------------------ | -------- | ------ | --------------------- |
|
||||
| `diegosouzapw/omniroute` | `latest` | ~250MB | Latest stable release |
|
||||
| `diegosouzapw/omniroute` | `3.7.8` | ~250MB | Current version |
|
||||
|
||||
Multi-platform: AMD64 + ARM64 native (Apple Silicon, AWS Graviton, Raspberry Pi).
|
||||
|
||||
## Important Notes
|
||||
|
||||
- **SQLite WAL Mode:** `docker stop` should be allowed to finish so OmniRoute can checkpoint the latest changes back into `storage.sqlite`. The bundled Compose files already set a 40s stop grace period. If you run the image directly, keep `--stop-timeout 40`.
|
||||
- **`DISABLE_SQLITE_AUTO_BACKUP`:** Set to `true` if backups are managed externally.
|
||||
- **Data Persistence:** Always mount a volume to `/app/data` to persist your database, keys, and configurations across container restarts.
|
||||
- **Port Configuration:** Override `PORT` environment variable to change the default `20128` port.
|
||||
|
||||
## See Also
|
||||
|
||||
- [VM Deployment Guide](VM_DEPLOYMENT_GUIDE.md) — VM + nginx + Cloudflare setup
|
||||
- [Fly.io Deployment Guide](FLY_IO_DEPLOYMENT_GUIDE.md) — Deploy to Fly.io
|
||||
- [Environment Config](ENVIRONMENT.md) — Complete `.env` reference
|
||||
@@ -1,679 +0,0 @@
|
||||
# Environment Variables Reference
|
||||
|
||||
> Complete reference for every environment variable recognized by OmniRoute.
|
||||
> For a quick-start template, see [`.env.example`](../.env.example).
|
||||
|
||||
---
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [1. Required Secrets](#1-required-secrets)
|
||||
- [2. Storage & Database](#2-storage--database)
|
||||
- [3. Network & Ports](#3-network--ports)
|
||||
- [4. Security & Authentication](#4-security--authentication)
|
||||
- [5. Input Sanitization & PII Protection](#5-input-sanitization--pii-protection)
|
||||
- [6. Tool & Routing Policies](#6-tool--routing-policies)
|
||||
- [7. URLs & Cloud Sync](#7-urls--cloud-sync)
|
||||
- [8. Outbound Proxy](#8-outbound-proxy)
|
||||
- [9. CLI Tool Integration](#9-cli-tool-integration)
|
||||
- [10. Internal Agent & MCP Integrations](#10-internal-agent--mcp-integrations)
|
||||
- [11. OAuth Provider Credentials](#11-oauth-provider-credentials)
|
||||
- [12. Provider User-Agent Overrides](#12-provider-user-agent-overrides)
|
||||
- [13. CLI Fingerprint Compatibility](#13-cli-fingerprint-compatibility)
|
||||
- [14. API Key Providers](#14-api-key-providers)
|
||||
- [15. Timeout Settings](#15-timeout-settings)
|
||||
- [16. Logging](#16-logging)
|
||||
- [17. Memory Optimization](#17-memory-optimization)
|
||||
- [18. Pricing Sync](#18-pricing-sync)
|
||||
- [19. Model Sync (Dev)](#19-model-sync-dev)
|
||||
- [20. Provider-Specific Settings](#20-provider-specific-settings)
|
||||
- [21. Proxy Health](#21-proxy-health)
|
||||
- [22. Debugging](#22-debugging)
|
||||
- [23. GitHub Integration](#23-github-integration)
|
||||
- [Deployment Scenarios](#deployment-scenarios)
|
||||
- [Audit: Removed / Dead Variables](#audit-removed--dead-variables)
|
||||
|
||||
---
|
||||
|
||||
## 1. Required Secrets
|
||||
|
||||
These **must** be set before the first run. Without them, the application will either refuse to start or operate with insecure defaults.
|
||||
|
||||
| Variable | Required | Default | Source File | Description |
|
||||
| ------------------ | -------- | -------- | ----------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `JWT_SECRET` | **Yes** | _(none)_ | `src/lib/auth` | Signs/verifies all dashboard session cookies (JWT). Generate with `openssl rand -base64 48`. |
|
||||
| `API_KEY_SECRET` | **Yes** | _(none)_ | `src/lib/db/apiKeys.ts` | AES encryption key for API key values at rest in SQLite. Generate with `openssl rand -hex 32`. |
|
||||
| `INITIAL_PASSWORD` | **Yes** | `123456` | Bootstrap script | Sets the initial admin dashboard password. **Change before first use.** After login, change via Dashboard → Settings → Security. |
|
||||
|
||||
### Generation Commands
|
||||
|
||||
```bash
|
||||
# Generate all three secrets at once:
|
||||
echo "JWT_SECRET=$(openssl rand -base64 48)"
|
||||
echo "API_KEY_SECRET=$(openssl rand -hex 32)"
|
||||
echo "INITIAL_PASSWORD=$(openssl rand -base64 16)"
|
||||
```
|
||||
|
||||
> [!CAUTION]
|
||||
> Never commit `.env` files with real secrets to version control. The `.gitignore` already excludes `.env`, but verify before pushing.
|
||||
|
||||
---
|
||||
|
||||
## 2. Storage & Database
|
||||
|
||||
OmniRoute uses **SQLite** (via `better-sqlite3`) for all persistence. These variables control data location, encryption, and lifecycle.
|
||||
|
||||
| Variable | Default | Source File | Description |
|
||||
| -------------------------------- | -------------------- | ----------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ |
|
||||
| `DATA_DIR` | `~/.omniroute/` | `src/lib/db/core.ts` | Root directory for SQLite DB, backups, and data files. Override for Docker volumes or custom paths. |
|
||||
| `STORAGE_ENCRYPTION_KEY` | _(empty = disabled)_ | `src/lib/db/encryption.ts` | AES key for full SQLite database encryption at rest. Generate with `openssl rand -hex 32`. |
|
||||
| `STORAGE_ENCRYPTION_KEY_VERSION` | `v1` | `scripts/bootstrap-env.mjs`, `electron/main.js` | Version label for the encryption key. Increment when performing key rotation to support decryption of old backups. |
|
||||
| `DISABLE_SQLITE_AUTO_BACKUP` | `false` | `src/lib/db/backup.ts` | When `true`, skips the automatic database backup that runs before migrations on every startup. |
|
||||
| `OMNIROUTE_CRYPT_KEY` | _(unset)_ | `src/lib/db/encryption.ts` | **Legacy alias** for `STORAGE_ENCRYPTION_KEY`. Accepted as a fallback when the primary variable is absent. |
|
||||
| `OMNIROUTE_API_KEY_BASE64` | _(unset)_ | `src/lib/db/encryption.ts` | **Legacy alias** (Base64-encoded form) accepted as a fallback. Decoded automatically before use. |
|
||||
|
||||
### Scenarios
|
||||
|
||||
| Scenario | Configuration |
|
||||
| --------------------- | -------------------------------------------------------------------------------- |
|
||||
| **Local development** | Leave all defaults. DB lives at `~/.omniroute/omniroute.db`. |
|
||||
| **Docker** | `DATA_DIR=/data` + mount a volume at `/data`. |
|
||||
| **Encrypted at rest** | Set `STORAGE_ENCRYPTION_KEY` + keep backups of the key! Losing it = losing data. |
|
||||
| **CI/Testing** | `DATA_DIR=/tmp/omniroute-test` — ephemeral, no encryption needed. |
|
||||
|
||||
---
|
||||
|
||||
## 3. Network & Ports
|
||||
|
||||
| Variable | Default | Source File | Description |
|
||||
| --------------------- | ------------ | -------------------------- | -------------------------------------------------------------------------------------- |
|
||||
| `PORT` | `20128` | `src/lib/runtime/ports.ts` | Primary port for both Dashboard UI and API endpoints (single-port mode). |
|
||||
| `API_PORT` | _(unset)_ | `src/lib/runtime/ports.ts` | When set, serves the `/v1/*` proxy API on this separate port. |
|
||||
| `API_HOST` | `0.0.0.0` | `src/lib/runtime/ports.ts` | Bind address for the API port. |
|
||||
| `DASHBOARD_PORT` | _(unset)_ | `src/lib/runtime/ports.ts` | When set, serves the Dashboard UI on this separate port. |
|
||||
| `PROD_DASHBOARD_PORT` | `20130` | `docker-compose.prod.yml` | Host-side published port for the Dashboard in Docker production mode. |
|
||||
| `PROD_API_PORT` | `20131` | `docker-compose.prod.yml` | Host-side published port for the API in Docker production mode. |
|
||||
| `OMNIROUTE_PORT` | _(unset)_ | `src/lib/runtime/ports.ts` | Takes precedence over `PORT` when running inside Electron or other wrappers. |
|
||||
| `NODE_ENV` | `production` | Next.js core | Controls logging verbosity, caching, error detail exposure, and Next.js optimizations. |
|
||||
|
||||
### Port Modes
|
||||
|
||||
```
|
||||
┌─────────────────────────── Single Port (default) ──────────────────────────┐
|
||||
│ PORT=20128 │
|
||||
│ → Dashboard: http://localhost:20128 │
|
||||
│ → API: http://localhost:20128/v1/chat/completions │
|
||||
└─────────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
┌─────────────────────────── Split Ports ─────────────────────────────────────┐
|
||||
│ DASHBOARD_PORT=20128 │
|
||||
│ API_PORT=20129 │
|
||||
│ API_HOST=0.0.0.0 │
|
||||
│ → Dashboard: http://localhost:20128 │
|
||||
│ → API: http://0.0.0.0:20129/v1/chat/completions │
|
||||
│ Use case: Expose API to LAN while restricting Dashboard to localhost. │
|
||||
└─────────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
┌─────────────────────────── Docker Production ──────────────────────────────┐
|
||||
│ PROD_DASHBOARD_PORT=443 PROD_API_PORT=8443 │
|
||||
│ → Maps container ports to host ports in docker-compose.prod.yml. │
|
||||
└─────────────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Security & Authentication
|
||||
|
||||
| Variable | Default | Source File | Description |
|
||||
| ----------------------------- | --------------------- | ---------------------------------------- | --------------------------------------------------------------------------------------------------------- |
|
||||
| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | Salt combined with hardware identifiers for machine fingerprinting. Change per-deployment for isolation. |
|
||||
| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | Sets the `Secure` flag on session cookies. **Must be `true`** when running behind HTTPS. |
|
||||
| `REQUIRE_API_KEY` | `false` | API middleware | When `true`, all `/v1/*` proxy requests must include a valid API key. |
|
||||
| `ALLOW_API_KEY_REVEAL` | `false` | Dashboard providers page | Allows revealing full API key values in the Dashboard UI. Security risk on shared instances. |
|
||||
| `NO_LOG_API_KEY_IDS` | _(empty)_ | `src/lib/compliance/index.ts` | Comma-separated API key IDs that bypass request logging (GDPR compliance). |
|
||||
| `MAX_BODY_SIZE_BYTES` | `10485760` (10 MB) | `src/shared/middleware/bodySizeGuard.ts` | Maximum allowed request body size. Rejects payloads exceeding this limit. |
|
||||
| `CORS_ORIGIN` | `*` | Next.js middleware | CORS `Access-Control-Allow-Origin` value. Restrict for production. |
|
||||
| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuard.ts` | Block provider calls targeting private/loopback/link-local IP ranges. Disable only in isolated test envs. |
|
||||
|
||||
### Hardening Checklist
|
||||
|
||||
```bash
|
||||
# Production security minimum:
|
||||
AUTH_COOKIE_SECURE=true # Requires HTTPS
|
||||
REQUIRE_API_KEY=true # Authenticate all proxy calls
|
||||
ALLOW_API_KEY_REVEAL=false # Never expose keys in UI
|
||||
CORS_ORIGIN=https://your.domain.com
|
||||
MAX_BODY_SIZE_BYTES=5242880 # 5 MB limit
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Input Sanitization & PII Protection
|
||||
|
||||
OmniRoute provides a two-layer defense: request-side injection scanning and response-side PII stripping.
|
||||
|
||||
### Request-Side: Prompt Injection Guard
|
||||
|
||||
| Variable | Default | Source File | Description |
|
||||
| ------------------------- | --------- | ---------------------------------------- | ------------------------------------------------------------------------------------------- |
|
||||
| `INPUT_SANITIZER_ENABLED` | `false` | `src/middleware/promptInjectionGuard.ts` | Enable scanning of incoming messages for prompt injection patterns. |
|
||||
| `INPUT_SANITIZER_MODE` | `warn` | `src/middleware/promptInjectionGuard.ts` | `warn` = log only, `block` = reject request with 400, `redact` = strip suspicious patterns. |
|
||||
| `INJECTION_GUARD_MODE` | _(unset)_ | `src/middleware/promptInjectionGuard.ts` | Legacy alias for `INPUT_SANITIZER_MODE` — same behavior. |
|
||||
| `PII_REDACTION_ENABLED` | `false` | `src/middleware/promptInjectionGuard.ts` | Detect PII (emails, phones, SSNs) in incoming requests. |
|
||||
|
||||
### Response-Side: PII Sanitizer
|
||||
|
||||
| Variable | Default | Source File | Description |
|
||||
| -------------------------------- | -------- | ------------------------- | ----------------------------------------------------------------------- |
|
||||
| `PII_RESPONSE_SANITIZATION` | `false` | `src/lib/piiSanitizer.ts` | Scan LLM responses for leaked PII before returning to client. |
|
||||
| `PII_RESPONSE_SANITIZATION_MODE` | `redact` | `src/lib/piiSanitizer.ts` | `redact` = mask PII, `warn` = log only, `block` = drop entire response. |
|
||||
|
||||
### Scenarios
|
||||
|
||||
| Scenario | Configuration |
|
||||
| ------------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **Enterprise compliance** | `INPUT_SANITIZER_ENABLED=true`, `INPUT_SANITIZER_MODE=block`, `PII_REDACTION_ENABLED=true`, `PII_RESPONSE_SANITIZATION=true` |
|
||||
| **Monitoring only** | `INPUT_SANITIZER_ENABLED=true`, `INPUT_SANITIZER_MODE=warn` — logs but never blocks |
|
||||
| **Personal use** | Leave all disabled — zero overhead |
|
||||
|
||||
---
|
||||
|
||||
## 6. Tool & Routing Policies
|
||||
|
||||
| Variable | Default | Source File | Description |
|
||||
| ------------------ | ---------- | ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `TOOL_POLICY_MODE` | `disabled` | `src/lib/toolPolicy.ts` | Controls LLM tool/function-calling access. `allowlist` = only listed tools, `denylist` = all except listed, `disabled` = no restrictions. |
|
||||
|
||||
---
|
||||
|
||||
## 7. URLs & Cloud Sync
|
||||
|
||||
| Variable | Default | Source File | Description |
|
||||
| ----------------------- | ------------------------ | ------------------------------------------- | --------------------------------------------------------------------------------------------------------------- |
|
||||
| `BASE_URL` | `http://localhost:20128` | `src/lib/cloudSync.ts` | Server-side URL for internal sync jobs to call `/api/sync/cloud`. |
|
||||
| `CLOUD_URL` | _(empty)_ | `src/lib/cloudSync.ts` | Cloud relay endpoint URL (premium feature). |
|
||||
| `CLOUD_SYNC_TIMEOUT_MS` | `12000` | `src/lib/cloudSync.ts` | HTTP timeout for cloud sync requests. |
|
||||
| `NEXT_PUBLIC_BASE_URL` | `http://localhost:20128` | OAuth, Dashboard, sync | Public-facing URL for OAuth redirect_uri, Dashboard links. **Must match your public URL behind reverse proxy.** |
|
||||
| `NEXT_PUBLIC_CLOUD_URL` | _(empty)_ | Client-side | Client-side mirror of `CLOUD_URL`. |
|
||||
| `NEXT_PUBLIC_APP_URL` | _(unset)_ | `src/shared/services/cloudSyncScheduler.ts` | Legacy fallback for `NEXT_PUBLIC_BASE_URL`. |
|
||||
|
||||
> [!IMPORTANT]
|
||||
> When deploying behind a reverse proxy (nginx, Caddy), `NEXT_PUBLIC_BASE_URL` **must** be set to your public URL (e.g., `https://omniroute.example.com`). Without this, OAuth callbacks will fail because the redirect_uri won't match.
|
||||
|
||||
---
|
||||
|
||||
## 8. Outbound Proxy
|
||||
|
||||
Route upstream LLM provider calls through an HTTP or SOCKS5 proxy for egress control, geo-routing, or IP masking.
|
||||
|
||||
| Variable | Default | Source File | Description |
|
||||
| --------------------------------- | --------- | -------------------- | ----------------------------------------------------------------------------------- |
|
||||
| `ENABLE_SOCKS5_PROXY` | `true` | `open-sse/executors` | Enable SOCKS5 proxy agent for upstream calls. |
|
||||
| `NEXT_PUBLIC_ENABLE_SOCKS5_PROXY` | `true` | Client-side | Client-side awareness of SOCKS5 availability. |
|
||||
| `HTTP_PROXY` | _(unset)_ | Node.js standard | HTTP proxy for upstream calls. |
|
||||
| `HTTPS_PROXY` | _(unset)_ | Node.js standard | HTTPS proxy for upstream calls. |
|
||||
| `ALL_PROXY` | _(unset)_ | Node.js standard | Universal proxy (supports `socks5://`). |
|
||||
| `NO_PROXY` | _(unset)_ | Node.js standard | Comma-separated hostnames/IPs to bypass the proxy. |
|
||||
| `ENABLE_TLS_FINGERPRINT` | `false` | `open-sse/executors` | Spoof TLS fingerprint using wreq-js (mimics Chrome 124). Counters JA3/JA4 blocking. |
|
||||
|
||||
### Scenarios
|
||||
|
||||
| Scenario | Configuration |
|
||||
| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **SOCKS5 through SSH tunnel** | `ALL_PROXY=socks5://127.0.0.1:7890`, `ENABLE_SOCKS5_PROXY=true` |
|
||||
| **Corporate HTTP proxy** | `HTTP_PROXY=http://proxy.corp.com:3128`, `HTTPS_PROXY=http://proxy.corp.com:3128`, `NO_PROXY=localhost,internal.corp.com` |
|
||||
| **Anti-fingerprint** | `ENABLE_TLS_FINGERPRINT=true` — requires `wreq-js` (included) |
|
||||
|
||||
---
|
||||
|
||||
## 9. CLI Tool Integration
|
||||
|
||||
Controls how OmniRoute discovers and launches CLI sidecars (Claude Code, Codex, etc.).
|
||||
|
||||
| Variable | Default | Source File | Description |
|
||||
| ------------------------- | ---------- | ----------------------------------- | -------------------------------------------------------------------------- |
|
||||
| `CLI_MODE` | `auto` | `src/shared/services/cliRuntime.ts` | `auto` = search system PATH; `manual` = use explicit paths only. |
|
||||
| `CLI_EXTRA_PATHS` | _(unset)_ | `src/shared/services/cliRuntime.ts` | Additional PATH entries for CLI binary discovery (colon-separated). |
|
||||
| `CLI_CONFIG_HOME` | _(unset)_ | `src/shared/services/cliRuntime.ts` | Override home directory for reading CLI configs (`~/.claude`, `~/.codex`). |
|
||||
| `CLI_ALLOW_CONFIG_WRITES` | `false` | `src/shared/services/cliRuntime.ts` | Allow OmniRoute to write CLI config files (token refresh, session data). |
|
||||
| `CLI_CLAUDE_BIN` | `claude` | `src/shared/services/cliRuntime.ts` | Custom path to Claude CLI binary. |
|
||||
| `CLI_CODEX_BIN` | `codex` | `src/shared/services/cliRuntime.ts` | Custom path to Codex CLI binary. |
|
||||
| `CLI_DROID_BIN` | `droid` | `src/shared/services/cliRuntime.ts` | Custom path to Droid CLI binary. |
|
||||
| `CLI_OPENCLAW_BIN` | `openclaw` | `src/shared/services/cliRuntime.ts` | Custom path to OpenClaw CLI binary. |
|
||||
| `CLI_CURSOR_BIN` | `agent` | `src/shared/services/cliRuntime.ts` | Custom path to Cursor agent binary. |
|
||||
| `CLI_CLINE_BIN` | `cline` | `src/shared/services/cliRuntime.ts` | Custom path to Cline CLI binary. |
|
||||
| `CLI_CONTINUE_BIN` | `cn` | `src/shared/services/cliRuntime.ts` | Custom path to Continue CLI binary. |
|
||||
| `CLI_QODER_BIN` | `qoder` | `src/shared/services/cliRuntime.ts` | Custom path to Qoder CLI binary. |
|
||||
|
||||
### Docker Example
|
||||
|
||||
```bash
|
||||
# Mount host binaries into the container and tell OmniRoute where they are:
|
||||
CLI_EXTRA_PATHS=/host-cli/bin
|
||||
CLI_CONFIG_HOME=/root
|
||||
CLI_ALLOW_CONFIG_WRITES=true
|
||||
CLI_CLAUDE_BIN=/host-cli/bin/claude
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 10. Internal Agent & MCP Integrations
|
||||
|
||||
| Variable | Default | Source File | Description |
|
||||
| --------------------------------------- | ----------- | ------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `OMNIROUTE_BASE_URL` | auto-detect | `open-sse/mcp-server/server.ts` | Explicit URL for MCP/A2A tools to reach OmniRoute. Overrides localhost auto-detection. |
|
||||
| `OMNIROUTE_API_KEY` | _(unset)_ | MCP/A2A modules | API key for internal MCP tool and A2A skill calls. |
|
||||
| `OMNIROUTE_API_KEY_ID` | _(unset)_ | `open-sse/mcp-server/audit.ts` | Key ID for MCP audit log attribution. |
|
||||
| `ROUTER_API_KEY` | _(unset)_ | Legacy | Legacy alias for `OMNIROUTE_API_KEY`. |
|
||||
| `OMNIROUTE_MCP_ENFORCE_SCOPES` | `false` | `open-sse/mcp-server/server.ts` | Enforce scope-based access control on MCP tool calls. |
|
||||
| `OMNIROUTE_MCP_SCOPES` | _(all)_ | `open-sse/mcp-server/server.ts` | Comma-separated scopes: `admin`, `combos`, `health`, `models`, `routing`, `budget`, `metrics`, `pricing`, `memory`, `skills`. |
|
||||
| `MODEL_SYNC_INTERVAL_HOURS` | `24` | `src/shared/services/modelSyncScheduler.ts` | Model catalog sync interval in hours. |
|
||||
| `PROVIDER_LIMITS_SYNC_INTERVAL_MINUTES` | `70` | `src/server-init.ts` | Provider rate-limit and quota polling interval. |
|
||||
| `OMNIROUTE_DISABLE_BACKGROUND_SERVICES` | `false` | `src/instrumentation-node.ts` | Disable all background services (sync, pricing, model refresh). Useful for CI/test. |
|
||||
| `OMNIROUTE_BOOTSTRAPPED` | `false` | `src/app/(dashboard)/dashboard/page.tsx` | Set `true` by bootstrap script after initial setup. Controls setup wizard visibility. |
|
||||
| `OMNIROUTE_ALLOW_BODY_PROJECT_OVERRIDE` | `0` | `open-sse/executors/antigravity.ts` | Escape hatch: allow request body to override the Antigravity project field. |
|
||||
|
||||
### OAuth CLI Bridge (Internal)
|
||||
|
||||
| Variable | Default | Source File | Description |
|
||||
| ------------------- | ----------- | ------------------------------- | ----------------------------------------- |
|
||||
| `OMNIROUTE_SERVER` | auto-detect | `src/lib/oauth/config/index.ts` | Server URL for CLI↔OmniRoute auth bridge. |
|
||||
| `OMNIROUTE_TOKEN` | _(unset)_ | `src/lib/oauth/config/index.ts` | Auth token for CLI bridge. |
|
||||
| `OMNIROUTE_USER_ID` | `cli` | `src/lib/oauth/config/index.ts` | User ID for CLI bridge sessions. |
|
||||
| `SERVER_URL` | _(unset)_ | `src/lib/oauth/config/index.ts` | Legacy alias for `OMNIROUTE_SERVER`. |
|
||||
| `CLI_TOKEN` | _(unset)_ | `src/lib/oauth/config/index.ts` | Legacy alias for `OMNIROUTE_TOKEN`. |
|
||||
| `CLI_USER_ID` | _(unset)_ | `src/lib/oauth/config/index.ts` | Legacy alias for `OMNIROUTE_USER_ID`. |
|
||||
|
||||
---
|
||||
|
||||
## 11. OAuth Provider Credentials
|
||||
|
||||
Built-in credentials for **localhost development**. For remote deployments, register your own at each provider's developer console.
|
||||
|
||||
| Variable | Provider | Notes |
|
||||
| --------------------------------- | ----------------------- | --------------------------------------------------------------------------------- |
|
||||
| `CLAUDE_OAUTH_CLIENT_ID` | Claude Code (Anthropic) | Public client — no secret needed. |
|
||||
| `CLAUDE_CODE_REDIRECT_URI` | Claude Code | Override redirect URI. Default: `https://platform.claude.com/oauth/code/callback` |
|
||||
| `CODEX_OAUTH_CLIENT_ID` | Codex / OpenAI | Public client. |
|
||||
| `GEMINI_OAUTH_CLIENT_ID` | Gemini (Google) | Requires matching `_SECRET`. |
|
||||
| `GEMINI_OAUTH_CLIENT_SECRET` | Gemini (Google) | — |
|
||||
| `GEMINI_CLI_OAUTH_CLIENT_ID` | Gemini CLI | Usually same as Gemini. |
|
||||
| `GEMINI_CLI_OAUTH_CLIENT_SECRET` | Gemini CLI | — |
|
||||
| `QWEN_OAUTH_CLIENT_ID` | Qwen (Alibaba) | Public client. |
|
||||
| `KIMI_CODING_OAUTH_CLIENT_ID` | Kimi Coding (Moonshot) | Public client. |
|
||||
| `ANTIGRAVITY_OAUTH_CLIENT_ID` | Antigravity (Google) | Requires matching `_SECRET`. |
|
||||
| `ANTIGRAVITY_OAUTH_CLIENT_SECRET` | Antigravity (Google) | — |
|
||||
| `GITHUB_OAUTH_CLIENT_ID` | GitHub Copilot | Public client. |
|
||||
| `QODER_OAUTH_CLIENT_SECRET` | Qoder | — |
|
||||
| `QODER_OAUTH_AUTHORIZE_URL` | Qoder | Set to enable Qoder OAuth. |
|
||||
| `QODER_OAUTH_TOKEN_URL` | Qoder | — |
|
||||
| `QODER_OAUTH_USERINFO_URL` | Qoder | — |
|
||||
| `QODER_OAUTH_CLIENT_ID` | Qoder | — |
|
||||
| `QODER_PERSONAL_ACCESS_TOKEN` | Qoder | Direct API key fallback (bypasses OAuth). |
|
||||
| `QODER_CLI_WORKSPACE` | Qoder | Workspace ID for Qoder CLI. |
|
||||
| `OMNIROUTE_QODER_WORKSPACE` | Qoder | Alias for `QODER_CLI_WORKSPACE`. |
|
||||
|
||||
> [!WARNING]
|
||||
> **Google OAuth** (Antigravity, Gemini CLI) credentials **only work on localhost**. For remote servers:
|
||||
>
|
||||
> 1. Go to [Google Cloud Console → Credentials](https://console.cloud.google.com/apis/credentials)
|
||||
> 2. Create an OAuth 2.0 Client ID (type: "Web application")
|
||||
> 3. Add your server URL as Authorized redirect URI
|
||||
> 4. Replace the credential values in `.env`.
|
||||
|
||||
---
|
||||
|
||||
## 12. Provider User-Agent Overrides
|
||||
|
||||
Override the `User-Agent` header sent to each upstream provider. This is dynamically resolved at runtime by the executor base class:
|
||||
|
||||
```
|
||||
process.env[`${PROVIDER_ID}_USER_AGENT`]
|
||||
```
|
||||
|
||||
> **Source:** `open-sse/executors/base.ts` → `buildHeaders()`
|
||||
|
||||
| Variable | Default Value | When to Update |
|
||||
| ------------------------ | --------------------------------------------- | ------------------------------------------------------------- |
|
||||
| `CLAUDE_USER_AGENT` | `claude-cli/2.1.121 (external, cli)` | When Anthropic releases a new CLI version |
|
||||
| `CODEX_USER_AGENT` | `codex-cli/0.125.0 (Windows 10.0.26100; x64)` | When OpenAI updates the Codex CLI |
|
||||
| `CODEX_CLIENT_VERSION` | `0.125.0` | Override Codex client version independently of full UA string |
|
||||
| `GITHUB_USER_AGENT` | `GitHubCopilotChat/0.45.1` | When GitHub Copilot Chat updates |
|
||||
| `ANTIGRAVITY_USER_AGENT` | `antigravity/1.107.0 darwin/arm64` | When Antigravity IDE updates |
|
||||
| `KIRO_USER_AGENT` | `AWS-SDK-JS/3.0.0 kiro-ide/1.0.0` | When Kiro IDE updates |
|
||||
| `QODER_USER_AGENT` | `Qoder-Cli` | When Qoder CLI updates |
|
||||
| `QWEN_USER_AGENT` | `QwenCode/0.15.3 (linux; x64)` | When Qwen Code updates |
|
||||
| `CURSOR_USER_AGENT` | `connect-es/1.6.1` | When Cursor updates |
|
||||
| `GEMINI_CLI_USER_AGENT` | `google-api-nodejs-client/10.3.0` | When Google API client updates |
|
||||
|
||||
> [!TIP]
|
||||
> You can add User-Agent overrides for **any** provider using the pattern `{PROVIDER_ID}_USER_AGENT`. The executor dynamically constructs the env var name.
|
||||
|
||||
---
|
||||
|
||||
## 13. CLI Fingerprint Compatibility
|
||||
|
||||
When enabled, OmniRoute reorders HTTP headers and JSON body fields to match the exact signature of official CLI tools. This reduces the risk of account flagging while preserving your proxy IP.
|
||||
|
||||
**Source:** `open-sse/config/cliFingerprints.ts`, `open-sse/executors/base.ts`
|
||||
|
||||
### Per-Provider
|
||||
|
||||
| Variable | Effect |
|
||||
| -------------------------- | --------------------------------------- |
|
||||
| `CLI_COMPAT_CODEX=1` | Mimics Codex CLI request signature |
|
||||
| `CLI_COMPAT_CLAUDE=1` | Mimics Claude Code request signature |
|
||||
| `CLI_COMPAT_GITHUB=1` | Mimics GitHub Copilot request signature |
|
||||
| `CLI_COMPAT_ANTIGRAVITY=1` | Mimics Antigravity request signature |
|
||||
| `CLI_COMPAT_KIRO=1` | Mimics Kiro IDE request signature |
|
||||
| `CLI_COMPAT_CURSOR=1` | Mimics Cursor request signature |
|
||||
| `CLI_COMPAT_KIMI_CODING=1` | Mimics Kimi Coding request signature |
|
||||
| `CLI_COMPAT_KILOCODE=1` | Mimics Kilo Code request signature |
|
||||
| `CLI_COMPAT_CLINE=1` | Mimics Cline request signature |
|
||||
| `CLI_COMPAT_QWEN=1` | Mimics Qwen Code request signature |
|
||||
|
||||
### Global
|
||||
|
||||
| Variable | Effect |
|
||||
| ------------------ | --------------------------------------------------------------- |
|
||||
| `CLI_COMPAT_ALL=1` | Enable fingerprint compatibility for **all** providers at once. |
|
||||
|
||||
> [!NOTE]
|
||||
> This feature works alongside the User-Agent overrides (§12). The fingerprint system handles header ordering and body field ordering, while User-Agent overrides handle the specific UA string. Both can be enabled independently.
|
||||
|
||||
---
|
||||
|
||||
## 14. API Key Providers
|
||||
|
||||
API keys for providers that use direct authentication. **Preferred setup:** Dashboard → Providers → Add API Key.
|
||||
|
||||
Setting via environment variables is an alternative for Docker or headless deployments.
|
||||
|
||||
Recognized pattern: `{PROVIDER_ID}_API_KEY`
|
||||
|
||||
| Variable | Provider |
|
||||
| -------------------- | ------------------- |
|
||||
| `DEEPSEEK_API_KEY` | DeepSeek |
|
||||
| `GROQ_API_KEY` | Groq |
|
||||
| `XAI_API_KEY` | xAI (Grok) |
|
||||
| `MISTRAL_API_KEY` | Mistral AI |
|
||||
| `PERPLEXITY_API_KEY` | Perplexity |
|
||||
| `TOGETHER_API_KEY` | Together AI |
|
||||
| `FIREWORKS_API_KEY` | Fireworks AI |
|
||||
| `CEREBRAS_API_KEY` | Cerebras |
|
||||
| `COHERE_API_KEY` | Cohere |
|
||||
| `NVIDIA_API_KEY` | NVIDIA NIM |
|
||||
| `NEBIUS_API_KEY` | Nebius (embeddings) |
|
||||
| `QIANFAN_API_KEY` | Baidu Qianfan |
|
||||
|
||||
> [!TIP]
|
||||
> Keys set via the Dashboard are stored encrypted in SQLite and take precedence over environment variables.
|
||||
|
||||
---
|
||||
|
||||
## 15. Timeout Settings
|
||||
|
||||
All values are in **milliseconds**. Centralized resolution in `src/shared/utils/runtimeTimeouts.ts`.
|
||||
|
||||
### Timeout Hierarchy
|
||||
|
||||
```
|
||||
REQUEST_TIMEOUT_MS (global override)
|
||||
├─→ FETCH_TIMEOUT_MS (upstream provider calls, default: 600000)
|
||||
│ ├─→ FETCH_HEADERS_TIMEOUT_MS (inherits from FETCH_TIMEOUT_MS)
|
||||
│ ├─→ FETCH_BODY_TIMEOUT_MS (inherits from FETCH_TIMEOUT_MS)
|
||||
│ ├─→ TLS_CLIENT_TIMEOUT_MS (inherits from FETCH_TIMEOUT_MS)
|
||||
│ ├── FETCH_CONNECT_TIMEOUT_MS (independent, default: 30000)
|
||||
│ └── FETCH_KEEPALIVE_TIMEOUT_MS (independent, default: 4000)
|
||||
├─→ STREAM_IDLE_TIMEOUT_MS (inherits from REQUEST_TIMEOUT_MS, default: 600000)
|
||||
└─→ API_BRIDGE_PROXY_TIMEOUT_MS (inherits from REQUEST_TIMEOUT_MS, default: 30000)
|
||||
├─→ API_BRIDGE_SERVER_REQUEST_TIMEOUT_MS (derived, default: 300000)
|
||||
├── API_BRIDGE_SERVER_HEADERS_TIMEOUT_MS (default: 60000)
|
||||
├── API_BRIDGE_SERVER_KEEPALIVE_TIMEOUT_MS (default: 5000)
|
||||
└── API_BRIDGE_SERVER_SOCKET_TIMEOUT_MS (default: 0 = disabled)
|
||||
```
|
||||
|
||||
| Variable | Default | Description |
|
||||
| ---------------------------------------- | -------------------- | ------------------------------------------------------------------------------------------- |
|
||||
| `REQUEST_TIMEOUT_MS` | _(unset)_ | Global shortcut — overrides both `FETCH_TIMEOUT_MS` and `STREAM_IDLE_TIMEOUT_MS` defaults. |
|
||||
| `FETCH_TIMEOUT_MS` | `600000` | Total HTTP request timeout for upstream provider calls. |
|
||||
| `STREAM_IDLE_TIMEOUT_MS` | `600000` | Max silence between SSE chunks before aborting. Extended-thinking models rarely pause >90s. |
|
||||
| `FETCH_HEADERS_TIMEOUT_MS` | = `FETCH_TIMEOUT_MS` | Time to receive response headers. |
|
||||
| `FETCH_BODY_TIMEOUT_MS` | = `FETCH_TIMEOUT_MS` | Time to receive the full response body. |
|
||||
| `FETCH_CONNECT_TIMEOUT_MS` | `30000` | TCP connection establishment timeout. |
|
||||
| `FETCH_KEEPALIVE_TIMEOUT_MS` | `4000` | Keep-alive socket idle timeout. |
|
||||
| `TLS_CLIENT_TIMEOUT_MS` | = `FETCH_TIMEOUT_MS` | TLS fingerprint proxy (wreq-js) timeout. |
|
||||
| `API_BRIDGE_PROXY_TIMEOUT_MS` | `30000` | Proxy hop timeout for `/v1` bridge requests. |
|
||||
| `API_BRIDGE_SERVER_REQUEST_TIMEOUT_MS` | `300000` | Overall server request timeout for the bridge. |
|
||||
| `API_BRIDGE_SERVER_HEADERS_TIMEOUT_MS` | `60000` | Time to send response headers via the bridge. |
|
||||
| `API_BRIDGE_SERVER_KEEPALIVE_TIMEOUT_MS` | `5000` | Bridge keep-alive idle timeout. |
|
||||
| `API_BRIDGE_SERVER_SOCKET_TIMEOUT_MS` | `0` | Raw socket timeout (0 = disabled). |
|
||||
| `SHUTDOWN_TIMEOUT_MS` | `30000` | Grace period on SIGTERM/SIGINT before force-exit. |
|
||||
|
||||
### Scenarios
|
||||
|
||||
| Scenario | Configuration |
|
||||
| -------------------------------- | ------------------------------------------------------ |
|
||||
| **Long-running code generation** | `REQUEST_TIMEOUT_MS=900000` (15 min) |
|
||||
| **Fast-fail for production API** | `API_BRIDGE_PROXY_TIMEOUT_MS=10000` |
|
||||
| **Extended thinking models** | `STREAM_IDLE_TIMEOUT_MS=300000` (5 min between chunks) |
|
||||
|
||||
---
|
||||
|
||||
## 16. Logging
|
||||
|
||||
The logging system writes to both stdout and rotated log files. All configuration is read by `src/lib/logEnv.ts`.
|
||||
|
||||
| Variable | Default | Description |
|
||||
| ----------------------------------------- | -------------------------- | -------------------------------------------------------------------------------- |
|
||||
| `APP_LOG_LEVEL` | `info` | Minimum log level: `debug`, `info`, `warn`, `error`. |
|
||||
| `APP_LOG_FORMAT` | `text` | Output format: `text` (human-readable) or `json` (structured). |
|
||||
| `APP_LOG_TO_FILE` | `true` | Write logs to file alongside stdout. |
|
||||
| `APP_LOG_FILE_PATH` | `logs/application/app.log` | Log file path (relative to project root or `DATA_DIR`). |
|
||||
| `APP_LOG_MAX_FILE_SIZE` | `50M` | Max file size before rotation. Accepts: `50M`, `1G`, `512K`, or plain bytes. |
|
||||
| `APP_LOG_RETENTION_DAYS` | `7` | Days to keep rotated application log files. |
|
||||
| `APP_LOG_MAX_FILES` | `20` | Maximum rotated log file backups. |
|
||||
| `CALL_LOG_RETENTION_DAYS` | `7` | Days to keep request/call log entries in the database. |
|
||||
| `CALL_LOG_MAX_ENTRIES` | `10000` | Max call log entries in the in-memory buffer. |
|
||||
| `CALL_LOGS_TABLE_MAX_ROWS` | `100000` | Max rows in the `call_logs` SQLite table before pruning. |
|
||||
| `CALL_LOG_PIPELINE_CAPTURE_STREAM_CHUNKS` | `true` | Store stream chunks in pipeline artifacts when `call_log_pipeline_enabled=true`. |
|
||||
| `CALL_LOG_PIPELINE_MAX_SIZE_KB` | `512` | Max pipeline call log artifact size in KB when `call_log_pipeline_enabled=true`. |
|
||||
| `PROXY_LOGS_TABLE_MAX_ROWS` | `100000` | Max rows in the `proxy_logs` SQLite table before pruning. |
|
||||
|
||||
---
|
||||
|
||||
## 17. Memory Optimization
|
||||
|
||||
| Variable | Default | Description |
|
||||
| -------------------------- | ------------------------------- | ---------------------------------------------------------------------- |
|
||||
| `OMNIROUTE_MEMORY_MB` | `256` (Docker) / system default | V8 heap limit. Sets `--max-old-space-size`. |
|
||||
| `PROMPT_CACHE_MAX_SIZE` | `50` | Max cached system prompt entries. |
|
||||
| `PROMPT_CACHE_MAX_BYTES` | `2097152` (2 MB) | Max total prompt cache size. |
|
||||
| `PROMPT_CACHE_TTL_MS` | `300000` (5 min) | Prompt cache entry TTL. |
|
||||
| `SEMANTIC_CACHE_MAX_SIZE` | `100` | Max cached temperature=0 responses. |
|
||||
| `SEMANTIC_CACHE_MAX_BYTES` | `4194304` (4 MB) | Max total semantic cache size. |
|
||||
| `SEMANTIC_CACHE_TTL_MS` | `1800000` (30 min) | Semantic cache entry TTL. |
|
||||
| `STREAM_HISTORY_MAX` | `50` | Max recent stream events in the Dashboard live view buffer. |
|
||||
| `CONTEXT_LENGTH_DEFAULT` | `128000` | Global fallback max context length for models without explicit config. |
|
||||
| `USAGE_TOKEN_BUFFER` | `100` | Extra token headroom reserved when tracking usage quotas. |
|
||||
|
||||
### Compression
|
||||
|
||||
| Variable | Default | Description |
|
||||
| ------------------------------------- | ------- | ------------------------------------------------------------------------------------------------------------- |
|
||||
| `OMNIROUTE_RTK_TRUST_PROJECT_FILTERS` | unset | Trust project `.rtk/filters.json` without a `.rtk/trust.json` hash. Use only in controlled local development. |
|
||||
|
||||
### Low-RAM Docker Example
|
||||
|
||||
```bash
|
||||
OMNIROUTE_MEMORY_MB=128
|
||||
PROMPT_CACHE_MAX_SIZE=20
|
||||
PROMPT_CACHE_MAX_BYTES=524288 # 512 KB
|
||||
SEMANTIC_CACHE_MAX_SIZE=25
|
||||
SEMANTIC_CACHE_MAX_BYTES=1048576 # 1 MB
|
||||
STREAM_HISTORY_MAX=10
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 18. Pricing Sync
|
||||
|
||||
Automatic model pricing data synchronization from external sources.
|
||||
|
||||
| Variable | Default | Source File | Description |
|
||||
| ----------------------- | ------------- | ------------------------ | ----------------------------- |
|
||||
| `PRICING_SYNC_ENABLED` | `false` | `src/lib/pricingSync.ts` | Opt-in periodic pricing sync. |
|
||||
| `PRICING_SYNC_INTERVAL` | `86400` (24h) | `src/lib/pricingSync.ts` | Sync interval in seconds. |
|
||||
| `PRICING_SYNC_SOURCES` | `litellm` | `src/lib/pricingSync.ts` | Comma-separated data sources. |
|
||||
|
||||
---
|
||||
|
||||
## 19. Model Sync (Dev)
|
||||
|
||||
| Variable | Default | Source File | Description |
|
||||
| -------------------------- | ------------- | -------------------------- | -------------------------------------------------------- |
|
||||
| `MODELS_DEV_SYNC_INTERVAL` | `86400` (24h) | `src/lib/modelsDevSync.ts` | Development-time model catalog sync interval in seconds. |
|
||||
|
||||
---
|
||||
|
||||
## 20. Provider-Specific Settings
|
||||
|
||||
| Variable | Default | Source File | Description |
|
||||
| ----------------------------------------- | ------------------ | ------------------------------------------ | ------------------------------------------------------------------------------------- |
|
||||
| `OPENROUTER_CATALOG_TTL_MS` | `86400000` (24h) | `src/lib/catalog/openrouterCatalog.ts` | OpenRouter model catalog cache TTL. |
|
||||
| `NANOBANANA_POLL_TIMEOUT_MS` | `120000` | `open-sse/handlers/imageGeneration.ts` | Max wait for NanoBanana image generation jobs. |
|
||||
| `NANOBANANA_POLL_INTERVAL_MS` | `2500` | `open-sse/handlers/imageGeneration.ts` | NanoBanana job polling frequency. |
|
||||
| `CLOUDFLARE_ACCOUNT_ID` | _(unset)_ | `open-sse/executors/cloudflare-ai.ts` | Account ID for Cloudflare Workers AI. |
|
||||
| `CLOUDFLARED_BIN` | auto-detect | `src/lib/cloudflaredTunnel.ts` | Custom path to `cloudflared` binary. |
|
||||
| `SEARCH_CACHE_TTL_MS` | `300000` (5 min) | `open-sse/services/searchCache.ts` | TTL for search API (Perplexity, Brave, etc.) response caching. |
|
||||
| `ALLOW_MULTI_CONNECTIONS_PER_COMPAT_NODE` | `false` | `src/app/api/providers/route.ts` | Allow multiple simultaneous connections per OpenAI-compatible provider. |
|
||||
| `ENABLE_CC_COMPATIBLE_PROVIDER` | `false` | `src/shared/utils/featureFlags.ts` | Reveal the experimental CC-compatible provider UI for Claude Code-only relays. |
|
||||
| `CLIPROXYAPI_HOST` | `127.0.0.1` | `open-sse/executors/cliproxyapi.ts` | CLIProxyAPI bridge host (legacy integration). |
|
||||
| `CLIPROXYAPI_PORT` | `5544` | `open-sse/executors/cliproxyapi.ts` | CLIProxyAPI bridge port. |
|
||||
| `CLIPROXYAPI_CONFIG_DIR` | `~/.cli-proxy-api` | `src/lib/versionManager/processManager.ts` | CLIProxyAPI config directory. |
|
||||
| `LOCAL_HOSTNAMES` | _(empty)_ | `open-sse/config/providerRegistry.ts` | Comma-separated additional hostnames treated as "local" (Docker service names, etc.). |
|
||||
|
||||
`ENABLE_CC_COMPATIBLE_PROVIDER` is only for third-party relays that accept Claude Code clients
|
||||
exclusively. OmniRoute rewrites requests so those relays accept them. If you only want to use
|
||||
Claude Code CLI, or you are not sure what these relays are, keep this disabled and add a regular
|
||||
Anthropic-compatible provider instead.
|
||||
|
||||
---
|
||||
|
||||
## 21. Proxy Health
|
||||
|
||||
| Variable | Default | Source File | Description |
|
||||
| ---------------------------- | ---------------- | ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
|
||||
| `PROXY_FAST_FAIL_TIMEOUT_MS` | `2000` | `src/lib/proxyHealth.ts` | Fast-fail health check timeout. |
|
||||
| `PROXY_HEALTH_CACHE_TTL_MS` | `30000` | `src/lib/proxyHealth.ts` | Health check result cache TTL. |
|
||||
| `RATE_LIMIT_MAX_WAIT_MS` | `120000` (2 min) | `open-sse/services/rateLimitManager.ts` | Max time to wait on a 429 before failing the request. |
|
||||
| `REQUEST_RETRY` | `2` | `src/sse/services/cooldownAwareRetry.ts` | Number of automatic retries on model-scoped cooldown responses before returning error to client. |
|
||||
| `MAX_RETRY_INTERVAL_SEC` | `30` | `src/sse/services/cooldownAwareRetry.ts` | Max backoff interval (seconds) between cooldown retries. Capped by this value regardless of upstream `Retry-After`. |
|
||||
|
||||
---
|
||||
|
||||
## 22. Debugging
|
||||
|
||||
> [!CAUTION]
|
||||
> These variables produce **verbose output** and may leak sensitive data. **Never enable in production.**
|
||||
|
||||
| Variable | Default | Source File | Description |
|
||||
| -------------------------------- | --------- | ----------------------------------------- | -------------------------------------------------------------- |
|
||||
| `CURSOR_PROTOBUF_DEBUG` | _(unset)_ | `open-sse/utils/cursorProtobuf.ts` | Set `1` to dump Cursor protobuf decode/encode details. |
|
||||
| `CURSOR_STREAM_DEBUG` | _(unset)_ | `open-sse/executors/cursor.ts` | Set `1` to dump raw Cursor SSE stream data. |
|
||||
| `DEBUG_RESPONSES_SSE_TO_JSON` | _(unset)_ | `open-sse/handlers/responseTranslator.ts` | Set `true` to log Responses API SSE→JSON translation details. |
|
||||
| `NEXT_PUBLIC_OMNIROUTE_E2E_MODE` | _(unset)_ | E2E test harness | Set `true` to enable E2E test mode (relaxed auth, test hooks). |
|
||||
|
||||
---
|
||||
|
||||
## 23. GitHub Integration
|
||||
|
||||
Allow users to report issues directly from the Dashboard.
|
||||
|
||||
| Variable | Default | Source File | Description |
|
||||
| --------------------- | --------- | --------------------------------------- | ------------------------------------------------------- |
|
||||
| `GITHUB_ISSUES_REPO` | _(unset)_ | `src/app/api/v1/issues/report/route.ts` | Repository in `owner/repo` format. |
|
||||
| `GITHUB_ISSUES_TOKEN` | _(unset)_ | `src/app/api/v1/issues/report/route.ts` | GitHub Personal Access Token with `issues:write` scope. |
|
||||
|
||||
---
|
||||
|
||||
## Deployment Scenarios
|
||||
|
||||
### Minimal Local Development
|
||||
|
||||
```bash
|
||||
JWT_SECRET=$(openssl rand -base64 48)
|
||||
API_KEY_SECRET=$(openssl rand -hex 32)
|
||||
INITIAL_PASSWORD=dev123
|
||||
PORT=20128
|
||||
NODE_ENV=development
|
||||
```
|
||||
|
||||
### Docker Production
|
||||
|
||||
```bash
|
||||
JWT_SECRET=<generated>
|
||||
API_KEY_SECRET=<generated>
|
||||
INITIAL_PASSWORD=<generated>
|
||||
STORAGE_ENCRYPTION_KEY=<generated>
|
||||
DATA_DIR=/data
|
||||
PORT=20128
|
||||
API_PORT=20129
|
||||
NODE_ENV=production
|
||||
AUTH_COOKIE_SECURE=true
|
||||
REQUIRE_API_KEY=true
|
||||
NEXT_PUBLIC_BASE_URL=https://omniroute.example.com
|
||||
BASE_URL=http://localhost:20128
|
||||
OMNIROUTE_MEMORY_MB=512
|
||||
CORS_ORIGIN=https://your-frontend.example.com
|
||||
```
|
||||
|
||||
### Air-Gapped / CI
|
||||
|
||||
```bash
|
||||
JWT_SECRET=test-jwt-secret-for-ci
|
||||
API_KEY_SECRET=test-api-key-secret-for-ci
|
||||
INITIAL_PASSWORD=testpass
|
||||
NODE_ENV=production
|
||||
OMNIROUTE_DISABLE_BACKGROUND_SERVICES=true
|
||||
APP_LOG_TO_FILE=false
|
||||
```
|
||||
|
||||
### VPS with Reverse Proxy (nginx + Cloudflare)
|
||||
|
||||
```bash
|
||||
JWT_SECRET=<generated>
|
||||
API_KEY_SECRET=<generated>
|
||||
STORAGE_ENCRYPTION_KEY=<generated>
|
||||
PORT=20128
|
||||
AUTH_COOKIE_SECURE=true
|
||||
REQUIRE_API_KEY=true
|
||||
NEXT_PUBLIC_BASE_URL=https://omniroute.example.com
|
||||
BASE_URL=http://127.0.0.1:20128
|
||||
CORS_ORIGIN=https://omniroute.example.com
|
||||
ENABLE_TLS_FINGERPRINT=true
|
||||
CLI_COMPAT_ALL=1
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Audit: Removed / Dead Variables
|
||||
|
||||
The following variables appeared in previous versions of `.env.example` but have **no runtime references** in the current codebase. They have been removed:
|
||||
|
||||
| Variable | Reason |
|
||||
| ----------------------------------------------------- | ------------------------------------------------------------------------------------------------------- |
|
||||
| `STORAGE_DRIVER=sqlite` | Never read by any source file. SQLite is the only supported driver — no selection needed. |
|
||||
| `INSTANCE_NAME=omniroute` | Present in old docs/env templates but unused at runtime. May return in a future multi-instance feature. |
|
||||
| `SQLITE_MAX_SIZE_MB=2048` | Not referenced in source code. Database size is not artificially limited. |
|
||||
| `SQLITE_CLEAN_LEGACY_FILES=true` | Not referenced in source code. Legacy cleanup was likely removed. |
|
||||
| `CLI_ROO_BIN` | Not registered in `src/shared/services/cliRuntime.ts`. |
|
||||
| `CLI_KIMI_CODING_BIN` | Not registered in `src/shared/services/cliRuntime.ts` (Kimi Coding uses OAuth, not a CLI binary). |
|
||||
| `IFLOW_OAUTH_CLIENT_ID` / `IFLOW_OAUTH_CLIENT_SECRET` | Not referenced anywhere in source code. |
|
||||
|
||||
### Default Value Corrections
|
||||
|
||||
| Variable | Old `.env.example` Value | Actual Code Default | Fixed |
|
||||
| ------------------------- | ------------------------ | ------------------- | ------------------------------------------------------ |
|
||||
| `APP_LOG_RETENTION_DAYS` | `90` | `7` | ✅ Removed misleading value; documented `7` as default |
|
||||
| `CALL_LOG_RETENTION_DAYS` | `90` | `7` | ✅ Removed misleading value; documented `7` as default |
|
||||
@@ -1,407 +0,0 @@
|
||||
# 🆓 Free LLM API Providers — Consolidated Directory
|
||||
|
||||
> **The ultimate aggregated reference for all permanently free LLM API providers.**
|
||||
> Consolidated from 6 community repositories. Use with OmniRoute to route through 25+ free providers simultaneously.
|
||||
|
||||
_Last consolidated: May 2026 · Sources: awesome-free-llm-apis, awesome-free-llm-apis2, free-llm-api-resources, Free-LLM-Collection, FREE-LLM-API-Provider, gpt4free_
|
||||
|
||||
---
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Quick Comparison](#quick-comparison)
|
||||
- [Provider APIs (First-Party)](#provider-apis-first-party)
|
||||
- [Inference Providers (Third-Party)](#inference-providers-third-party)
|
||||
- [China-Based Providers](#china-based-providers)
|
||||
- [Trial Credit Providers](#trial-credit-providers)
|
||||
- [Using with OmniRoute](#using-with-omniroute)
|
||||
- [Glossary](#glossary)
|
||||
|
||||
---
|
||||
|
||||
## Quick Comparison
|
||||
|
||||
All free providers at a glance, sorted by generosity of free tier:
|
||||
|
||||
| Provider | Type | Best Free Model | RPM | RPD | Tokens | OpenAI Compat | Speed |
|
||||
| ----------------- | --------- | ---------------------- | ------- | ------------ | ---------------- | --------------- | --------- |
|
||||
| **Groq** | Inference | Llama 3.3 70B | 30 | 14,400 | 6K TPM | ✅ | 🟢 Fast |
|
||||
| **Cerebras** | Inference | Qwen3 235B | 30 | 14,400 | 1M TPD | ✅ | 🟢 Fast |
|
||||
| **Mistral AI** | Provider | Mistral Large 3 | 60 | Unlimited | 1B/month | ✅ | 🟡 Medium |
|
||||
| **Google Gemini** | Provider | Gemini 2.5 Flash | 5–15 | 20–1,500 | 250K TPM | ✅ | 🟢 Fast |
|
||||
| **NVIDIA NIM** | Inference | 129 models | 40 | — | — | ✅ | 🟡 Medium |
|
||||
| **Ollama Cloud** | Inference | 400+ models | — | — | Session limits | ❌ (Ollama API) | 🟡 Medium |
|
||||
| **OpenRouter** | Inference | 35+ free models | 20 | 50–1,000 | — | ✅ | 🟡 Medium |
|
||||
| **GitHub Models** | Inference | GPT-4.1, GPT-5 | 10–15 | 50–150 | 8K in/4K out | ✅ | 🟡 Medium |
|
||||
| **Cloudflare AI** | Inference | 50+ models | — | 10K neurons | — | ⚠️ Partial | 🟡 Medium |
|
||||
| **Hugging Face** | Inference | Thousands | — | — | $0.10/mo credits | ✅ | 🔴 Slow |
|
||||
| **Cohere** | Provider | Command A (111B) | 20 | — | 1K calls/month | ⚠️ Partial | 🟡 Medium |
|
||||
| **Pollinations** | Inference | Text+Image+Video+Audio | — | Hourly reset | — | ✅ | 🟡 Medium |
|
||||
| **Z.AI (Zhipu)** | Provider | GLM-4.7-Flash | — | — | Undocumented | ✅ | 🟡 Medium |
|
||||
| **SiliconFlow** | Inference | Qwen3-8B | 1,000 | — | 50K TPM | ✅ | 🟡 Medium |
|
||||
| **Kilo Code** | Inference | Free auto-router | ~200/hr | — | — | ✅ | 🟡 Medium |
|
||||
| **LLM7.io** | Inference | 30+ models | 15–30 | — | — | ✅ | 🟡 Medium |
|
||||
| **Kluster AI** | Inference | DeepSeek-R1 | — | — | Undocumented | ✅ | 🟡 Medium |
|
||||
| **ModelScope** | Inference | Qwen, DeepSeek | — | 2,000 | ≤500/model/day | ✅ | 🟡 Medium |
|
||||
| **IBM watsonx** | Provider | Granite models | 2/sec | — | 300K/month | ❌ | 🟡 Medium |
|
||||
|
||||
---
|
||||
|
||||
## Provider APIs (First-Party)
|
||||
|
||||
APIs from the companies that train or fine-tune the models.
|
||||
|
||||
### Google Gemini 🇺🇸
|
||||
|
||||
🔗 [Get API Key](https://aistudio.google.com/app/apikey) · Base URL: `https://generativelanguage.googleapis.com/v1beta`
|
||||
|
||||
> ⚠️ Free tier NOT available in EU/UK/Switzerland. Prompts may be used by Google to improve products.
|
||||
|
||||
| Model | Context | Max Output | Modality | RPM | RPD |
|
||||
| --------------------------------- | ------- | ---------- | ---------------------- | --- | ------ |
|
||||
| Gemini 2.5 Flash / Gemini 3 Flash | 1M | 65K | Text+Image+Audio+Video | 5 | 20 |
|
||||
| Gemini 2.5 Flash-Lite | 1M | 65K | Text+Image+Audio+Video | 10 | 20 |
|
||||
| Gemini 3.1 Flash-Lite | 1M | 65K | Text+Image+Audio+Video | 15 | 1,500 |
|
||||
| Gemma 4 26B/31B | — | — | Text | 15 | 1,500 |
|
||||
| Gemma 3 (1B/4B/12B/27B) | — | — | Text | 30 | 14,400 |
|
||||
|
||||
### Mistral AI 🇫🇷
|
||||
|
||||
🔗 [Get API Key](https://console.mistral.ai/api-keys) · Base URL: `https://api.mistral.ai/v1`
|
||||
|
||||
Free "Experiment" plan, no credit card. ~1B tokens/month. Requires phone verification.
|
||||
|
||||
| Model | Context | Max Output | Modality | Rate Limit |
|
||||
| ------------------ | ------- | ---------- | --------------- | --------------- |
|
||||
| Mistral Small 4 | 256K | 256K | Text+Image+Code | 1 RPS, 500K TPM |
|
||||
| Mistral Medium 3 | 128K | 128K | Text | 1 RPS, 500K TPM |
|
||||
| Mistral Large 3 | 256K | 256K | Text | 1 RPS, 500K TPM |
|
||||
| Mistral Nemo (12B) | 128K | 128K | Text | 1 RPS, 500K TPM |
|
||||
| Codestral | 256K | 256K | Code | 30 RPM, 2K RPD |
|
||||
| Pixtral Large | 128K | 128K | Text+Image | 1 RPS, 500K TPM |
|
||||
|
||||
### Cohere 🇨🇦
|
||||
|
||||
🔗 [Get API Key](https://dashboard.cohere.com/api-keys) · Base URL: `https://api.cohere.com/v2`
|
||||
|
||||
Free "Trial" key. 1,000 API calls/month. Non-commercial use only. 20 RPM.
|
||||
|
||||
| Model | Context | Max Output | Modality |
|
||||
| ------------------- | ------- | ---------- | ----------------------- |
|
||||
| Command A (111B) | 256K | 4K | Text |
|
||||
| Command A Reasoning | 256K | 4K | Text (reasoning) |
|
||||
| Command A Vision | 256K | 4K | Text+Image |
|
||||
| Command A Translate | 256K | 4K | Translation |
|
||||
| Command R+ | 128K | 4K | Text |
|
||||
| Command R | 128K | 4K | Text |
|
||||
| Command R7B | 128K | 4K | Text |
|
||||
| Embed 4 | — | — | Embeddings (Text+Image) |
|
||||
| Rerank 3.5 | — | — | Reranking |
|
||||
|
||||
### Z.AI (Zhipu AI) 🇨🇳
|
||||
|
||||
🔗 [Get API Key](https://open.bigmodel.cn/usercenter/apikeys) · Base URL: `https://open.bigmodel.cn/api/paas/v4`
|
||||
|
||||
Permanent free models, no credit card. No published rate limits.
|
||||
|
||||
| Model | Context | Max Output | Modality |
|
||||
| --------------- | ------- | ---------- | ---------- |
|
||||
| GLM-4.7-Flash | 200K | 128K | Text |
|
||||
| GLM-4.5-Flash | 128K | ~8K | Text |
|
||||
| GLM-4.6V-Flash | 128K | ~4K | Text+Image |
|
||||
| GLM-5 / GLM-5.1 | — | — | Text |
|
||||
|
||||
### IBM watsonx 🇺🇸
|
||||
|
||||
🔗 [Pricing](https://www.ibm.com/products/watsonx-ai/pricing)
|
||||
|
||||
Free tier: 2 RPS, 300K tokens/month. Granite foundation models.
|
||||
|
||||
---
|
||||
|
||||
## Inference Providers (Third-Party)
|
||||
|
||||
### Groq 🇺🇸
|
||||
|
||||
🔗 [Get API Key](https://console.groq.com/keys) · Base URL: `https://api.groq.com/openai/v1`
|
||||
|
||||
Ultra-fast LPU inference (~300–500 tok/s). No credit card required.
|
||||
|
||||
| Model | RPM | RPD | TPM | Modality |
|
||||
| ---------------------------------- | --- | ------ | --- | ---------------- |
|
||||
| llama-3.3-70b-versatile | 30 | 1,000 | 12K | Text |
|
||||
| llama-3.1-8b-instant | 30 | 14,400 | 6K | Text |
|
||||
| llama-4-scout-17b-16e-instruct | 30 | 1,000 | 30K | Text+Vision |
|
||||
| llama-4-maverick-17b-128e-instruct | 30 | 1,000 | 6K | Text+Vision |
|
||||
| qwen3-32b | 60 | 1,000 | 6K | Text |
|
||||
| kimi-k2-instruct | 60 | 1,000 | 10K | Text |
|
||||
| gpt-oss-120b / gpt-oss-20b | 30 | 1,000 | 8K | Text |
|
||||
| deepseek-r1-distill-70b | 30 | 14,400 | — | Text (reasoning) |
|
||||
| whisper-large-v3 / v3-turbo | 20 | 2,000 | — | Audio→Text |
|
||||
|
||||
### Cerebras 🇺🇸
|
||||
|
||||
🔗 [Get API Key](https://cloud.cerebras.ai/) · Base URL: `https://api.cerebras.ai/v1`
|
||||
|
||||
Wafer-scale chip inference (~2,600 tok/s). 1M tokens/day cap.
|
||||
|
||||
| Model | RPM | RPH | RPD | TPM | TPD |
|
||||
| ------------------------------ | --- | --- | ------ | --- | --- |
|
||||
| gpt-oss-120b | 30 | 900 | 14,400 | 64K | 1M |
|
||||
| llama3.1-8b | 30 | 900 | 14,400 | 60K | 1M |
|
||||
| qwen-3-235b-a22b-instruct-2507 | 30 | 900 | 14,400 | 60K | 1M |
|
||||
| zai-glm-4.7 | 10 | 100 | 100 | 60K | 1M |
|
||||
|
||||
### NVIDIA NIM 🇺🇸
|
||||
|
||||
🔗 [Explore Models](https://build.nvidia.com/explore/discover) · Base URL: `https://integrate.api.nvidia.com/v1`
|
||||
|
||||
Free with NVIDIA Developer Program. **129 models**, 40 RPM. Phone verification required.
|
||||
|
||||
**Notable models:** DeepSeek-R1, DeepSeek-V3.2, Nemotron Ultra 253B, Llama 3.1 405B, Qwen3 Coder 480B, Mistral Large 3, Kimi K2, GLM-5.1, MiniMax M2.7, Gemma 4 31B, + 100 more.
|
||||
|
||||
### OpenRouter 🇺🇸
|
||||
|
||||
🔗 [Get API Key](https://openrouter.ai/keys) · Base URL: `https://openrouter.ai/api/v1`
|
||||
|
||||
35+ free models (suffix `:free`). 20 RPM.
|
||||
|
||||
| Credits Purchased | RPD |
|
||||
| ----------------- | ----- |
|
||||
| < $10 | 50 |
|
||||
| ≥ $10 (one-time) | 1,000 |
|
||||
|
||||
**Notable free models:** DeepSeek R1, DeepSeek V3, Qwen3 Coder 480B, Llama 4 Scout/Maverick, GPT-OSS 120B, Nemotron 3 Super 120B, MiniMax M2.5, Gemma 4 31B, Devstral, + 23 more.
|
||||
|
||||
### GitHub Models 🇺🇸
|
||||
|
||||
🔗 [Marketplace](https://github.com/marketplace/models) · Base URL: `https://models.inference.ai.azure.com`
|
||||
|
||||
Free for all GitHub users. 45+ models including frontier models.
|
||||
|
||||
| Tier | RPM | RPD | Tokens/Request |
|
||||
| ----------------------- | --- | --- | -------------- |
|
||||
| Low tier models | 15 | 150 | 8K in / 4K out |
|
||||
| High tier models | 10 | 50 | 8K in / 4K out |
|
||||
| DeepSeek-R1 / MAI-DS-R1 | 1 | 8 | 4K in / 4K out |
|
||||
| Grok-3 | 1 | 15 | 4K in / 4K out |
|
||||
|
||||
**Notable models:** GPT-4.1, GPT-4o, GPT-5, GPT-5-mini, o3-mini, o4-mini, DeepSeek-R1, Llama 4 Scout/Maverick, Codestral, Mistral Medium 3, Phi-4, Grok-3.
|
||||
|
||||
### Cloudflare Workers AI 🇺🇸
|
||||
|
||||
🔗 [Get Token](https://dash.cloudflare.com/profile/api-tokens) · 10,000 Neurons/day free. 50+ models.
|
||||
|
||||
**Notable models:** Llama 3.3 70B, Llama 4 Scout, Qwen3 30B-A3B, QwQ 32B, DeepSeek R1 Distill, Gemma 4 26B, GLM 4.7 Flash, Nemotron 3 120B, Kimi K2.5/K2.6, Mistral Small 3.1, GPT-OSS 120B/20B, + 40 more.
|
||||
|
||||
### Hugging Face 🇺🇸
|
||||
|
||||
🔗 [Get Token](https://huggingface.co/settings/tokens) · Base URL: `https://api-inference.huggingface.co/v1`
|
||||
|
||||
$0.10/month free credits (auto-replenished). Thousands of models. Serverless limited to <10GB models.
|
||||
|
||||
### Ollama Cloud 🇺🇸
|
||||
|
||||
🔗 [Get Key](https://ollama.com/settings/keys) · Base URL: `https://api.ollama.com`
|
||||
|
||||
400+ models. Session/weekly limits (unpublished). NOT OpenAI SDK-compatible.
|
||||
|
||||
**Notable models:** GPT-OSS 120B, DeepSeek V3.2/V4, Kimi K2/K2.5/K2.6, GLM-5/5.1, Qwen3 Coder 480B, Gemini 3 Flash, MiniMax M2.7, Cogito 2.1 671B, Nemotron 3 Super 120B.
|
||||
|
||||
### Pollinations AI 🇩🇪
|
||||
|
||||
🔗 [Get Key](https://enter.pollinations.ai) · Base URL: `https://gen.pollinations.ai/v1`
|
||||
|
||||
No sign-up required for basic use. Unique: **text + image + video + audio** all free.
|
||||
|
||||
**Text models:** openai, openai-large, openai-reasoning, gemini, mistral, llama.
|
||||
**Image models:** flux, gpt-image, seedream, kontext.
|
||||
**Video:** wan-fast. **Audio:** tts-1, 30+ ElevenLabs voices.
|
||||
|
||||
### SiliconFlow 🇨🇳
|
||||
|
||||
🔗 [Get Key](https://cloud.siliconflow.cn/account/ak) · Base URL: `https://api.siliconflow.cn/v1`
|
||||
|
||||
14 CNY signup credits. Permanently free models: 1,000 RPM, 50K TPM.
|
||||
|
||||
| Model | Context | Modality |
|
||||
| --------------------------- | ------- | ---------------- |
|
||||
| Qwen/Qwen3-8B | 131K | Text |
|
||||
| DeepSeek-R1-0528-Qwen3-8B | ~33K | Text (reasoning) |
|
||||
| DeepSeek-R1-Distill-Qwen-7B | 131K | Text (reasoning) |
|
||||
| THUDM/glm-4-9b-chat | 32K | Text |
|
||||
| THUDM/GLM-4.1V-9B-Thinking | 66K | Vision+Text |
|
||||
| DeepSeek-OCR | — | Vision (OCR) |
|
||||
|
||||
### Kilo Code 🇺🇸
|
||||
|
||||
🔗 [Get Key](https://kilo.ai) · Base URL: `https://api.kilo.ai/api/gateway`
|
||||
|
||||
Free models, no credit card. ~200 req/hr. Auto-router `kilo-auto/free`.
|
||||
|
||||
### LLM7.io 🇬🇧
|
||||
|
||||
🔗 [Get Token](https://token.llm7.io) · Base URL: `https://api.llm7.io/v1`
|
||||
|
||||
30+ models. 15 RPM (30 RPM with free token). No registration for basic access.
|
||||
|
||||
### Kluster AI 🇺🇸
|
||||
|
||||
🔗 [Get Key](https://platform.kluster.ai/apikeys) · DeepSeek-R1, Llama 4 Maverick, Qwen3-235B + more.
|
||||
|
||||
### OpenCode Zen
|
||||
|
||||
🔗 [Docs](https://opencode.ai/docs/zen/) · Free models (Big Pickle Stealth, MiniMax M2.5 Free, Arcee Large).
|
||||
|
||||
### Vercel AI Gateway
|
||||
|
||||
🔗 [Docs](https://vercel.com/docs/ai-gateway) · $5/month free credits. Routes to various providers.
|
||||
|
||||
---
|
||||
|
||||
## China-Based Providers
|
||||
|
||||
### ModelScope (魔搭社区) 🇨🇳
|
||||
|
||||
🔗 [Get Token](https://modelscope.cn/my/myaccesstoken) · Base URL: `https://api-inference.modelscope.cn/v1`
|
||||
|
||||
2,000 req/day total, ≤500/model/day. Requires Alibaba Cloud account + real-name verification.
|
||||
|
||||
**Models:** DeepSeek V4 Pro/Flash, DeepSeek V3.2, GLM-5/5.1, MiniMax M2.5, Qwen3-235B, Qwen3 Coder 480B, Ling-2.6-1T.
|
||||
|
||||
### Tencent Hunyuan (腾讯混元)
|
||||
|
||||
Hunyuan-Lite: free. Other models: 100M tokens free (1-year expiry).
|
||||
|
||||
### Volcengine (火山引擎)
|
||||
|
||||
500 resource points/day. Tongyi Qwen free (100 calls/day). Doubao models with tiered pricing.
|
||||
|
||||
### ChatAnywhere
|
||||
|
||||
🔗 Base URL: `https://api.chatanywhere.tech` · GPT-5.4-mini, DeepSeek-V4, and more.
|
||||
|
||||
### InternAI (书生)
|
||||
|
||||
🔗 Base URL: `https://chat.intern-ai.org.cn/api/v1` · 10 RPM. Keys valid 6 months.
|
||||
|
||||
**Models:** intern-latest, intern-s1-pro, internvl3.5-241b-a28b.
|
||||
|
||||
### Bigmodel (智谱)
|
||||
|
||||
🔗 Base URL: `https://open.bigmodel.cn/api/paas/v4/` · 30 concurrent requests.
|
||||
|
||||
**Models:** GLM-4-Flash, GLM-4V-Flash, GLM-4.1V-Thinking-Flash, GLM-4.6V-Flash, GLM-4.7-Flash.
|
||||
|
||||
---
|
||||
|
||||
## Trial Credit Providers
|
||||
|
||||
These offer one-time or time-limited credits (not permanent free tiers):
|
||||
|
||||
| Provider | Credits | Expiry | Notable Models |
|
||||
| ---------------------------------------------------------- | ---------------- | -------- | ----------------------------- |
|
||||
| [Baseten](https://app.baseten.co/) | $30 | — | Any model (pay by compute) |
|
||||
| [NLP Cloud](https://nlpcloud.com) | $15 | — | Various open models |
|
||||
| [AI21](https://studio.ai21.com/) | $10 | 3 months | Jamba family |
|
||||
| [Upstage](https://console.upstage.ai/) | $10 | 3 months | Solar Pro/Mini |
|
||||
| [Modal](https://modal.com) | $5/mo | Monthly | Any model (compute time) |
|
||||
| [SambaNova](https://cloud.sambanova.ai/) | $5 | 3 months | Llama 3.3, Qwen3, DeepSeek R1 |
|
||||
| [Scaleway](https://console.scaleway.com/generative-api) | 1M tokens | One-time | Llama 3.3, Gemma 3, GPT-OSS |
|
||||
| [Alibaba Cloud](https://bailian.console.alibabacloud.com/) | 1M tokens/model | — | Qwen family |
|
||||
| [Fireworks](https://fireworks.ai/) | $1 | — | Various open models |
|
||||
| [Nebius](https://tokenfactory.nebius.com/) | $1 | — | Various open models |
|
||||
| [Inference.net](https://inference.net) | $1 (+$25 survey) | — | Various open models |
|
||||
| [Hyperbolic](https://app.hyperbolic.ai/) | $1 | — | DeepSeek V3, Llama 3.3 |
|
||||
| [Novita](https://novita.ai/) | $0.50 | 1 year | Various open models |
|
||||
|
||||
---
|
||||
|
||||
## Using with OmniRoute
|
||||
|
||||
OmniRoute supports **all providers listed above** as connections. Here's how to maximize free usage:
|
||||
|
||||
### 1. Add Multiple Free Providers
|
||||
|
||||
```
|
||||
Dashboard → Providers → Add Connection
|
||||
```
|
||||
|
||||
Add API keys for Groq, Cerebras, Mistral, Google Gemini, OpenRouter, GitHub Models, etc.
|
||||
|
||||
### 2. Create a Free-Tier Combo
|
||||
|
||||
```
|
||||
Dashboard → Combos → Create Combo → Add all free providers as targets
|
||||
```
|
||||
|
||||
Use the **"priority"** or **"round-robin"** strategy to distribute load across free tiers.
|
||||
|
||||
### 3. Recommended Free Combo Strategy
|
||||
|
||||
| Priority | Provider | Why |
|
||||
| -------- | ----------------- | --------------------------------------------- |
|
||||
| 1 | **Groq** | Fastest inference, 14,400 RPD on small models |
|
||||
| 2 | **Cerebras** | 1M TPD, fast wafer-scale chips |
|
||||
| 3 | **Mistral** | 1B tokens/month, large model selection |
|
||||
| 4 | **Google Gemini** | 1M context, multimodal |
|
||||
| 5 | **NVIDIA NIM** | 129 models, 40 RPM |
|
||||
| 6 | **OpenRouter** | 35+ free models as final fallback |
|
||||
|
||||
### 4. Environment Variables
|
||||
|
||||
```bash
|
||||
# These providers work out of the box with OmniRoute:
|
||||
GROQ_API_KEY=your-key
|
||||
CEREBRAS_API_KEY=your-key
|
||||
MISTRAL_API_KEY=your-key
|
||||
GOOGLE_AI_API_KEY=your-key
|
||||
NVIDIA_API_KEY=your-key
|
||||
OPENROUTER_API_KEY=your-key
|
||||
GITHUB_TOKEN=your-token
|
||||
CLOUDFLARE_API_TOKEN=your-token
|
||||
COHERE_API_KEY=your-key
|
||||
SILICONFLOW_API_KEY=your-key
|
||||
```
|
||||
|
||||
### 5. Estimated Free Capacity
|
||||
|
||||
With all top-6 providers combined in a combo:
|
||||
|
||||
| Metric | Combined Free Capacity |
|
||||
| -------------------- | ---------------------- |
|
||||
| **Requests/Day** | ~31,000+ RPD |
|
||||
| **Tokens/Month** | ~32B+ tokens |
|
||||
| **Models Available** | 200+ unique models |
|
||||
| **Cost** | $0.00 |
|
||||
|
||||
---
|
||||
|
||||
## Glossary
|
||||
|
||||
| Term | Meaning |
|
||||
| ----------- | ------------------------------------------- |
|
||||
| **RPM** | Requests per minute |
|
||||
| **RPD** | Requests per day |
|
||||
| **RPH** | Requests per hour |
|
||||
| **RPS** | Requests per second |
|
||||
| **TPM** | Tokens per minute |
|
||||
| **TPD** | Tokens per day |
|
||||
| **Neurons** | Cloudflare's compute unit (~1 output token) |
|
||||
|
||||
---
|
||||
|
||||
## Sources
|
||||
|
||||
This document consolidates data from 6 community repositories:
|
||||
|
||||
| Repository | Focus |
|
||||
| -------------------------------------------------------------------------- | ------------------------------------------------ |
|
||||
| [awesome-free-llm-apis](https://github.com/mnfst/awesome-free-llm-apis) | Curated list with detailed model tables |
|
||||
| [awesome-free-llm-apis2](https://github.com/) | Extended list with speed tiers and code snippets |
|
||||
| [free-llm-api-resources](https://github.com/) | Auto-generated model lists with trial credits |
|
||||
| [Free-LLM-Collection](https://github.com/for-the-zero/Free-LLM-Collection) | Chinese + global providers with rate limits |
|
||||
| [FREE-LLM-API-Provider](https://github.com/CYBIRD-D/FREE-LLM-API-Provider) | Deep provider analysis with CN platforms |
|
||||
| [gpt4free](https://github.com/xtekky/gpt4free) | Config-based routing with quota awareness |
|
||||
|
||||
> ⚠️ **Disclaimer:** Rate limits change frequently. Always verify with the provider's official documentation before relying on specific limits. Trial credits and time-limited promotions are separated from permanent free tiers.
|
||||
409
docs/I18N.md
409
docs/I18N.md
@@ -1,409 +0,0 @@
|
||||
# i18n — Internationalization Guide
|
||||
|
||||
OmniRoute supports **30 languages** with full dashboard UI translation, translated documentation, and RTL support for Arabic and Hebrew.
|
||||
|
||||
🌐 **Languages:** 🇺🇸 [English](../I18N.md) | 🇧🇷 [Português (Brasil)](./pt-BR/I18N.md) | 🇪🇸 [Español](./es/I18N.md) | 🇫🇷 [Français](./fr/I18N.md) | 🇩🇪 [Deutsch](./de/I18N.md) | 🇮🇹 [Italiano](./it/I18N.md) | 🇷🇺 [Русский](./ru/I18N.md) | 🇨🇳 [中文 (简体)](./zh-CN/I18N.md) | 🇯🇵 [日本語](./ja/I18N.md) | 🇰🇷 [한국어](./ko/I18N.md) | 🇸🇦 [العربية](./ar/I18N.md) | 🇮🇳 [हिन्दी](./hi/I18N.md) | 🇹🇭 [ไทย](./th/I18N.md) | 🇹🇷 [Türkçe](./tr/I18N.md) | 🇺🇦 [Українська](./uk-UA/I18N.md) | 🇻🇳 [Tiếng Việt](./vi/I18N.md) | 🇧🇬 [Български](./bg/I18N.md) | 🇩🇰 [Dansk](./da/I18N.md) | 🇫🇮 [Suomi](./fi/I18N.md) | 🇮🇱 [עברית](./he/I18N.md) | 🇭🇺 [Magyar](./hu/I18N.md) | 🇮🇩 [Bahasa Indonesia](./id/I18N.md) | 🇲🇾 [Bahasa Melayu](./ms/I18N.md) | 🇳🇱 [Nederlands](./nl/I18N.md) | 🇳🇴 [Norsk](./no/I18N.md) | 🇵🇹 [Português (Portugal)](./pt/I18N.md) | 🇷🇴 [Română](./ro/I18N.md) | 🇵🇱 [Polski](./pl/I18N.md) | 🇸🇰 [Slovenčina](./sk/I18N.md) | 🇸🇪 [Svenska](./sv/I18N.md) | 🇵🇭 [Filipino](./phi/I18N.md) | 🇨🇿 [Čeština](./cs/I18N.md)
|
||||
|
||||
## Quick Reference
|
||||
|
||||
| Task | Command |
|
||||
|------|---------|
|
||||
| Generate translations | `node scripts/i18n/generate-multilang.mjs messages` |
|
||||
| Translate docs (LLM) | `python3 scripts/i18n_autotranslate.py --api-url <url> --api-key <key> --model <model>` |
|
||||
| Validate a locale | `python3 scripts/validate_translation.py quick -l cs` |
|
||||
| Check code keys | `python3 scripts/check_translations.py` |
|
||||
| Generate QA report | `node scripts/i18n/generate-qa-checklist.mjs` |
|
||||
| Visual QA (Playwright) | `node scripts/i18n/run-visual-qa.mjs` |
|
||||
|
||||
## Architecture
|
||||
|
||||
### Source of Truth
|
||||
- **UI strings**: `src/i18n/messages/en.json` (English source, ~2800 keys)
|
||||
- **Locale files**: `src/i18n/messages/{locale}.json` (30 translations)
|
||||
- **Framework**: `next-intl` with cookie-based locale resolution
|
||||
- **Config**: `src/i18n/config.ts` — defines all 30 locales, language names, flags
|
||||
|
||||
### Runtime Flow
|
||||
1. User selects language → `NEXT_LOCALE` cookie set
|
||||
2. `src/i18n/request.ts` resolves locale: cookie → `Accept-Language` header → fallback `en`
|
||||
3. Dynamic import loads `messages/{locale}.json`
|
||||
4. Components use `useTranslations("namespace")` and `t("key")`
|
||||
|
||||
### Supported Locales
|
||||
|
||||
| Code | Language | RTL | Google Translate Code |
|
||||
|------|----------|-----|----------------------|
|
||||
| `ar` | العربية | Yes | `ar` |
|
||||
| `bg` | Български | No | `bg` |
|
||||
| `cs` | Čeština | No | `cs` |
|
||||
| `da` | Dansk | No | `da` |
|
||||
| `de` | Deutsch | No | `de` |
|
||||
| `es` | Español | No | `es` |
|
||||
| `fi` | Suomi | No | `fi` |
|
||||
| `fr` | Français | No | `fr` |
|
||||
| `he` | עברית | Yes | `iw` |
|
||||
| `hi` | हिन्दी | No | `hi` |
|
||||
| `hu` | Magyar | No | `hu` |
|
||||
| `id` | Bahasa Indonesia | No | `id` |
|
||||
| `it` | Italiano | No | `it` |
|
||||
| `ja` | 日本語 | No | `ja` |
|
||||
| `ko` | 한국어 | No | `ko` |
|
||||
| `ms` | Bahasa Melayu | No | `ms` |
|
||||
| `nl` | Nederlands | No | `nl` |
|
||||
| `no` | Norsk | No | `no` |
|
||||
| `phi` | Filipino | No | `tl` |
|
||||
| `pl` | Polski | No | `pl` |
|
||||
| `pt` | Português (Portugal) | No | `pt` |
|
||||
| `pt-BR` | Português (Brasil) | No | `pt` |
|
||||
| `ro` | Română | No | `ro` |
|
||||
| `ru` | Русский | No | `ru` |
|
||||
| `sk` | Slovenčina | No | `sk` |
|
||||
| `sv` | Svenska | No | `sv` |
|
||||
| `th` | ไทย | No | `th` |
|
||||
| `tr` | Türkçe | No | `tr` |
|
||||
| `uk-UA` | Українська | No | `uk` |
|
||||
| `vi` | Tiếng Việt | No | `vi` |
|
||||
| `zh-CN` | 中文 (简体) | No | `zh-CN` |
|
||||
|
||||
## Adding a New Language
|
||||
|
||||
### 1. Register the Locale
|
||||
Edit `src/i18n/config.ts`:
|
||||
```ts
|
||||
// Add to LOCALES array
|
||||
"xx",
|
||||
// Add to LANGUAGES array
|
||||
{ code: "xx", label: "XX", name: "Language Name", flag: "🏳️" },
|
||||
```
|
||||
|
||||
### 2. Add to Generator
|
||||
Edit `scripts/i18n/generate-multilang.mjs` — add entry to `LOCALE_SPECS`:
|
||||
```js
|
||||
{
|
||||
code: "xx",
|
||||
googleTl: "xx",
|
||||
label: "XX",
|
||||
flag: "🏳️",
|
||||
languageName: "Language Name",
|
||||
readmeName: "Language Name",
|
||||
docsName: "Language Name",
|
||||
},
|
||||
```
|
||||
|
||||
### 3. Generate Initial Translation
|
||||
```bash
|
||||
node scripts/i18n/generate-multilang.mjs messages
|
||||
```
|
||||
This creates `src/i18n/messages/xx.json` auto-translated from `en.json` via Google Translate.
|
||||
|
||||
### 4. Review & Fix Auto-Translations
|
||||
Auto-translations are a starting point. Review manually for:
|
||||
- Technical accuracy
|
||||
- Context-appropriate terminology
|
||||
- Proper handling of placeholders (`{count}`, `{value}`, etc.)
|
||||
|
||||
### 5. Validate
|
||||
```bash
|
||||
python3 scripts/validate_translation.py quick -l xx
|
||||
python3 scripts/validate_translation.py diff common -l xx
|
||||
```
|
||||
|
||||
### 6. Generate Translated Documentation
|
||||
```bash
|
||||
node scripts/i18n/generate-multilang.mjs docs
|
||||
```
|
||||
|
||||
## Auto-Translation Pipeline
|
||||
|
||||
### generate-multilang.mjs (Google Translate)
|
||||
|
||||
**Primary auto-translation engine** — uses Google Translate free API to generate translations for UI strings, READMEs, and documentation.
|
||||
|
||||
```bash
|
||||
node scripts/i18n/generate-multilang.mjs [messages|readme|docs|all]
|
||||
```
|
||||
|
||||
| Mode | What it does |
|
||||
|------|-------------|
|
||||
| `messages` | Translates missing keys in `src/i18n/messages/{locale}.json` from `en.json` |
|
||||
| `readme` | Translates `README.md` into all locales as `README.{code}.md` in project root |
|
||||
| `docs` | Translates `DOC_SOURCE_FILES` into `docs/i18n/{locale}/{docName}` |
|
||||
| `all` | Runs all three modes |
|
||||
|
||||
**Features:**
|
||||
- **Text protection**: Masks code blocks (```` ``` ````), inline code (`` ` ``), markdown links/images (`[text](url)`), HTML tags, tables, and ICU placeholders (`{count}`, `{value}`, `{total}`, etc.) before translation, then restores them
|
||||
- **Chunked batching**: Joins multiple strings with `__OMNIROUTE_I18N_SEPARATOR__` delimiters to minimize API calls (max 1800 chars per request)
|
||||
- **In-memory cache**: Avoids redundant API calls for repeated strings within a session
|
||||
- **Retry logic**: Exponential backoff (up to 5 attempts with 300ms × attempt delay) for 429/5xx errors
|
||||
- **Timeout**: 20 seconds per request
|
||||
- **Skip existing**: If target file already exists, it is NOT overwritten
|
||||
|
||||
**Important behaviors:**
|
||||
- `docs/i18n/README.md` is **regenerated** each run — it's an auto-generated index of all docs
|
||||
- Root `README.{code}.md` files are only created if they don't exist (skips locales in `EXISTING_README_CODES`)
|
||||
- Language bars (`🌐 **Languages:** ...`) are automatically inserted/updated in all translated docs
|
||||
|
||||
### i18n_autotranslate.py (LLM-based)
|
||||
|
||||
**Secondary translator** — uses any OpenAI-compatible LLM API (including OmniRoute itself) to translate existing `docs/i18n/` markdown files. Best for polishing or re-translating docs with better quality than Google Translate.
|
||||
|
||||
```bash
|
||||
python3 scripts/i18n_autotranslate.py \
|
||||
--api-url http://localhost:20128/v1 \
|
||||
--api-key sk-your-key \
|
||||
--model gpt-4o
|
||||
```
|
||||
|
||||
**Features:**
|
||||
- Scans `docs/i18n/` markdown files for English paragraphs
|
||||
- Skips code blocks, tables, and already-translated content
|
||||
- Sends paragraphs to LLM with technical translation system prompt
|
||||
- Supports all 30 languages
|
||||
|
||||
## Validation & QA
|
||||
|
||||
### validate_translation.py
|
||||
|
||||
**Translation validator** — compares any locale JSON against `en.json` and reports issues.
|
||||
|
||||
```bash
|
||||
# Quick check (counts only)
|
||||
python3 scripts/validate_translation.py quick -l cs
|
||||
# Output:
|
||||
# Missing: 0
|
||||
# Untranslated: 0
|
||||
# Ignored (UNTRANSLATABLE_KEYS): 236
|
||||
|
||||
# Detailed diff by category
|
||||
python3 scripts/validate_translation.py diff common -l cs
|
||||
python3 scripts/validate_translation.py diff settings -l cs
|
||||
|
||||
# Export to CSV
|
||||
python3 scripts/validate_translation.py csv -l cs > report.csv
|
||||
|
||||
# Export to Markdown
|
||||
python3 scripts/validate_translation.py md -l cs > report.md
|
||||
|
||||
# Full report (default)
|
||||
python3 scripts/validate_translation.py -l cs
|
||||
```
|
||||
|
||||
**Detects:**
|
||||
- **Missing keys** — keys in `en.json` but not in locale file
|
||||
- **Extra keys** — keys in locale file but not in `en.json`
|
||||
- **Untranslated keys** — keys where locale value equals English source (excluding allowlist)
|
||||
- **Placeholder mismatches** — ICU placeholders that don't match between source and translation
|
||||
|
||||
**Exit codes:**
|
||||
| Code | Meaning |
|
||||
|------|---------|
|
||||
| 0 | OK |
|
||||
| 1 | Generic error |
|
||||
| 2 | Missing strings (hard error) |
|
||||
| 3 | Untranslated warning (soft) |
|
||||
|
||||
**Environment:** Set `TRANSLATION_LANG=cs` or use `-l cs` flag.
|
||||
|
||||
### check_translations.py
|
||||
|
||||
**Code-to-JSON key checker** — scans `src/**/*.tsx` and `src/**/*.ts` for `useTranslations()` calls and verifies all referenced keys exist in `en.json`.
|
||||
|
||||
```bash
|
||||
# Basic check
|
||||
python3 scripts/check_translations.py
|
||||
|
||||
# Verbose output
|
||||
python3 scripts/check_translations.py --verbose
|
||||
|
||||
# Auto-fix (adds missing keys to en.json)
|
||||
python3 scripts/check_translations.py --fix
|
||||
```
|
||||
|
||||
### generate-qa-checklist.mjs
|
||||
|
||||
**Static analysis QA** — scans Next.js page files for i18n risk metrics and generates a Markdown report.
|
||||
|
||||
```bash
|
||||
node scripts/i18n/generate-qa-checklist.mjs
|
||||
```
|
||||
|
||||
**Checks:**
|
||||
- Fixed-width class usage (overflow risk)
|
||||
- Directional left/right classes (RTL risk)
|
||||
- Clipping-prone patterns
|
||||
- Locale parity (missing/extra keys vs `en.json`)
|
||||
- README language selector bars in priority locales (`es`, `fr`, `de`, `ja`, `ar`)
|
||||
|
||||
**Output:** `docs/reports/i18n-qa-checklist-{date}.md`
|
||||
|
||||
### run-visual-qa.mjs
|
||||
|
||||
**Visual QA via Playwright** — takes screenshots of all dashboard routes in multiple locales and viewports, then evaluates page health.
|
||||
|
||||
```bash
|
||||
# Default: es, fr, de, ja, ar on localhost:20128
|
||||
node scripts/i18n/run-visual-qa.mjs
|
||||
|
||||
# Custom base URL and locales
|
||||
QA_BASE_URL=http://staging.example.com QA_LOCALES=de,fr node scripts/i18n/run-visual-qa.mjs
|
||||
|
||||
# Custom routes
|
||||
QA_ROUTES=/dashboard/settings,/dashboard/providers node scripts/i18n/run-visual-qa.mjs
|
||||
```
|
||||
|
||||
**Detects:**
|
||||
- Text overflow
|
||||
- Element clipping
|
||||
- RTL layout mismatches
|
||||
|
||||
**Output:** `docs/reports/i18n-visual-qa-{date}.md` + JSON report
|
||||
|
||||
## Managing Untranslatable Keys
|
||||
|
||||
### untranslatable-keys.json
|
||||
|
||||
**File:** `scripts/i18n/untranslatable-keys.json`
|
||||
|
||||
Allowlist of keys that should remain identical to English source. Used by `validate_translation.py` to avoid false-positive "untranslated" warnings.
|
||||
|
||||
```json
|
||||
{
|
||||
"description": "Keys that should remain untranslated...",
|
||||
"keys": [
|
||||
"common.model",
|
||||
"common.oauth",
|
||||
"health.cpu",
|
||||
...
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**What belongs here:**
|
||||
- Brand/product names: `landing.brandName`, `common.social-github`
|
||||
- Technical terms/acronyms: `health.cpu`, `mcpDashboard.pid`, `settings.ai`
|
||||
- ICU/format strings: `apiManager.modelsCount`, `health.millisecondsShort`
|
||||
- Placeholder values: `providers.openaiBaseUrlPlaceholder`, `cliTools.baseUrlPlaceholder`
|
||||
- Protocol names: `common.http`, `common.oauth`, `providers.oauth2Label`
|
||||
- Navigation sections: `sidebar.primarySection`, `sidebar.cliSection`
|
||||
|
||||
**To add a key:** Edit the `keys` array in `scripts/i18n/untranslatable-keys.json` and re-run validation.
|
||||
|
||||
## CI Integration
|
||||
|
||||
### GitHub Actions (`.github/workflows/ci.yml`)
|
||||
|
||||
The CI pipeline validates all locales on every push and PR:
|
||||
|
||||
1. **`i18n-matrix` job** — dynamically discovers all locale files (excluding `en.json`)
|
||||
2. **`i18n` job** — runs `validate_translation.py quick -l '<lang>'` for each locale in parallel
|
||||
3. **`ci-summary` job** — aggregates results into a dashboard summary
|
||||
|
||||
```yaml
|
||||
# i18n-matrix: discovers languages
|
||||
LANGS=$(ls src/i18n/messages/*.json | xargs -n1 basename | sed 's/.json$//' | grep -v '^en$')
|
||||
|
||||
# i18n: validates each language
|
||||
python3 scripts/validate_translation.py quick -l '${{ matrix.lang }}'
|
||||
```
|
||||
|
||||
**Dashboard output:**
|
||||
```
|
||||
## 🌍 Translations
|
||||
| Metric | Value |
|
||||
|--------|------|
|
||||
| Languages checked | 30 |
|
||||
| Total untranslated | 0 |
|
||||
|
||||
✅ All translations complete
|
||||
```
|
||||
|
||||
## File Structure
|
||||
|
||||
```
|
||||
src/i18n/
|
||||
├── config.ts # Locale definitions (30 locales, RTL config)
|
||||
├── request.ts # Runtime locale resolution
|
||||
└── messages/
|
||||
├── en.json # Source of truth (~2800 keys)
|
||||
├── cs.json # Czech translation
|
||||
├── de.json # German translation
|
||||
└── ... # 30 locale files total
|
||||
|
||||
scripts/
|
||||
├── i18n/
|
||||
│ ├── generate-multilang.mjs # Auto-translation engine (Google Translate, 888 lines)
|
||||
│ ├── generate-qa-checklist.mjs # Static analysis QA
|
||||
│ ├── run-visual-qa.mjs # Playwright visual QA
|
||||
│ └── untranslatable-keys.json # Allowlist for validation (236 keys)
|
||||
├── validate_translation.py # Translation validator
|
||||
├── check_translations.py # Code-to-JSON key checker
|
||||
└── i18n_autotranslate.py # LLM-based doc translator
|
||||
|
||||
.github/workflows/
|
||||
└── ci.yml # i18n validation in CI matrix
|
||||
|
||||
docs/
|
||||
├── I18N.md # This file — i18n toolchain documentation
|
||||
├── i18n/
|
||||
│ ├── README.md # Auto-generated language index
|
||||
│ ├── cs/ # Czech docs
|
||||
│ │ └── docs/
|
||||
│ │ ├── I18N.md # Czech translation of this file
|
||||
│ │ └── ...
|
||||
│ ├── de/ # German docs
|
||||
│ └── ... # 30 locale directories
|
||||
└── reports/
|
||||
├── i18n-qa-checklist-*.md # Static analysis reports
|
||||
└── i18n-visual-qa-*.md # Visual QA reports
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### When Editing Translations
|
||||
1. **Always edit `en.json` first** — it's the source of truth
|
||||
2. **Run `generate-multilang.mjs messages`** to propagate new keys to all locales
|
||||
3. **Review auto-translations** — Google Translate is a starting point, not final
|
||||
4. **Validate before committing** — `python3 scripts/validate_translation.py quick -l <lang>`
|
||||
5. **Update `untranslatable-keys.json`** if a key should remain in English
|
||||
|
||||
### Placeholder Safety
|
||||
- ICU placeholders (`{count}`, `{value}`, `{total}`, `{seconds}`) must be preserved exactly
|
||||
- Plural formats (`{count, plural, one {# model} other {# models}}`) must maintain structure
|
||||
- The validator detects placeholder mismatches automatically
|
||||
|
||||
### Adding New Translation Keys in Code
|
||||
```tsx
|
||||
// Use namespaced keys
|
||||
const t = useTranslations("settings");
|
||||
t("cacheSettings"); // maps to settings.cacheSettings in JSON
|
||||
|
||||
// Run check_translations.py to verify keys exist
|
||||
python3 scripts/check_translations.py --verbose
|
||||
```
|
||||
|
||||
### RTL Considerations
|
||||
- Arabic (`ar`) and Hebrew (`he`) are RTL locales
|
||||
- Avoid hardcoded `left`/`right` CSS — use `start`/`end` logical properties
|
||||
- Visual QA catches RTL layout mismatches via `run-visual-qa.mjs`
|
||||
|
||||
## Known Issues & History
|
||||
|
||||
### `in.json` → `hi.json` Fix
|
||||
The generator originally used `code: "in"` (deprecated Google Translate code) for Hindi instead of the correct ISO 639-1 `hi`. This created an orphaned `in.json` duplicate of `hi.json`. Fixed by changing `code: "in"` to `code: "hi"` in `generate-multilang.mjs` and removing the orphaned file.
|
||||
|
||||
### `docs/i18n/README.md` Is Auto-Generated
|
||||
The `docs/i18n/README.md` file is completely regenerated by `generate-multilang.mjs docs`. Any manual edits will be lost. Use `docs/I18N.md` (this file) for hand-written documentation that should persist.
|
||||
|
||||
### External Untranslatable Keys List
|
||||
The `untranslatable-keys.json` allowlist was moved from an inline Python set in `validate_translation.py` to an external JSON file for easier maintenance. The validator loads it at runtime.
|
||||
|
||||
### `generate-multilang.mjs` Hindi Code Fix
|
||||
The generator originally used `code: "in"` (deprecated Google Translate code) for Hindi instead of the correct ISO 639-1 `hi`. This was introduced in upstream commit `952b0b22c` by `diegosouzapw`. Fixed by changing `code: "in"` to `code: "hi"` in the `LOCALE_SPECS` array and removing the orphaned `in.json` file.
|
||||
|
||||
### `validate_translation.py` Ignored Count Output
|
||||
The `quick` check now displays the count of ignored keys from `untranslatable-keys.json`:
|
||||
```
|
||||
Missing: 0
|
||||
Untranslated: 0
|
||||
Ignored (UNTRANSLATABLE_KEYS): 236
|
||||
```
|
||||
@@ -1,119 +0,0 @@
|
||||
# OmniRoute MCP Server Documentation
|
||||
|
||||
> Model Context Protocol server with 37 tools across routing, cache, compression, memory, skills, and proxy operations
|
||||
|
||||
## Installation
|
||||
|
||||
OmniRoute MCP is built-in. Start it with:
|
||||
|
||||
```bash
|
||||
omniroute --mcp
|
||||
```
|
||||
|
||||
Or via the open-sse transport:
|
||||
|
||||
```bash
|
||||
# HTTP streamable transport (port 20130)
|
||||
omniroute --dev # MCP auto-starts on /mcp endpoint
|
||||
```
|
||||
|
||||
## IDE Configuration
|
||||
|
||||
See [MCP Client Configuration](SETUP_GUIDE.md#mcp-client-configuration) for Claude Desktop,
|
||||
Cursor, Cline, and compatible MCP client setup.
|
||||
|
||||
---
|
||||
|
||||
## Essential Tools (8)
|
||||
|
||||
| Tool | Description |
|
||||
| :------------------------------ | :--------------------------------------- |
|
||||
| `omniroute_get_health` | Gateway health, circuit breakers, uptime |
|
||||
| `omniroute_list_combos` | All configured combos with models |
|
||||
| `omniroute_get_combo_metrics` | Performance metrics for a specific combo |
|
||||
| `omniroute_switch_combo` | Switch active combo by ID/name |
|
||||
| `omniroute_check_quota` | Quota status per provider or all |
|
||||
| `omniroute_route_request` | Send a chat completion through OmniRoute |
|
||||
| `omniroute_cost_report` | Cost analytics for a time period |
|
||||
| `omniroute_list_models_catalog` | Full model catalog with capabilities |
|
||||
|
||||
## Advanced Tools (8)
|
||||
|
||||
| Tool | Description |
|
||||
| :--------------------------------- | :---------------------------------------------------------- |
|
||||
| `omniroute_simulate_route` | Dry-run routing simulation with fallback tree |
|
||||
| `omniroute_set_budget_guard` | Session budget with degrade/block/alert actions |
|
||||
| `omniroute_set_resilience_profile` | Apply conservative/balanced/aggressive preset |
|
||||
| `omniroute_test_combo` | Live-test all models in a combo via a real upstream request |
|
||||
| `omniroute_get_provider_metrics` | Detailed metrics for one provider |
|
||||
| `omniroute_best_combo_for_task` | Task-fitness recommendation with alternatives |
|
||||
| `omniroute_explain_route` | Explain a past routing decision |
|
||||
| `omniroute_get_session_snapshot` | Full session state: costs, tokens, errors |
|
||||
|
||||
## Cache Tools (2)
|
||||
|
||||
| Tool | Description |
|
||||
| :---------------------- | :-------------------------------------------------- |
|
||||
| `omniroute_cache_stats` | Semantic cache, prompt-cache, and idempotency stats |
|
||||
| `omniroute_cache_flush` | Flush cache globally or by signature/model |
|
||||
|
||||
## Compression Tools (5)
|
||||
|
||||
| Tool | Description |
|
||||
| :---------------------------------- | :------------------------------------------------------------- |
|
||||
| `omniroute_compression_status` | Compression settings, analytics summary, and cache-aware stats |
|
||||
| `omniroute_compression_configure` | Configure compression mode, threshold, and runtime options |
|
||||
| `omniroute_set_compression_engine` | Set Caveman, RTK, or stacked compression mode and pipeline |
|
||||
| `omniroute_list_compression_combos` | List named compression combos and routing assignments |
|
||||
| `omniroute_compression_combo_stats` | Analytics grouped by compression combo and engine |
|
||||
|
||||
`omniroute_compression_status` reports MCP description compression separately under
|
||||
`analytics.mcpDescriptionCompression`. Those values are metadata-size estimates for MCP listable
|
||||
descriptions (`tools`, `prompts`, `resources`, and `resourceTemplates`); they are not provider usage
|
||||
receipts and are marked with `source: "mcp_metadata_estimate"`.
|
||||
|
||||
See [Compression Engines](COMPRESSION_ENGINES.md) and [RTK Compression](RTK_COMPRESSION.md) for
|
||||
the runtime compression model behind these tools.
|
||||
|
||||
## Other Tool Groups
|
||||
|
||||
The remaining MCP surface includes 1proxy tools, memory tools, and skill tools. The live source of
|
||||
truth is `open-sse/mcp-server/tools/` and `open-sse/mcp-server/schemas/tools.ts`.
|
||||
|
||||
## Authentication
|
||||
|
||||
MCP tools are authenticated via API key scopes. Each tool requires specific scopes:
|
||||
|
||||
| Scope | Tools |
|
||||
| :-------------------- | :------------------------------------------------------------------- |
|
||||
| `read:health` | get_health, get_provider_metrics |
|
||||
| `read:combos` | list_combos, get_combo_metrics |
|
||||
| `write:combos` | switch_combo |
|
||||
| `read:quota` | check_quota |
|
||||
| `write:route` | route_request, simulate_route, test_combo |
|
||||
| `read:usage` | cost_report, get_session_snapshot, explain_route |
|
||||
| `write:config` | set_budget_guard, set_resilience_profile |
|
||||
| `read:models` | list_models_catalog, best_combo_for_task |
|
||||
| `read:cache` | cache_stats |
|
||||
| `write:cache` | cache_flush |
|
||||
| `read:compression` | compression_status, list_compression_combos, compression_combo_stats |
|
||||
| `write:compression` | compression_configure, set_compression_engine |
|
||||
| `execute:completions` | route_request, test_combo |
|
||||
|
||||
## Audit Logging
|
||||
|
||||
Every tool call is logged to `mcp_tool_audit` with:
|
||||
|
||||
- Tool name, arguments, result
|
||||
- Duration (ms), success/failure
|
||||
- API key hash, timestamp
|
||||
|
||||
## Files
|
||||
|
||||
| File | Purpose |
|
||||
| :------------------------------------------- | :------------------------------------------------ |
|
||||
| `open-sse/mcp-server/server.ts` | MCP server creation and scoped tool registrations |
|
||||
| `open-sse/mcp-server/transport.ts` | Stdio + HTTP transport |
|
||||
| `open-sse/mcp-server/auth.ts` | API key + scope validation |
|
||||
| `open-sse/mcp-server/audit.ts` | Tool call audit logging |
|
||||
| `open-sse/mcp-server/tools/advancedTools.ts` | 8 advanced tool handlers |
|
||||
117
docs/README.md
Normal file
117
docs/README.md
Normal file
@@ -0,0 +1,117 @@
|
||||
---
|
||||
title: "OmniRoute Documentation"
|
||||
version: 3.8.0
|
||||
lastUpdated: 2026-05-13
|
||||
---
|
||||
|
||||
# OmniRoute Documentation
|
||||
|
||||
Navigable index of the OmniRoute documentation set. Topics are grouped by intent so you can find what you need quickly.
|
||||
|
||||
> Looking for the project overview, install steps, or release notes? See the root [README.md](../README.md), [CHANGELOG.md](../CHANGELOG.md), and [CONTRIBUTING.md](../CONTRIBUTING.md).
|
||||
|
||||
---
|
||||
|
||||
## architecture/
|
||||
|
||||
How the system is put together — read these to understand the runtime, code layout, and resilience model.
|
||||
|
||||
- [ARCHITECTURE.md](architecture/ARCHITECTURE.md) — high-level system architecture (request pipeline, layers, modules).
|
||||
- [CODEBASE_DOCUMENTATION.md](architecture/CODEBASE_DOCUMENTATION.md) — engineering reference for the codebase.
|
||||
- [REPOSITORY_MAP.md](architecture/REPOSITORY_MAP.md) — directory-by-directory navigation guide.
|
||||
- [AUTHZ_GUIDE.md](architecture/AUTHZ_GUIDE.md) — authorization pipeline (route classifier + policy engine).
|
||||
- [RESILIENCE_GUIDE.md](architecture/RESILIENCE_GUIDE.md) — provider circuit breaker, connection cooldown, and model lockout.
|
||||
|
||||
## guides/
|
||||
|
||||
Task-focused walkthroughs for operators and end users.
|
||||
|
||||
- [SETUP_GUIDE.md](guides/SETUP_GUIDE.md) — first-time setup of OmniRoute.
|
||||
- [USER_GUIDE.md](guides/USER_GUIDE.md) — daily usage of the dashboard and API.
|
||||
- [DOCKER_GUIDE.md](guides/DOCKER_GUIDE.md) — running OmniRoute under Docker.
|
||||
- [ELECTRON_GUIDE.md](guides/ELECTRON_GUIDE.md) — desktop (Electron) builds.
|
||||
- [TERMUX_GUIDE.md](guides/TERMUX_GUIDE.md) — running on Android via Termux.
|
||||
- [PWA_GUIDE.md](guides/PWA_GUIDE.md) — installing the dashboard as a PWA.
|
||||
- [TROUBLESHOOTING.md](guides/TROUBLESHOOTING.md) — common issues and fixes.
|
||||
- [UNINSTALL.md](guides/UNINSTALL.md) — clean removal steps.
|
||||
- [I18N.md](guides/I18N.md) — translation and locale workflow.
|
||||
- [FEATURES.md](guides/FEATURES.md) — dashboard feature gallery.
|
||||
|
||||
## reference/
|
||||
|
||||
Lookup material — API surface, environment variables, CLI flags, provider catalog.
|
||||
|
||||
- [API_REFERENCE.md](reference/API_REFERENCE.md) — REST API endpoints and shapes.
|
||||
- [PROVIDER_REFERENCE.md](reference/PROVIDER_REFERENCE.md) — auto-generated provider catalog.
|
||||
- [openapi.yaml](reference/openapi.yaml) — OpenAPI 3.1 spec for the public API.
|
||||
- [ENVIRONMENT.md](reference/ENVIRONMENT.md) — environment variables reference.
|
||||
- [CLI-TOOLS.md](reference/CLI-TOOLS.md) — bundled CLI commands.
|
||||
- [FREE_TIERS.md](reference/FREE_TIERS.md) — free-tier LLM provider directory.
|
||||
|
||||
## frameworks/
|
||||
|
||||
Pluggable subsystems exposed to clients, agents, and operators.
|
||||
|
||||
- [MCP-SERVER.md](frameworks/MCP-SERVER.md) — Model Context Protocol server.
|
||||
- [A2A-SERVER.md](frameworks/A2A-SERVER.md) — Agent-to-Agent (A2A) JSON-RPC server.
|
||||
- [AGENT_PROTOCOLS_GUIDE.md](frameworks/AGENT_PROTOCOLS_GUIDE.md) — A2A / ACP / Cloud agent overview.
|
||||
- [CLOUD_AGENT.md](frameworks/CLOUD_AGENT.md) — cloud agent runtime and providers.
|
||||
- [SKILLS.md](frameworks/SKILLS.md) — Skills framework (sandboxed extension).
|
||||
- [MEMORY.md](frameworks/MEMORY.md) — persistent memory (FTS5 + Qdrant).
|
||||
- [WEBHOOKS.md](frameworks/WEBHOOKS.md) — webhook events and dispatch.
|
||||
- [EVALS.md](frameworks/EVALS.md) — eval suites.
|
||||
|
||||
## routing/
|
||||
|
||||
Combo routing, scoring, and replay.
|
||||
|
||||
- [AUTO-COMBO.md](routing/AUTO-COMBO.md) — Auto-Combo (9-factor scoring, 14 strategies).
|
||||
- [REASONING_REPLAY.md](routing/REASONING_REPLAY.md) — reasoning replay flow.
|
||||
|
||||
## security/
|
||||
|
||||
Guardrails, compliance, and stealth.
|
||||
|
||||
- [GUARDRAILS.md](security/GUARDRAILS.md) — PII, prompt injection, vision guardrails.
|
||||
- [COMPLIANCE.md](security/COMPLIANCE.md) — audit trails and compliance.
|
||||
- [STEALTH_GUIDE.md](security/STEALTH_GUIDE.md) — TLS / fingerprint stealth.
|
||||
|
||||
## compression/
|
||||
|
||||
Prompt compression engines, rules, and language packs.
|
||||
|
||||
- [COMPRESSION_GUIDE.md](compression/COMPRESSION_GUIDE.md) — top-level compression overview.
|
||||
- [COMPRESSION_ENGINES.md](compression/COMPRESSION_ENGINES.md) — available compression engines.
|
||||
- [COMPRESSION_RULES_FORMAT.md](compression/COMPRESSION_RULES_FORMAT.md) — rule file format.
|
||||
- [COMPRESSION_LANGUAGE_PACKS.md](compression/COMPRESSION_LANGUAGE_PACKS.md) — language packs.
|
||||
- [RTK_COMPRESSION.md](compression/RTK_COMPRESSION.md) — RTK engine deep dive.
|
||||
|
||||
## ops/
|
||||
|
||||
Release, deployment, proxies, tunnels, coverage.
|
||||
|
||||
- [RELEASE_CHECKLIST.md](ops/RELEASE_CHECKLIST.md) — release flow checklist.
|
||||
- [COVERAGE_PLAN.md](ops/COVERAGE_PLAN.md) — test coverage plan.
|
||||
- [FLY_IO_DEPLOYMENT_GUIDE.md](ops/FLY_IO_DEPLOYMENT_GUIDE.md) — Fly.io deployment.
|
||||
- [VM_DEPLOYMENT_GUIDE.md](ops/VM_DEPLOYMENT_GUIDE.md) — generic VM deployment.
|
||||
- [PROXY_GUIDE.md](ops/PROXY_GUIDE.md) — upstream proxy configuration.
|
||||
- [TUNNELS_GUIDE.md](ops/TUNNELS_GUIDE.md) — Cloudflare tunnel and friends.
|
||||
|
||||
## diagrams/
|
||||
|
||||
Mermaid sources and exported SVG/PNG diagrams referenced from the docs above. Populated incrementally — see [diagrams/README.md](diagrams/README.md).
|
||||
|
||||
## i18n/
|
||||
|
||||
Translated mirrors of the documentation in 40 locales. See [i18n/README.md](i18n/README.md) for the supported language list.
|
||||
|
||||
## screenshots/
|
||||
|
||||
Static screenshots used by the dashboard and the README. Not part of the doc body.
|
||||
|
||||
---
|
||||
|
||||
## Auto-generated artifacts
|
||||
|
||||
- [reference/PROVIDER_REFERENCE.md](reference/PROVIDER_REFERENCE.md) is generated by `scripts/gen-provider-reference.ts` from `src/shared/constants/providers.ts`. Do not edit by hand.
|
||||
- The dashboard sidebar (`/docs` UI) is generated by `scripts/generate-docs-index.mjs`, which walks the subfolders above.
|
||||
@@ -1,40 +0,0 @@
|
||||
# Release Checklist
|
||||
|
||||
Use this checklist before tagging or publishing a new OmniRoute release.
|
||||
|
||||
## Version and Changelog
|
||||
|
||||
1. Bump `package.json` version (`x.y.z`) in the release branch.
|
||||
2. Move release notes from `## [Unreleased]` in `CHANGELOG.md` to a dated section:
|
||||
- `## [x.y.z] — YYYY-MM-DD`
|
||||
3. Keep `## [Unreleased]` as the first changelog section for upcoming work.
|
||||
4. Ensure the latest semver section in `CHANGELOG.md` equals `package.json` version.
|
||||
|
||||
## API Docs
|
||||
|
||||
1. Update `docs/openapi.yaml`:
|
||||
- `info.version` must equal `package.json` version.
|
||||
2. Validate endpoint examples if API contracts changed.
|
||||
|
||||
## Runtime Docs
|
||||
|
||||
1. Review `docs/ARCHITECTURE.md` for storage/runtime drift.
|
||||
2. Review `docs/TROUBLESHOOTING.md` for env var and operational drift.
|
||||
3. Verify the release/runtime Node.js version still satisfies the supported secure floor:
|
||||
- `>=20.20.2 <21`, `>=22.22.2 <23`, or `>=24.0.0 <25`
|
||||
- `npm run check:node-runtime`
|
||||
4. Validate the npm publish artifact after building the standalone package:
|
||||
- `npm run build:cli`
|
||||
- `npm run check:pack-artifact`
|
||||
- confirm no `app.__qa_backup`, `scripts/scratch`, `package-lock.json`, or other local residue
|
||||
5. Update localized docs if source docs changed significantly.
|
||||
|
||||
## Automated Check
|
||||
|
||||
Run the sync guard locally before opening PR:
|
||||
|
||||
```bash
|
||||
npm run check:docs-sync
|
||||
```
|
||||
|
||||
CI also runs this check in `.github/workflows/ci.yml` (lint job).
|
||||
@@ -1,145 +0,0 @@
|
||||
# 🛡️ Resilience Guide — OmniRoute
|
||||
|
||||
> How OmniRoute keeps your AI coding workflow running when providers fail.
|
||||
|
||||
## Overview
|
||||
|
||||
OmniRoute implements a multi-layered resilience system that ensures zero downtime:
|
||||
|
||||
```
|
||||
Client Request
|
||||
→ Rate Limit Check (per-IP, per-connection)
|
||||
→ Combo Routing (13 strategies)
|
||||
→ Connection Selection (P2C, round-robin, etc.)
|
||||
→ Request Queue & Pacing
|
||||
→ Execute (provider-specific executor)
|
||||
→ On Failure:
|
||||
→ Connection Cooldown (exponential backoff)
|
||||
→ Circuit Breaker (provider-level)
|
||||
→ Wait For Cooldown (auto-retry)
|
||||
→ Next Combo Target (fallback chain)
|
||||
→ Response
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Request Queue & Pacing
|
||||
|
||||
Per-connection request buckets smooth bursts before they hit upstream rate caps.
|
||||
|
||||
Configure in `Dashboard → Settings → Resilience`:
|
||||
|
||||
| Setting | Default | Description |
|
||||
| --------------- | ------- | ------------------------------------ |
|
||||
| Queue Size | `10` | Max queued requests per connection |
|
||||
| Pacing Interval | `0ms` | Minimum gap between requests |
|
||||
| Max Concurrent | `5` | Simultaneous requests per connection |
|
||||
|
||||
---
|
||||
|
||||
## Connection Cooldown
|
||||
|
||||
A single connection cools down after retryable failures. Features:
|
||||
|
||||
- **Exponential Backoff** — progressively longer cooldowns after each failure
|
||||
- **`Retry-After` Header Support** — respects upstream hints
|
||||
- **Configurable Base/Max** — tune cooldown duration per use case
|
||||
- **Auto-Recovery** — connection automatically becomes available after cooldown expires
|
||||
|
||||
---
|
||||
|
||||
## Circuit Breaker
|
||||
|
||||
Provider-level protection against cascading failures:
|
||||
|
||||
1. **Connection-scoped `429` rate limits** stay in Connection Cooldown (don't trip the breaker)
|
||||
2. **Provider-wide transient errors** (5xx, network timeouts) increment the failure counter
|
||||
3. **Breaker trips** only after fallback is exhausted AND the provider still fails
|
||||
4. **Recovery** — breaker automatically moves to half-open state after timeout, tests with probe request
|
||||
|
||||
Configure thresholds in `Dashboard → Settings → Resilience`.
|
||||
|
||||
---
|
||||
|
||||
## Wait For Cooldown
|
||||
|
||||
Instead of immediately failing when all connections are in cooldown, OmniRoute can wait for the earliest connection to expire and retry:
|
||||
|
||||
- **Automatic** — server waits for the earliest cooldown to expire
|
||||
- **Transparent** — client sees a slightly delayed response instead of an error
|
||||
- **Configurable** — enable/disable per combo or globally
|
||||
|
||||
---
|
||||
|
||||
## Anti-Thundering Herd
|
||||
|
||||
When multiple concurrent requests hit a failing provider simultaneously:
|
||||
|
||||
- **Mutex Protection** — only one retry attempt at a time per connection
|
||||
- **Semaphore** — limits concurrent retry storms across connections
|
||||
- **Deduplication** — identical requests within 5s window are deduplicated
|
||||
|
||||
---
|
||||
|
||||
## Combo Fallback Chains
|
||||
|
||||
The primary resilience mechanism. Configure in `Dashboard → Combos`:
|
||||
|
||||
```txt
|
||||
Combo: "always-on"
|
||||
1. cc/claude-opus-4-7 ← Primary (subscription)
|
||||
2. cx/gpt-5.2-codex ← Secondary (subscription)
|
||||
3. glm/glm-4.7 ← Cheap backup ($0.5/1M)
|
||||
4. if/kimi-k2-thinking ← Free fallback (unlimited)
|
||||
```
|
||||
|
||||
When provider #1 fails (quota, rate, or health), OmniRoute automatically routes to #2, then #3, then #4 — with zero manual intervention.
|
||||
|
||||
### 13 Routing Strategies
|
||||
|
||||
| Strategy | Description |
|
||||
| ------------------- | ---------------------------------- |
|
||||
| `priority` | First available in order |
|
||||
| `weighted` | Weighted distribution |
|
||||
| `fill-first` | Fill primary before moving |
|
||||
| `round-robin` | Rotate through all targets |
|
||||
| `p2c` | Power-of-two choices (quota-aware) |
|
||||
| `random` | Random selection |
|
||||
| `least-used` | Least recently used |
|
||||
| `cost-optimized` | Cheapest available |
|
||||
| `strict-random` | True random (no tracking) |
|
||||
| `auto` | OmniRoute selects based on context |
|
||||
| `lkgp` | Last Known Good Provider |
|
||||
| `context-optimized` | Best for current context window |
|
||||
| `context-relay` | Session handoff during rotation |
|
||||
|
||||
---
|
||||
|
||||
## TLS Fingerprint Spoofing
|
||||
|
||||
OmniRoute makes proxied traffic look like legitimate browser/CLI requests:
|
||||
|
||||
- **Browser-like TLS** via `wreq-js` — prevents bot detection
|
||||
- **CLI Fingerprint Matching** — reorders headers and body fields to match native CLI binary signatures (Claude Code, Codex, etc.)
|
||||
- **Proxy IP Preservation** — stealth features work on top of proxy IP masking
|
||||
|
||||
---
|
||||
|
||||
## Health Dashboard
|
||||
|
||||
Monitor all resilience components in real-time at `Dashboard → Health`:
|
||||
|
||||
- **Uptime** — server uptime and last restart
|
||||
- **Provider Breaker States** — open/closed/half-open per provider
|
||||
- **Connection Cooldowns** — active cooldowns with expiry times
|
||||
- **Cache Stats** — signature + semantic cache hit rates
|
||||
- **Lockouts** — API key lockouts and IP bans
|
||||
- **Latency** — p50/p95/p99 percentiles
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- [Architecture Guide](ARCHITECTURE.md) — System architecture and internals
|
||||
- [User Guide](USER_GUIDE.md) — Providers, combos, CLI integration
|
||||
- [Auto-Combo Engine](AUTO-COMBO.md) — 6-factor scoring, mode packs
|
||||
@@ -1,518 +0,0 @@
|
||||
# RFC: Auto-Assessment & Self-Healing Combo Engine
|
||||
|
||||
## Summary
|
||||
|
||||
Omniroute's combo system currently requires manual configuration: users must know which providers and models are actually working, then manually wire them into combo chains. When providers fail (rate limits, auth errors, model deprecation), combos silently degrade — routing to dead endpoints that timeout or return errors. There is no automated way to:
|
||||
|
||||
1. **Discover** which provider/model pairs actually respond to chat completions
|
||||
2. **Categorize** models by capability (coding, reasoning, vision, speed, etc.)
|
||||
3. **Self-heal** combos by removing dead models and promoting working ones
|
||||
4. **Auto-generate** sensible combo configurations from available providers
|
||||
|
||||
This proposes an **Auto-Assessment Engine** that continuously tests, categorizes, and self-heals combo configurations — making omniroute truly "plug and play" for non-technical users.
|
||||
|
||||
---
|
||||
|
||||
## Problem Statement
|
||||
|
||||
### What we encountered (real production incident)
|
||||
|
||||
While configuring omniroute for production use, we discovered:
|
||||
|
||||
- **44 combos had models from providers that returned "`Invalid model`" errors** — `kiro/claude-opus-4.6`, `kiro/claude-sonnet-4.6`, `gh/claude-sonnet-4.5` all fail with 400/404
|
||||
- **No automated way to know which models actually work** — the `/v1/models` endpoint lists 1,236 models, but `/v1/chat/completions` fails for most of them
|
||||
- **Weight-based routing sends traffic to dead models** — a model weighted at 30% that returns errors wastes 30% of requests
|
||||
- **Manual diagnosis took hours** — we had to curl each model individually, categorize results, then update the SQLite DB
|
||||
- **Provider `test_status` field exists but isn't used for routing** — `provider_connections` has `test_status` (active/banned/expired/credits_exhausted) but the combo resolver ignores it
|
||||
|
||||
### Current flow (broken)
|
||||
|
||||
```
|
||||
User adds providers → Manually creates combos → Manually assigns models → ???
|
||||
↓
|
||||
Some models work,
|
||||
some return errors,
|
||||
some timeout...
|
||||
BUT routing doesn't know!
|
||||
```
|
||||
|
||||
### Proposed flow (self-healing)
|
||||
|
||||
```
|
||||
User adds providers → Auto-Assessment runs → Working models discovered
|
||||
↓
|
||||
Capability categorization
|
||||
↓
|
||||
Combos auto-generated/updated with working models
|
||||
↓
|
||||
Continuous health monitoring keeps combos healthy
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Architecture
|
||||
|
||||
### New Components
|
||||
|
||||
```
|
||||
┌────────────────────────────────────────────────────────┐
|
||||
│ Auto-Assessment Engine │
|
||||
├────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
|
||||
│ │ Assessor │ │ Categorizer │ │ Self-Healer │ │
|
||||
│ │ │ │ │ │ │ │
|
||||
│ │ • Probe all │ │ • Classify │ │ • Remove │ │
|
||||
│ │ models │ │ models by │ │ dead │ │
|
||||
│ │ • Measure │ │ capability │ │ models │ │
|
||||
│ │ latency │ │ • Assign │ │ • Promote │ │
|
||||
│ │ • Track │ │ tier tags │ │ working │ │
|
||||
│ │ success │ │ • Build │ │ models │ │
|
||||
│ │ rates │ │ fitness │ │ • Re-weight │ │
|
||||
│ │ │ │ scores │ │ combos │ │
|
||||
│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │
|
||||
│ │ │ │ │
|
||||
│ ▼ ▼ ▼ │
|
||||
│ ┌──────────────────────────────────────────────────┐ │
|
||||
│ │ Assessment Database │ │
|
||||
│ │ │ │
|
||||
│ │ model_assessments: │ │
|
||||
│ │ model_id | provider | status | latency_p50 │ │
|
||||
│ │ latency_p95 | success_rate | last_tested │ │
|
||||
│ │ error_type | tier | categories[] | fitness │ │
|
||||
│ │ context_window | output_tokens | vision | tbc │ │
|
||||
│ │ │ │
|
||||
│ │ assessment_runs: │ │
|
||||
│ │ run_id | started_at | completed_at │ │
|
||||
│ │ models_tested | models_passed | models_failed │ │
|
||||
│ │ │ │
|
||||
│ │ combo_health: │ │
|
||||
│ │ combo_id | healthy_models | dead_models │ │
|
||||
│ │ last_auto_fix | auto_fix_count │ │
|
||||
│ └──────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
└──────────────────────────────┬─────────────────────────┘
|
||||
│
|
||||
▼
|
||||
Existing combo system (weighted-fallback, priority, etc.)
|
||||
+ Enhanced comboResolver that skips dead models
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Detailed Design
|
||||
|
||||
### 1. Assessor — `src/domain/assessor.ts`
|
||||
|
||||
**Purpose**: Probe every provider/model pair with a lightweight chat completion to determine if it works and measure performance.
|
||||
|
||||
```typescript
|
||||
interface ModelAssessment {
|
||||
modelId: string;
|
||||
providerId: string;
|
||||
status: "working" | "broken" | "rate_limited" | "timeout" | "auth_error" | "unknown";
|
||||
|
||||
// Performance metrics
|
||||
latencyP50: number; // milliseconds
|
||||
latencyP95: number; // milliseconds
|
||||
successRate: number; // 0..1 over last N probes
|
||||
|
||||
// Capability detection
|
||||
supportsVision: boolean;
|
||||
supportsToolCall: boolean;
|
||||
supportsStreaming: boolean;
|
||||
maxContextWindow: number;
|
||||
maxOutputTokens: number;
|
||||
categories: ModelCategory[]; // 'coding' | 'reasoning' | 'chat' | 'fast' | 'vision' | 'reasoning_deep'
|
||||
tier: "premium" | "balanced" | "fast" | "free";
|
||||
|
||||
// Metadata
|
||||
lastTested: string; // ISO timestamp
|
||||
lastError: string | null;
|
||||
consecutiveFails: number;
|
||||
probeCount: number;
|
||||
}
|
||||
|
||||
type ModelCategory =
|
||||
| "coding" // Good at code generation, debugging, refactoring
|
||||
| "reasoning" // Strong logical reasoning, math, analysis
|
||||
| "reasoning_deep" // Extended thinking, complex multi-step reasoning
|
||||
| "chat" // Good conversational ability
|
||||
| "fast" // Sub-2s response time
|
||||
| "vision" // Image input support
|
||||
| "tool_call" // Function/tool calling support
|
||||
| "structured_output"; // JSON mode / structured output
|
||||
```
|
||||
|
||||
**Assessment Probes** — three tiers of testing:
|
||||
|
||||
| Probe | Prompt | Max Tokens | Purpose |
|
||||
| ------------ | ------------------------------------------ | ---------- | ---------------------------------------------- |
|
||||
| **Quick** | `"ok"` | 1 | Does it respond at all? |
|
||||
| **Standard** | `"Write a function that adds two numbers"` | 50 | Coding, tool call, structured output detection |
|
||||
| **Deep** | Vision input + multi-turn | 100 | Vision, streaming, context window |
|
||||
|
||||
**Scheduling**:
|
||||
|
||||
- Full assessment on startup (or first provider addition)
|
||||
- Quick probe every 5 minutes for working models
|
||||
- Standard probe every 30 minutes
|
||||
- Deep probe every 6 hours (or on demand)
|
||||
- Immediate probe after any model returns an error
|
||||
- Exponential backoff: 1 min → 5 min → 15 min → 30 min for consistently failing models
|
||||
|
||||
### 2. Categorizer — `src/domain/categorizer.ts`
|
||||
|
||||
**Purpose**: Classify each working model into capability categories and assign fitness scores per category.
|
||||
|
||||
**Category Detection Logic**:
|
||||
|
||||
```typescript
|
||||
function categorizeModel(assessment: ModelAssessment): ModelCategory[] {
|
||||
const categories: ModelCategory[] = [];
|
||||
|
||||
// Speed classification
|
||||
if (assessment.latencyP50 < 2000) categories.push("fast");
|
||||
|
||||
// Capability from probe responses
|
||||
if (assessment.supportsToolCall) categories.push("tool_call");
|
||||
if (assessment.supportsVision) categories.push("vision");
|
||||
if (assessment.supportsStreaming) categories.push("structured_output"); // if supports JSON mode
|
||||
|
||||
// Tier-based reasoning classification
|
||||
if (assessment.tier === "premium") {
|
||||
categories.push("reasoning_deep", "coding", "reasoning");
|
||||
} else if (assessment.tier === "balanced") {
|
||||
categories.push("coding", "reasoning");
|
||||
} else if (assessment.tier === "fast") {
|
||||
categories.push("chat");
|
||||
}
|
||||
|
||||
return categories;
|
||||
}
|
||||
```
|
||||
|
||||
**Fitness Scores** (0..1 per category):
|
||||
|
||||
| Category | Scoring Formula |
|
||||
| ---------------- | -------------------------------------------------------------------- |
|
||||
| `coding` | `0.4 * successRate + 0.3 * (1 - latencyP95/10000) + 0.3 * tierScore` |
|
||||
| `reasoning` | `0.5 * tierScore + 0.3 * successRate + 0.2 * (1 - latencyP95/15000)` |
|
||||
| `reasoning_deep` | `0.7 * tierScore + 0.3 * successRate` (only premium tier eligible) |
|
||||
| `chat` | `0.4 * successRate + 0.4 * (1 - latencyP95/5000) + 0.2 * tierScore` |
|
||||
| `fast` | `0.6 * (1 - latencyP50/3000) + 0.3 * successRate + 0.1 * costInv` |
|
||||
| `vision` | `0.5 * successRate + 0.3 * (1 - latencyP95/15000) + 0.2 * tierScore` |
|
||||
|
||||
### 3. Self-Healer — `src/domain/selfHealer.ts`
|
||||
|
||||
**Purpose**: Automatically update combo model lists based on assessment results.
|
||||
|
||||
**Auto-Heal Rules**:
|
||||
|
||||
```
|
||||
IF model.status == 'broken' OR model.consecutiveFails >= 3:
|
||||
REMOVE model from all combos
|
||||
LOG "Auto-heal: removed {model} from {combo} (status: {status}, fails: {n})"
|
||||
|
||||
IF model.status == 'rate_limited' OR model.status == 'timeout':
|
||||
REDUCE model weight by 50% (minimum weight: 5)
|
||||
LOG "Auto-heal: reduced weight of {model} in {combo} (status: {status})"
|
||||
|
||||
IF combo has 0 working models:
|
||||
FIND best working model for combo's category
|
||||
ADD to combo with weight 100
|
||||
LOG "Auto-heal: emergency added {model} to {combo} (was empty)"
|
||||
|
||||
IF combo has fewer than 3 working models:
|
||||
FIND additional working models in same category
|
||||
ADD with proportional weights
|
||||
LOG "Auto-heal: expanded {combo} with {n} models"
|
||||
|
||||
IF model.status transitions from 'broken' → 'working':
|
||||
RESTORE original weight (or proportional weight)
|
||||
LOG "Auto-heal: restored {model} in {combo}"
|
||||
```
|
||||
|
||||
**Auto-Generation of Combos**:
|
||||
|
||||
When a new provider is added, or on first startup, auto-generate standard combos:
|
||||
|
||||
```typescript
|
||||
const AUTO_COMBOS = [
|
||||
{ name: "auto/best-coding", categories: ["coding"], tier: ["premium", "balanced"] },
|
||||
{ name: "auto/best-reasoning", categories: ["reasoning_deep"], tier: ["premium"] },
|
||||
{ name: "auto/best-fast", categories: ["fast"], tier: ["fast", "balanced"] },
|
||||
{ name: "auto/best-vision", categories: ["vision"], tier: ["premium", "balanced"] },
|
||||
{ name: "auto/best-chat", categories: ["chat"], tier: ["balanced", "premium"] },
|
||||
{ name: "auto/coding", categories: ["coding"], tier: ["balanced", "fast", "premium"] },
|
||||
{ name: "auto/fast", categories: ["fast"], tier: ["fast"] },
|
||||
{ name: "auto/pro-coding", categories: ["coding"], tier: ["premium"] },
|
||||
{ name: "auto/pro-reasoning", categories: ["reasoning_deep"], tier: ["premium"] },
|
||||
{ name: "auto/pro-vision", categories: ["vision"], tier: ["premium"] },
|
||||
{ name: "auto/pro-chat", categories: ["chat"], tier: ["premium"] },
|
||||
{ name: "auto/pro-fast", categories: ["fast"], tier: ["fast"] },
|
||||
];
|
||||
```
|
||||
|
||||
### 4. Database Schema
|
||||
|
||||
```sql
|
||||
-- New tables for assessment engine
|
||||
|
||||
CREATE TABLE IF NOT EXISTS model_assessments (
|
||||
id TEXT PRIMARY KEY,
|
||||
model_id TEXT NOT NULL,
|
||||
provider_id TEXT NOT NULL,
|
||||
status TEXT NOT NULL DEFAULT 'unknown', -- working|broken|rate_limited|timeout|auth_error|unknown
|
||||
latency_p50 INTEGER, -- milliseconds
|
||||
latency_p95 INTEGER, -- milliseconds
|
||||
success_rate REAL DEFAULT 0, -- 0..1
|
||||
supports_vision INTEGER DEFAULT 0,
|
||||
supports_tool_call INTEGER DEFAULT 0,
|
||||
supports_streaming INTEGER DEFAULT 0,
|
||||
supports_structured_output INTEGER DEFAULT 0,
|
||||
max_context_window INTEGER,
|
||||
max_output_tokens INTEGER,
|
||||
categories TEXT DEFAULT '[]', -- JSON array of ModelCategory
|
||||
fitness_scores TEXT DEFAULT '{}', -- JSON object: {category: score}
|
||||
tier TEXT DEFAULT 'balanced', -- premium|balanced|fast|free
|
||||
last_tested TEXT,
|
||||
last_error TEXT,
|
||||
consecutive_fails INTEGER DEFAULT 0,
|
||||
probe_count INTEGER DEFAULT 0,
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||
UNIQUE(model_id, provider_id)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS assessment_runs (
|
||||
id TEXT PRIMARY KEY,
|
||||
started_at TEXT NOT NULL,
|
||||
completed_at TEXT,
|
||||
models_tested INTEGER DEFAULT 0,
|
||||
models_passed INTEGER DEFAULT 0,
|
||||
models_failed INTEGER DEFAULT 0,
|
||||
models_rate_limited INTEGER DEFAULT 0,
|
||||
duration_ms INTEGER,
|
||||
trigger TEXT DEFAULT 'scheduled', -- scheduled|on_demand|on_provider_change|on_error
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS combo_health (
|
||||
combo_id TEXT PRIMARY KEY,
|
||||
healthy_model_count INTEGER DEFAULT 0,
|
||||
dead_model_count INTEGER DEFAULT 0,
|
||||
total_model_count INTEGER DEFAULT 0,
|
||||
last_auto_fix TEXT,
|
||||
auto_fix_count INTEGER DEFAULT 0,
|
||||
health_score REAL DEFAULT 0, -- 0..1, weighted by model health
|
||||
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||
FOREIGN KEY (combo_id) REFERENCES combos(id)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_model_assessments_status ON model_assessments(status);
|
||||
CREATE INDEX IF NOT EXISTS idx_model_assessments_provider ON model_assessments(provider_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_model_assessments_tier ON model_assessments(tier);
|
||||
CREATE INDEX IF NOT EXISTS idx_combo_health_health_score ON combo_health(health_score);
|
||||
```
|
||||
|
||||
### 5. API Endpoints
|
||||
|
||||
```bash
|
||||
# Trigger assessment (blocking or background)
|
||||
POST /api/assess/models
|
||||
Body: { "scope": "all" | "provider:<id>" | "model:<id>", "tier": "quick" | "standard" | "deep" }
|
||||
Response: { "run_id": "...", "status": "started" }
|
||||
|
||||
# Get assessment results
|
||||
GET /api/assess/results
|
||||
Query: ?status=working|broken|rate_limited&provider=kiro&category=coding
|
||||
Response: { "models": [...] }
|
||||
|
||||
# Get combo health dashboard
|
||||
GET /api/assess/combo-health
|
||||
Response: { "combos": [{ "id": "...", "name": "...", "healthy_models": 5, "dead_models": 2, "health_score": 0.71 }] }
|
||||
|
||||
# Auto-fix all combos
|
||||
POST /api/assess/auto-fix
|
||||
Response: { "fixed_combos": 3, "removed_models": ["ollamacloud/glm-5.1", "..."], "added_models": [...] }
|
||||
|
||||
# Auto-generate combos from assessments
|
||||
POST /api/assess/auto-generate
|
||||
Response: { "generated_combos": ["auto/best-coding", "..."], "models_per_combo": { "auto/best-coding": 5 } }
|
||||
|
||||
# Get assessment run history
|
||||
GET /api/assess/runs
|
||||
Response: { "runs": [...] }
|
||||
```
|
||||
|
||||
### 6. Integration with Existing comboResolver
|
||||
|
||||
The existing `comboResolver.ts` already handles `priority`, `weighted`, `round-robin`, `random`, and `least-used` strategies. The enhancement:
|
||||
|
||||
```typescript
|
||||
// In comboResolver.ts — add health-aware filtering
|
||||
export function resolveComboModel(combo, context = {}) {
|
||||
const models = combo.models || [];
|
||||
if (models.length === 0) {
|
||||
throw new Error(`Combo "${combo.name}" has no models configured`);
|
||||
}
|
||||
|
||||
const normalized = models
|
||||
.map((entry) => ({
|
||||
model: getComboStepTarget(entry) || "",
|
||||
weight: getComboStepWeight(entry) || 1,
|
||||
}))
|
||||
.filter((entry) => entry.model);
|
||||
|
||||
// NEW: Filter out models known to be broken/rate_limited
|
||||
const healthy = normalized.filter((entry) => {
|
||||
const assessment = getAssessment(entry.model);
|
||||
if (!assessment) return true; // Unknown → allow (haven't tested yet)
|
||||
return assessment.status === "working" || assessment.status === "unknown";
|
||||
});
|
||||
|
||||
// If all models are unhealthy, fall back to full list (better to try than to fail)
|
||||
const pool = healthy.length > 0 ? healthy : normalized;
|
||||
|
||||
const strategy = combo.strategy || "priority";
|
||||
// ... existing resolution logic using `pool` instead of `normalized`
|
||||
}
|
||||
```
|
||||
|
||||
### 7. Integration with Existing Auto-Combo Scoring (`open-sse/services/autoCombo/scoring.ts`)
|
||||
|
||||
The existing scoring function already uses 6 factors + tier. The assessment engine enriches these:
|
||||
|
||||
| Existing Factor | Current Source | Enhanced Source |
|
||||
| ------------------- | ------------------------ | ----------------------------------------- |
|
||||
| Quota (0.20) | Provider connection data | Same + assessment success_rate |
|
||||
| Health (0.25) | Circuit breaker state | Same + assessment status (working/broken) |
|
||||
| CostInv (0.20) | Static cost data | Same + live cost measurement |
|
||||
| LatencyInv (0.15) | P95 latency from logs | Same + assessment probe latency |
|
||||
| TaskFit (0.10) | Static fitness table | **NEW: from assessment categories** |
|
||||
| Stability (0.05) | Latency stddev | Same + assessment consecutive_fails |
|
||||
| TierPriority (0.05) | Account tier | Same |
|
||||
|
||||
The key enhancement: `taskFit` currently uses a **static** fitness lookup (`taskFitness.ts`). With assessments, we derive fitness from **live probe results** — a model that actually passes coding probes gets a high `coding` fitness, not just because its name contains "coder".
|
||||
|
||||
### 8. Dashboard UI (Future PR)
|
||||
|
||||
The assessment state should be visible in the omniroute dashboard:
|
||||
|
||||
- **Model Health Grid**: table showing each provider/model with colored status indicators
|
||||
- **Combo Health Summary**: per-combo health score with expandable model details
|
||||
- **Assessment History**: timeline of assessment runs with pass/fail counts
|
||||
- **Auto-Fix Log**: history of automatic combo modifications
|
||||
|
||||
---
|
||||
|
||||
## Migration Path
|
||||
|
||||
### Phase 1: Assessment Engine (This PR)
|
||||
|
||||
- New `model_assessments`, `assessment_runs`, `combo_health` tables
|
||||
- Assessor service with quick/standard/deep probes
|
||||
- Categorizer with fitness scoring
|
||||
- Self-healer with auto-fix rules
|
||||
- REST API endpoints
|
||||
- Integration with comboResolver to skip broken models
|
||||
|
||||
### Phase 2: Auto-Generation (Follow-up PR)
|
||||
|
||||
- Auto-generate combos from assessed models
|
||||
- Smart combo naming and categorization
|
||||
- Cross-provider fallback chains
|
||||
- Dashboard UI for assessment results
|
||||
|
||||
### Phase 3: Continuous Learning (Follow-up PR)
|
||||
|
||||
- Feeds assessment results back into auto-combo scoring weights
|
||||
- Adapts fitness scores based on real request outcomes
|
||||
- A/B testing across providers to find optimal routing
|
||||
- Automatic mode pack switching (ship-fast during peak, cost-saver off-peak)
|
||||
|
||||
---
|
||||
|
||||
## Implementation Files
|
||||
|
||||
| File | Purpose | New/Modified |
|
||||
| ---------------------------------------- | ---------------------------------------- | ------------ |
|
||||
| `src/domain/assessor.ts` | Probe engine (quick/standard/deep) | **NEW** |
|
||||
| `src/domain/categorizer.ts` | Model categorization & fitness | **NEW** |
|
||||
| `src/domain/selfHealer.ts` | Auto-fix combos, remove dead models | **NEW** |
|
||||
| `src/domain/comboResolver.ts` | Add health-aware filtering | **MODIFIED** |
|
||||
| `src/domain/types.ts` | Add ModelAssessment, ModelCategory types | **MODIFIED** |
|
||||
| `src/lib/db/assessments.ts` | DB access for assessment tables | **NEW** |
|
||||
| `open-sse/services/autoCombo/scoring.ts` | Use assessment data for taskFit | **MODIFIED** |
|
||||
| `src/app/api/assess/route.ts` | REST API routes | **NEW** |
|
||||
| `scripts/assess-models.mjs` | CLI script for on-demand assessment | **NEW** |
|
||||
| `scripts/migrate-assessments.mjs` | DB migration script | **NEW** |
|
||||
|
||||
---
|
||||
|
||||
## Testing Plan
|
||||
|
||||
### Unit Tests
|
||||
|
||||
- Assessor probe logic (mock provider responses)
|
||||
- Categorizer fitness score calculations
|
||||
- Self-healer rules (remove, reduce, restore, emergency add)
|
||||
- comboResolver integration (skip broken models)
|
||||
|
||||
### Integration Tests
|
||||
|
||||
- Full assessment cycle: add provider → probe models → categorize → auto-fix combos
|
||||
- Health-aware routing: broken model skipped, restored model re-included
|
||||
- Assessment run persistence and recovery
|
||||
|
||||
### Manual Testing (our experience as reference)
|
||||
|
||||
```bash
|
||||
# Our actual test sequence that should be automated:
|
||||
# 1. Start omniroute with 406 provider connections (51 providers)
|
||||
# 2. Discover only 8 models actually work from 2 providers (kiro, ollamacloud)
|
||||
# 3. Manually update 44 combos with working models only
|
||||
# 4. Verify all 15 key combos pass end-to-end
|
||||
# 5. Set up auto-sync cron for model list updates
|
||||
|
||||
# With auto-assessment, this entire process should be:
|
||||
# 1. Start omniroute
|
||||
# 2. Run: curl -X POST http://localhost:20128/api/assess/models -d '{"scope":"all"}'
|
||||
# 3. Wait for assessment to complete
|
||||
# 4. Run: curl -X POST http://localhost:20128/api/assess/auto-fix
|
||||
# 5. All combos are now healthy
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Success Metrics
|
||||
|
||||
| Metric | Current (Manual) | Target (Auto-Assessment) |
|
||||
| -------------------------------- | --------------------------------- | ------------------------------- |
|
||||
| Time to configure working combos | 2-4 hours | < 5 minutes |
|
||||
| Dead model detection | Manual curl testing | Automatic, continuous |
|
||||
| Combo health visibility | None | Dashboard + API |
|
||||
| Provider failure recovery | Manual DB updates | Auto-heal within 5 minutes |
|
||||
| New provider onboarding | Manual combo editing | Auto-discover + auto-categorize |
|
||||
| Model deprecation handling | Manual detection (users complain) | Proactive removal + alerting |
|
||||
|
||||
---
|
||||
|
||||
## Backwards Compatibility
|
||||
|
||||
- All new tables are additive — no schema changes to existing tables
|
||||
- comboResolver filtering is opt-in via config flag (default: enabled)
|
||||
- Existing combo configurations are preserved — self-healer only modifies them, doesn't replace
|
||||
- Assessment can be disabled with `OMNIRoute_DISABLE_ASSESSMENT=1` env var
|
||||
- All API endpoints are new — no existing endpoints changed
|
||||
|
||||
---
|
||||
|
||||
## Open Questions
|
||||
|
||||
1. **Probe cost**: Who pays for assessment probes? Should we limit to free-tier models or use a separate assessment budget?
|
||||
2. **Assessment frequency**: How often should deep probes run? Current proposal: 6h, but some users may want more/less frequent.
|
||||
3. **Auto-fix aggression**: Should self-healer remove models immediately on first failure, or wait for N consecutive failures?
|
||||
4. **Cross-provider model equivalence**: Should `kiro/claude-sonnet-4.5` and `gh/claude-sonnet-4.5` be treated as the same model for combo purposes?
|
||||
5. **Assessment during startup**: Should assessment block startup or run in background?
|
||||
@@ -1,8 +1,14 @@
|
||||
---
|
||||
title: "OmniRoute Architecture"
|
||||
version: 3.8.0
|
||||
lastUpdated: 2026-05-13
|
||||
---
|
||||
|
||||
# OmniRoute Architecture
|
||||
|
||||
🌐 **Languages:** 🇺🇸 [English](ARCHITECTURE.md) | 🇧🇷 [Português (Brasil)](i18n/pt-BR/ARCHITECTURE.md) | 🇪🇸 [Español](i18n/es/ARCHITECTURE.md) | 🇫🇷 [Français](i18n/fr/ARCHITECTURE.md) | 🇮🇹 [Italiano](i18n/it/ARCHITECTURE.md) | 🇷🇺 [Русский](i18n/ru/ARCHITECTURE.md) | 🇨🇳 [中文 (简体)](i18n/zh-CN/ARCHITECTURE.md) | 🇩🇪 [Deutsch](i18n/de/ARCHITECTURE.md) | 🇮🇳 [हिन्दी](i18n/in/ARCHITECTURE.md) | 🇹🇭 [ไทย](i18n/th/ARCHITECTURE.md) | 🇺🇦 [Українська](i18n/uk-UA/ARCHITECTURE.md) | 🇸🇦 [العربية](i18n/ar/ARCHITECTURE.md) | 🇯🇵 [日本語](i18n/ja/ARCHITECTURE.md) | 🇻🇳 [Tiếng Việt](i18n/vi/ARCHITECTURE.md) | 🇧🇬 [Български](i18n/bg/ARCHITECTURE.md) | 🇩🇰 [Dansk](i18n/da/ARCHITECTURE.md) | 🇫🇮 [Suomi](i18n/fi/ARCHITECTURE.md) | 🇮🇱 [עברית](i18n/he/ARCHITECTURE.md) | 🇭🇺 [Magyar](i18n/hu/ARCHITECTURE.md) | 🇮🇩 [Bahasa Indonesia](i18n/id/ARCHITECTURE.md) | 🇰🇷 [한국어](i18n/ko/ARCHITECTURE.md) | 🇲🇾 [Bahasa Melayu](i18n/ms/ARCHITECTURE.md) | 🇳🇱 [Nederlands](i18n/nl/ARCHITECTURE.md) | 🇳🇴 [Norsk](i18n/no/ARCHITECTURE.md) | 🇵🇹 [Português (Portugal)](i18n/pt/ARCHITECTURE.md) | 🇷🇴 [Română](i18n/ro/ARCHITECTURE.md) | 🇵🇱 [Polski](i18n/pl/ARCHITECTURE.md) | 🇸🇰 [Slovenčina](i18n/sk/ARCHITECTURE.md) | 🇸🇪 [Svenska](i18n/sv/ARCHITECTURE.md) | 🇵🇭 [Filipino](i18n/phi/ARCHITECTURE.md) | 🇨🇿 [Čeština](i18n/cs/ARCHITECTURE.md)
|
||||
🌐 **Languages:** 🇺🇸 [English](./ARCHITECTURE.md) | 🇧🇷 [Português (Brasil)](../i18n/pt-BR/docs/architecture/ARCHITECTURE.md) | 🇪🇸 [Español](../i18n/es/docs/architecture/ARCHITECTURE.md) | 🇫🇷 [Français](../i18n/fr/docs/architecture/ARCHITECTURE.md) | 🇮🇹 [Italiano](../i18n/it/docs/architecture/ARCHITECTURE.md) | 🇷🇺 [Русский](../i18n/ru/docs/architecture/ARCHITECTURE.md) | 🇨🇳 [中文 (简体)](../i18n/zh-CN/docs/architecture/ARCHITECTURE.md) | 🇩🇪 [Deutsch](../i18n/de/docs/architecture/ARCHITECTURE.md) | 🇮🇳 [हिन्दी](../i18n/in/docs/architecture/ARCHITECTURE.md) | 🇹🇭 [ไทย](../i18n/th/docs/architecture/ARCHITECTURE.md) | 🇺🇦 [Українська](../i18n/uk-UA/docs/architecture/ARCHITECTURE.md) | 🇸🇦 [العربية](../i18n/ar/docs/architecture/ARCHITECTURE.md) | 🇯🇵 [日本語](../i18n/ja/docs/architecture/ARCHITECTURE.md) | 🇻🇳 [Tiếng Việt](../i18n/vi/docs/architecture/ARCHITECTURE.md) | 🇧🇬 [Български](../i18n/bg/docs/architecture/ARCHITECTURE.md) | 🇩🇰 [Dansk](../i18n/da/docs/architecture/ARCHITECTURE.md) | 🇫🇮 [Suomi](../i18n/fi/docs/architecture/ARCHITECTURE.md) | 🇮🇱 [עברית](../i18n/he/docs/architecture/ARCHITECTURE.md) | 🇭🇺 [Magyar](../i18n/hu/docs/architecture/ARCHITECTURE.md) | 🇮🇩 [Bahasa Indonesia](../i18n/id/docs/architecture/ARCHITECTURE.md) | 🇰🇷 [한국어](../i18n/ko/docs/architecture/ARCHITECTURE.md) | 🇲🇾 [Bahasa Melayu](../i18n/ms/docs/architecture/ARCHITECTURE.md) | 🇳🇱 [Nederlands](../i18n/nl/docs/architecture/ARCHITECTURE.md) | 🇳🇴 [Norsk](../i18n/no/docs/architecture/ARCHITECTURE.md) | 🇵🇹 [Português (Portugal)](../i18n/pt/docs/architecture/ARCHITECTURE.md) | 🇷🇴 [Română](../i18n/ro/docs/architecture/ARCHITECTURE.md) | 🇵🇱 [Polski](../i18n/pl/docs/architecture/ARCHITECTURE.md) | 🇸🇰 [Slovenčina](../i18n/sk/docs/architecture/ARCHITECTURE.md) | 🇸🇪 [Svenska](../i18n/sv/docs/architecture/ARCHITECTURE.md) | 🇵🇭 [Filipino](../i18n/phi/docs/architecture/ARCHITECTURE.md) | 🇨🇿 [Čeština](../i18n/cs/docs/architecture/ARCHITECTURE.md)
|
||||
|
||||
_Last updated: 2026-05-02_
|
||||
_Last updated: 2026-05-13_
|
||||
|
||||
## Executive Summary
|
||||
|
||||
@@ -11,13 +17,13 @@ It provides a single OpenAI-compatible endpoint (`/v1/*`) and routes traffic acr
|
||||
|
||||
Core capabilities:
|
||||
|
||||
- OpenAI-compatible API surface for CLI/tools (160+ providers, 16 executors)
|
||||
- OpenAI-compatible API surface for CLI/tools (177 providers, 31 executors)
|
||||
- Request/response translation across provider formats
|
||||
- Model combo fallback (multi-model sequence)
|
||||
- Structured combo steps (`provider + model + connection`) with runtime ordering by `compositeTiers`
|
||||
- Account-level fallback (multi-account per provider)
|
||||
- Quota preflight and quota-aware P2C account selection in the main chat path
|
||||
- OAuth + API-key provider connection management (13 OAuth modules)
|
||||
- OAuth + API-key provider connection management (14 OAuth modules)
|
||||
- Embedding generation via `/v1/embeddings` (6 providers, 9 models)
|
||||
- Image generation via `/v1/images/generations` (10+ providers, 20+ models)
|
||||
- Audio transcription via `/v1/audio/transcriptions` (7 providers)
|
||||
@@ -60,7 +66,7 @@ Core capabilities:
|
||||
- Prompt injection guard middleware
|
||||
- Prompt compression pipeline with Caveman, RTK, stacked pipelines, compression combos, language packs, and analytics
|
||||
- ACP (Agent Communication Protocol) registry
|
||||
- Modular OAuth providers (13 individual modules under `src/lib/oauth/providers/`)
|
||||
- Modular OAuth providers (14 individual modules under `src/lib/oauth/providers/`)
|
||||
- Uninstall/full-uninstall scripts
|
||||
- OAuth environment repair action
|
||||
- WebSocket bridge for OpenAI-compatible WS clients (`/v1/ws`)
|
||||
@@ -78,6 +84,21 @@ Primary runtime model:
|
||||
- Next.js app routes under `src/app/api/*` implement both dashboard APIs and compatibility APIs
|
||||
- A shared SSE/routing core in `src/sse/*` + `open-sse/*` handles provider execution, translation, streaming, fallback, and usage
|
||||
|
||||
## Reference Diagrams
|
||||
|
||||
Canonical, version-controlled Mermaid sources for the v3.8.0 platform live in
|
||||
[`docs/diagrams/`](../diagrams/README.md). Two are reproduced below for orientation;
|
||||
the rest are linked from their domain-specific guides.
|
||||
|
||||

|
||||
|
||||
> Source: [diagrams/request-pipeline.mmd](../diagrams/request-pipeline.mmd)
|
||||
|
||||

|
||||
|
||||
> Source: [diagrams/resilience-3layers.mmd](../diagrams/resilience-3layers.mmd) — also linked from
|
||||
> [RESILIENCE_GUIDE.md](./RESILIENCE_GUIDE.md) and the `CLAUDE.md` resilience reference.
|
||||
|
||||
## Scope and Boundaries
|
||||
|
||||
### In Scope
|
||||
@@ -103,11 +124,22 @@ Main pages under `src/app/(dashboard)/dashboard/`:
|
||||
- `/dashboard/endpoint` — endpoint proxy + MCP + A2A + API endpoint tabs
|
||||
- `/dashboard/providers` — provider connections and credentials
|
||||
- `/dashboard/combos` — combo strategies, templates, step-based builder, model routing rules, manual persisted ordering
|
||||
- `/dashboard/auto-combo` — Auto Combo Engine: scoring weights, mode packs, virtual factory presets, telemetry
|
||||
- `/dashboard/costs` — cost aggregation and pricing visibility
|
||||
- `/dashboard/analytics` — usage analytics, evaluations, combo target health
|
||||
- `/dashboard/limits` — quota/rate controls
|
||||
- `/dashboard/cli-tools` — CLI onboarding, runtime detection, config generation
|
||||
- `/dashboard/agents` — detected ACP agents + custom agent registration
|
||||
- `/dashboard/cloud-agents` — cloud-hosted agent tasks (Codex Cloud, Devin, Jules) and task lifecycle
|
||||
- `/dashboard/skills` — A2A skill registry, sandbox execution, built-in skill catalog
|
||||
- `/dashboard/memory` — persistent conversational memory inspection and retrieval
|
||||
- `/dashboard/webhooks` — outbound webhook subscriptions, secret rotation, retry stats
|
||||
- `/dashboard/batch` — batch job submission and progress
|
||||
- `/dashboard/cache` — read-through and reasoning cache statistics, eviction controls
|
||||
- `/dashboard/playground` — interactive chat playground against any configured combo/model
|
||||
- `/dashboard/changelog` — in-app changelog viewer (renders `CHANGELOG.md`)
|
||||
- `/dashboard/system` — runtime diagnostics, version info, environment validation surface
|
||||
- `/dashboard/onboarding` — first-run setup wizard for new installations
|
||||
- `/dashboard/media` — image/video/music playground
|
||||
- `/dashboard/search-tools` — search provider testing and history
|
||||
- `/dashboard/health` — uptime, circuit breakers, rate limits, quota-monitored sessions
|
||||
@@ -116,6 +148,10 @@ Main pages under `src/app/(dashboard)/dashboard/`:
|
||||
- `/dashboard/context/caveman` — Caveman compression rules, language packs, preview, and output mode
|
||||
- `/dashboard/context/rtk` — RTK command-output filters, preview, and runtime safety settings
|
||||
- `/dashboard/context/combos` — named compression pipelines assigned to routing combos
|
||||
- `/dashboard/translator` — translator inspection and request format conversion preview
|
||||
- `/dashboard/audit` — compliance audit log browser with pagination and structured metadata
|
||||
- `/dashboard/usage` — per-request usage browser tied to `usage_history`
|
||||
- `/dashboard/compression` — compression analytics, statistics, and pipeline assignment
|
||||
- `/dashboard/api-manager` — API key lifecycle and model permissions
|
||||
|
||||
## High-Level System Context
|
||||
@@ -285,12 +321,172 @@ Domain layer modules:
|
||||
- Eval runner: `src/lib/domain/evalRunner.ts`
|
||||
- Domain state persistence: `src/lib/db/domainState.ts` — SQLite CRUD for fallback chains, budgets, cost history, lockout state, circuit breakers
|
||||
|
||||
OAuth provider modules (13 individual files under `src/lib/oauth/providers/`):
|
||||
OAuth provider modules (14 individual files under `src/lib/oauth/providers/`):
|
||||
|
||||
- Registry index: `src/lib/oauth/providers/index.ts`
|
||||
- Individual providers: `claude.ts`, `codex.ts`, `gemini.ts`, `antigravity.ts`, `qoder.ts`, `qwen.ts`, `kimi-coding.ts`, `github.ts`, `kiro.ts`, `cursor.ts`, `kilocode.ts`, `cline.ts`
|
||||
- Individual providers: `claude.ts`, `codex.ts`, `gemini.ts`, `antigravity.ts`, `qoder.ts`, `qwen.ts`, `kimi-coding.ts`, `github.ts`, `kiro.ts`, `cursor.ts`, `kilocode.ts`, `cline.ts`, `windsurf.ts`, `gitlab-duo.ts`
|
||||
- Thin wrapper: `src/lib/oauth/providers.ts` — re-exports from individual modules
|
||||
|
||||
## Major Subsystems (v3.8.0)
|
||||
|
||||
### A. Auto Combo Engine
|
||||
|
||||
Auto Combo dynamically scores and picks routing targets at request time, rather than
|
||||
relying on a static combo definition. It powers the `auto/*` model prefix family.
|
||||
|
||||
- Engine entry: `open-sse/services/autoCombo/` (`autoComboEngine.ts`,
|
||||
`scoringEngine.ts`, `virtualFactory.ts`, `modePacks.ts`)
|
||||
- Resolver: `src/domain/comboResolver.ts` (auto-detection of `auto/` prefix)
|
||||
- Dashboard: `/dashboard/auto-combo`
|
||||
- Telemetry: `auto_combo_decisions` SQLite table
|
||||
|
||||
Key capabilities:
|
||||
|
||||
- **14 routing strategies** (priority, weighted, fill-first, round-robin, P2C, random,
|
||||
least-used, cost-optimized, strict-random, **auto**, lkgp, context-optimized,
|
||||
context-relay, plus a fallback path) — auto is the headline addition in v3.8.0.
|
||||
- **9-factor scoring**: cost, latency p95, success rate, quota headroom, lockout
|
||||
proximity, breaker state, recent failures, model availability, and tag affinity.
|
||||
- **Virtual factory** materializes ephemeral combos when no matching named combo
|
||||
exists, sourcing candidates from healthy active provider connections.
|
||||
- **Auto prefixes**: `auto/coding`, `auto/cheap`, `auto/fast`, `auto/offline`,
|
||||
`auto/smart`, `auto/lkgp` — each backed by a tuned weight profile.
|
||||
- **4 mode packs**: coding, fast, cheap, smart — shipped as preset weight
|
||||
configurations callable from the dashboard.
|
||||
|
||||
For full algorithmic detail (factor formulas, weight tuning), see
|
||||
[`docs/routing/AUTO-COMBO.md`](../routing/AUTO-COMBO.md).
|
||||
|
||||
### B. Cloud Agents
|
||||
|
||||
Cloud Agents wraps third-party hosted code-agent platforms (Codex Cloud, Devin,
|
||||
Jules) behind a uniform DB-backed task lifecycle. All task creation/inspection
|
||||
endpoints require management authentication.
|
||||
|
||||
- Module root: `src/lib/cloudAgent/` (`baseAgent.ts`, `registry.ts`, `api.ts`,
|
||||
`types.ts`, `db.ts`, plus per-agent subdirectories under `agents/`)
|
||||
- Per-agent implementations: `agents/codex/`, `agents/devin/`, `agents/jules/`
|
||||
- Public endpoints: `/api/v1/agents/tasks/*` (list/create/get/cancel)
|
||||
- Management endpoints: `/api/cloud/*` (provisioning, status, batch)
|
||||
- Dashboard: `/dashboard/cloud-agents`
|
||||
- Storage: `cloud_agent_tasks` table
|
||||
|
||||
For per-agent provisioning and OAuth specifics, see
|
||||
[`docs/frameworks/CLOUD_AGENT.md`](../frameworks/CLOUD_AGENT.md).
|
||||
|
||||
### C. Guardrails
|
||||
|
||||
The guardrails module is a hot-reloadable middleware layer that inspects requests
|
||||
and responses for PII, prompt injection, and unsafe vision content. Violations
|
||||
short-circuit the request with HTTP **503** plus a structured error code, allowing
|
||||
downstream callers to retry or branch.
|
||||
|
||||
- Module root: `src/lib/guardrails/` (`base.ts`, `registry.ts`, `piiMasker.ts`,
|
||||
`promptInjection.ts`, `visionBridge.ts`, `visionBridgeHelpers.ts`)
|
||||
- Hot reload: registry watches for config changes and rebuilds the chain in place
|
||||
- Wire-in points: chat handler entry, image generation handler, response sanitizer
|
||||
- HTTP contract: violations surface as `503` with `error.code = "GUARDRAIL_VIOLATION"`
|
||||
|
||||
For ruleset authoring and threshold tuning, see
|
||||
[`docs/security/GUARDRAILS.md`](../security/GUARDRAILS.md).
|
||||
|
||||
### D. Domain Layer
|
||||
|
||||
The `src/domain/` namespace centralizes policy decisions so route handlers do not
|
||||
have to assemble lockout/budget/fallback logic themselves.
|
||||
|
||||
- Policy engine: `src/domain/policyEngine.ts` — single entry point for
|
||||
pre-execution evaluation (lockout → budget → fallback ordering)
|
||||
- Cost rules: `src/domain/costRules.ts`
|
||||
- Fallback policy: `src/domain/fallbackPolicy.ts`
|
||||
- Lockout policy: `src/domain/lockoutPolicy.ts`
|
||||
- Tag-based routing: `src/domain/tagRouter.ts`
|
||||
- Combo resolver: `src/domain/comboResolver.ts` — resolves combo names, auto/\*
|
||||
prefixes, and wildcard model targets to concrete execution plans
|
||||
- Connection/model rule joiner: `src/domain/connectionModelRules.ts`
|
||||
- Model availability snapshots: `src/domain/modelAvailability.ts`
|
||||
- Provider expiration tracking: `src/domain/providerExpiration.ts`
|
||||
- Quota cache: `src/domain/quotaCache.ts`
|
||||
- Degradation state: `src/domain/degradation.ts`
|
||||
- Configuration audit: `src/domain/configAudit.ts`
|
||||
- OmniRoute response metadata builder: `src/domain/omnirouteResponseMeta.ts`
|
||||
- Assessment subsystem: `src/domain/assessment/` — periodic evaluation jobs
|
||||
|
||||
### E. Authorization Pipeline
|
||||
|
||||
The authorization pipeline classifies every incoming request and applies the
|
||||
appropriate policy chain before dispatch.
|
||||
|
||||
- Pipeline entry: `src/server/authz/pipeline.ts`
|
||||
- Request classifier: `src/server/authz/classify.ts` — distinguishes public
|
||||
compatibility routes from management routes
|
||||
- Public route inventory: `src/shared/constants/publicApiRoutes.ts`
|
||||
- Policies: `src/server/authz/policies/` — composable predicates
|
||||
(`requireApiKey`, `requireManagement`, `requireFreshAuth`, etc.)
|
||||
- Header utilities: `src/server/authz/headers.ts`
|
||||
- Assertion helper: `src/server/authz/assertAuth.ts`
|
||||
- Request context: `src/server/authz/context.ts`
|
||||
|
||||
Public vs management routes are a hard boundary: agent/cooldown APIs and
|
||||
provider mutations require management auth (HTTP 401 if missing).
|
||||
|
||||
For the full route classification rules, see
|
||||
[`docs/architecture/AUTHZ_GUIDE.md`](./AUTHZ_GUIDE.md).
|
||||
|
||||
### F. Workflow FSM and Task-Aware Router
|
||||
|
||||
A finite-state-machine driven router layered above combo selection to direct
|
||||
traffic based on the detected workflow stage (planning, execution,
|
||||
review) and background-task affinity.
|
||||
|
||||
- Workflow FSM: `open-sse/services/workflowFSM.ts`
|
||||
- Task-aware router: `open-sse/services/taskAwareRouter.ts`
|
||||
- Background task detector: `open-sse/services/backgroundTaskDetector.ts`
|
||||
- Intent classifier: `open-sse/services/intentClassifier.ts`
|
||||
|
||||
The FSM transitions feed into Auto Combo's scoring, biasing toward cheaper models
|
||||
for background/automation tasks and toward stronger models for interactive
|
||||
planning/review turns.
|
||||
|
||||
### G. Provider-Specific Resilience
|
||||
|
||||
Several providers ship dedicated resilience and stealth modules that piggy-back on
|
||||
the global circuit breaker / connection cooldown / model lockout layers:
|
||||
|
||||
- Antigravity 429 engine: `open-sse/services/antigravity429Engine.ts` (rotates
|
||||
identity, scrubs response headers, drives credits/version tracking via
|
||||
`antigravityCredits.ts`, `antigravityHeaderScrub.ts`, `antigravityHeaders.ts`,
|
||||
`antigravityIdentity.ts`, `antigravityObfuscation.ts`, `antigravityVersion.ts`)
|
||||
- ModelScope quota policy: `open-sse/services/modelscopePolicy.ts`
|
||||
- Claude Code CCH (Compatibility Channel Handshake): `open-sse/services/claudeCodeCCH.ts`,
|
||||
plus `claudeCodeCompatible.ts`, `claudeCodeConstraints.ts`, `claudeCodeExtraRemap.ts`,
|
||||
`claudeCodeToolRemapper.ts`
|
||||
- Claude Code fingerprint shaping: `open-sse/services/claudeCodeFingerprint.ts`
|
||||
- Claude Code obfuscation: `open-sse/services/claudeCodeObfuscation.ts`
|
||||
- ChatGPT TLS client: `open-sse/services/chatgptTlsClient.ts` (curl-impersonate
|
||||
style for ChatGPT-Web sessions)
|
||||
- ChatGPT image cache: `open-sse/services/chatgptImageCache.ts`
|
||||
|
||||
For the full stealth playbook and operational guidance, see
|
||||
[`docs/security/STEALTH_GUIDE.md`](../security/STEALTH_GUIDE.md).
|
||||
|
||||
### H. Webhooks, Reasoning Cache, Read Cache
|
||||
|
||||
- **Webhooks** — outbound dispatch for provider/account/task events.
|
||||
- Dispatcher: `src/lib/webhookDispatcher.ts`
|
||||
- Storage: `webhooks` SQLite table (via `src/lib/db/webhooks.ts`)
|
||||
- Dashboard: `/dashboard/webhooks` (subscriptions, secrets, retry history)
|
||||
- For event taxonomy and retry semantics, see [`docs/frameworks/WEBHOOKS.md`](../frameworks/WEBHOOKS.md).
|
||||
- **Reasoning Cache** — replayable reasoning blocks for providers that emit
|
||||
thinking tokens (Claude, GLMT, etc.) so consecutive turns can skip re-thinking.
|
||||
- DB layer: `src/lib/db/reasoningCache.ts`
|
||||
- Service layer: `open-sse/services/reasoningCache.ts`
|
||||
- For replay semantics, see [`docs/routing/REASONING_REPLAY.md`](../routing/REASONING_REPLAY.md).
|
||||
- **Read Cache** — short-lived response cache keyed by signature and used to
|
||||
collapse identical retries from broken upstream SDKs.
|
||||
- DB layer: `src/lib/db/readCache.ts`
|
||||
- Stats endpoint: `GET /api/cache/stats`, dashboard at `/dashboard/cache`
|
||||
|
||||
## 3) Persistence Layer
|
||||
|
||||
Primary state DB (SQLite):
|
||||
@@ -660,9 +856,12 @@ flowchart LR
|
||||
### Translation Registry and Format Converters
|
||||
|
||||
- `open-sse/translator/index.ts`: translator registry and orchestration
|
||||
- Request translators: `open-sse/translator/request/*`
|
||||
- Response translators: `open-sse/translator/response/*`
|
||||
- Request translators: `open-sse/translator/request/*` (9 modules — `antigravity-to-openai`, `claude-to-gemini`, `claude-to-openai`, `gemini-to-openai`, `openai-responses`, `openai-to-claude`, `openai-to-cursor`, `openai-to-gemini`, `openai-to-kiro`)
|
||||
- Response translators: `open-sse/translator/response/*` (8 modules — `claude-to-openai`, `cursor-to-openai`, `gemini-to-claude`, `gemini-to-openai`, `kiro-to-openai`, `openai-responses`, `openai-to-antigravity`, `openai-to-claude`)
|
||||
- Helpers: `open-sse/translator/helpers/*` (8 modules — `claudeHelper`, `geminiHelper`, `geminiToolsSanitizer`, `maxTokensHelper`, `openaiHelper`, `responsesApiHelper`, `schemaCoercion`, `toolCallHelper`)
|
||||
- Format constants: `open-sse/translator/formats.ts`
|
||||
- Bootstrap and registry: `open-sse/translator/bootstrap.ts`, `open-sse/translator/registry.ts`
|
||||
- Image-format helpers: `open-sse/translator/image/`
|
||||
|
||||
### Persistence
|
||||
|
||||
@@ -674,66 +873,108 @@ flowchart LR
|
||||
|
||||
Each provider has a specialized executor extending `BaseExecutor` (in `open-sse/executors/base.ts`), which provides URL building, header construction, retry with exponential backoff, credential refresh hooks, and the `execute()` orchestration method.
|
||||
|
||||
| Executor | Provider(s) | Special Handling |
|
||||
| ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------- |
|
||||
| `DefaultExecutor` | OpenAI, Claude, Gemini, Qwen, OpenRouter, GLM, Kimi, MiniMax, DeepSeek, Groq, xAI, Mistral, Perplexity, Together, Fireworks, Cerebras, Cohere, NVIDIA, etc. | Dynamic URL/header config per provider |
|
||||
| `AntigravityExecutor` | Google Antigravity | Custom project/session IDs, Retry-After parsing |
|
||||
| `CliProxyApiExecutor` | CLIProxyAPI-compatible providers | Custom auth and protocol handling |
|
||||
| `CloudflareAiExecutor` | Cloudflare Workers AI | Account ID injection, Neurons-based usage tracking |
|
||||
| `CodexExecutor` | OpenAI Codex | Injects system instructions, forces reasoning effort |
|
||||
| `CursorExecutor` | Cursor IDE | ConnectRPC protocol, Protobuf encoding, request signing via checksum |
|
||||
| `GithubExecutor` | GitHub Copilot | Copilot token refresh, VSCode-mimicking headers |
|
||||
| `GeminiCLIExecutor` | Gemini CLI | Google OAuth token refresh cycle |
|
||||
| `KiroExecutor` | AWS CodeWhisperer/Kiro | AWS EventStream binary format → SSE conversion |
|
||||
| `OpenCodeExecutor` | OpenCode | AI SDK compatible provider setup |
|
||||
| `PollinationsExecutor` | Pollinations AI | No API key required, rate-limited requests |
|
||||
| `PuterExecutor` | Puter | Browser-based provider integration |
|
||||
| `QoderExecutor` | Qoder AI | PAT and OAuth support, multi-model free tier |
|
||||
| `VertexExecutor` | Google Vertex AI | Service account auth, region-based endpoints |
|
||||
| Executor | Provider(s) | Special Handling |
|
||||
| ------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------- |
|
||||
| `DefaultExecutor` | OpenAI, Claude, Gemini, Qwen, OpenRouter, GLM, Kimi, MiniMax, DeepSeek, Groq, xAI, Mistral, Perplexity, Together, Fireworks, Cerebras, Cohere, NVIDIA, etc. | Dynamic URL/header config per provider |
|
||||
| `AntigravityExecutor` | Google Antigravity | Custom project/session IDs, Retry-After parsing, 429 obfuscation |
|
||||
| `AzureOpenAIExecutor` | Azure OpenAI | Deployment-based routing, api-version query enforcement |
|
||||
| `BlackboxWebExecutor` | Blackbox AI (web-mode) | Web-session reverse with TLS fingerprint emulation |
|
||||
| `ChatGPTWebExecutor` | ChatGPT web | TLS client + session cookie management (`chatgptTlsClient.ts`) |
|
||||
| `ClaudeIdentityExecutor` | Claude.ai (CCH path) | Constraint + tool-remap pipelines, fingerprint shaping |
|
||||
| `CliProxyApiExecutor` | CLIProxyAPI-compatible providers | Custom auth and protocol handling |
|
||||
| `CloudflareAiExecutor` | Cloudflare Workers AI | Account ID injection, Neurons-based usage tracking |
|
||||
| `CodexExecutor` | OpenAI Codex | Injects system instructions, forces reasoning effort |
|
||||
| `CommandCodeExecutor` | Command Code | OAuth + per-session header rotation |
|
||||
| `CursorExecutor` | Cursor IDE | ConnectRPC protocol, Protobuf encoding, request signing via checksum |
|
||||
| `DevinCliExecutor` | Devin CLI | Devin task lifecycle bridging via cloud agent module |
|
||||
| `GeminiCLIExecutor` | Gemini CLI | Google OAuth token refresh cycle |
|
||||
| `GithubExecutor` | GitHub Copilot | Copilot token refresh, VSCode-mimicking headers |
|
||||
| `GitlabExecutor` | GitLab Duo | GitLab OAuth + project-scoped routing |
|
||||
| `GlmExecutor` | Z.AI GLM (incl. `glmt` preset) | Thinking-budget aware, GLMT preset constants |
|
||||
| `GrokWebExecutor` | xAI Grok web | Web-session reverse, mode selection (think/standard) |
|
||||
| `KieExecutor` | KIE | Custom token issuance with rotating session anchors |
|
||||
| `KiroExecutor` | AWS CodeWhisperer/Kiro | AWS EventStream binary format → SSE conversion |
|
||||
| `MuseSparkWebExecutor` | Muse Spark (web) | Web-session reverse with image-message bridging |
|
||||
| `NlpCloudExecutor` | NLP Cloud | Provider-specific request body shape |
|
||||
| `OpenCodeExecutor` | OpenCode | AI SDK compatible provider setup |
|
||||
| `PerplexityWebExecutor` | Perplexity web | Web-session reverse for chat continuation |
|
||||
| `PetalsExecutor` | Petals distributed inference | Decentralized swarm routing |
|
||||
| `PollinationsExecutor` | Pollinations AI | No API key required, rate-limited requests |
|
||||
| `PuterExecutor` | Puter | Browser-based provider integration |
|
||||
| `QoderExecutor` | Qoder AI | PAT and OAuth support, multi-model free tier |
|
||||
| `VertexExecutor` | Google Vertex AI | Service account auth, region-based endpoints |
|
||||
| `WindsurfExecutor` | Windsurf (Codeium) | Codeium OAuth + session token refresh |
|
||||
|
||||
All other providers (including custom compatible nodes) use the `DefaultExecutor`.
|
||||
|
||||
## Provider Compatibility Matrix
|
||||
|
||||
| Provider | Format | Auth | Stream | Non-Stream | Token Refresh | Usage API |
|
||||
| ---------------- | ---------------- | --------------------- | ---------------- | ---------- | ------------- | ------------------ |
|
||||
| Claude | claude | API Key / OAuth | ✅ | ✅ | ✅ | ⚠️ Admin only |
|
||||
| Gemini | gemini | API Key / OAuth | ✅ | ✅ | ✅ | ⚠️ Cloud Console |
|
||||
| Gemini CLI | gemini-cli | OAuth | ✅ | ✅ | ✅ | ⚠️ Cloud Console |
|
||||
| Antigravity | antigravity | OAuth | ✅ | ✅ | ✅ | ✅ Full quota API |
|
||||
| OpenAI | openai | API Key | ✅ | ✅ | ❌ | ❌ |
|
||||
| Codex | openai-responses | OAuth | ✅ forced | ❌ | ✅ | ✅ Rate limits |
|
||||
| GitHub Copilot | openai | OAuth + Copilot Token | ✅ | ✅ | ✅ | ✅ Quota snapshots |
|
||||
| Cursor | cursor | Custom checksum | ✅ | ✅ | ❌ | ❌ |
|
||||
| Kiro | kiro | AWS SSO OIDC | ✅ (EventStream) | ❌ | ✅ | ✅ Usage limits |
|
||||
| Qwen | openai | OAuth | ✅ | ✅ | ✅ | ⚠️ Per request |
|
||||
| Qoder | openai | OAuth / PAT | ✅ | ✅ | ✅ | ⚠️ Per request |
|
||||
| Kilo Code | openai | OAuth | ✅ | ✅ | ✅ | ❌ |
|
||||
| Cline | openai | OAuth | ✅ | ✅ | ✅ | ❌ |
|
||||
| Kimi Coding | openai | OAuth | ✅ | ✅ | ✅ | ❌ |
|
||||
| OpenRouter | openai | API Key | ✅ | ✅ | ❌ | ❌ |
|
||||
| GLM/Kimi/MiniMax | claude | API Key | ✅ | ✅ | ❌ | ❌ |
|
||||
| DeepSeek | openai | API Key | ✅ | ✅ | ❌ | ❌ |
|
||||
| Groq | openai | API Key | ✅ | ✅ | ❌ | ❌ |
|
||||
| xAI (Grok) | openai | API Key | ✅ | ✅ | ❌ | ❌ |
|
||||
| Mistral | openai | API Key | ✅ | ✅ | ❌ | ❌ |
|
||||
| Perplexity | openai | API Key | ✅ | ✅ | ❌ | ❌ |
|
||||
| Together AI | openai | API Key | ✅ | ✅ | ❌ | ❌ |
|
||||
| Fireworks AI | openai | API Key | ✅ | ✅ | ❌ | ❌ |
|
||||
| Cerebras | openai | API Key | ✅ | ✅ | ❌ | ❌ |
|
||||
| Cohere | openai | API Key | ✅ | ✅ | ❌ | ❌ |
|
||||
| NVIDIA NIM | openai | API Key | ✅ | ✅ | ❌ | ❌ |
|
||||
| Cloudflare AI | openai | API Token + Acct ID | ✅ | ✅ | ❌ | ❌ |
|
||||
| Pollinations | openai | None (no key) | ✅ | ✅ | ❌ | ❌ |
|
||||
| Scaleway AI | openai | API Key | ✅ | ✅ | ❌ | ❌ |
|
||||
| LongCat | openai | API Key | ✅ | ✅ | ❌ | ❌ |
|
||||
| Ollama Cloud | openai | API Key (optional) | ✅ | ✅ | ❌ | ❌ |
|
||||
| HuggingFace | openai | API Key | ✅ | ✅ | ❌ | ❌ |
|
||||
| Nebius | openai | API Key | ✅ | ✅ | ❌ | ❌ |
|
||||
| SiliconFlow | openai | API Key | ✅ | ✅ | ❌ | ❌ |
|
||||
| Hyperbolic | openai | API Key | ✅ | ✅ | ❌ | ❌ |
|
||||
| Vertex AI | gemini | Service Account | ✅ | ✅ | ✅ | ⚠️ Cloud Console |
|
||||
| Puter | openai | API Key | ✅ | ✅ | ❌ | ❌ |
|
||||
> **Note:** The matrix below is a representative sample of the 177 registered providers in
|
||||
> OmniRoute v3.8.0. For the canonical and continuously-updated list, refer to
|
||||
> [`docs/reference/PROVIDER_REFERENCE.md`](../reference/PROVIDER_REFERENCE.md) (auto-generated) or the source of
|
||||
> truth at `src/shared/constants/providers.ts` (Zod-validated at load).
|
||||
|
||||
| Provider | Format | Auth | Stream | Non-Stream | Token Refresh | Usage API |
|
||||
| ----------------- | ---------------- | --------------------- | ---------------- | ---------- | ------------- | ------------------ |
|
||||
| Claude | claude | API Key / OAuth | ✅ | ✅ | ✅ | ⚠️ Admin only |
|
||||
| Gemini | gemini | API Key / OAuth | ✅ | ✅ | ✅ | ⚠️ Cloud Console |
|
||||
| Gemini CLI | gemini-cli | OAuth | ✅ | ✅ | ✅ | ⚠️ Cloud Console |
|
||||
| Antigravity | antigravity | OAuth | ✅ | ✅ | ✅ | ✅ Full quota API |
|
||||
| OpenAI | openai | API Key | ✅ | ✅ | ❌ | ❌ |
|
||||
| Codex | openai-responses | OAuth | ✅ forced | ❌ | ✅ | ✅ Rate limits |
|
||||
| GitHub Copilot | openai | OAuth + Copilot Token | ✅ | ✅ | ✅ | ✅ Quota snapshots |
|
||||
| Cursor | cursor | Custom checksum | ✅ | ✅ | ❌ | ❌ |
|
||||
| Kiro | kiro | AWS SSO OIDC | ✅ (EventStream) | ❌ | ✅ | ✅ Usage limits |
|
||||
| Qwen | openai | OAuth | ✅ | ✅ | ✅ | ⚠️ Per request |
|
||||
| Qoder | openai | OAuth / PAT | ✅ | ✅ | ✅ | ⚠️ Per request |
|
||||
| Kilo Code | openai | OAuth | ✅ | ✅ | ✅ | ❌ |
|
||||
| Cline | openai | OAuth | ✅ | ✅ | ✅ | ❌ |
|
||||
| Kimi Coding | openai | OAuth | ✅ | ✅ | ✅ | ❌ |
|
||||
| OpenRouter | openai | API Key | ✅ | ✅ | ❌ | ❌ |
|
||||
| GLM/Kimi/MiniMax | claude | API Key | ✅ | ✅ | ❌ | ❌ |
|
||||
| DeepSeek | openai | API Key | ✅ | ✅ | ❌ | ❌ |
|
||||
| Groq | openai | API Key | ✅ | ✅ | ❌ | ❌ |
|
||||
| xAI (Grok) | openai | API Key | ✅ | ✅ | ❌ | ❌ |
|
||||
| Mistral | openai | API Key | ✅ | ✅ | ❌ | ❌ |
|
||||
| Perplexity | openai | API Key | ✅ | ✅ | ❌ | ❌ |
|
||||
| Together AI | openai | API Key | ✅ | ✅ | ❌ | ❌ |
|
||||
| Fireworks AI | openai | API Key | ✅ | ✅ | ❌ | ❌ |
|
||||
| Cerebras | openai | API Key | ✅ | ✅ | ❌ | ❌ |
|
||||
| Cohere | openai | API Key | ✅ | ✅ | ❌ | ❌ |
|
||||
| NVIDIA NIM | openai | API Key | ✅ | ✅ | ❌ | ❌ |
|
||||
| Cloudflare AI | openai | API Token + Acct ID | ✅ | ✅ | ❌ | ❌ |
|
||||
| Pollinations | openai | None (no key) | ✅ | ✅ | ❌ | ❌ |
|
||||
| Scaleway AI | openai | API Key | ✅ | ✅ | ❌ | ❌ |
|
||||
| LongCat | openai | API Key | ✅ | ✅ | ❌ | ❌ |
|
||||
| Ollama Cloud | openai | API Key (optional) | ✅ | ✅ | ❌ | ❌ |
|
||||
| HuggingFace | openai | API Key | ✅ | ✅ | ❌ | ❌ |
|
||||
| Nebius | openai | API Key | ✅ | ✅ | ❌ | ❌ |
|
||||
| SiliconFlow | openai | API Key | ✅ | ✅ | ❌ | ❌ |
|
||||
| Hyperbolic | openai | API Key | ✅ | ✅ | ❌ | ❌ |
|
||||
| Vertex AI | gemini | Service Account | ✅ | ✅ | ✅ | ⚠️ Cloud Console |
|
||||
| Puter | openai | API Key | ✅ | ✅ | ❌ | ❌ |
|
||||
| Command Code | openai | OAuth | ✅ | ✅ | ✅ | ⚠️ Per request |
|
||||
| Z.AI / GLM | openai | API Key / OAuth | ✅ | ✅ | ❌ | ❌ |
|
||||
| GLMT (preset) | claude | API Key | ✅ | ✅ | ❌ | ⚠️ Per request |
|
||||
| Kimi Coding | openai | OAuth / API Key | ✅ | ✅ | ✅ | ❌ |
|
||||
| KIE | openai | API Key | ✅ | ✅ | ❌ | ❌ |
|
||||
| Windsurf | openai | OAuth (Codeium) | ✅ | ✅ | ✅ | ⚠️ Per request |
|
||||
| GitLab Duo | openai | OAuth (GitLab) | ✅ | ✅ | ✅ | ❌ |
|
||||
| Devin CLI | openai | OAuth | ✅ | ✅ | ✅ | ✅ Task API |
|
||||
| Codex Cloud | openai-responses | OAuth | ✅ | ❌ | ✅ | ✅ Rate limits |
|
||||
| Jules | openai | OAuth | ✅ | ✅ | ✅ | ✅ Task API |
|
||||
| AgentRouter | openai | API Key | ✅ | ✅ | ❌ | ❌ |
|
||||
| ChatGPT-Web | openai | Session cookie + TLS | ✅ | ✅ | ❌ | ❌ |
|
||||
| Grok-Web | openai | Session cookie | ✅ | ✅ | ❌ | ❌ |
|
||||
| Perplexity-Web | openai | Session cookie | ✅ | ✅ | ❌ | ❌ |
|
||||
| BlackBox-Web | openai | Session cookie + TLS | ✅ | ✅ | ❌ | ❌ |
|
||||
| Muse-Spark-Web | openai | Session cookie | ✅ | ✅ | ❌ | ❌ |
|
||||
| ModelScope | openai | API Key | ✅ | ✅ | ❌ | ⚠️ Quota policy |
|
||||
| BazaarLink | openai | API Key | ✅ | ✅ | ❌ | ❌ |
|
||||
| Petals | openai | None | ✅ | ✅ | ❌ | ❌ |
|
||||
| Qoder | openai | OAuth / PAT | ✅ | ✅ | ✅ | ⚠️ Per request |
|
||||
| OpenCode (Go/Zen) | openai | OAuth | ✅ | ✅ | ✅ | ❌ |
|
||||
| CLIProxyAPI | openai | Custom | ✅ | ✅ | ❌ | ❌ |
|
||||
|
||||
## Format Translation Coverage
|
||||
|
||||
211
docs/architecture/AUTHZ_GUIDE.md
Normal file
211
docs/architecture/AUTHZ_GUIDE.md
Normal file
@@ -0,0 +1,211 @@
|
||||
---
|
||||
title: "Authorization Guide"
|
||||
version: 3.8.0
|
||||
lastUpdated: 2026-05-13
|
||||
---
|
||||
|
||||
# Authorization Guide
|
||||
|
||||
> **Source of truth:** `src/server/authz/`, `src/shared/constants/publicApiRoutes.ts`, `src/lib/api/requireManagementAuth.ts`, `src/shared/utils/apiAuth.ts`
|
||||
> **Last updated:** 2026-05-13 — v3.8.0
|
||||
|
||||
OmniRoute has a route-aware authorization pipeline that gates every API request. Classification is **deterministic** and **fail-closed** — anything that cannot be classified ends up as `MANAGEMENT` and demands a session or management-grade token. This page explains the model for engineers maintaining routes or designing new endpoints.
|
||||
|
||||

|
||||
|
||||
> Source: [diagrams/authz-pipeline.mmd](../diagrams/authz-pipeline.mmd)
|
||||
|
||||
## Two Auth Modes
|
||||
|
||||
### 1. API Key (Bearer)
|
||||
|
||||
Used for the OpenAI/Anthropic/Gemini-compatible client APIs and a few management routes when the key has the `manage` scope.
|
||||
|
||||
```
|
||||
Authorization: Bearer <api-key>
|
||||
```
|
||||
|
||||
Validated by `isValidApiKey()` / `extractApiKey()` in `src/sse/services/auth.ts` and re-exported through `src/shared/utils/apiAuth.ts`. The validator also accepts the `OMNIROUTE_API_KEY` / `ROUTER_API_KEY` env vars as persistent passthrough keys (issue #1350).
|
||||
|
||||
### 2. Dashboard Session (auth_token cookie)
|
||||
|
||||
For dashboard pages and admin operations.
|
||||
|
||||
```
|
||||
Cookie: auth_token=<JWT signed with JWT_SECRET>
|
||||
```
|
||||
|
||||
Verified by `isDashboardSessionAuthenticated()` in `src/shared/utils/apiAuth.ts`. The pipeline auto-refreshes the JWT when it has fewer than 7 days left in its 30-day lifetime.
|
||||
|
||||
Some management routes accept **either** mode: cookie OR `Bearer <key>` when the API key has the `manage` (or `admin`) scope. This is what enables the "configurable via API calls" workflow added in v3.8.
|
||||
|
||||
## Route Classes
|
||||
|
||||
`src/server/authz/types.ts` defines three classes; any route that cannot be classified deterministically falls back to `MANAGEMENT`.
|
||||
|
||||
| Class | Description | Auth required |
|
||||
| ------------ | ---------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------- |
|
||||
| `PUBLIC` | Explicitly safe routes — login, logout, status, init, health, onboarding bootstrap. | None |
|
||||
| `CLIENT_API` | Model-serving endpoints — `/api/v1/*`, plus aliases `/v1/*`, `/chat/completions`, `/responses`, `/models`, `/codex/*`. | Bearer key (unless `REQUIRE_API_KEY != "true"`) |
|
||||
| `MANAGEMENT` | Dashboard pages, settings, providers, keys, admin and diagnostics endpoints. | Dashboard session OR Bearer with `manage` scope |
|
||||
|
||||
## Pipeline
|
||||
|
||||
```
|
||||
Incoming request → src/middleware.ts
|
||||
→ runAuthzPipeline() in src/server/authz/pipeline.ts
|
||||
1. Strip trusted internal headers (x-omniroute-auth-*, x-omniroute-route-class)
|
||||
2. Generate request id, classify route via classifyRoute()
|
||||
3. If pathname == "/" → redirect /dashboard
|
||||
4. If draining (graceful shutdown) and /api/* → 503
|
||||
5. If non-GET /api/* → checkBodySize() guard
|
||||
6. If OPTIONS → CORS preflight 204
|
||||
7. If options.enforce == false → pass-through with route-class headers
|
||||
8. Otherwise: POLICIES[routeClass].evaluate(ctx)
|
||||
- allow → stamp x-omniroute-auth-{kind,id,label,scopes} → NextResponse.next()
|
||||
- reject → JSON error w/ correlation_id (dashboard pages → 302 /login)
|
||||
```
|
||||
|
||||
Trusted internal headers (defined in `src/server/authz/headers.ts`) are **stripped from incoming requests** before classification — clients cannot pre-populate `x-omniroute-auth-*` to impersonate a subject.
|
||||
|
||||
### Policy contracts
|
||||
|
||||
Each route class has a policy in `src/server/authz/policies/`:
|
||||
|
||||
- **`publicPolicy`** (`policies/public.ts`) — always returns `allow({ kind: "anonymous", id: "anonymous" })`.
|
||||
- **`clientApiPolicy`** (`policies/clientApi.ts`) — extracts Bearer, validates via `validateApiKey()`. Falls through to anonymous if `REQUIRE_API_KEY != "true"`. Allows dashboard-session GET on `/api/v1/models` (used by the dashboard model catalog).
|
||||
- **`managementPolicy`** (`policies/management.ts`) — accepts dashboard session, internal model-sync requests (matched against `/api/providers/[name]/(sync-models|models)`), or skips entirely if `isAuthRequired()` returns false. Returns 403 (`AUTH_001`) when a Bearer token is present but invalid, 401 otherwise.
|
||||
|
||||
A successful policy returns `AuthSubject` with `kind ∈ { client_api_key, dashboard_session, management_key, anonymous }`. Downstream handlers can read it via `assertAuth(request, "CLIENT_API")` in `src/server/authz/assertAuth.ts` instead of re-running auth logic.
|
||||
|
||||
## Public Routes List
|
||||
|
||||
`src/shared/constants/publicApiRoutes.ts` is the explicit allowlist:
|
||||
|
||||
```ts
|
||||
PUBLIC_API_ROUTE_PREFIXES = [
|
||||
"/api/auth/login",
|
||||
"/api/auth/logout",
|
||||
"/api/auth/status",
|
||||
"/api/init",
|
||||
"/api/v1/", // treated as CLIENT_API in classify, not as "no-auth public"
|
||||
"/api/cloud/",
|
||||
"/api/sync/bundle",
|
||||
"/api/oauth/",
|
||||
];
|
||||
|
||||
PUBLIC_READONLY_API_ROUTE_PREFIXES = ["/api/monitoring/health", "/api/settings/require-login"];
|
||||
|
||||
PUBLIC_READONLY_METHODS = new Set(["GET", "HEAD", "OPTIONS"]);
|
||||
```
|
||||
|
||||
Read-only prefixes are public **only** for safe methods. Note: `classifyRoute()` excludes `/api/v1/*` from the PUBLIC fall-through — those are always `CLIENT_API` so the Bearer-key policy still applies.
|
||||
|
||||
## Adding a New Route
|
||||
|
||||
### Pattern 1 — Public client API endpoint (Bearer-auth)
|
||||
|
||||
Routes under `/api/v1/` are classified `CLIENT_API` automatically. The middleware enforces the Bearer check; route handlers don't need to redo it but can read the subject if useful.
|
||||
|
||||
```typescript
|
||||
// src/app/api/v1/your-route/route.ts
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { assertAuth } from "@/server/authz/assertAuth";
|
||||
|
||||
export async function POST(req: NextRequest) {
|
||||
const subject = assertAuth(req, "CLIENT_API");
|
||||
// subject.kind === "client_api_key" | "anonymous" | "dashboard_session"
|
||||
// ... handler logic
|
||||
}
|
||||
```
|
||||
|
||||
### Pattern 2 — Management endpoint (session or Bearer + manage)
|
||||
|
||||
Use `requireManagementAuth()` from `src/lib/api/requireManagementAuth.ts`:
|
||||
|
||||
```typescript
|
||||
import { requireManagementAuth } from "@/lib/api/requireManagementAuth";
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const rejection = await requireManagementAuth(request);
|
||||
if (rejection) return rejection;
|
||||
// ... handler logic
|
||||
}
|
||||
```
|
||||
|
||||
`requireManagementAuth()` returns `null` on success or a JSON error `Response`:
|
||||
|
||||
- 401 `AUTH_001` "Authentication required" — no credentials at all
|
||||
- 403 — invalid Bearer **or** Bearer present but key lacks the `manage` / `admin` scope
|
||||
|
||||
`hasManageScope(scopes)` returns true for `"manage"` or `"admin"`.
|
||||
|
||||
### Pattern 3 — Adding to the public allowlist
|
||||
|
||||
Add the prefix to `PUBLIC_API_ROUTE_PREFIXES` (or `PUBLIC_READONLY_API_ROUTE_PREFIXES` for GET-only). Update unit tests at `tests/unit/public-api-routes.test.ts` and `tests/unit/authz/classify.test.ts`.
|
||||
|
||||
## Scopes
|
||||
|
||||
API keys carry a `scopes` array (stored as JSON in `api_keys.scopes`, see `src/lib/db/apiKeys.ts`).
|
||||
|
||||
### Management scope
|
||||
|
||||
- `manage` / `admin` — grants the key access to management API endpoints when sent as Bearer.
|
||||
|
||||
### MCP scopes (`src/shared/constants/mcpScopes.ts`)
|
||||
|
||||
Each MCP tool requires specific scopes via `MCP_TOOL_SCOPES`. Full list (`MCP_SCOPE_LIST`):
|
||||
|
||||
```
|
||||
read:health, read:combos, write:combos, read:quota, read:usage,
|
||||
read:models, execute:completions, execute:search, write:budget,
|
||||
write:resilience, pricing:write, read:cache, write:cache,
|
||||
read:compression, write:compression, read:proxies
|
||||
```
|
||||
|
||||
Preset bundles (`MCP_SCOPE_PRESETS`): `readonly`, `full`, `monitor`, `agent`. Use `hasRequiredScopes(granted, toolName)` and `getMissingScopes()` for enforcement inside MCP handlers.
|
||||
|
||||
## Auth Required Toggle
|
||||
|
||||
`isAuthRequired()` in `src/shared/utils/apiAuth.ts` decides whether **any** auth is enforced for a request:
|
||||
|
||||
- `settings.requireLogin === false` → auth is globally disabled.
|
||||
- No password configured **and** no `INITIAL_PASSWORD` env var → bootstrap mode allows the onboarding wizard and loopback requests, but exposed network requests still need credentials.
|
||||
- Any DB error → fails closed (secure-by-default).
|
||||
|
||||
## Breaking Change — v3.8.0
|
||||
|
||||
The `/api/v1/agents/tasks/*` and `/api/resilience/model-cooldowns` endpoints **now require management auth** (commit `588a0333`). Clients previously sending a normal API key without the `manage` scope receive `403`. Migration: either issue the key the `manage` scope in the API Manager dashboard, or use a logged-in dashboard session.
|
||||
|
||||
## Testing
|
||||
|
||||
- Unit tests: `tests/unit/authz/` — `classify.test.ts`, `pipeline.test.ts`, `client-api-policy.test.ts`, `management-policy.test.ts`, `public-policy.test.ts`.
|
||||
- Public allowlist: `tests/unit/public-api-routes.test.ts`.
|
||||
- Run focused: `node --import tsx/esm --test tests/unit/authz/classify.test.ts`.
|
||||
|
||||
## Debugging
|
||||
|
||||
The pipeline always stamps responses with:
|
||||
|
||||
```
|
||||
x-request-id: <correlation id, echoed in error bodies>
|
||||
x-omniroute-route-class: PUBLIC | CLIENT_API | MANAGEMENT
|
||||
```
|
||||
|
||||
For authenticated requests the upstream (handler-side) request headers also include:
|
||||
|
||||
```
|
||||
x-omniroute-auth-kind: client_api_key | dashboard_session | management_key | anonymous
|
||||
x-omniroute-auth-id: key_<last-4> | "dashboard" | "anonymous"
|
||||
x-omniroute-auth-label: (optional)
|
||||
x-omniroute-auth-scopes: comma-separated list
|
||||
```
|
||||
|
||||
Use `assertAuth(req, expectedClass)` inside handlers — it throws `AuthzAssertionError` with code `AUTHZ_NOT_INITIALIZED` if the middleware was bypassed (helpful for catching configuration regressions in tests).
|
||||
|
||||
## See Also
|
||||
|
||||
- [API_REFERENCE.md](../reference/API_REFERENCE.md) — auth marker per endpoint
|
||||
- [COMPLIANCE.md](../security/COMPLIANCE.md) — audit log for auth events
|
||||
- [MCP-SERVER.md](../frameworks/MCP-SERVER.md) — MCP scope enforcement details
|
||||
- Source: `src/server/authz/`, `src/lib/api/requireManagementAuth.ts`
|
||||
806
docs/architecture/CODEBASE_DOCUMENTATION.md
Normal file
806
docs/architecture/CODEBASE_DOCUMENTATION.md
Normal file
@@ -0,0 +1,806 @@
|
||||
---
|
||||
title: "OmniRoute Codebase Documentation"
|
||||
version: 3.8.0
|
||||
lastUpdated: 2026-05-13
|
||||
---
|
||||
|
||||
# OmniRoute Codebase Documentation
|
||||
|
||||
> **Version:** v3.8.0
|
||||
> **Last updated:** 2026-05-13
|
||||
> **Audience:** Engineers contributing to OmniRoute or building integrations on top of it.
|
||||
>
|
||||
> For high-level architecture diagrams and the reasoning behind each subsystem, read
|
||||
> [ARCHITECTURE.md](./ARCHITECTURE.md). For deep dives on individual subsystems
|
||||
> (Auto Combo, MCP server, A2A server, Skills, Memory, Cloud Agents, Resilience,
|
||||
> Compression, etc.) see their dedicated files in this `docs/` directory.
|
||||
|
||||
This file describes **what exists in the repository today** so that a new engineer
|
||||
can navigate the tree, understand the runtime layering, and know where to add code
|
||||
without inventing new modules.
|
||||
|
||||
---
|
||||
|
||||
## 1. Tech Stack
|
||||
|
||||
| Concern | Choice |
|
||||
| ------------- | ------------------------------------------------------------------------------------------------------------------------ | --- | ------------- | --- | ------------------------------------ |
|
||||
| Web framework | **Next.js 16** (App Router, standalone output, no global middleware) |
|
||||
| Language | **TypeScript 5.9+** — target `ES2022`, `module: esnext`, `moduleResolution: bundler`, `strict: false` |
|
||||
| Runtime | **Node.js** `>=20.20.2 <21 | | >=22.22.2 <23 | | >=24.0.0 <27`(enforced via`engines`) |
|
||||
| Database | **SQLite** via `better-sqlite3` (singleton, WAL journaling) |
|
||||
| Desktop | **Electron 41** + `electron-builder` 26.10 (separate workspace at `electron/`) |
|
||||
| Tests | **Node native test runner** (unit/integration), **Vitest** (MCP, autoCombo, cache), **Playwright** (e2e + protocols-e2e) |
|
||||
| Build | Next.js standalone via `scripts/build/build-next-isolated.mjs` |
|
||||
| Lint/format | ESLint flat config + Prettier (`lint-staged` via Husky pre-commit) |
|
||||
| Module system | ESM everywhere (`"type": "module"`) |
|
||||
| Workspaces | npm workspace — `open-sse` is the only sub-workspace |
|
||||
|
||||
Path aliases (`tsconfig.json`):
|
||||
|
||||
- `@/*` → `src/*`
|
||||
- `@omniroute/open-sse` → `open-sse/index.ts`
|
||||
- `@omniroute/open-sse/*` → `open-sse/*`
|
||||
|
||||
Default HTTP port: **`20128`** (API and dashboard share the same process). Data
|
||||
directory is `DATA_DIR` env var, defaulting to `~/.omniroute/`.
|
||||
|
||||
---
|
||||
|
||||
## 2. Repository Layout
|
||||
|
||||
```
|
||||
OmniRoute/
|
||||
├── src/ Next.js application (App Router, libs, domain, server, shared)
|
||||
├── open-sse/ Streaming engine workspace (@omniroute/open-sse)
|
||||
├── electron/ Desktop wrapper (Electron 41 main + preload)
|
||||
├── bin/ CLI entry points (omniroute, reset-password)
|
||||
├── tests/ Unit, integration, e2e, protocols-e2e, translator, security, fixtures
|
||||
├── scripts/ Build, sync, check, migration, and runtime helper scripts
|
||||
├── docs/ Public documentation (this directory)
|
||||
├── public/ Static assets, PWA manifest, service worker
|
||||
├── config/ Runtime config samples
|
||||
├── images/ Marketing/screenshot assets
|
||||
├── _ideia/, _references/, _mono_repo/, _tasks/ Internal scratch / planning (not shipped)
|
||||
├── CLAUDE.md Repo rules for Claude Code
|
||||
├── AGENTS.md Deeper architecture reference for agents
|
||||
├── package.json v3.8.0, workspace root
|
||||
└── tsconfig.json Path aliases + core compiler options
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. `src/` — Next.js Application
|
||||
|
||||
```
|
||||
src/
|
||||
├── app/ App Router pages + API routes
|
||||
├── lib/ Core libraries (DB, auth, OAuth, skills, memory, …)
|
||||
├── domain/ Pure domain layer (policy, fallback, cost, lockout, …)
|
||||
├── server/ Server-only modules (authz, cors, auth)
|
||||
├── shared/ Types, constants, validation, contracts, utils (cross-boundary safe)
|
||||
├── mitm/ Man-in-the-middle proxy helpers for CLI integration
|
||||
├── models/ Local model metadata / aliasing
|
||||
├── sse/ Legacy SSE handlers that still live under src/ (not open-sse/)
|
||||
├── store/ Client-side state stores
|
||||
├── middleware/ Route-level middleware utilities (not Next.js global middleware)
|
||||
├── scripts/ In-tree scripts importable by app code
|
||||
├── types/ Ambient and shared TS types
|
||||
├── i18n/ Locale bundles
|
||||
├── instrumentation.ts Next.js instrumentation hook
|
||||
├── instrumentation-node.ts
|
||||
├── server-init.ts Process-level bootstrap (env, DB, jobs, sync)
|
||||
└── proxy.ts Top-level proxy bootstrap helper
|
||||
```
|
||||
|
||||
### 3.1 `src/app/` — App Router
|
||||
|
||||
The App Router exposes both the dashboard UI and the public/management HTTP API.
|
||||
There is **no global middleware** — interception is done per-route.
|
||||
|
||||
Top-level segments under `src/app/`:
|
||||
|
||||
| Path | Purpose |
|
||||
| ----------------------------------------------------------------------------- | ----------------------------------------- |
|
||||
| `api/` | All HTTP API routes (see breakdown below) |
|
||||
| `a2a/` | A2A JSON-RPC 2.0 endpoint (`POST /a2a`) |
|
||||
| `.well-known/agent.json/` | A2A Agent Card discovery document |
|
||||
| `(dashboard)/` | Dashboard UI (route group, no URL prefix) |
|
||||
| `auth/`, `login/`, `forgot-password/`, `callback/` | Auth flows |
|
||||
| `landing/` | Marketing/landing page |
|
||||
| `docs/` | Embedded API docs viewer |
|
||||
| `status/`, `maintenance/`, `offline/` | Operational pages |
|
||||
| `privacy/`, `terms/` | Legal pages |
|
||||
| `400/`, `401/`, `403/`, `408/`, `429/`, `500/`, `502/`, `503/` | Static error pages |
|
||||
| `error.tsx`, `global-error.tsx`, `not-found.tsx`, `forbidden/`, `loading.tsx` | Framework error/loading boundaries |
|
||||
| `layout.tsx`, `page.tsx`, `globals.css`, `manifest.ts` | Root shell |
|
||||
|
||||
#### 3.1.1 `src/app/(dashboard)/dashboard/` — UI pages
|
||||
|
||||
`agents`, `analytics`, `api-manager`, `audit`, `auto-combo`, `batch`, `cache`,
|
||||
`changelog`, `cli-tools`, `cloud-agents`, `combos`, `compression`, `context`,
|
||||
`costs`, `endpoint`, `health`, `limits`, `logs`, `memory`, `onboarding`,
|
||||
`playground`, `providers`, `search-tools`, `settings`, `skills`, `system`,
|
||||
`translator`, `usage`, `webhooks`, plus root `page.tsx`, `HomePageClient.tsx`,
|
||||
`BootstrapBanner.tsx`.
|
||||
|
||||
#### 3.1.2 `src/app/api/` — Top-level API groups
|
||||
|
||||
```
|
||||
src/app/api/
|
||||
├── a2a/{status, tasks}
|
||||
├── acp/
|
||||
├── admin/
|
||||
├── analytics/
|
||||
├── assess/
|
||||
├── auth/
|
||||
├── batches/
|
||||
├── cache/
|
||||
├── cli-tools/
|
||||
├── cloud/{codex-responses-ws}
|
||||
├── combos/
|
||||
├── compliance/
|
||||
├── compression/
|
||||
├── context/
|
||||
├── db/, db-backups/
|
||||
├── evals/
|
||||
├── fallback/
|
||||
├── files/
|
||||
├── health/
|
||||
├── init/
|
||||
├── internal/{concurrency}
|
||||
├── keys/
|
||||
├── logs/
|
||||
├── mcp/{audit, sse, status, stream, tools}
|
||||
├── memory/{health, [id]/, route.ts}
|
||||
├── model-combo-mappings/
|
||||
├── models/
|
||||
├── monitoring/
|
||||
├── oauth/
|
||||
├── openapi/
|
||||
├── policies/
|
||||
├── pricing/
|
||||
├── provider-metrics/, provider-models/, provider-nodes/
|
||||
├── providers/
|
||||
├── rate-limit/, rate-limits/
|
||||
├── resilience/
|
||||
├── restart/, shutdown/
|
||||
├── search/
|
||||
├── sessions/
|
||||
├── settings/
|
||||
├── skills/{executions, [id], install, marketplace, route.ts, skillssh}
|
||||
├── storage/
|
||||
├── sync/, synced-available-models/
|
||||
├── system/
|
||||
├── tags/
|
||||
├── telemetry/
|
||||
├── token-health/
|
||||
├── translator/
|
||||
├── tunnels/
|
||||
├── upstream-proxy/
|
||||
├── usage/
|
||||
├── v1/ OpenAI-compatible public API
|
||||
├── v1beta/ Gemini-style compat
|
||||
├── version-manager/
|
||||
└── webhooks/
|
||||
```
|
||||
|
||||
#### 3.1.3 `src/app/api/v1/` — OpenAI-compatible public API
|
||||
|
||||
```
|
||||
v1/
|
||||
├── accounts/[id]/ account lookup
|
||||
├── agents/tasks/[id]/, agents/tasks/ A2A-flavored task endpoints
|
||||
├── api/ internal API helpers exposed under v1/api
|
||||
├── audio/{speech, transcriptions}/ TTS + STT
|
||||
├── batches/[id]/{cancel}, batches/ OpenAI Batches API
|
||||
├── chat/completions/ Chat Completions (the main endpoint)
|
||||
├── chatgpt-web/ ChatGPT-Web compat
|
||||
├── completions/ Legacy text completions
|
||||
├── embeddings/ Embeddings
|
||||
├── files/[id]/, files/ Files API
|
||||
├── _helpers/ Shared route helpers (no public URL)
|
||||
├── images/{edits, generations}/ Image gen + edit
|
||||
├── issues/ Triage helper endpoints
|
||||
├── management/{proxies}/ Management-scoped routes inside v1
|
||||
├── messages/{count_tokens}/ Anthropic-style messages compat
|
||||
├── models/ Model listing (`route.ts`, `catalog.ts`)
|
||||
├── moderations/ Moderation
|
||||
├── music/ Music gen
|
||||
├── providers/[provider]/ Per-provider operations
|
||||
├── quotas/{check} Quota probes
|
||||
├── registered-keys/ Registered key admin
|
||||
├── rerank/ Reranking
|
||||
├── responses/[...path]/ OpenAI Responses API (catch-all)
|
||||
├── search/ Web search
|
||||
├── videos/ Video gen
|
||||
├── ws/ WebSocket bridge
|
||||
└── route.ts Index handler
|
||||
```
|
||||
|
||||
Every route file follows the same pattern:
|
||||
|
||||
```
|
||||
Route → CORS preflight → Zod body validation → optional auth
|
||||
→ API key policy enforcement → handler delegation (open-sse)
|
||||
```
|
||||
|
||||
`v1beta/` is the Gemini-style compat surface (a thin wrapper that translates into
|
||||
the same `open-sse/handlers/` pipeline).
|
||||
|
||||
### 3.2 `src/lib/` — Core libraries
|
||||
|
||||
Always import data, sync, OAuth, skill, memory, etc. through these modules. The
|
||||
table groups the actual directories and notable top-level files.
|
||||
|
||||
| Module | Purpose |
|
||||
| ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `a2a/` | A2A protocol server: `taskManager.ts`, `streaming.ts`, `taskExecution.ts`, `routingLogger.ts`, `skills/` (5 skills: cost analysis, health report, provider discovery, quota management, smart routing) |
|
||||
| `acp/` | Agent-Control-Protocol: `index.ts`, `manager.ts`, `registry.ts` |
|
||||
| `api/` | Internal API helpers: `requireManagementAuth.ts`, `requireCliToolsAuth.ts`, `errorResponse.ts` |
|
||||
| `auth/` | `managementPassword.ts` (password reset / hashing) |
|
||||
| `batches/` | OpenAI Batches API service (`service.ts`) |
|
||||
| `catalog/` | OpenRouter catalog sync (`openrouterCatalog.ts`) |
|
||||
| `cloudAgent/` | Cloud agent registry: `api.ts`, `baseAgent.ts`, `db.ts`, `index.ts`, `registry.ts`, `types.ts`, `agents/{codex, devin, jules}.ts` |
|
||||
| `combos/` | Combo resolution helpers |
|
||||
| `compliance/` | Audit + provider audit: `index.ts`, `providerAudit.ts` |
|
||||
| `config/` | Runtime config glue |
|
||||
| `db/` | SQLite domain modules (see §3.2.1) |
|
||||
| `display/` | UI/display helpers used by API responses |
|
||||
| `embeddings/` | Embedding service registry |
|
||||
| `env/` | Env loading + introspection |
|
||||
| `evals/` | Eval runtime |
|
||||
| `guardrails/` | `piiMasker.ts`, `promptInjection.ts`, `visionBridge.ts`, `visionBridgeHelpers.ts`, `registry.ts`, `base.ts` |
|
||||
| `jobs/` | Background jobs (`autoUpdate.ts`, …) |
|
||||
| `memory/` | Persistent memory: `store.ts`, `cache.ts`, `retrieval.ts`, `summarization.ts`, `extraction.ts`, `injection.ts`, `qdrant.ts`, `settings.ts`, `verify.ts`, `schemas.ts`, `types.ts` |
|
||||
| `monitoring/` | `observability.ts` |
|
||||
| `oauth/` | OAuth providers (14): `antigravity`, `claude`, `cline`, `codex`, `cursor`, `gemini`, `github`, `gitlab-duo`, `kilocode`, `kimi-coding`, `kiro`, `qoder`, `qwen`, `windsurf` plus `services/`, `utils/{pkce, server, banner, codexAuthFile, ui}`, `constants/oauth.ts` |
|
||||
| `plugins/` | Plugin loader (`index.ts`) |
|
||||
| `promptCache/` | `prefixAnalyzer.ts`, `index.ts` |
|
||||
| `providerModels/` | Managed model lifecycle: `modelDiscovery.ts`, `managedModelImport.ts`, `managedAvailableModels.ts`, `cursorAgent.ts` |
|
||||
| `providers/` | Provider helpers: `catalog.ts`, `validation.ts`, `imageValidation.ts`, `claudeExtraUsage.ts`, `codexConnectionDefaults.ts`, `codexFastTier.ts`, `webCookieAuth.ts`, `managedAvailableModels.ts`, `requestDefaults.ts` |
|
||||
| `resilience/` | `settings.ts` — settings for circuit breaker, cooldown, lockout |
|
||||
| `runtime/` | Runtime feature detection |
|
||||
| `search/` | `executeWebSearch.ts` |
|
||||
| `skills/` | Skill framework: `registry.ts`, `executor.ts`, `interception.ts`, `injection.ts`, `sandbox.ts`, `custom.ts`, `hybrid.ts`, `builtins.ts`, `a2a.ts`, `providerSettings.ts`, `schemas.ts`, `skillssh.ts`, `types.ts`, plus `builtin/browser.ts` |
|
||||
| `spend/` | `batchWriter.ts` (write-behind buffer) |
|
||||
| `sync/` | `bundle.ts`, `tokens.ts` (Cloud Sync) |
|
||||
| `system/` | System-level helpers |
|
||||
| `translator/` | Top-level translator glue (delegates into `open-sse/translator/`) |
|
||||
| `usage/` | Usage accounting: `costCalculator.ts`, `tokenAccounting.ts`, `usageHistory.ts`, `aggregateHistory.ts`, `usageStats.ts`, `callLogs.ts`, `callLogArtifacts.ts`, `fetcher.ts`, `providerLimits.ts`, `migrations.ts` |
|
||||
| `versionManager/` | Auto-update + version manifest |
|
||||
| `ws/` | WebSocket bridge |
|
||||
| `zed-oauth/` | Zed editor OAuth flow |
|
||||
|
||||
Top-level files in `src/lib/`:
|
||||
|
||||
- `localDb.ts` — re-export layer only. **Never** add logic here.
|
||||
- `proxyHealth.ts`, `proxyLogger.ts`, `tokenHealthCheck.ts`, `localHealthCheck.ts`
|
||||
- `oneproxyRotator.ts`, `oneproxySync.ts`
|
||||
- `apiBridgeServer.ts`, `cacheLayer.ts`, `semanticCache.ts`, `settingsCache.ts`
|
||||
- `cloudSync.ts`, `initCloudSync.ts`
|
||||
- `cloudflaredTunnel.ts`, `ngrokTunnel.ts`, `tailscaleTunnel.ts`
|
||||
- `consoleInterceptor.ts`, `container.ts`, `gracefulShutdown.ts`, `idempotencyLayer.ts`
|
||||
- `ipUtils.ts`, `logEnv.ts`, `logPayloads.ts`, `logRotation.ts`
|
||||
- `modelAliasSeed.ts`, `modelCapabilities.ts`, `modelMetadataRegistry.ts`, `modelsDevSync.ts`
|
||||
- `piiSanitizer.ts`, `pricingSync.ts`
|
||||
- `apiKeyExposure.ts`, `cacheControlSettings.ts`, `dataPaths.ts`, `toolPolicy.ts`
|
||||
- `translatorEvents.ts`, `usageDb.ts`, `usageAnalytics.ts`, `webhookDispatcher.ts`
|
||||
|
||||
#### 3.2.1 `src/lib/db/`
|
||||
|
||||
Singleton SQLite database (`getDbInstance()` in `core.ts`, WAL journaling).
|
||||
**Never write raw SQL in routes or handlers** — go through these modules.
|
||||
|
||||

|
||||
|
||||
> Source: [diagrams/db-schema-overview.mmd](../diagrams/db-schema-overview.mmd)
|
||||
|
||||
Domain modules (each owns one or more tables): `apiKeys.ts`, `backup.ts`,
|
||||
`batches.ts`, `cleanup.ts`, `cliToolState.ts`, `combos.ts`,
|
||||
`commandCodeAuth.ts`, `compression.ts`, `compressionAnalytics.ts`,
|
||||
`compressionCacheStats.ts`, `compressionCombos.ts`, `compressionScheduler.ts`,
|
||||
`contextHandoffs.ts`, `core.ts`, `creditBalance.ts`, `databaseSettings.ts`,
|
||||
`detailedLogs.ts`, `domainState.ts`, `encryption.ts`, `evals.ts`, `files.ts`,
|
||||
`healthCheck.ts`, `jsonMigration.ts`, `migrationRunner.ts`,
|
||||
`modelComboMappings.ts`, `models.ts`, `oneproxy.ts`, `prompts.ts`,
|
||||
`providers.ts`, `providerLimits.ts`, `proxies.ts`, `quotaSnapshots.ts`,
|
||||
`readCache.ts`, `reasoningCache.ts`, `registeredKeys.ts`, `secrets.ts`,
|
||||
`sessionAccountAffinity.ts`, `settings.ts`, `stateReset.ts`, `stats.ts`,
|
||||
`syncTokens.ts`, `tierConfig.ts`, `upstreamProxy.ts`, `versionManager.ts`,
|
||||
`webhooks.ts`.
|
||||
|
||||
`migrations/` holds 55 versioned `.sql` files (idempotent, transactional) and is
|
||||
executed by `migrationRunner.ts` at boot.
|
||||
|
||||
Tables created across the migrations (52 total):
|
||||
|
||||
`a`, `account_key_limits`, `api_keys`, `batches`, `call_logs`,
|
||||
`combo_adaptation_state`, `combos`, `command_code_auth_sessions`,
|
||||
`compression_analytics`, `compression_cache_stats`,
|
||||
`compression_combo_assignments`, `compression_combos`, `context_handoffs`,
|
||||
`daily_usage_summary`, `db_meta`, `domain_budgets`, `domain_circuit_breakers`,
|
||||
`domain_cost_history`, `domain_fallback_chains`, `domain_lockout_state`,
|
||||
`eval_cases`, `eval_runs`, `eval_suites`, `files`, `hourly_usage_summary`,
|
||||
`key_value`, `mcp_tool_audit`, `memories`, `model_combo_mappings`,
|
||||
`provider_connections`, `provider_key_limits`, `provider_nodes`,
|
||||
`proxy_assignments`, `proxy_logs`, `proxy_registry`, `quota_snapshots`,
|
||||
`reasoning_cache`, `registered_keys`, `request_detail_logs`,
|
||||
`routing_decisions`, `semantic_cache`, `session_account_affinity`,
|
||||
`skill_executions`, `skills`, `sync_tokens`, `tier_assignments`,
|
||||
`tier_config`, `upstream_proxy_config`, `usage_history`, `version_manager`,
|
||||
`webhooks` (plus FTS5 virtual tables for memory search).
|
||||
|
||||
### 3.3 `src/domain/` — Domain layer
|
||||
|
||||
Pure business logic, no I/O. Imported by routes and handlers.
|
||||
|
||||
| File | Purpose |
|
||||
| ------------------------------------------ | ------------------------------------------------- |
|
||||
| `policyEngine.ts` | Top-level policy resolver |
|
||||
| `fallbackPolicy.ts` | Fallback decision tree |
|
||||
| `costRules.ts` | Cost calculation rules |
|
||||
| `lockoutPolicy.ts` | Model lockout decisions |
|
||||
| `tagRouter.ts` | Tag-based routing |
|
||||
| `comboResolver.ts` | Combo resolution from request → target list |
|
||||
| `connectionModelRules.ts` | Per-connection model filters |
|
||||
| `modelAvailability.ts` | Model availability check |
|
||||
| `degradation.ts` | Degraded-mode transitions |
|
||||
| `providerExpiration.ts` | Expired account/key detection |
|
||||
| `quotaCache.ts` | Cached quota decisions |
|
||||
| `responses.ts`, `omnirouteResponseMeta.ts` | Response shape helpers |
|
||||
| `configAudit.ts` | Config change audit |
|
||||
| `assessment/` | Model assessment (per RFC, partially implemented) |
|
||||
| `types.ts` | Shared domain types |
|
||||
|
||||
### 3.4 `src/server/` — Server-only
|
||||
|
||||
Cannot be imported from client components.
|
||||
|
||||
```
|
||||
server/
|
||||
├── auth/loginGuard.ts
|
||||
├── authz/
|
||||
│ ├── classify.ts Classifies routes as public vs management
|
||||
│ ├── assertAuth.ts Assertion helper
|
||||
│ ├── context.ts Per-request authz context
|
||||
│ ├── headers.ts
|
||||
│ ├── pipeline.ts Authz pipeline
|
||||
│ ├── policies/ Concrete policies
|
||||
│ └── types.ts
|
||||
└── cors/origins.ts CORS origin allowlist
|
||||
```
|
||||
|
||||
### 3.5 `src/shared/` — Safe-to-share
|
||||
|
||||
Split into focused subdirectories:
|
||||
|
||||
- `constants/` — `providers.ts` (Zod-validated provider catalog), `models.ts`,
|
||||
`modelSpecs.ts`, `modelCompat.ts`, `pricing.ts`, `cliTools.ts`,
|
||||
`cliCompatProviders.ts`, `routingStrategies.ts`, `comboConfigMode.ts`,
|
||||
`headers.ts`, `upstreamHeaders.ts` (denylist), `mcpScopes.ts`,
|
||||
`errorCodes.ts`, `publicApiRoutes.ts`, `batch.ts`, `batchEndpoints.ts`,
|
||||
`bodySize.ts`, `colors.ts`, `appConfig.ts`, `config.ts`,
|
||||
`sidebarVisibility.ts`, `visionBridgeDefaults.ts`.
|
||||
- `validation/` — `schemas.ts` (~80 Zod schemas), `compressionConfigSchemas.ts`,
|
||||
`oneproxySchemas.ts`, `providerSchema.ts`, `settingsSchemas.ts`, `helpers.ts`.
|
||||
- `contracts/` — public API contracts shipped to npm.
|
||||
- `types/` — shared TS types.
|
||||
- `utils/` — `circuitBreaker.ts`, `apiAuth.ts`, `apiKey.ts`, `apiKeyPolicy.ts`,
|
||||
`apiResponse.ts`, `api.ts`, `classify429.ts`, `cliCompat.ts`, `clipboard.ts`,
|
||||
`cloud.ts`, `cn.ts`, `cors.ts`, `costEstimator.ts`, `featureFlags.ts`,
|
||||
`fetchTimeout.ts`, `formatting.ts`, `inputSanitizer.ts`, `logger.ts`,
|
||||
`machine.ts`, `machineId.ts`, `maskEmail.ts`, `modelCatalogSearch.ts`,
|
||||
`nodeRuntimeSupport.ts`, `parseApiKeys.ts`, `providerHints.ts`,
|
||||
`providerModelAliases.ts`, `rateLimiter.ts`, `releaseNotes.ts`,
|
||||
`a11yAudit.ts`, plus dashboard hooks/components under `services/`, `network/`,
|
||||
`middleware/`, `schemas/`, `hooks/`, `components/`.
|
||||
|
||||
---
|
||||
|
||||
## 4. `open-sse/` — Streaming engine workspace
|
||||
|
||||
Separate npm workspace published as `@omniroute/open-sse`. Owns request
|
||||
processing, executors, translators, services, transformer, and the MCP server.
|
||||
|
||||
```
|
||||
open-sse/
|
||||
├── index.ts Public exports
|
||||
├── package.json Workspace manifest
|
||||
├── tsconfig.json
|
||||
├── types.d.ts
|
||||
├── config/ Provider registries, header profiles, identity, …
|
||||
├── handlers/ Request handlers (chat, embeddings, audio, image, …)
|
||||
├── executors/ 31 provider-specific HTTP executors
|
||||
├── translator/ Format conversion (OpenAI ↔ Claude ↔ Gemini ↔ Cursor ↔ Kiro)
|
||||
├── transformer/ Responses API ↔ Chat Completions stream transformer
|
||||
├── services/ 80+ service modules (combos, fallback, quotas, identity, …)
|
||||
├── utils/ Streaming helpers, TLS client, AWS SigV4, proxy fetch, …
|
||||
└── mcp-server/ MCP server (3 transports, 13 scopes, 42 tools)
|
||||
```
|
||||
|
||||
### 4.1 `open-sse/handlers/`
|
||||
|
||||
| Handler | Purpose |
|
||||
| ----------------------- | ------------------------------------------------------------------------ |
|
||||
| `chatCore.ts` | Main chat pipeline (cache, rate limit, combo routing, executor dispatch) |
|
||||
| `responsesHandler.ts` | OpenAI Responses API entry point |
|
||||
| `embeddings.ts` | Embeddings |
|
||||
| `imageGeneration.ts` | Image generation |
|
||||
| `audioSpeech.ts` | Text-to-speech |
|
||||
| `audioTranscription.ts` | Speech-to-text |
|
||||
| `videoGeneration.ts` | Video generation |
|
||||
| `musicGeneration.ts` | Music generation |
|
||||
| `rerank.ts` | Reranking |
|
||||
| `moderations.ts` | Moderation |
|
||||
| `search.ts` | Web search |
|
||||
| `sseParser.ts` | SSE event parser |
|
||||
| `usageExtractor.ts` | Pull token counts out of upstream streams |
|
||||
| `responseSanitizer.ts` | Strip provider-specific noise |
|
||||
| `responseTranslator.ts` | Glue between provider response and translator layer |
|
||||
|
||||
### 4.2 `open-sse/executors/`
|
||||
|
||||
31 provider executors, each extending `BaseExecutor` (`base.ts`):
|
||||
|
||||
`antigravity`, `azure-openai`, `blackbox-web`, `chatgpt-web`, `cliproxyapi`,
|
||||
`cloudflare-ai`, `codex`, `commandCode`, `cursor`, `default`, `devin-cli`,
|
||||
`gemini-cli`, `github`, `gitlab`, `glm`, `grok-web`, `kie`, `kiro`,
|
||||
`muse-spark-web`, `nlpcloud`, `opencode`, `perplexity-web`, `petals`,
|
||||
`pollinations`, `puter`, `qoder`, `vertex`, `windsurf`, plus `claudeIdentity.ts`
|
||||
(shared identity helper) and `index.ts` (registry).
|
||||
|
||||
> Note: providers not listed here are served by `default.ts` using the generic
|
||||
> OpenAI-compatible executor. The full provider catalog (177+ entries) lives in
|
||||
> `src/shared/constants/providers.ts`.
|
||||
|
||||
### 4.3 `open-sse/translator/`
|
||||
|
||||
Hub-and-spoke translation (OpenAI is the hub).
|
||||
|
||||
- **9 request translators** (`translator/request/`):
|
||||
`antigravity-to-openai`, `claude-to-gemini`, `claude-to-openai`,
|
||||
`gemini-to-openai`, `openai-responses`, `openai-to-claude`,
|
||||
`openai-to-cursor`, `openai-to-gemini`, `openai-to-kiro`.
|
||||
- **8 response translators** (`translator/response/`):
|
||||
`claude-to-openai`, `cursor-to-openai`, `gemini-to-claude`, `gemini-to-openai`,
|
||||
`kiro-to-openai`, `openai-responses`, `openai-to-antigravity`,
|
||||
`openai-to-claude`.
|
||||
- **9 helpers** (`translator/helpers/`):
|
||||
`claudeHelper`, `geminiHelper`, `geminiToolsSanitizer`, `maxTokensHelper`,
|
||||
`openaiHelper`, `responsesApiHelper`, `schemaCoercion`, `toolCallHelper`, plus
|
||||
helper tests.
|
||||
- **Image helpers** (`translator/image/sizeMapper.ts`).
|
||||
- Top-level: `bootstrap.ts`, `formats.ts`, `registry.ts`, `index.ts`.
|
||||
|
||||
### 4.4 `open-sse/transformer/`
|
||||
|
||||
- `responsesTransformer.ts` — `TransformStream`-based Responses API ↔ Chat
|
||||
Completions converter (used by the `responses/` route catch-all).
|
||||
|
||||
### 4.5 `open-sse/services/`
|
||||
|
||||
Highlights (full list under `open-sse/services/`):
|
||||
|
||||
| Concern | Files |
|
||||
| ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| Combo routing | `combo.ts` (14 strategies), `comboConfig.ts`, `comboMetrics.ts`, `comboManifestMetrics.ts`, `comboAgentMiddleware.ts` |
|
||||
| Auto Combo engine | `autoCombo/` — `engine.ts`, `scoring.ts`, `taskFitness.ts`, `virtualFactory.ts`, `modePacks.ts`, `autoPrefix.ts`, `persistence.ts`, `providerDiversity.ts`, `providerRegistryAccessor.ts`, `routerStrategy.ts`, `selfHealing.ts`, `index.ts` |
|
||||
| Resilience | `accountFallback.ts` (cooldown + lockout), `errorClassifier.ts`, `emergencyFallback.ts`, `rateLimitManager.ts`, `rateLimitSemaphore.ts`, `accountSemaphore.ts`, `accountSelector.ts` |
|
||||
| Quotas | `quotaMonitor.ts`, `quotaPreflight.ts`, `bailianQuotaFetcher.ts`, `codexQuotaFetcher.ts`, `deepseekQuotaFetcher.ts`, `crofUsageFetcher.ts`, `antigravityCredits.ts` |
|
||||
| Provider-specific shaping | `claudeCodeCCH.ts`, `claudeCodeCompatible.ts`, `claudeCodeConstraints.ts`, `claudeCodeExtraRemap.ts`, `claudeCodeFingerprint.ts`, `claudeCodeObfuscation.ts`, `claudeCodeToolRemapper.ts`, `cloudCodeHeaders.ts`, `cloudCodeThinking.ts`, `geminiCliHeaders.ts`, `geminiThoughtSignatureStore.ts`, `gigachatAuth.ts`, `antigravityHeaders.ts`, `antigravityHeaderScrub.ts`, `antigravityIdentity.ts`, `antigravityObfuscation.ts`, `antigravityVersion.ts`, `antigravity429Engine.ts`, `chatgptTlsClient.ts`, `chatgptImageCache.ts`, `cursorSessionManager.ts`, `qoderCli.ts`, `qwenThinking.ts`, `modelscopePolicy.ts` |
|
||||
| Caching | `reasoningCache.ts`, `searchCache.ts`, `signatureCache.ts`, `requestDedup.ts` |
|
||||
| Routing intelligence | `intentClassifier.ts`, `taskAwareRouter.ts`, `backgroundTaskDetector.ts`, `volumeDetector.ts`, `wildcardRouter.ts`, `workflowFSM.ts`, `specificityDetector.ts`, `specificityRules.ts`, `specificityTypes.ts` |
|
||||
| Model handling | `modelCapabilities.ts`, `modelDeprecation.ts`, `modelFamilyFallback.ts`, `modelStrip.ts`, `model.ts`, `provider.ts`, `providerRequestDefaults.ts`, `providerCostData.ts`, `payloadRules.ts` |
|
||||
| Compression | `compression/` — full compression engine wiring |
|
||||
| Token + session | `tokenRefresh.ts`, `sessionManager.ts`, `apiKeyRotator.ts`, `contextManager.ts`, `contextHandoff.ts`, `systemPrompt.ts`, `roleNormalizer.ts`, `responsesInputSanitizer.ts`, `responsesToolCallState.ts`, `toolSchemaSanitizer.ts`, `toolLimitDetector.ts`, `thinkingBudget.ts` |
|
||||
| Tier / manifest | `tierResolver.ts`, `tierConfig.ts`, `tierDefaults.json`, `tierTypes.ts`, `manifestAdapter.ts` |
|
||||
| IP / network | `ipFilter.ts`, `webSearchFallback.ts` |
|
||||
| Batches | `batchProcessor.ts` |
|
||||
| Usage | `usage.ts` |
|
||||
|
||||
### 4.6 `open-sse/mcp-server/`
|
||||
|
||||
- **31 registered tools** wired in `server.ts` (12 scoped under `schemas/tools.ts`,
|
||||
5 compression tools, 3 memory tools, 4 skills tools, plus advanced tools added
|
||||
through `advancedTools.ts`).
|
||||
- **3 transports**: stdio, HTTP Streamable, SSE.
|
||||
- **13 scopes** declared in `src/shared/constants/mcpScopes.ts`.
|
||||
- Audit table: `mcp_tool_audit` (populated by `audit.ts`).
|
||||
- Files: `server.ts`, `index.ts`, `httpTransport.ts`, `audit.ts`, `scopeEnforcement.ts`,
|
||||
`runtimeHeartbeat.ts`, `descriptionCompressor.ts`, `schemas/{tools, a2a, audit, index}.ts`,
|
||||
`tools/{advancedTools, compressionTools, memoryTools, skillTools}.ts`,
|
||||
plus tests under `__tests__/`.
|
||||
- See [MCP-SERVER.md](../frameworks/MCP-SERVER.md) for the full tool catalog.
|
||||
|
||||
### 4.7 `open-sse/config/`
|
||||
|
||||
Provider registries (`providerRegistry.ts`, `providerModels.ts`,
|
||||
`providerHeaderProfiles.ts`), per-format model registries (`audioRegistry.ts`,
|
||||
`embeddingRegistry.ts`, `imageRegistry.ts`, `moderationRegistry.ts`,
|
||||
`musicRegistry.ts`, `rerankRegistry.ts`, `searchRegistry.ts`, `videoRegistry.ts`),
|
||||
identity helpers (`codexIdentity.ts`, `codexInstructions.ts`,
|
||||
`anthropicHeaders.ts`, `antigravityUpstream.ts`, `antigravityModelAliases.ts`,
|
||||
`cliFingerprints.ts`, `toolCloaking.ts`, `defaultThinkingSignature.ts`),
|
||||
credential helpers (`credentialLoader.ts`, `codexClient.ts`), and cloud
|
||||
adapters (`azureAi.ts`, `bedrock.ts`, `datarobot.ts`, `glmProvider.ts`,
|
||||
`maritalk.ts`, `oci.ts`, `petals.ts`, `runway.ts`, `sap.ts`, `watsonx.ts`,
|
||||
`ollamaModels.ts`, `errorConfig.ts`, `constants.ts`, `registryUtils.ts`).
|
||||
|
||||
### 4.8 `open-sse/utils/`
|
||||
|
||||
Streaming primitives and provider helpers: `stream.ts`, `streamHandler.ts`,
|
||||
`streamHelpers.ts`, `streamPayloadCollector.ts`, `streamReadiness.ts`,
|
||||
`sseHeartbeat.ts`, `proxyFetch.ts`, `proxyDispatcher.ts`, `tlsClient.ts`,
|
||||
`networkProxy.ts`, `awsSigV4.ts`, `cacheControlPolicy.ts`,
|
||||
`cursorChecksum.ts`, `cursorAgentProtobuf.ts`, `cursorVersionDetector.ts`,
|
||||
`comfyuiClient.ts`, `kieTask.ts`, `bypassHandler.ts`, `aiSdkCompat.ts`,
|
||||
`thinkTagParser.ts`, `urlSanitize.ts`, `usageTracking.ts`, `requestLogger.ts`,
|
||||
`progressTracker.ts`, `cors.ts`, `error.ts`, `logger.ts`, `sleep.ts`,
|
||||
`ollamaTransform.ts`.
|
||||
|
||||
---
|
||||
|
||||
## 5. `electron/` — Desktop wrapper
|
||||
|
||||
```
|
||||
electron/
|
||||
├── main.js Electron main process
|
||||
├── preload.js Preload bridge (contextIsolation enabled)
|
||||
├── types.d.ts
|
||||
├── package.json electron-builder config, version 3.8.0
|
||||
├── README.md
|
||||
├── assets/ Build resources (icons, entitlements, …)
|
||||
├── node_modules/ Dedicated node_modules (better-sqlite3, electron-updater)
|
||||
└── dist-electron/ Build output (not committed)
|
||||
```
|
||||
|
||||
Five npm scripts at the workspace root: `electron:dev`, `electron:build`,
|
||||
`electron:build:{win,mac,linux}`, `electron:smoke:packaged`. Auto-update is via
|
||||
`electron-updater` pointing at the GitHub release feed.
|
||||
|
||||
---
|
||||
|
||||
## 6. `bin/` — CLI
|
||||
|
||||
```
|
||||
bin/
|
||||
├── omniroute.mjs Main CLI entry (Node ESM)
|
||||
├── reset-password.mjs Reset the management password from CLI
|
||||
├── mcp-server.mjs MCP server launcher (stdio)
|
||||
├── cli-commands.mjs Command dispatcher
|
||||
├── nodeRuntimeSupport.mjs Node version guard
|
||||
└── cli/
|
||||
├── index.mjs
|
||||
├── args.mjs
|
||||
├── data-dir.mjs
|
||||
├── encryption.mjs
|
||||
├── io.mjs
|
||||
├── provider-catalog.mjs
|
||||
├── provider-store.mjs
|
||||
├── provider-test.mjs
|
||||
├── settings-store.mjs
|
||||
├── sqlite.mjs
|
||||
└── commands/
|
||||
├── setup.mjs
|
||||
├── doctor.mjs
|
||||
└── providers.mjs
|
||||
```
|
||||
|
||||
Two binaries are exposed in `package.json` → `bin`:
|
||||
|
||||
- `omniroute` → `bin/omniroute.mjs`
|
||||
- `omniroute-reset-password` → `bin/reset-password.mjs`
|
||||
|
||||
---
|
||||
|
||||
## 7. `tests/`
|
||||
|
||||
| Directory | Type |
|
||||
| ------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------ |
|
||||
| `tests/unit/` | Unit tests via Node native test runner (506 files, plus `api/`, `auth/`, `authz/` subdirs) |
|
||||
| `tests/integration/` | Cross-module + DB-state tests |
|
||||
| `tests/e2e/` | Playwright UI tests |
|
||||
| `tests/protocols-e2e/` | MCP/A2A protocol e2e |
|
||||
| `tests/translator/` | Translator-specific tests |
|
||||
| `tests/security/` | Security regressions |
|
||||
| `tests/load/` | Load / stress tests |
|
||||
| `tests/golden-set/` | Reference outputs for translator regressions |
|
||||
| `tests/helpers/`, `tests/fixtures/`, `tests/manual/`, `tests/scratch_test.mjs` | Support |
|
||||
|
||||
Common commands:
|
||||
|
||||
| Command | What it runs |
|
||||
| -------------------------------------------------------- | ---------------------------------------------------------------- |
|
||||
| `npm run test:unit` | All `tests/unit/*.test.ts` via Node test runner (concurrency 10) |
|
||||
| `npm run test:vitest` | Vitest suite (MCP, autoCombo, cache) |
|
||||
| `npm run test:e2e` | Playwright UI suite |
|
||||
| `npm run test:protocols:e2e` | MCP + A2A protocol e2e |
|
||||
| `npm run test:coverage` | Coverage gate (≥60% lines/statements/functions/branches) |
|
||||
| `node --import tsx/esm --test tests/unit/<file>.test.ts` | Single file run |
|
||||
|
||||
---
|
||||
|
||||
## 8. `scripts/`
|
||||
|
||||
Organized into 6 subfolders by purpose.
|
||||
|
||||
- **`scripts/build/`** — `build-next-isolated.mjs`, `prepublish.ts`,
|
||||
`prepare-electron-standalone.mjs`, `pack-artifact-policy.ts`,
|
||||
`validate-pack-artifact.ts`, `postinstall.mjs`, `postinstallSupport.mjs`,
|
||||
`uninstall.mjs`, `bootstrap-env.mjs`, `runtime-env.mjs`,
|
||||
`native-binary-compat.mjs`.
|
||||
- **`scripts/dev/`** — `run-next.mjs`, `run-next-playwright.mjs`,
|
||||
`run-standalone.mjs`, `standalone-server-ws.mjs`, `responses-ws-proxy.mjs`,
|
||||
`v1-ws-bridge.mjs`, `smoke-electron-packaged.mjs`,
|
||||
`run-playwright-tests.mjs`, `run-ecosystem-tests.mjs`,
|
||||
`run-protocol-clients-tests.mjs`, `sync-env.mjs`, `healthcheck.mjs`,
|
||||
`system-info.mjs`.
|
||||
- **`scripts/check/`** — `check-cycles.mjs`, `check-docs-sync.mjs`,
|
||||
`check-docs-counts-sync.mjs`, `check-env-doc-sync.mjs`,
|
||||
`check-deprecated-versions.mjs`, `check-route-validation.mjs`,
|
||||
`check-t11-any-budget.mjs`, `check-pr-test-policy.mjs`,
|
||||
`check-supported-node-runtime.ts`, `test-report-summary.mjs`.
|
||||
- **`scripts/docs/`** — `generate-docs-index.mjs`, `gen-provider-reference.ts`.
|
||||
- **`scripts/i18n/`** — `generate-multilang.mjs`, `run-visual-qa.mjs`,
|
||||
`generate-qa-checklist.mjs`, `apply-priority-overrides.mjs`,
|
||||
`validate_translation.py`, `check_translations.py`, `i18n_autotranslate.py`,
|
||||
`untranslatable-keys.json`.
|
||||
- **`scripts/ad-hoc/`** — `cursor-tap.cjs`, `sync-cursor-models.mjs`,
|
||||
`migrate-env.mjs`, `dbsetup.js`.
|
||||
|
||||
---
|
||||
|
||||
## 9. Request Pipeline (Summary)
|
||||
|
||||

|
||||
|
||||
> Source: [diagrams/request-pipeline.mmd](../diagrams/request-pipeline.mmd)
|
||||
|
||||
```
|
||||
Client request
|
||||
→ /v1/chat/completions (route.ts)
|
||||
CORS preflight check
|
||||
Zod validation (chatCompletionsSchema in shared/validation/schemas.ts)
|
||||
Auth (extractApiKey + isValidApiKey OR requireManagementAuth)
|
||||
Policy engine (src/server/authz/pipeline.ts)
|
||||
Guardrails (PII masker, prompt injection, vision bridge)
|
||||
→ handleChatCore() (open-sse/handlers/chatCore.ts)
|
||||
Cache check (semantic + read cache)
|
||||
Rate limit (rateLimitManager, accountSemaphore)
|
||||
Combo routing (if model resolves to a combo)
|
||||
comboResolver → loop per target → handleSingleModel()
|
||||
translateRequest() (open-sse/translator/request/*)
|
||||
getExecutor(providerId).execute() (open-sse/executors/*)
|
||||
fetch upstream → retry/backoff via accountFallback
|
||||
translateResponse() (open-sse/translator/response/*)
|
||||
SSE stream OR JSON response
|
||||
If Responses API: TransformStream via open-sse/transformer/responsesTransformer.ts
|
||||
→ Compliance audit (src/lib/compliance/)
|
||||
→ Response to client
|
||||
```
|
||||
|
||||
### Resilience runtime state (three mechanisms)
|
||||
|
||||
| Mechanism | Scope | Where |
|
||||
| ------------------------ | ----------------------------- | ------------------------------------------------------------------------------------------------------------ |
|
||||
| Provider circuit breaker | Whole provider | `src/shared/utils/circuitBreaker.ts`, persisted in `domain_circuit_breakers` |
|
||||
| Connection cooldown | One account/key | `markAccountUnavailable()` in `src/sse/services/auth.ts`; consumed by `accountFallback.checkFallbackError()` |
|
||||
| Model lockout | Provider + connection + model | `open-sse/services/accountFallback.ts`, persisted in `domain_lockout_state` |
|
||||
|
||||
See [RESILIENCE_GUIDE.md](./RESILIENCE_GUIDE.md) and the dedicated section in
|
||||
[CLAUDE.md](../../CLAUDE.md).
|
||||
|
||||
---
|
||||
|
||||
## 10. How to Contribute
|
||||
|
||||
### Add a new provider
|
||||
|
||||
1. Register in `src/shared/constants/providers.ts` (Zod-validated at load).
|
||||
2. Add an executor in `open-sse/executors/` if custom logic is required
|
||||
(extend `BaseExecutor`).
|
||||
3. Add a translator in `open-sse/translator/` if it does not speak OpenAI format.
|
||||
4. If OAuth-based, add config under `src/lib/oauth/providers/` and
|
||||
`src/lib/oauth/services/`.
|
||||
5. Register models in `open-sse/config/providerRegistry.ts` (or the format-specific
|
||||
registry under `open-sse/config/`).
|
||||
6. Write tests under `tests/unit/`.
|
||||
|
||||
### Add a new API route
|
||||
|
||||
1. Create `src/app/api/your-route/route.ts`.
|
||||
2. Follow the pattern: CORS → Zod body validation → auth → handler delegation.
|
||||
3. If new request shape: add the Zod schema in `src/shared/validation/schemas.ts`.
|
||||
4. If management-only: add the path to `src/shared/constants/publicApiRoutes.ts`
|
||||
(denylist for the public API surface).
|
||||
5. Add tests under `tests/unit/`.
|
||||
6. Update `docs/reference/API_REFERENCE.md` and `docs/reference/openapi.yaml`.
|
||||
|
||||
### Add a new DB module
|
||||
|
||||
1. Create `src/lib/db/yourModule.ts` and import `getDbInstance()` from `./core.ts`.
|
||||
2. Export CRUD functions for your domain.
|
||||
3. If new tables: add a migration under `src/lib/db/migrations/`, numbered
|
||||
sequentially, idempotent, transactional.
|
||||
4. Re-export from `src/lib/localDb.ts` (re-export only — **no logic**).
|
||||
5. Add tests under `tests/unit/`.
|
||||
|
||||
### Add a new MCP tool
|
||||
|
||||
1. Add the tool definition under `open-sse/mcp-server/tools/` (or extend
|
||||
`open-sse/mcp-server/schemas/tools.ts`).
|
||||
2. Assign the appropriate scope(s) in `src/shared/constants/mcpScopes.ts`.
|
||||
3. Register the tool in `open-sse/mcp-server/server.ts`.
|
||||
4. Add tests under `open-sse/mcp-server/__tests__/`.
|
||||
5. Update [MCP-SERVER.md](../frameworks/MCP-SERVER.md).
|
||||
|
||||
### Add a new A2A skill
|
||||
|
||||
See [A2A-SERVER.md § Adding a New Skill](../frameworks/A2A-SERVER.md). Skills live in
|
||||
`src/lib/a2a/skills/` and are registered through the A2A task manager.
|
||||
|
||||
---
|
||||
|
||||
## 11. Conventions
|
||||
|
||||
- **Code style**: 2-space indent, double quotes, 100 char width, semicolons,
|
||||
`es5` trailing commas — enforced by Prettier via `lint-staged`.
|
||||
- **Imports**: external → internal (`@/`, `@omniroute/open-sse`) → relative.
|
||||
- **Naming**: files `camelCase` or `kebab-case`, components `PascalCase`,
|
||||
constants `UPPER_SNAKE`.
|
||||
- **ESLint**: `no-eval`, `no-implied-eval`, `no-new-func` = `error` everywhere;
|
||||
`no-explicit-any` = `warn` in `open-sse/` and `tests/`, error elsewhere.
|
||||
- **TypeScript**: `strict: false` (legacy posture). Prefer explicit types over
|
||||
inference for cross-module boundaries.
|
||||
- **Database**: never write raw SQL in routes or handlers — always go through
|
||||
`src/lib/db/` modules. Never add logic to `src/lib/localDb.ts`.
|
||||
- **Errors**: try/catch with specific error types, log with pino context. Never
|
||||
silently swallow errors in SSE streams; use abort signals for cleanup.
|
||||
- **Security**: never use `eval()` / `new Function()` / implied eval. Validate
|
||||
all inputs with Zod. Encrypt credentials at rest (AES-256-GCM). Keep
|
||||
`src/shared/constants/upstreamHeaders.ts` denylist aligned with the
|
||||
sanitize/validation layer.
|
||||
- **Commits**: Conventional Commits — `feat(scope): subject`. Allowed scopes:
|
||||
`db`, `sse`, `oauth`, `dashboard`, `api`, `cli`, `docker`, `ci`, `mcp`,
|
||||
`a2a`, `memory`, `skills`.
|
||||
- **Branches**: prefixes `feat/`, `fix/`, `refactor/`, `docs/`, `test/`,
|
||||
`chore/`. Never commit directly to `main`.
|
||||
- **Husky**: pre-commit runs `lint-staged` + `check:docs-sync` +
|
||||
`check:any-budget:t11`; pre-push runs `npm run test:unit`.
|
||||
|
||||
---
|
||||
|
||||
## 12. Hard Rules (from CLAUDE.md)
|
||||
|
||||
1. Never commit secrets or credentials.
|
||||
2. Never add logic to `src/lib/localDb.ts`.
|
||||
3. Never use `eval()` / `new Function()` / implied eval.
|
||||
4. Never commit directly to `main`.
|
||||
5. Never write raw SQL in routes — always go through `src/lib/db/` modules.
|
||||
6. Never silently swallow errors in SSE streams.
|
||||
7. Always validate inputs with Zod schemas.
|
||||
8. Always include tests when changing production code.
|
||||
9. Coverage must stay ≥ 60% (statements, lines, functions, branches).
|
||||
|
||||
---
|
||||
|
||||
## 13. See Also
|
||||
|
||||
- [ARCHITECTURE.md](./ARCHITECTURE.md) — high-level architecture and module
|
||||
responsibilities.
|
||||
- [API_REFERENCE.md](../reference/API_REFERENCE.md) — public + management API reference.
|
||||
- [FEATURES.md](../guides/FEATURES.md) — feature matrix and version highlights.
|
||||
- [RESILIENCE_GUIDE.md](./RESILIENCE_GUIDE.md) — circuit breaker, cooldown,
|
||||
lockout deep dive.
|
||||
- [AUTO-COMBO.md](../routing/AUTO-COMBO.md) — Auto Combo scoring and strategies.
|
||||
- [MCP-SERVER.md](../frameworks/MCP-SERVER.md) — full MCP tool catalog + transports.
|
||||
- [A2A-SERVER.md](../frameworks/A2A-SERVER.md) — A2A protocol skills and discovery.
|
||||
- [COMPRESSION_GUIDE.md](../compression/COMPRESSION_GUIDE.md) — RTK + Caveman compression.
|
||||
- [CLI-TOOLS.md](../reference/CLI-TOOLS.md) — CLI integrations.
|
||||
- [ELECTRON_GUIDE.md](../guides/ELECTRON_GUIDE.md) (if present), [DOCKER_GUIDE.md](../guides/DOCKER_GUIDE.md), [FLY_IO_DEPLOYMENT_GUIDE.md](../ops/FLY_IO_DEPLOYMENT_GUIDE.md), [VM_DEPLOYMENT_GUIDE.md](../ops/VM_DEPLOYMENT_GUIDE.md), [TERMUX_GUIDE.md](../guides/TERMUX_GUIDE.md), [PWA_GUIDE.md](../guides/PWA_GUIDE.md) — deployment targets.
|
||||
- [TROUBLESHOOTING.md](../guides/TROUBLESHOOTING.md) — common operational issues.
|
||||
- [CONTRIBUTING.md](../../CONTRIBUTING.md) — contributor workflow.
|
||||
- [CLAUDE.md](../../CLAUDE.md) — repo rules for Claude Code (the source of truth
|
||||
for many of the conventions above).
|
||||
- [AGENTS.md](../../AGENTS.md) — deeper architecture reference used by agents.
|
||||
512
docs/architecture/REPOSITORY_MAP.md
Normal file
512
docs/architecture/REPOSITORY_MAP.md
Normal file
@@ -0,0 +1,512 @@
|
||||
---
|
||||
title: "Repository Map"
|
||||
version: 3.8.0
|
||||
lastUpdated: 2026-05-13
|
||||
---
|
||||
|
||||
# Repository Map
|
||||
|
||||
> **One-line description for every directory and root file.**
|
||||
> Last updated: 2026-05-13 — OmniRoute v3.8.0
|
||||
>
|
||||
> Use this map to navigate the codebase quickly. For deep dives, follow links to dedicated docs.
|
||||
|
||||
## Top-level tree
|
||||
|
||||
```
|
||||
OmniRoute/
|
||||
├── src/ # Next.js 16 application (UI + API routes + libs + domain + server)
|
||||
├── open-sse/ # Streaming engine workspace (handlers, executors, translator, MCP server)
|
||||
├── electron/ # Desktop wrapper (Electron 41 + electron-builder 26.10)
|
||||
├── bin/ # CLI entry point and command handlers
|
||||
├── scripts/ # Build, check, sync, and one-off scripts
|
||||
├── docs/ # Public documentation (you are here)
|
||||
├── tests/ # All test suites (unit, integration, e2e, protocols-e2e)
|
||||
├── public/ # Next.js static assets, PWA manifest, service worker, icons
|
||||
├── config/ # Static config files
|
||||
├── images/ # Marketing / README image assets
|
||||
├── .github/ # GitHub Actions workflows + issue templates + PR template
|
||||
├── .husky/ # Git hooks (pre-commit, pre-push)
|
||||
├── .claude/ # Claude Code slash commands (project-scoped)
|
||||
├── .agents/ # Codex / generic agent workflows + skills (mirror of .claude/)
|
||||
├── .vscode/ # VS Code workspace settings
|
||||
├── _ideia/ # Planning notes (informal; not shipped)
|
||||
├── _mono_repo/ # Historic subprojects (cloud, site, vscode-extension)
|
||||
├── _references/ # Read-only reference clones from related OSS projects
|
||||
├── _tasks/ # Per-release task tracking files (informal)
|
||||
├── .issues/ # Local issue cache (gitignored)
|
||||
├── .playwright-mcp/ # Playwright MCP test artifacts
|
||||
├── coverage/ # c8 coverage output (gitignored)
|
||||
├── logs/ # Runtime logs (gitignored)
|
||||
├── node_modules/ # Dependencies (gitignored)
|
||||
├── package/ # npm pack staging area (build artifact)
|
||||
├── .next/ # Next.js build output (gitignored)
|
||||
└── (root files — see below)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Root files
|
||||
|
||||
| File | Purpose |
|
||||
| ------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **README.md** | Marketing landing page + quick start + feature matrix (see also `llm.txt`) |
|
||||
| **CHANGELOG.md** | Per-release changelog (auto-generated by `/version-bump-cc` skill) |
|
||||
| **LICENSE** | MIT license text |
|
||||
| **CLAUDE.md** | Project rules for Claude Code agents (hard rules, conventions, scenarios) |
|
||||
| **AGENTS.md** | Same as CLAUDE.md but for non-Claude AI agents (Codex, Cursor, etc.) |
|
||||
| **GEMINI.md** | Concise rules for Gemini-based agents (subset of CLAUDE.md) |
|
||||
| **CONTRIBUTING.md** | Contributor guide: setup, conventional commits, testing, PR flow |
|
||||
| **SECURITY.md** | Vulnerability reporting policy, supported versions, threat model |
|
||||
| **CODE_OF_CONDUCT.md** | Contributor Covenant — community behavior expectations |
|
||||
| **llm.txt** | Plain-text landing optimized for LLM crawlers (SEO for AI assistants) |
|
||||
| **Tuto_Qdrant.md** | Tutorial for enabling Qdrant vector memory — **integration currently dormant** (see banner; primary memory docs in `docs/frameworks/MEMORY.md`) |
|
||||
| **package.json** | npm manifest, scripts, dependencies, engines, c8 coverage gate |
|
||||
| **package-lock.json** | Locked dependency tree |
|
||||
| **tsconfig.json** | Root TypeScript config |
|
||||
| **tsconfig.typecheck-core.json** | Typecheck config for `src/` core |
|
||||
| **tsconfig.typecheck-noimplicit-core.json** | Strict (`noImplicitAny`) typecheck |
|
||||
| **tsconfig.tsbuildinfo** | TS incremental build cache (gitignored) |
|
||||
| **next.config.mjs** | Next.js 16 build configuration (standalone output) |
|
||||
| **next-env.d.ts** | Next.js auto-generated env types |
|
||||
| **eslint.config.mjs** | ESLint flat config (rules per project area) |
|
||||
| **prettier.config.mjs** | Prettier formatting rules |
|
||||
| **postcss.config.mjs** | PostCSS config for Tailwind/CSS pipeline |
|
||||
| **playwright.config.ts** | Playwright E2E test config |
|
||||
| **vitest.config.ts** | Vitest config (default suite) |
|
||||
| **vitest.mcp.config.ts** | Vitest config for MCP server / autoCombo / cache suites |
|
||||
| **sonar-project.properties** | SonarQube/SonarCloud config (code quality) |
|
||||
| **Dockerfile** | Multi-stage Docker build (builder → runner-base → runner-cli) |
|
||||
| **docker-compose.yml** | Dev compose with 4 profiles (base, cli, host, cliproxyapi) + redis sidecar |
|
||||
| **docker-compose.prod.yml** | Production compose (port 20130, redis, named volumes) |
|
||||
| **.dockerignore** | Files excluded from Docker context |
|
||||
| **fly.toml** | Fly.io deployment config (region `sin`, port 20128, /data volume) |
|
||||
| **.env.example** | Template env file (815 lines, auto-copied to `.env` on first install) |
|
||||
| **.gitignore** | Git ignore patterns |
|
||||
| **.npmignore** | npm publish exclusion list |
|
||||
| **.npmrc** | npm config (registry, lockfile policy) |
|
||||
| **.node-version** | Node version pin (used by nvm-compatible tools) |
|
||||
| **.nvmrc** | Node version pin for nvm |
|
||||
|
||||
---
|
||||
|
||||
## `src/` — Next.js application
|
||||
|
||||
```
|
||||
src/
|
||||
├── app/ # App Router (pages + API routes + status pages + landing)
|
||||
├── lib/ # Core libraries / domain modules (~50 subdirs + ~30 top-level files)
|
||||
├── domain/ # Pure domain logic (policy engine, fallback, cost, lockout, comboResolver, assessment)
|
||||
├── server/ # Server-only modules (authz pipeline, cors, auth middleware) — cannot import from client
|
||||
├── shared/ # Shared between server and client where safe (constants, types, validation, contracts, utils)
|
||||
├── i18n/ # next-intl config + per-locale message JSON (30+ locales)
|
||||
├── middleware/ # Next.js middleware (request enrichment, locale detection)
|
||||
├── mitm/ # MITM proxy helpers (Linux cert install, antigravity stealth)
|
||||
├── models/ # Model adapter glue (legacy shim)
|
||||
├── scripts/ # In-tree maintenance scripts (e.g., backfillAggregation)
|
||||
├── sse/ # Legacy SSE handlers/services (chat.ts, chatHelpers.ts, services/auth.ts)
|
||||
├── store/ # Legacy in-memory store (being phased out for src/lib/db)
|
||||
├── types/ # Shared TS type files
|
||||
├── instrumentation.ts # Next.js telemetry hook (browser + edge)
|
||||
├── instrumentation-node.ts # Node-only instrumentation
|
||||
├── server-init.ts # Server bootstrap (DB migrations, jobs, cleanup)
|
||||
└── proxy.ts # HTTP-proxy entry shim
|
||||
```
|
||||
|
||||
### `src/app/` — App Router (Next.js 16)
|
||||
|
||||
| Path | Purpose |
|
||||
| ---------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| `app/api/v1/` | Public OpenAI-compat API (~25 sub-routes: chat, completions, embeddings, files, batches, audio, images, videos, music, rerank, moderations, search, ws, agents, accounts, providers, etc.) |
|
||||
| `app/api/v1beta/` | Gemini-style API endpoints |
|
||||
| `app/api/` (non-v1) | Management/admin routes (~60 directories: providers, combos, settings, mcp, a2a, evals, memory, skills, webhooks, compliance, resilience, monitoring, tunnels, cli-tools, etc.) |
|
||||
| `app/a2a/` | A2A JSON-RPC 2.0 entry point (`POST /a2a`) |
|
||||
| `app/.well-known/agent.json/` | A2A Agent Card (discovery) |
|
||||
| `app/(dashboard)/dashboard/` | Dashboard UI pages (~30 pages: providers, combos, settings, memory, skills, webhooks, evals, audit, batch, cache, costs, health, system, etc.) |
|
||||
| `app/docs/` | Embedded documentation viewer (renders `docs/*.md`) |
|
||||
| `app/landing/` | Marketing landing page |
|
||||
| `app/login/`, `forgot-password/`, `forbidden/` | Auth-related pages |
|
||||
| `app/{400,401,403,408,429,500,502,503}/` | HTTP error pages |
|
||||
| `app/maintenance/`, `offline/`, `status/`, `privacy/`, `terms/`, `callback/` | Static/status pages |
|
||||
| `app/layout.tsx`, `page.tsx`, `manifest.ts`, `globals.css` | Root layout, home, PWA manifest, global CSS |
|
||||
| `app/error.tsx`, `global-error.tsx`, `not-found.tsx`, `loading.tsx` | Error boundaries |
|
||||
|
||||
### `src/lib/` — Core libraries (~50 modules)
|
||||
|
||||
| Module | Purpose |
|
||||
| ---------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `a2a/` | A2A protocol task manager, skills (5), streaming |
|
||||
| `acp/` | CLI Agent Registry (local CLI discovery — see `docs/frameworks/AGENT_PROTOCOLS_GUIDE.md`) |
|
||||
| `api/` | Shared API helpers (`requireManagementAuth`, validation) |
|
||||
| `auth/` | Session, password hashing, token validation |
|
||||
| `batches/` | OpenAI Batches API handlers |
|
||||
| `catalog/` | Provider catalog Zod validation + capability resolution |
|
||||
| `cloudAgent/` | Cloud Agents (Codex Cloud, Devin, Jules) — see `docs/frameworks/CLOUD_AGENT.md` |
|
||||
| `combos/` | Combo resolution + reorder helpers |
|
||||
| `compliance/` | Audit log + provider audit — see `docs/security/COMPLIANCE.md` |
|
||||
| `compression/` | Compression engine glue (engines live in `open-sse/services/compression/`) |
|
||||
| `config/` | Runtime config helpers |
|
||||
| `db/` | 45+ domain DB modules + 55 migrations (always go through here for SQLite) |
|
||||
| `display/` | UI formatting helpers (cost, latency, etc.) |
|
||||
| `embeddings/` | Embeddings service helpers |
|
||||
| `env/` | Env variable parsing + validation |
|
||||
| `evals/` | Eval framework (suites, runner, runtime) — see `docs/frameworks/EVALS.md` |
|
||||
| `guardrails/` | PII masker, prompt injection, vision bridge — see `docs/security/GUARDRAILS.md` |
|
||||
| `jobs/` | Background jobs (cron-like) |
|
||||
| `memory/` | Conversational memory (SQLite FTS5 + Qdrant) — see `docs/frameworks/MEMORY.md` |
|
||||
| `monitoring/` | Health checks, metrics emission |
|
||||
| `oauth/` | OAuth flows for 14 providers (claude, codex, antigravity, cursor, github, gemini, kimi-coding, kilocode, cline, qwen, kiro, qoder, gitlab-duo, windsurf) |
|
||||
| `plugins/` | Plugin registry |
|
||||
| `promptCache/` | Anthropic-style prompt cache breakpoints |
|
||||
| `skills/` | Skills framework (built-in + marketplace + SkillsSH) — see `docs/frameworks/SKILLS.md` |
|
||||
| `webhookDispatcher.ts` | HMAC webhook delivery — see `docs/frameworks/WEBHOOKS.md` |
|
||||
| `cloudflaredTunnel.ts`, `ngrokTunnel.ts` | Tunnel managers — see `docs/ops/TUNNELS_GUIDE.md` |
|
||||
| `oneproxySync.ts`, `oneproxyRotator.ts` | 1proxy free proxy marketplace — see `docs/ops/PROXY_GUIDE.md` |
|
||||
| `cloudSync.ts`, `initCloudSync.ts` | Optional cloud sync of state |
|
||||
| `localDb.ts` | Re-export barrel for db modules (no logic — re-exports only) |
|
||||
| `cacheLayer.ts`, `idempotencyLayer.ts` | Request caching + idempotency |
|
||||
| (~30 more top-level files) | Specialized helpers (logEnv, modelsDevSync, piiSanitizer, etc.) |
|
||||
|
||||
### `src/db/` — Database (45+ modules + 55 migrations)
|
||||
|
||||
| Subdir | Purpose |
|
||||
| ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `db/core.ts` | `getDbInstance()` singleton with WAL journaling |
|
||||
| `db/migrations/` | 55 versioned SQL files (idempotent, transactional, numbered `001`..`055`) |
|
||||
| `db/<domain>.ts` | One module per domain: providers, combos, apiKeys, users, sessions, usage, audit*log, webhooks, skills, memory_entries, cloud_agent_tasks, evals*\*, reasoning_cache, etc. |
|
||||
|
||||
### `src/domain/`
|
||||
|
||||
| Module | Purpose |
|
||||
| ---------------------- | ----------------------------------------------------------------------- |
|
||||
| `policy.ts` | Policy engine |
|
||||
| `fallbackPolicy.ts` | Fallback decision tree |
|
||||
| `costRules.ts` | Cost calculation rules |
|
||||
| `lockoutPolicy.ts` | Model/connection lockout policy |
|
||||
| `tagRouter.ts` | Tag-based routing |
|
||||
| `comboResolver.ts` | Combo resolution (used by combo engine) |
|
||||
| `modelAvailability.ts` | Per-model availability check |
|
||||
| `assessment/` | Model assessment (Phase 1 of RFC-AUTO-ASSESSMENT — see `docs/archive/`) |
|
||||
|
||||
### `src/server/`
|
||||
|
||||
| Module | Purpose |
|
||||
| -------- | ---------------------------------------------------------------------------------------------------- |
|
||||
| `authz/` | Authorization pipeline: `classify` → `policies` → `enforce` — see `docs/architecture/AUTHZ_GUIDE.md` |
|
||||
| `cors/` | CORS configuration |
|
||||
| `auth/` | Session middleware |
|
||||
|
||||
### `src/shared/`
|
||||
|
||||
| Module | Purpose |
|
||||
| -------------------------------- | ---------------------------------------------------------------------- |
|
||||
| `constants/providers.ts` | **177 providers** with Zod validation (source of truth) |
|
||||
| `constants/cliTools.ts` | External CLI tool registry |
|
||||
| `constants/routingStrategies.ts` | **14 routing strategies** with priorities |
|
||||
| `constants/publicApiRoutes.ts` | Routes that require Bearer (vs management) auth |
|
||||
| `constants/upstreamHeaders.ts` | Header denylist for upstream requests |
|
||||
| `validation/schemas.ts` | ~80 Zod schemas (single source of truth for API contracts) |
|
||||
| `validation/helpers.ts` | Zod validation helpers (`validateBody`, etc.) |
|
||||
| `types/` | Shared TS types |
|
||||
| `contracts/` | Public API contracts (consumed by `files:` in `package.json`) |
|
||||
| `utils/circuitBreaker.ts` | Provider circuit breaker (see `docs/architecture/RESILIENCE_GUIDE.md`) |
|
||||
| `utils/apiAuth.ts` | API key validation, scope checking |
|
||||
| `utils/fetchTimeout.ts` | Timeout/abort wrappers for upstream fetch |
|
||||
|
||||
---
|
||||
|
||||
## `open-sse/` — Streaming Engine Workspace
|
||||
|
||||
Separate npm workspace (`@omniroute/open-sse`). Handles request processing + provider execution.
|
||||
|
||||
```
|
||||
open-sse/
|
||||
├── handlers/ # 15 files (11 handlers + 4 helpers): chatCore, responsesHandler, embeddings, audio, image, video, music, rerank, moderations, search, etc.
|
||||
├── executors/ # 31 provider-specific executors (extend BaseExecutor)
|
||||
├── translator/ # Format converters (9 request, 8 response, 9 helpers)
|
||||
├── transformer/ # Responses API ↔ Chat Completions (TransformStream)
|
||||
├── services/ # ~80+ service modules (combo, accountFallback, autoCombo, reasoningCache, claude code/chatgpt stealth, modelDeprecation, taskAwareRouter, workflowFSM, etc.)
|
||||
├── mcp-server/ # MCP server (37 tools, 3 transports, ~13 scopes)
|
||||
├── config/ # Provider/model registries, header config, model aliases
|
||||
├── utils/ # TLS client, proxy fetch/dispatcher, network helpers
|
||||
├── index.ts # Workspace entry
|
||||
├── package.json # Workspace manifest
|
||||
├── tsconfig.json # Workspace TS config
|
||||
└── types.d.ts # Workspace type declarations
|
||||
```
|
||||
|
||||
### `open-sse/mcp-server/`
|
||||
|
||||
| Path | Purpose |
|
||||
| --------------------------- | ------------------------------------------------------------------------------ |
|
||||
| `server.ts` | MCP server lifecycle (stdio + HTTP transports) |
|
||||
| `httpTransport.ts` | HTTP Streamable + SSE transports (`/api/mcp/sse`, `/api/mcp/stream`) |
|
||||
| `audit.ts` | Audit logging to `mcp_tool_audit` table |
|
||||
| `scopeEnforcement.ts` | Per-tool scope validation |
|
||||
| `runtimeHeartbeat.ts` | Health heartbeat to `DATA_DIR/runtime/mcp-heartbeat.json` |
|
||||
| `descriptionCompressor.ts` | Compress tool description metadata to save context |
|
||||
| `schemas/tools.ts` | 30 base tool definitions + scopes |
|
||||
| `tools/advancedTools.ts` | Advanced tool implementations |
|
||||
| `tools/memoryTools.ts` | 3 memory tools (search/add/clear) |
|
||||
| `tools/skillTools.ts` | 4 skill tools (list/enable/execute/executions) |
|
||||
| `tools/compressionTools.ts` | 5 compression tools |
|
||||
| `README.md` | Internal MCP server README (cross-linked from `docs/frameworks/MCP-SERVER.md`) |
|
||||
|
||||
---
|
||||
|
||||
## `electron/` — Desktop Wrapper
|
||||
|
||||
| File | Purpose |
|
||||
| ---------------- | --------------------------------------------------------------------------------- |
|
||||
| `main.js` | Electron main process (BrowserWindow, embedded Next.js server, tray, auto-update) |
|
||||
| `preload.js` | IPC bridge (contextBridge → `window.omniroute`) |
|
||||
| `package.json` | electron-builder config + Electron 41 + electron-builder 26.10 deps |
|
||||
| `assets/` | App icons (Windows .ico, macOS .icns, Linux .png) |
|
||||
| `dist-electron/` | Build output (gitignored) |
|
||||
| `types.d.ts` | Type declarations for renderer bridge |
|
||||
| `README.md` | Internal Electron README (see also `docs/guides/ELECTRON_GUIDE.md`) |
|
||||
|
||||
---
|
||||
|
||||
## `bin/` — CLI
|
||||
|
||||
| File | Purpose |
|
||||
| ----------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `omniroute.mjs` | Main CLI entry — `omniroute serve`, `omniroute setup`, `omniroute doctor`, `omniroute providers`, `omniroute combos`, etc. |
|
||||
| `reset-password.mjs` | Standalone password reset CLI |
|
||||
| `cli/commands/setup.mjs` | Interactive + non-interactive setup wizard |
|
||||
| `cli/commands/doctor.mjs` | System health diagnostics (8+ checks) |
|
||||
| `cli/commands/providers.mjs` | Provider list/test/validate |
|
||||
| `cli/{args,data-dir,encryption,io,provider-catalog,provider-store,provider-test,settings-store,sqlite}.mjs` | CLI helper modules |
|
||||
| `nodeRuntimeSupport.mjs` | Validate supported Node.js version on install |
|
||||
|
||||
---
|
||||
|
||||
## `scripts/` — Build & Check Scripts
|
||||
|
||||
| Script | Purpose |
|
||||
| ----------------------------------- | -------------------------------------------------------------------------- |
|
||||
| `run-next.mjs` | Dev/start runner with env hydration |
|
||||
| `build-next-isolated.mjs` | Standalone build (Next.js 16 standalone) |
|
||||
| `prepublish.ts` | Package preparation before `npm pack` |
|
||||
| `postinstall.mjs` | Auto-create `.env` from `.env.example` on first install |
|
||||
| `sync-env.mjs` | Re-sync `.env` keys with `.env.example` |
|
||||
| `check-cycles.mjs` | Detect circular dependencies |
|
||||
| `check-route-validation.mjs` | Validate all API routes have Zod validation |
|
||||
| `check-t11-any-budget.mjs` | Enforce explicit `any` budget per file |
|
||||
| `check-docs-sync.mjs` | Validate docs version sync (existing pre-commit) |
|
||||
| **`check-env-doc-sync.mjs`** | NEW: cross-check env vars in code vs `.env.example` vs `ENVIRONMENT.md` |
|
||||
| **`check-docs-counts-sync.mjs`** | NEW: validate counts (executors, strategies, OAuth, A2A skills) match docs |
|
||||
| **`check-deprecated-versions.mjs`** | NEW: flag stale versions/dates in docs |
|
||||
| `check-supported-node-runtime.ts` | Validate current Node version is supported |
|
||||
| `check-pr-test-policy.mjs` | Enforce "tests required" rule on production code changes |
|
||||
| **`gen-provider-reference.ts`** | NEW: auto-generate `docs/reference/PROVIDER_REFERENCE.md` from catalog |
|
||||
| `generate-docs-index.mjs` | Build `src/app/docs/lib/docs-auto-generated.ts` from `docs/*.md` |
|
||||
| `i18n/generate-multilang.mjs` | Translate UI strings + docs via Google Translate |
|
||||
| `i18n_autotranslate.py` | LLM-based doc translation pipeline |
|
||||
| `validate_translation.py` | Per-locale translation validation |
|
||||
| `check_translations.py` | Code-side i18n key check |
|
||||
| `run-playwright-tests.mjs` | Playwright E2E runner |
|
||||
| `run-protocol-clients-tests.mjs` | MCP/A2A E2E runner |
|
||||
| `run-ecosystem-tests.mjs` | Ecosystem (provider integration) tests |
|
||||
| `test-report-summary.mjs` | Generate coverage summary markdown |
|
||||
| `smoke-electron-packaged.mjs` | Smoke-test packaged Electron build |
|
||||
| `native-binary-compat.mjs` | Validate native deps (`better-sqlite3`) match Electron's Node |
|
||||
| `validate-pack-artifact.ts` | Validate npm pack output |
|
||||
| `responses-ws-proxy.mjs` | WebSocket bridge for Codex Responses API |
|
||||
| `v1-ws-bridge.mjs` | WebSocket bridge for `/api/v1/ws` endpoint |
|
||||
| `standalone-server-ws.mjs` | Standalone WS server runner |
|
||||
| `system-info.mjs` | Print system/runtime info for support |
|
||||
| `healthcheck.mjs` | One-shot health check (used by Docker HEALTHCHECK) |
|
||||
| `uninstall.mjs` | Clean uninstall script |
|
||||
|
||||
---
|
||||
|
||||
## `docs/` — Public Documentation (44 files + 4 subdirs)
|
||||
|
||||
### Top-level guides
|
||||
|
||||
| Doc | Purpose |
|
||||
| --------------------------- | ------------------------------------------------------------------------------------- |
|
||||
| `ARCHITECTURE.md` | High-level architecture, subsystem map, dashboard surface |
|
||||
| `CODEBASE_DOCUMENTATION.md` | Engineering reference: directories, modules, conventions |
|
||||
| `FEATURES.md` | Feature matrix with v3.8 highlights |
|
||||
| `USER_GUIDE.md` | End-user manual (setup, models, combos, CLIs, audio, etc.) |
|
||||
| `API_REFERENCE.md` | API endpoint reference with auth model |
|
||||
| `openapi.yaml` | OpenAPI 3.0 spec (121 paths) |
|
||||
| `SETUP_GUIDE.md` | Install methods (npm, npx, Docker, Electron, Termux, source) |
|
||||
| `ENVIRONMENT.md` | All env vars (~219 used in code, ~810 lines `.env.example`) |
|
||||
| `TROUBLESHOOTING.md` | Common errors + v3.8.0 known issues |
|
||||
| `RELEASE_CHECKLIST.md` | Full release flow (skills, husky, conventional commits, deploy) |
|
||||
| `COVERAGE_PLAN.md` | Coverage goals and current state |
|
||||
| `FREE_TIERS.md` | Curated free-tier providers (48+ free + 11 OAuth) |
|
||||
| `CLI-TOOLS.md` | External CLI integrations + Internal OmniRoute CLI |
|
||||
| `I18N.md` | i18n architecture, adding a language, 30 locales |
|
||||
| `UNINSTALL.md` | Clean uninstall steps |
|
||||
| `PROVIDER_REFERENCE.md` | **Auto-generated** catalog of 177 providers (regen: `npm run gen:provider-reference`) |
|
||||
|
||||
### Subsystem deep-dives
|
||||
|
||||
| Doc | Purpose |
|
||||
| -------------------------- | ------------------------------------------------------------------- |
|
||||
| `MCP-SERVER.md` | MCP server: 37 tools, 3 transports, ~13 scopes, REST endpoints |
|
||||
| `A2A-SERVER.md` | A2A v0.3: JSON-RPC, 5 skills, REST helpers, agent card |
|
||||
| `AGENT_PROTOCOLS_GUIDE.md` | Unified guide: A2A vs ACP vs Cloud Agents |
|
||||
| `CLOUD_AGENT.md` | Codex Cloud / Devin / Jules orchestration |
|
||||
| `SKILLS.md` | Skills framework (built-in + marketplace + SkillsSH + sandbox) |
|
||||
| `MEMORY.md` | Memory system (SQLite FTS5 + Qdrant) |
|
||||
| `EVALS.md` | Eval framework (suites, runs, rubrics) |
|
||||
| `GUARDRAILS.md` | PII masker, prompt injection, vision bridge |
|
||||
| `COMPLIANCE.md` | Audit log, retention, noLog opt-out |
|
||||
| `WEBHOOKS.md` | HMAC-signed webhook delivery |
|
||||
| `REASONING_REPLAY.md` | Hybrid memory/SQLite cache for `reasoning_content` |
|
||||
| `AUTHZ_GUIDE.md` | Authorization pipeline (`classify` → `policies` → `enforce`) |
|
||||
| `RESILIENCE_GUIDE.md` | Circuit breaker + cooldown + model lockout |
|
||||
| `STEALTH_GUIDE.md` | TLS fingerprinting (JA3/JA4), Claude Code CCH, MITM cert |
|
||||
| `AUTO-COMBO.md` | Auto Combo engine (9-factor scoring, 4 mode packs, virtual factory) |
|
||||
|
||||
### Compression
|
||||
|
||||
| Doc | Purpose |
|
||||
| ------------------------------- | ---------------------------------------- |
|
||||
| `COMPRESSION_GUIDE.md` | Overview of compression modes + roadmap |
|
||||
| `COMPRESSION_ENGINES.md` | Caveman + RTK engines, registry contract |
|
||||
| `COMPRESSION_RULES_FORMAT.md` | Caveman rule pack JSON schema |
|
||||
| `COMPRESSION_LANGUAGE_PACKS.md` | Per-language rule pack inventory |
|
||||
| `RTK_COMPRESSION.md` | RTK declarative pipeline (49 filters) |
|
||||
|
||||
### Deployment
|
||||
|
||||
| Doc | Purpose |
|
||||
| ---------------------------- | ----------------------------------------------------------------- |
|
||||
| `DOCKER_GUIDE.md` | Docker build, profiles (base/cli/host/cliproxyapi), Redis sidecar |
|
||||
| `VM_DEPLOYMENT_GUIDE.md` | Generic VM/VPS deployment (Ubuntu/Debian + nginx + systemd) |
|
||||
| `FLY_IO_DEPLOYMENT_GUIDE.md` | Fly.io deployment (currently Chinese-only) |
|
||||
| `TERMUX_GUIDE.md` | Android headless via Termux |
|
||||
| `PWA_GUIDE.md` | Progressive Web App install + service worker |
|
||||
| `ELECTRON_GUIDE.md` | Desktop app build + sign + distribute |
|
||||
| `TUNNELS_GUIDE.md` | Cloudflared + ngrok + Tailscale Funnel |
|
||||
| `PROXY_GUIDE.md` | 4-level outbound proxy + 1proxy marketplace |
|
||||
|
||||
### Subdirectories
|
||||
|
||||
| Subdir | Purpose |
|
||||
| ------------------------- | ------------------------------------------------------------------------------------- |
|
||||
| `docs/archive/` | Archived/historical docs (e.g., `RFC-AUTO-ASSESSMENT-DRAFT.md` — superseded by EVALS) |
|
||||
| `docs/i18n/` | Localized doc translations (~40 locales) |
|
||||
| `docs/screenshots/` | Image assets for guides |
|
||||
| `docs/superpowers/plans/` | Implementation plans (generated by `superpowers:writing-plans` skill) |
|
||||
|
||||
---
|
||||
|
||||
## `tests/` — Test Suites
|
||||
|
||||
| Subdir | Type | Runner |
|
||||
| ---------------------- | --------------------------------------- | --------------------------------------- |
|
||||
| `tests/unit/` | Unit tests (~500 files, fastest) | Node native test runner |
|
||||
| `tests/integration/` | Multi-module + DB integration tests | Node native test runner (concurrency 1) |
|
||||
| `tests/e2e/` | UI + workflow E2E | Playwright |
|
||||
| `tests/protocols-e2e/` | MCP + A2A real-client E2E | Custom protocol clients |
|
||||
| `tests/ecosystem/` | Provider integration (network-touching) | Node native test runner |
|
||||
|
||||
---
|
||||
|
||||
## `public/` — Static Assets
|
||||
|
||||
| Path | Purpose |
|
||||
| ------------------- | ---------------------------------------------------------------- |
|
||||
| `public/` (root) | Favicons, robots.txt, manifest, service worker, marketing images |
|
||||
| `public/providers/` | Provider logo PNG/SVG (used in dashboard) |
|
||||
|
||||
---
|
||||
|
||||
## `config/` — Static Configs
|
||||
|
||||
Shipped configuration templates and sample files (referenced by setup wizard).
|
||||
|
||||
---
|
||||
|
||||
## `.github/` — GitHub Integration
|
||||
|
||||
| Path | Purpose |
|
||||
| ---------------------------------- | -------------------------------------------------------------- |
|
||||
| `.github/workflows/` | GitHub Actions CI/CD workflows (lint, test, coverage, release) |
|
||||
| `.github/ISSUE_TEMPLATE/` | Bug/feature issue templates |
|
||||
| `.github/PULL_REQUEST_TEMPLATE.md` | PR template |
|
||||
| `.github/dependabot.yml` | Dependency update config |
|
||||
|
||||
---
|
||||
|
||||
## `.husky/` — Git Hooks
|
||||
|
||||
| File | Purpose |
|
||||
| ------------ | ----------------------------------------------------------------- |
|
||||
| `pre-commit` | Runs `lint-staged + check-docs-sync + check:any-budget:t11` |
|
||||
| `pre-push` | Currently disabled (commented). Run `npm run test:unit` manually. |
|
||||
| `_/` | Husky internals |
|
||||
|
||||
---
|
||||
|
||||
## `.claude/` — Claude Code Slash Commands
|
||||
|
||||
| File | Purpose |
|
||||
| ----------------------------------------------------------------- | -------------------------------------------------- |
|
||||
| `commands/version-bump-cc.md` | `/version-bump-cc` — bump version + auto-changelog |
|
||||
| `commands/generate-release-cc.md` | `/generate-release-cc` — full release workflow |
|
||||
| `commands/deploy-vps-{local,akamai,both}-cc.md` | Deploy to VPS |
|
||||
| `commands/capture-release-evidences-cc.md` | Browser-record new features as WebP |
|
||||
| `commands/review-{prs,discussions}-cc.md` | Triage GitHub PRs/discussions |
|
||||
| `commands/{issue-triage,resolve-issues,implement-features}-cc.md` | Issue workflows |
|
||||
| `settings.local.json` | Per-project Claude Code settings |
|
||||
|
||||
---
|
||||
|
||||
## `.agents/` — Generic Agent Workflows (Codex / Cursor / etc.)
|
||||
|
||||
| Path | Purpose |
|
||||
| ------------------------ | ------------------------------------------------------- |
|
||||
| `workflows/*-ag.md` | 11 workflow definitions (mirror of `.claude/commands/`) |
|
||||
| `skills/<name>/SKILL.md` | 9 skill definitions with Codex Execution Notes |
|
||||
|
||||
> **Note:** Workflows and commands are currently identical byte-by-byte. If `.agents/` is meant to target a different agent runtime (Codex), the variants need to diverge meaningfully.
|
||||
|
||||
---
|
||||
|
||||
## `_ideia/`, `_mono_repo/`, `_references/`, `_tasks/` — Out-of-tree
|
||||
|
||||
These underscore-prefixed directories hold non-shipping content:
|
||||
|
||||
- **`_ideia/`** — design notes (defer / notfit / viable categories)
|
||||
- **`_mono_repo/`** — historic subprojects (omnirouteCloud, omnirouteSite, vscode-extension)
|
||||
- **`_references/`** — read-only clones of related OSS projects (LiteLLM, 9router, ClawRouter, CLIProxyAPI, modelrelay, new-api, etc.) for cross-reference during development
|
||||
- **`_tasks/`** — per-release task tracking files (informal)
|
||||
|
||||
Not included in `npm pack` output. See `.npmignore`.
|
||||
|
||||
---
|
||||
|
||||
## Generated / Gitignored
|
||||
|
||||
| Path | Purpose |
|
||||
| ---------------------- | ----------------------------- |
|
||||
| `node_modules/` | npm dependencies |
|
||||
| `.next/` | Next.js build output |
|
||||
| `coverage/` | c8 coverage reports |
|
||||
| `logs/` | Runtime logs |
|
||||
| `package/` | npm pack staging |
|
||||
| `.playwright-mcp/` | Playwright MCP test artifacts |
|
||||
| `.issues/` | Local issue cache |
|
||||
| `tsconfig.tsbuildinfo` | TS incremental cache |
|
||||
|
||||
---
|
||||
|
||||
## Navigation tips
|
||||
|
||||
- **New contributor?** Read `CONTRIBUTING.md` → `CLAUDE.md` → `docs/architecture/ARCHITECTURE.md` → `docs/architecture/CODEBASE_DOCUMENTATION.md`.
|
||||
- **Adding a provider?** Follow `docs/architecture/ARCHITECTURE.md § Adding a New Provider` + cross-check `docs/reference/PROVIDER_REFERENCE.md`.
|
||||
- **Adding a route?** `docs/architecture/ARCHITECTURE.md § Adding a New API Route` + `src/shared/validation/schemas.ts`.
|
||||
- **Adding an MCP tool?** `docs/frameworks/MCP-SERVER.md § Adding a Tool`.
|
||||
- **Adding an A2A skill?** `docs/frameworks/A2A-SERVER.md § Adding a New Skill`.
|
||||
- **Running locally?** `docs/guides/SETUP_GUIDE.md`.
|
||||
- **Deploying?** `docs/guides/DOCKER_GUIDE.md` / `docs/ops/VM_DEPLOYMENT_GUIDE.md` / `docs/ops/FLY_IO_DEPLOYMENT_GUIDE.md`.
|
||||
- **Releasing?** `docs/ops/RELEASE_CHECKLIST.md` (and `/generate-release-cc` Claude Code skill).
|
||||
147
docs/architecture/RESILIENCE_GUIDE.md
Normal file
147
docs/architecture/RESILIENCE_GUIDE.md
Normal file
@@ -0,0 +1,147 @@
|
||||
---
|
||||
title: "Resilience Guide"
|
||||
version: 3.8.0
|
||||
lastUpdated: 2026-05-13
|
||||
---
|
||||
|
||||
# Resilience Guide
|
||||
|
||||
OmniRoute has three distinct but related resilience mechanisms. Each has a different scope and purpose. Keep them separate when debugging routing behavior.
|
||||
|
||||

|
||||
|
||||
> Source: [diagrams/resilience-3layers.mmd](../diagrams/resilience-3layers.mmd)
|
||||
|
||||
## 1. Provider Circuit Breaker
|
||||
|
||||
**Scope:** entire provider (e.g., `glm`, `openai`, `anthropic`).
|
||||
|
||||
**Purpose:** stop sending traffic to a provider that is repeatedly failing at the upstream/service level.
|
||||
|
||||
**Implementation:**
|
||||
|
||||
- Core class: `src/shared/utils/circuitBreaker.ts`
|
||||
- Wiring: `src/sse/handlers/chatHelpers.ts`, `src/sse/handlers/chat.ts`
|
||||
- Status API: `GET /api/monitoring/health`
|
||||
- Reset API: `POST /api/resilience/reset`
|
||||
- Wrappers: `open-sse/services/accountFallback.ts`
|
||||
- DB table: `domain_circuit_breakers`
|
||||
|
||||
**States:**
|
||||
|
||||
- `CLOSED` — normal traffic allowed
|
||||
- `OPEN` — provider temporarily blocked; combo routing skips it
|
||||
- `HALF_OPEN` — reset timeout elapsed; probe request allowed
|
||||
|
||||
**Defaults (`open-sse/config/constants.ts`):**
|
||||
|
||||
| Class | Threshold | Reset timeout |
|
||||
| ------- | ---------- | ------------- |
|
||||
| OAuth | 3 failures | 60s |
|
||||
| API-key | 5 failures | 30s |
|
||||
| Local | 2 failures | 15s |
|
||||
|
||||
**Trip codes:** only provider-level statuses `[408, 500, 502, 503, 504]`. Do NOT trip for account-level errors (most 401/403/429 — those belong to cooldown or lockout).
|
||||
|
||||
**Lazy recovery:** when `OPEN` expires, `getStatus()`, `canExecute()`, `getRetryAfterMs()` refresh state to `HALF_OPEN`. No background timer needed.
|
||||
|
||||
---
|
||||
|
||||
## 2. Connection Cooldown
|
||||
|
||||
**Scope:** single provider connection/account/key.
|
||||
|
||||
**Purpose:** skip one bad key while other connections for the same provider keep serving.
|
||||
|
||||
**Implementation:**
|
||||
|
||||
- Mark unavailable: `src/sse/services/auth.ts::markAccountUnavailable()`
|
||||
- Selection: `getProviderCredentials*` in same file
|
||||
- Cooldown calc: `open-sse/services/accountFallback.ts::checkFallbackError()`
|
||||
- Settings: `src/lib/resilience/settings.ts`
|
||||
|
||||
**Fields per connection:**
|
||||
|
||||
- `rateLimitedUntil` — timestamp until cooldown expires
|
||||
- `testStatus: "unavailable"`
|
||||
- `lastError`, `lastErrorType`, `errorCode`
|
||||
- `backoffLevel` — exponential backoff counter
|
||||
|
||||
**Default cooldowns:**
|
||||
|
||||
- OAuth base: 5s
|
||||
- API-key base: 3s
|
||||
- API-key 429: prefers upstream `Retry-After`/reset headers/parseable reset text
|
||||
- Backoff: `baseCooldownMs * 2 ** failureIndex`
|
||||
|
||||
**Anti-thundering-herd guard:** prevents concurrent failures from over-extending cooldown or double-incrementing `backoffLevel`.
|
||||
|
||||
**Terminal states (NOT cooldowns):**
|
||||
|
||||
- `banned`
|
||||
- `expired`
|
||||
- `credits_exhausted`
|
||||
|
||||
These persist until credentials change or an operator resets them. Do not overwrite terminal states with transient cooldown state.
|
||||
|
||||
**Lazy recovery:** when `rateLimitedUntil` is past, connection becomes eligible again. On successful use, `clearAccountError()` clears all error fields.
|
||||
|
||||
---
|
||||
|
||||
## 3. Model Lockout
|
||||
|
||||
**Scope:** provider + connection + model triple.
|
||||
|
||||
**Purpose:** avoid disabling a whole connection when only one model is unavailable or quota-limited.
|
||||
|
||||
**Examples:**
|
||||
|
||||
- Per-model quota providers returning 429
|
||||
- Local providers returning 404 for one missing model
|
||||
- Provider-specific mode/model permission failures (e.g., Grok modes)
|
||||
|
||||
**Implementation:** `open-sse/services/accountFallback.ts` — `lockModel()`, `clearModelLock()`, `getAllModelLockouts()`.
|
||||
|
||||
### Model Cooldowns Dashboard (v3.8.0)
|
||||
|
||||
UI: Settings → Model Cooldowns (`src/app/(dashboard)/dashboard/settings/components/ModelCooldownsCard.tsx`)
|
||||
|
||||
Lists active lockouts with: provider, connection, model, reason, expiresAt. Operators can manually re-enable a model from the card.
|
||||
|
||||
**REST API:**
|
||||
|
||||
- `GET /api/resilience/model-cooldowns` — list active lockouts
|
||||
- `DELETE /api/resilience/model-cooldowns` — manual re-enable. Body: `{provider, connection, model}`. Auth: management.
|
||||
|
||||
---
|
||||
|
||||
## Other Resilience Features
|
||||
|
||||
- **14 routing strategies** (priority, weighted, round-robin, context-relay, fill-first, p2c, random, least-used, cost-optimized, reset-aware, strict-random, auto, lkgp, context-optimized) — see [AUTO-COMBO.md](../routing/AUTO-COMBO.md).
|
||||
- **Reset-aware routing** (v3.8.0) — prioritizes connections by quota reset time.
|
||||
- **Background mode degradation** — Responses API `background: true` degraded to sync with warning.
|
||||
- **Dynamic tool limit detection** — backs off providers when tool count limits hit.
|
||||
|
||||
---
|
||||
|
||||
## Debugging
|
||||
|
||||
- All keys for a provider skipped → check both circuit breaker state AND each connection's `rateLimitedUntil`/`testStatus`.
|
||||
- Provider permanently excluded after reset window → code reading raw `state` instead of `getStatus()`/`canExecute()`.
|
||||
- One key fails, others should work → prefer connection cooldown over circuit breaker.
|
||||
- Only one model fails → prefer model lockout over connection cooldown.
|
||||
- State should self-recover but doesn't → check for future timestamp + read path that refreshes expired state. Permanent statuses require manual changes.
|
||||
|
||||
---
|
||||
|
||||
## TLS Fingerprinting & Stealth
|
||||
|
||||
Provider-specific stealth (JA3/JA4, CCH, obfuscation) is separately documented — see [STEALTH_GUIDE.md](../security/STEALTH_GUIDE.md).
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- [Architecture Guide](./ARCHITECTURE.md) — System architecture and internals
|
||||
- [User Guide](../guides/USER_GUIDE.md) — Providers, combos, CLI integration
|
||||
- [Auto-Combo Engine](../routing/AUTO-COMBO.md) — 6-factor scoring, mode packs
|
||||
@@ -1,3 +1,9 @@
|
||||
---
|
||||
title: "Compression Engines"
|
||||
version: 3.8.0
|
||||
lastUpdated: 2026-05-13
|
||||
---
|
||||
|
||||
# Compression Engines
|
||||
|
||||
OmniRoute compression is built around engine contracts. A mode can run one engine directly
|
||||
@@ -21,13 +27,24 @@ The registry lives in `open-sse/services/compression/engines/registry.ts`. Engin
|
||||
contract:
|
||||
|
||||
- `id`: stable engine id such as `caveman` or `rtk`
|
||||
- `label`: dashboard-readable name
|
||||
- `supports(mode)`: whether the engine can execute a compression mode
|
||||
- `compress(input)`: transforms text/messages and returns stats
|
||||
- `apply(text, config)`: legacy execution path used by stacked pipelines
|
||||
- `compress(input, config)`: primary execution path returning text + stats
|
||||
- `getConfigSchema()`: returns the JSON-Schema-like shape of valid config
|
||||
- `validateConfig(config)`: returns `{ valid, errors[] }`
|
||||
|
||||
Registration uses `registerCompressionEngine(engine)` (or `registerEngine` for advanced cases),
|
||||
which calls `assertValidEngine()` and `validateConfig(defaultConfig)` before accepting.
|
||||
Use `unregisterCompressionEngine(id)` to remove an engine at runtime.
|
||||
|
||||
`strategySelector.ts` registers the built-in engines before compression runs. This lets preview,
|
||||
runtime compression, stacked mode, tests, and future engines use the same execution path.
|
||||
|
||||
### MCP description compression (related)
|
||||
|
||||
A separate registry compresses MCP tool description metadata at registry-level — see
|
||||
`open-sse/mcp-server/descriptionCompressor.ts` and [MCP-SERVER.md](../frameworks/MCP-SERVER.md). It reuses
|
||||
Caveman rules but operates on tool metadata, not request payloads.
|
||||
|
||||
## Caveman
|
||||
|
||||
Caveman mode focuses on semantic condensation of normal prose:
|
||||
@@ -64,7 +81,7 @@ RTK mode focuses on command and tool output:
|
||||
The dashboard surface is `Dashboard -> Context & Cache -> RTK`.
|
||||
|
||||
Operational details for custom filters, trust, verify, and raw-output recovery live in
|
||||
[`RTK_COMPRESSION.md`](RTK_COMPRESSION.md).
|
||||
[`RTK_COMPRESSION.md`](./RTK_COMPRESSION.md).
|
||||
|
||||
RTK upstream reports `60-90%` savings for command-output compression. Its README example shows a
|
||||
30-minute Claude Code session going from `~118,000` tokens to `~23,900`, or `79.7%` saved.
|
||||
@@ -1,3 +1,9 @@
|
||||
---
|
||||
title: "🗜️ Prompt Compression Guide — OmniRoute"
|
||||
version: 3.8.0
|
||||
lastUpdated: 2026-05-13
|
||||
---
|
||||
|
||||
# 🗜️ Prompt Compression Guide — OmniRoute
|
||||
|
||||
> Save 15-95% on eligible context automatically. For a quick overview, see the [README Compression section](../README.md#%EF%B8%8F-prompt-compression--save-15-95-eligible-tokens-automatically).
|
||||
@@ -267,10 +273,10 @@ RTK mode is inspired by **[RTK - Rust Token Killer](https://github.com/rtk-ai/rt
|
||||
|
||||
## See Also
|
||||
|
||||
- [Environment Config](ENVIRONMENT.md) — Compression environment variables
|
||||
- [Architecture Guide](ARCHITECTURE.md) — Compression pipeline internals
|
||||
- [User Guide](USER_GUIDE.md) — Getting started with compression
|
||||
- [RTK Compression](RTK_COMPRESSION.md) — RTK filters, trust model, verify gate, raw-output recovery
|
||||
- [Compression Engines](COMPRESSION_ENGINES.md) — Caveman, RTK, stacked, APIs, MCP, dashboard
|
||||
- [Compression Rules Format](COMPRESSION_RULES_FORMAT.md) — JSON rule-pack format
|
||||
- [Compression Language Packs](COMPRESSION_LANGUAGE_PACKS.md) — Language-specific Caveman rules
|
||||
- [Environment Config](../reference/ENVIRONMENT.md) — Compression environment variables
|
||||
- [Architecture Guide](../architecture/ARCHITECTURE.md) — Compression pipeline internals
|
||||
- [User Guide](../guides/USER_GUIDE.md) — Getting started with compression
|
||||
- [RTK Compression](./RTK_COMPRESSION.md) — RTK filters, trust model, verify gate, raw-output recovery
|
||||
- [Compression Engines](./COMPRESSION_ENGINES.md) — Caveman, RTK, stacked, APIs, MCP, dashboard
|
||||
- [Compression Rules Format](./COMPRESSION_RULES_FORMAT.md) — JSON rule-pack format
|
||||
- [Compression Language Packs](./COMPRESSION_LANGUAGE_PACKS.md) — Language-specific Caveman rules
|
||||
@@ -1,3 +1,9 @@
|
||||
---
|
||||
title: "Compression Language Packs"
|
||||
version: 3.8.0
|
||||
lastUpdated: 2026-05-13
|
||||
---
|
||||
|
||||
# Compression Language Packs
|
||||
|
||||
Caveman compression can load language-specific rule packs in addition to the built-in English rules.
|
||||
@@ -12,16 +18,20 @@ Language packs live under:
|
||||
open-sse/services/compression/rules/<language>/
|
||||
```
|
||||
|
||||
Current shipped packs include:
|
||||
Current shipped packs (verified against `rules/` directory contents):
|
||||
|
||||
| Language | Directory |
|
||||
| ------------------- | -------------- |
|
||||
| English | `rules/en/` |
|
||||
| Portuguese (Brazil) | `rules/pt-BR/` |
|
||||
| Spanish | `rules/es/` |
|
||||
| German | `rules/de/` |
|
||||
| French | `rules/fr/` |
|
||||
| Japanese | `rules/ja/` |
|
||||
| Language | Directory | Rule categories present |
|
||||
| ------------------- | -------------- | --------------------------------------------------- |
|
||||
| English | `rules/en/` | `context`, `dedup`, `filler`, `structural`, `ultra` |
|
||||
| Spanish | `rules/es/` | `context`, `dedup`, `filler`, `structural`, `ultra` |
|
||||
| Portuguese (Brazil) | `rules/pt-BR/` | `context`, `filler`, `structural` |
|
||||
| German | `rules/de/` | `context`, `filler`, `structural` |
|
||||
| French | `rules/fr/` | `context`, `filler`, `structural` |
|
||||
| Japanese | `rules/ja/` | `context`, `filler`, `structural` |
|
||||
|
||||
> **Parity note:** `en` and `es` packs have the full 5 categories; `pt-BR`, `de`, `fr`, `ja` ship 3 categories. The missing `dedup` and `ultra` categories silently fall back to the English built-ins. Contributions welcome to add `dedup.json` and `ultra.json` for the smaller packs.
|
||||
>
|
||||
> The canonical category list and per-category schema live in [`open-sse/services/compression/rules/_schema.json`](../../open-sse/services/compression/rules/_schema.json) (JSON Schema draft 2020-12).
|
||||
|
||||
## Language Detection
|
||||
|
||||
@@ -58,7 +68,7 @@ used when Caveman compresses message text.
|
||||
## Adding a Language Pack
|
||||
|
||||
1. Create `open-sse/services/compression/rules/<language>/<pack>.json`.
|
||||
2. Use the Caveman rule format from `docs/COMPRESSION_RULES_FORMAT.md`.
|
||||
2. Use the Caveman rule format from `docs/compression/COMPRESSION_RULES_FORMAT.md`.
|
||||
3. Keep replacements conservative and avoid changing code, identifiers, URLs, or JSON.
|
||||
4. Add or update tests for language selection and replacement behavior.
|
||||
5. Expose new dashboard/i18n labels if the language appears in UI selectors.
|
||||
@@ -1,8 +1,17 @@
|
||||
---
|
||||
title: "Compression Rules Format"
|
||||
version: 3.8.0
|
||||
lastUpdated: 2026-05-13
|
||||
---
|
||||
|
||||
# Compression Rules Format
|
||||
|
||||
Compression rules are JSON files loaded at runtime. They are intentionally data-only so new
|
||||
language packs and RTK command filters can be reviewed without changing engine code.
|
||||
|
||||
> **Canonical schema (source of truth):** [`open-sse/services/compression/rules/_schema.json`](../../open-sse/services/compression/rules/_schema.json) (JSON Schema draft 2020-12).
|
||||
> The examples below are illustrative — when in doubt, validate your pack against `_schema.json`.
|
||||
|
||||
## Caveman Rule Packs
|
||||
|
||||
Caveman rule packs live under:
|
||||
@@ -1,3 +1,9 @@
|
||||
---
|
||||
title: "RTK Compression"
|
||||
version: 3.8.0
|
||||
lastUpdated: 2026-05-13
|
||||
---
|
||||
|
||||
# RTK Compression
|
||||
|
||||
RTK compression is OmniRoute's command-aware compression engine for terminal and tool output. It is
|
||||
@@ -70,7 +76,7 @@ skipped and reported by `/api/context/rtk/filters` diagnostics. Invalid built-in
|
||||
|
||||
## Filter DSL
|
||||
|
||||
Filters use the JSON schema described in [Compression Rules Format](COMPRESSION_RULES_FORMAT.md).
|
||||
Filters use the JSON schema described in [Compression Rules Format](./COMPRESSION_RULES_FORMAT.md).
|
||||
The runtime applies these stages in order:
|
||||
|
||||
```txt
|
||||
62
docs/diagrams/README.md
Normal file
62
docs/diagrams/README.md
Normal file
@@ -0,0 +1,62 @@
|
||||
---
|
||||
title: "Diagrams"
|
||||
version: 3.8.0
|
||||
lastUpdated: 2026-05-13
|
||||
---
|
||||
|
||||
# Diagrams
|
||||
|
||||
Mermaid sources (`.mmd`) and exported SVGs for OmniRoute v3.8.0 architecture flows.
|
||||
|
||||
## Canonical diagrams
|
||||
|
||||
| Source | Exported | Used in |
|
||||
| -------------------------------------------------- | ---------------------------------------- | ------------------------------------------------------------------------------ |
|
||||
| [request-pipeline.mmd](./request-pipeline.mmd) | [SVG](./exported/request-pipeline.svg) | docs/architecture/ARCHITECTURE.md, docs/architecture/CODEBASE_DOCUMENTATION.md |
|
||||
| [auto-combo-9factor.mmd](./auto-combo-9factor.mmd) | [SVG](./exported/auto-combo-9factor.svg) | docs/routing/AUTO-COMBO.md |
|
||||
| [resilience-3layers.mmd](./resilience-3layers.mmd) | [SVG](./exported/resilience-3layers.svg) | docs/architecture/RESILIENCE_GUIDE.md, CLAUDE.md |
|
||||
| [i18n-flow.mmd](./i18n-flow.mmd) | [SVG](./exported/i18n-flow.svg) | docs/guides/I18N.md |
|
||||
| [mcp-tools-37.mmd](./mcp-tools-37.mmd) | [SVG](./exported/mcp-tools-37.svg) | docs/frameworks/MCP-SERVER.md |
|
||||
| [cloud-agent-flow.mmd](./cloud-agent-flow.mmd) | [SVG](./exported/cloud-agent-flow.svg) | docs/frameworks/CLOUD_AGENT.md |
|
||||
| [authz-pipeline.mmd](./authz-pipeline.mmd) | [SVG](./exported/authz-pipeline.svg) | docs/architecture/AUTHZ_GUIDE.md |
|
||||
| [db-schema-overview.mmd](./db-schema-overview.mmd) | [SVG](./exported/db-schema-overview.svg) | docs/architecture/CODEBASE_DOCUMENTATION.md |
|
||||
|
||||
## How to update
|
||||
|
||||
1. Edit `*.mmd`.
|
||||
2. Re-render: `npm run docs:render-diagrams` (uses `@mermaid-js/mermaid-cli`).
|
||||
3. Commit both `.mmd` and `.svg`.
|
||||
|
||||
If `@mermaid-js/mermaid-cli` is not available locally, install it once:
|
||||
|
||||
```bash
|
||||
npm install -g @mermaid-js/mermaid-cli
|
||||
```
|
||||
|
||||
The script renders every `.mmd` in `docs/diagrams/` into `docs/diagrams/exported/*.svg`
|
||||
with a white background, suitable for both dark and light themes.
|
||||
|
||||
## Linking from a doc
|
||||
|
||||
From a doc in `docs/<subfolder>/`, the relative path becomes `../diagrams/...`:
|
||||
|
||||
```markdown
|
||||

|
||||
|
||||
> Source: [../diagrams/request-pipeline.mmd](../diagrams/request-pipeline.mmd)
|
||||
```
|
||||
|
||||
From the repo root (e.g. `CLAUDE.md`):
|
||||
|
||||
```markdown
|
||||

|
||||
```
|
||||
|
||||
## Conventions
|
||||
|
||||
- One concept per diagram. Don't try to fit the whole platform in one chart.
|
||||
- Keep node labels short (3-6 words). Use `<br/>` for line breaks inside nodes.
|
||||
- Prefer `flowchart LR` for pipelines and `flowchart TB` for layered models.
|
||||
- Use `sequenceDiagram` for interactive (request/response) flows.
|
||||
- Use `erDiagram` for database schema overviews.
|
||||
- Update both `.mmd` and `.svg` in the same commit. Keep them in lock-step.
|
||||
23
docs/diagrams/authz-pipeline.mmd
Normal file
23
docs/diagrams/authz-pipeline.mmd
Normal file
@@ -0,0 +1,23 @@
|
||||
%% AuthZ pipeline (3 route classes + policy evaluation)
|
||||
%% Reflects: src/middleware.ts, src/server/authz/pipeline.ts,
|
||||
%% src/server/authz/policies/{public,clientApi,management}.ts
|
||||
%% v3.8.0
|
||||
flowchart LR
|
||||
Req["Incoming request"] --> Strip["Strip trusted internal headers<br/>(x-omniroute-auth-*)"]
|
||||
Strip --> Classify["classifyRoute()"]
|
||||
Classify -->|PUBLIC| PubP["publicPolicy<br/>(login, health, status)"]
|
||||
Classify -->|CLIENT_API| CliP["clientApiPolicy<br/>(/api/v1/*, /v1/*)"]
|
||||
Classify -->|MANAGEMENT| MgmtP["managementPolicy<br/>(dashboard, settings, admin)"]
|
||||
|
||||
PubP -->|allow| Stamp["Stamp x-omniroute-auth-*<br/>(kind / id / label / scopes)"]
|
||||
CliP -->|extract Bearer| Validate["validateApiKey()"]
|
||||
Validate -->|valid| Stamp
|
||||
Validate -->|"invalid (REQUIRE_API_KEY=true)"| Reject401["401 AUTH_002"]
|
||||
Validate -->|"absent (REQUIRE_API_KEY=false)"| Anon["anonymous fallthrough"] --> Stamp
|
||||
|
||||
MgmtP -->|session ok| Stamp
|
||||
MgmtP -->|Bearer w/ manage scope| Stamp
|
||||
MgmtP -->|"Bearer invalid"| Reject403["403 AUTH_001"]
|
||||
MgmtP -->|no auth| Reject401Mgmt["401 / 302 /login"]
|
||||
|
||||
Stamp --> Handler["Handler<br/>(uses assertAuth)"]
|
||||
23
docs/diagrams/auto-combo-9factor.mmd
Normal file
23
docs/diagrams/auto-combo-9factor.mmd
Normal file
@@ -0,0 +1,23 @@
|
||||
%% Auto-Combo 9-factor scoring
|
||||
%% Reflects: open-sse/services/autoCombo/scoring.ts (DEFAULT_WEIGHTS, sum = 1.0)
|
||||
%% v3.8.0
|
||||
flowchart TB
|
||||
Request["Incoming request"] --> Candidates["Eligible candidates<br/>(provider × model × account)"]
|
||||
Candidates --> Score["Compute composite score<br/>per candidate"]
|
||||
|
||||
subgraph Factors["9-factor scoring weights (sum = 1.0)"]
|
||||
f1["health (0.22)"]
|
||||
f2["quota (0.17)"]
|
||||
f3["costInv (0.17)"]
|
||||
f4["latencyInv (0.13)"]
|
||||
f5["taskFit (0.08)"]
|
||||
f6["specificityMatch (0.08)"]
|
||||
f7["stability (0.05)"]
|
||||
f8["tierPriority (0.05)"]
|
||||
f9["tierAffinity (0.05)"]
|
||||
end
|
||||
|
||||
Score --> Factors
|
||||
Factors --> Final["Sort by score<br/>(desc)"]
|
||||
Final --> Top["Pick top-N targets"]
|
||||
Top --> Dispatch["Dispatch sequentially<br/>(short-circuit on success)"]
|
||||
32
docs/diagrams/cloud-agent-flow.mmd
Normal file
32
docs/diagrams/cloud-agent-flow.mmd
Normal file
@@ -0,0 +1,32 @@
|
||||
%% Cloud Agent task lifecycle
|
||||
%% Reflects: src/lib/cloudAgent/* and src/app/api/v1/agents/tasks/*
|
||||
%% Supported agents: codex-cloud, devin, jules
|
||||
%% v3.8.0
|
||||
sequenceDiagram
|
||||
participant U as User
|
||||
participant API as /v1/agents/tasks
|
||||
participant Reg as Cloud Agent Registry
|
||||
participant Ag as Cloud Agent<br/>(codex-cloud / devin / jules)
|
||||
participant DB as SQLite domain DB
|
||||
|
||||
U->>API: POST createTask (description, sources)
|
||||
API->>API: Zod validation + AuthZ (management)
|
||||
API->>Reg: getAgent(providerId)
|
||||
Reg->>Ag: instantiate with credentials
|
||||
Ag->>Ag: createTask() returns planning
|
||||
Ag-->>API: taskId + status: planning
|
||||
API->>DB: insertCloudAgentTask
|
||||
API-->>U: taskId
|
||||
|
||||
U->>API: GET /tasks/:id (poll)
|
||||
API->>Ag: getStatus(externalId)
|
||||
Ag-->>API: status, plan, messages
|
||||
API->>DB: updateCloudAgentTask
|
||||
API-->>U: snapshot
|
||||
|
||||
U->>API: POST approvePlan
|
||||
API->>Ag: approvePlan(externalId)
|
||||
Ag-->>API: status: running
|
||||
Note over Ag: Async execution upstream
|
||||
|
||||
Ag-->>DB: stream events / final result
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user