🌐 Languages: 🇺🇸 English · 🇪🇹 am · 🇸🇦 ar · 🇦🇿 az · 🇧🇬 bg · 🇧🇩 bn · 🇧🇦 bs · 🇨🇿 cs · 🇩🇰 da · 🇩🇪 de · 🇬🇷 el · 🇪🇸 es · 🇪🇪 et · 🇮🇷 fa · 🇫🇮 fi · 🇫🇷 fr · 🇮🇪 ga · 🇮🇳 gu · 🇳🇬 ha · 🇮🇳 hi · 🇭🇷 hr · 🇭🇺 hu · 🇦🇲 hy · 🇮🇩 id · 🇳🇬 ig · 🇮🇹 it · 🇯🇵 ja · 🇬🇪 ka · 🇰🇭 km · 🇮🇳 kn · 🇰🇷 ko · 🇱🇹 lt · 🇱🇻 lv · 🇮🇳 ml · 🇮🇳 mr · 🇲🇾 ms · 🇲🇹 mt · 🇲🇲 my · 🇳🇵 ne · 🇳🇱 nl · 🇳🇴 no · 🇮🇳 or · 🇮🇳 pa · 🇵🇭 phi · 🇵🇱 pl · 🇵🇹 pt · 🇧🇷 pt-BR · 🇷🇴 ro · 🇷🇺 ru · 🇱🇰 si · 🇸🇰 sk · 🇸🇮 sl · 🇷🇸 sr · 🇸🇪 sv · 🇰🇪 sw · 🇮🇳 ta · 🇮🇳 te · 🇹🇭 th · 🇹🇷 tr · 🇺🇦 uk-UA · 🇵🇰 ur · 🇺🇿 uz · 🇻🇳 vi · 🇳🇬 yo · 🇨🇳 zh-CN · 🇹🇼 zh-TW
גרסה: v3.8.44 עדכון אחרון: 2026-09-09 קהל יעד: מהנדסים שמוסיפים, מתחזקים או מנפים שגיאות בשירותים מוטמעים (9Router, CLIProxyAPI, Mux, Bifrost, open-wa).
שירותים מוטמעים הם כלי sidecar המותקנים מקומית כתהליכים, שאותם OmniRoute מתקין, מפקח עליהם וחושף כיעדי ניתוב מהמעלה הראשונה. בניגוד לספקים חיצוניים (שהגישה אליהם מתבצעת דרך האינטרנט באמצעות מפתחות API), שירותים מוטמעים פועלים באותו מחשב שבו פועל OmniRoute ומתקשרים דרך loopback.
- סקירה כללית
- ארכיטקטורה — 4 שכבות
- מכונת מצבי מחזור החיים
- מדריך עזר ל-API
- אבטחה
- הוספת שירות מוטמע חדש
- פתרון בעיות
- שאלות נפוצות
שישה שירותים מוטמעים:
| שירות | חבילת npm | פורט ברירת מחדל | מטרה |
|---|---|---|---|
| 9Router | 9router |
20130 | נתב AI שבו OmniRoute יכול להשתמש כתת-ספק. המודלים נחשפים בתבנית 9router/{sub}/{model} |
| CLIProxyAPI | קובץ בינארי ממהדורת GitHub (cliproxy) |
8317 | מתאם proxy מקומי עבור תהליכי אימות של Anthropic CLI. מספק ניתוב חלופי כאשר תוקפי OAuth פגים |
| Mux | mux (mux server ללא ממשק גרפי) |
8322 | daemon מקומי לתזמור סוכנים (coder/mux). מנוהל רק מבחינת מחזור החיים — אינו יעד ניתוב (ללא העברת LLM דרך proxy). |
| Bifrost | @maximhq/bifrost |
8080 | backend ממסר לשער AI ב-Go. כאשר הוא פועל, הוא נבחר אוטומטית על ידי נתיב הממסר (/v1/relay/) |
| Dario | @askalf/dario |
3456 | proxy למינוי Claude — חלופה/גיבוי ל-CLIProxyAPI עבור תעבורה במבנה Claude Code; המפתח המוזרק הופך ל-DARIO_ADMIN_TOKEN, המגן על מישור הבקרה של OAuth תחת /admin/* |
| open-wa | @open-wa/wa-automate |
8323 | אוטומציה של WhatsApp Web (Chromium ללא ממשק גרפי באמצעות Puppeteer). מנוהל רק מבחינת מחזור החיים — אינו יעד ניתוב. |
כל ששת השירותים פועלים לפי אותו מודל פיקוח:
- OmniRoute מתקין אותם תחת
DATA_DIR/services/{name}/(בבידוד מה-package.jsonשל OmniRoute עצמו) - OmniRoute מפעיל ומנטר אותם כתהליכי צאצא
- OmniRoute מזריק מפתח API ארעי לסביבת תהליך הצאצא ומחליף אותו ללא זמן השבתה (כאשר הדבר רלוונטי)
- כל נתיבי הניהול (
/api/services/*) הם LOCAL_ONLY — נגישים רק מ-loopback (כלל קשיח מס' 17)
| החלטה | ערך |
|---|---|
| גישת לוח המחוונים לממשק המקורי של 9Router | Reverse proxy ב-/dashboard/providers/services/9router/embed/* |
| מנגנון התקנה | npm install {package} באמצעות execFile (ללא אינטרפולציית shell) |
| מצב צריכה | הספק רשום כ-9router/{sub}/{model} במנוע הניתוב |
| ניהול מפתחות API | OmniRoute יוצר, מצפין במנוחה (AES-256-GCM) ומזריק באמצעות משתני סביבה |
| מיקום לוח המחוונים | /dashboard/providers/services (שלוש לשוניות) |
| הפעלה אוטומטית | מתג נפרד לכל שירות, כבוי כברירת מחדל |
┌────────────────────────────────────────────────────────────────────┐
│ שכבה 1 — ממשק משתמש │
│ /dashboard/providers/services (לשוניות: CLIProxyAPI | 9Router | Mux)│
│ יומנים בזמן אמת (SSE), הפעלה/עצירה/הפעלה מחדש/עדכון, הגדרות, התקנה│
│ │
│ src/app/(dashboard)/dashboard/providers/services/ │
│ ├── page.tsx מעטפת + ניתוב לשוניות לפי ?tab= │
│ ├── tabs/ CliproxyServiceTab, NinerouterServiceTab,│
│ │ MuxServiceTab │
│ └── components/ ServiceStatusCard, ServiceLifecycleButtons,│
│ ServiceLogsPanel, ApiKeyCard, ... │
└──────────────────────┬─────────────────────────────────────────────┘
│ HTTP (fetch של Next.js)
┌──────────────────────▼─────────────────────────────────────────────┐
│ שכבה 2 — API (LOCAL_ONLY — ממשק loopback בלבד) │
│ │
│ /api/services/9router/{install|start|stop|restart|update| │
│ rotate-key|status|auto-start|logs} │
│ /api/services/cliproxy/{install|start|stop|restart|update| │
│ status|auto-start|logs} │
│ /api/services/mux/{install|start|stop|restart|update| │
│ status|auto-start|logs} │
│ /dashboard/providers/services/9router/embed/[...path] │
│ (פרוקסי HTTP + WebSocket הפוך → שירות 9Router במעלה הזרם) │
│ │
│ שער: LOCAL_ONLY_API_PREFIXES כולל את "/api/services/" ואת │
│ "/dashboard/providers/services/*/embed/" │
└──────────────────────┬─────────────────────────────────────────────┘
│ קריאות בתוך התהליך
┌──────────────────────▼─────────────────────────────────────────────┐
│ שכבה 3 — ServiceSupervisor (src/lib/services/) │
│ │
│ ServiceSupervisor.ts מפקח כללי (child_process.spawn) │
│ ├── התקנה: execFile('npm', ['install', pkg, '--prefix']) │
│ ├── הפעלה: spawn(node, [entrypoint], {env, cwd}) │
│ ├── מפתח API: crypto.randomBytes(32) → env NINEROUTER_API_KEY │
│ ├── פורט: 20130 עבור 9Router (ניתן להגדרה) │
│ ├── יומנים: מאגר טבעתי של stdio בגודל 5 MB → אירועי SSE │
│ ├── תקינות: HTTP GET /health כל 2–5 שניות, התאוששות עצלה │
│ └── מחזור חיים: SIGTERM 15 שניות → SIGKILL │
│ │
│ registry.ts getSupervisor(name) / registerSupervisor() │
│ bootstrap.ts מאתחל את כל SERVICES[] בעת הפעלת התהליך │
│ apiKey.ts getOrCreateApiKey(), generateServiceApiKey() │
│ modelSync.ts GET /v1/models מחזורי → הטבלה service_models │
│ ringBuffer.ts מאגר יומנים מעגלי (5 MB לכל שירות) │
│ healthCheck.ts בדיקת תקינות HTTP בתשאול מחזורי │
│ installers/ ninerouter.ts, cliproxy.ts, mux.ts, openwa.ts │
│ (מתאמי התקנה) │
└──────────────────────┬─────────────────────────────────────────────┘
│ HTTP תואם OpenAI (loopback)
┌──────────────────────▼─────────────────────────────────────────────┐
│ שכבה 4 — ספק / ניתוב │
│ │
│ open-sse/executors/ninerouter.ts │
│ מאתר מחדש את הפורט ומפתח ה-API בכל בקשה (ללא מטמון). │
│ מסיר את הקידומת "9router/" ממזהה המודל לפני העברה בפרוקסי. │
│ מחזיר 503 service_not_running אם המפקח אינו במצב "running". │
│ │
│ src/shared/constants/providers.ts │
│ רשומה עבור "9router": isEmbeddedService: true │
│ │
│ open-sse/config/providerRegistry.ts │
│ המודלים נשמרים בתור "9router/{sub}/{model}" (עם קידומת). │
│ מסונכרנים כל 5 דקות על ידי modelSync.ts. │
│ │
│ Mux מנוהל מבחינת מחזור החיים בלבד (שכבות 1–3) — זהו daemon │
│ לתזמור סוכנים, ולא פרוקסי LLM, ולכן אין לו executor/רשומת ספק │
│ בשכבה 4 והוא לעולם אינו יעד ניתוב. │
└────────────────────────────────────────────────────────────────────┘
| קובץ | תפקיד |
|---|---|
src/lib/services/ServiceSupervisor.ts |
מחלקת ליבה: מחזור חיים, נעילה, תקינות, מאגר מעגלי |
src/lib/services/bootstrap.ts |
רישום ברמת התהליך והפעלה אוטומטית |
src/lib/services/registry.ts |
מפת יחידון tool → supervisor |
src/lib/services/apiKey.ts |
יצירת מפתחות, הצפנת AES-256-GCM במצב מנוחה |
src/lib/services/modelSync.ts |
סנכרון מודלים תקופתי (5 דקות) + לפי דרישה |
src/lib/services/ringBuffer.ts |
מאגר יומן מעגלי בנפח 5 MB עם הרשמת SSE |
src/lib/services/healthCheck.ts |
בדיקת תקינות HTTP (מרווח זמן ניתן להגדרה) |
src/lib/services/installers/ninerouter.ts |
התקנה/עדכון/הסרה באמצעות npm עבור 9Router |
src/lib/services/installers/cliproxy.ts |
התקנה/עדכון/הסרה באמצעות npm עבור CLIProxyAPI |
src/lib/services/installers/mux.ts |
התקנה/עדכון/הסרה באמצעות npm עבור Mux |
src/lib/services/installers/openwa.ts |
התקנה/עדכון/הסרה באמצעות npm עבור open-wa |
src/app/api/services/9router/_lib.ts |
פונקציית העזר getOrInitSupervisor() |
src/app/api/services/[name]/logs/route.ts |
נקודת קצה משותפת ליומני SSE |
open-sse/executors/ninerouter.ts |
מפעיל ספק (שכבה 4) |
install()
┌─────────────┐ ──────────► ┌─────────────┐
│ not_installed│ │ stopped │◄──────────────────┐
└─────────────┘ └──────┬──────┘ │
│ start() │
▼ │ stop()
┌──────────┐ │
│ starting │ │
└────┬─────┘ │
בדיקת התקינות הצליחה │ קריסה / SIGTERM │
┌────▼─────┐ (יציאה בתוך 5 שנ׳) │
│ running │──── קריסה ──────────►┤
└────┬─────┘ ┌─▼────┐
stop() │ │error │
▼ └──────┘
┌──────────┐
│ stopping │
└──────────┘
המצבים מאוחסנים בטבלת מסד הנתונים version_manager (בעמודה status) ומשוקפים
במצב שבזיכרון של ServiceSupervisor. המצב שבזיכרון הוא הסמכות הקובעת עבור
תהליך פועל; מצב מסד הנתונים הוא גיבוי מתמיד בעת האתחול.
| מ־ | אירוע | אל |
|---|---|---|
not_installed |
install() מסתיים בהצלחה |
stopped |
stopped |
מתבצעת קריאה ל־start() |
starting |
starting |
בדיקת התקינות מחזירה 200 | running |
starting |
התהליך מסתיים לפני שנהיה תקין | error |
running |
מתבצעת קריאה ל־stop() |
stopping → stopped |
running |
התהליך מסתיים באופן בלתי צפוי (< 5 שנ׳) | error (קריסה מהירה) |
running |
התהליך מסתיים באופן בלתי צפוי (> 5 שנ׳) | error |
error |
מתבצעת קריאה ל־start() |
starting |
| כל מצב | stop() בזמן stopping |
ללא פעולה |
ServiceSupervisor מסדר פעולות של מחזור החיים באופן סדרתי באמצעות נעילת פעולות אסינכרונית
(withLock()). קריאות מקבילות ל־start() באותו מפקח מובילות ליצירת תהליך אחד
בדיוק; הקורא השני ממתין ומחזיר את המצב הקיים. כך נמנעים מצבי מרוץ כאשר,
לדוגמה, הפעלה אוטומטית וכפתור בממשק המשתמש מופעלים בו־זמנית.
כל הנתיבים תחת /api/services/ הם LOCAL_ONLY (ממשק loopback בלבד, כלל קשיח מס׳ 17).
בקשות שאינן מ־loopback מקבלות 403 LOCAL_ONLY ללא תלות באסימון האימות.
התקנת 9Router מ־npm. יוצרת את DATA_DIR/services/9router/ עם
package.json ו־node_modules/ משלו. אינה מתנגשת עם יחסי התלות של OmniRoute.
גוף הבקשה (כל השדות אופציונליים):
{ "version": "latest" }| שדה | סוג | ברירת מחדל | תיאור |
|---|---|---|---|
version |
string |
"latest" |
תג גרסת npm או semver להתקנה |
תגובות:
| סטטוס | תיאור |
|---|---|
200 |
{ ok: true, installedVersion: "x.y.z", path: "..." } |
400 |
גוף בקשה לא תקין (כשל אימות של Zod) |
409 |
התקנה כבר מתבצעת (הנעילה מוחזקת) |
500 |
התקנת npm נכשלה — ראו message לקבלת שגיאה ידידותית |
הערות: נעשה שימוש ב־execFile('npm', [...]) — ללא מעטפת וללא אינטרפולציה (כלל קשיח מס׳ 13).
שגיאות EACCES מוצגות כהודעות ידידותיות.
הפעלת 9Router. רושמת מפקח אם עדיין לא נרשם, ולאחר מכן קוראת
ל־supervisor.start(). הפעולה אידמפוטנטית כאשר השירות כבר פועל.
גוף הבקשה: אין
תגובות:
| סטטוס | תיאור |
|---|---|
200 |
אובייקט ServiceStatus (ראו את הסכמה להלן) |
409 |
9Router אינו מותקן (status: "not_installed") |
503 |
ההפעלה נכשלה (שגיאת תהליך — ראו lastError) |
סכמת ServiceStatus:
{
"tool": "9router",
"state": "running",
"pid": 12345,
"port": 20130,
"health": "healthy",
"startedAt": "2026-05-25T10:00:00.000Z",
"lastError": null
}עצירה מסודרת של 9Router. שולחת SIGTERM, ממתינה 15 שניות ולאחר מכן שולחת SIGKILL אם התהליך עדיין פעיל. הפעולה אידמפוטנטית כאשר השירות כבר עצור.
גוף הבקשה: אין
תגובות:
| סטטוס | תיאור |
|---|---|
200 |
ServiceStatus (מצב: "stopped") |
503 |
העצירה נכשלה באופן בלתי צפוי |
שקול ל־stop() ולאחר מכן start() תחת נעילת הפעולות.
גוף הבקשה: אין
תגובות: זהות לאלו של start (מוחזר ServiceStatus הסופי).
מעדכן את 9Router לגרסת npm חדשה יותר. אם השירות פועל, הוא נעצר תחילה, התקנת npm מופעלת (והגרסה החדשה יותר מותקנת במקום), ולאחר מכן השירות מופעל מחדש.
גוף הבקשה (כל השדות אופציונליים):
{ "version": "latest" }תגובות:
| סטטוס | תיאור |
|---|---|
200 |
{ ok: true, previousVersion: "...", installedVersion: "..." } |
400 |
גוף לא תקין |
500 |
עדכון npm נכשל |
יוצר מפתח API חדש עבור 9Router, מצפין אותו במצב מנוחה ומפעיל מחדש את השירות (אם הוא פועל), כדי שיטען את המפתח החדש מהסביבה שלו. המפתח הישן מבוטל באופן מיידי.
גוף הבקשה: ללא
תגובות:
| סטטוס | תיאור |
|---|---|
200 |
{ keyRotated: true, restarted: boolean } |
500 |
סבב החלפת המפתח נכשל |
אבטחה: המפתח החדש לעולם אינו מוחזר בתגובה (אין דליפת פרטי גישה).
הוא מאוחסן כשהוא מוצפן (AES-256-GCM) בטבלה version_manager.
מחזיר סטטוס משולב בזמן אמת + ממסד הנתונים, כולל מטא-נתוני גרסה ותצוגה מקדימה של מפתח ה-API.
תגובות:
| סטטוס | תיאור |
|---|---|
200 |
ראו את הסכמה להלן |
500 |
קריאת הסטטוס נכשלה |
סכמת התגובה:
{
"tool": "9router",
"state": "running",
"pid": 12345,
"port": 20130,
"health": "healthy",
"startedAt": "2026-05-25T10:00:00.000Z",
"lastError": null,
"installedVersion": "1.2.3",
"latestVersion": "1.2.4",
"updateAvailable": true,
"apiKeyMasked": "nr_****abcd",
"autoStart": false,
"providerExpose": false
}מחליף את מצב דגל ההפעלה האוטומטית. כאשר enabled: true, השירות יופעל אוטומטית
בפעם הבאה ש-OmniRoute יאותחל (אם השירות מותקן).
גוף הבקשה:
{ "enabled": true }תגובות:
| סטטוס | תיאור |
|---|---|
200 |
{ autoStart: true } |
400 |
גוף לא תקין |
זרם SSE של יומנים בזמן אמת ממאגר הטבעת של stdout/stderr של 9Router.
פרמטרים של השאילתה:
| פרמטר | סוג | ברירת מחדל | תיאור |
|---|---|---|---|
tail |
integer |
200 | מספר השורות ההיסטוריות שיישלחו תחילה (מקסימום 1000) |
filter |
string |
ללא | מסנן מחרוזת משנה שאינו תלוי רישיות (ללא regex — מוגן מ-ReDoS) |
אירועי SSE:
| אירוע | נתונים | תיאור |
|---|---|---|
snapshot |
LogLine[] |
הסיומת ההיסטורית הראשונית |
log |
LogLine |
שורת יומן בזמן אמת |
heartbeat |
{} |
שמירת חיבור פעיל כל 15 שניות |
סכמת LogLine:
{
"ts": 1716633600000,
"stream": "stdout",
"line": "[9router] Listening on :20130"
}תגובות:
| סטטוס | תיאור |
|---|---|
200 |
text/event-stream |
400 |
הפרמטר filter ארוך מדי (> 200 תווים) |
404 |
השירות לא נמצא (המפקח אינו רשום) |
ל-CLIProxyAPI יש אותו מבנה נקודות קצה כמו ל-9Router, למעט rotate-key, ובתוספת
accounts, provider-expose ו-auto-restart-adopted. כעת הוא מקבל
מפתח API ייעודי למישור הנתונים שמוזרק בעת יצירת התהליך (needsApiKey: true בתוך
bootstrap.ts, ומשמש לסנכרון מודלים); status כולל פחות שדות.
| מתודה | נתיב | תיאור |
|---|---|---|
POST |
/api/services/cliproxy/install |
התקנת CLIProxyAPI מ-npm |
POST |
/api/services/cliproxy/start |
הפעלת CLIProxyAPI |
POST |
/api/services/cliproxy/stop |
עצירת CLIProxyAPI |
POST |
/api/services/cliproxy/restart |
הפעלה מחדש של CLIProxyAPI |
POST |
/api/services/cliproxy/update |
עדכון לגרסה חדשה יותר |
GET |
/api/services/cliproxy/status |
סטטוס בזמן אמת + ממסד הנתונים (ללא apiKeyMasked) |
POST |
/api/services/cliproxy/auto-start |
החלפת מצב ההפעלה האוטומטית |
נקודת הקצה המשותפת GET /api/services/{name}/logs (ראו §4.1) פועלת עבור כל
ארבעת השירותים באמצעות המקטע הדינמי [name].
ל-Mux יש אותו מבנה נקודות קצה כמו ל-CLIProxyAPI — אין נתיב rotate-key בממשק
ה-API (אסימון ה-bearer נוצר באותו אופן כמו זה של 9Router באמצעות
getOrCreateApiKey("mux") ומוזרק דרך משתנה הסביבה MUX_SERVER_AUTH_TOKEN, אך
עדיין אין נקודת קצה ייעודית להחלפתו). Mux מנוהל רק מבחינת מחזור החיים: בניגוד
ל-9Router, אין לו מבצע שכבה 4 והוא לעולם אינו נרשם כספק ניתוב.
| מתודה | נתיב | תיאור |
|---|---|---|
POST |
/api/services/mux/install |
התקנת Mux מ-npm (npm i mux) |
POST |
/api/services/mux/start |
הפעלת Mux (mux server) |
POST |
/api/services/mux/stop |
עצירת Mux |
POST |
/api/services/mux/restart |
הפעלה מחדש של Mux |
POST |
/api/services/mux/update |
עדכון לגרסת npm חדשה יותר |
GET |
/api/services/mux/status |
סטטוס בזמן אמת + ממסד הנתונים |
POST |
/api/services/mux/auto-start |
החלפת מצב ההפעלה האוטומטית |
Bifrost הוא backend ממסר של שער AI שנכתב ב-Go (@maximhq/bifrost). הוא משתמש באותו
מבנה נקודות קצה כמו CLIProxyAPI (ללא rotate-key — Bifrost מנהל את מפתחות
הספקים שלו בעצמו בתוך config.json תחת -app-dir).
| מתודה | נתיב | תיאור |
|---|---|---|
POST |
/api/services/bifrost/install |
התקנת Bifrost מ־npm (@maximhq/bifrost) |
POST |
/api/services/bifrost/start |
הפעלת Bifrost בפורט 8080 (ברירת המחדל) |
POST |
/api/services/bifrost/stop |
עצירת Bifrost |
POST |
/api/services/bifrost/restart |
הפעלה מחדש של Bifrost |
POST |
/api/services/bifrost/update |
עדכון לגרסה חדשה יותר |
GET |
/api/services/bifrost/status |
מצב בזמן אמת + מצב מסד הנתונים |
POST |
/api/services/bifrost/auto-start |
הפעלה או השבתה של הפעלה אוטומטית |
GET |
/api/services/bifrost/logs |
זנב יומן דרך SSE (באמצעות הנתיב הדינמי המשותף [name]/logs) |
חיווט ניתוב: כאשר BIFROST_BASE_URL אינו מוגדר ומופע Bifrost
המנוהל פועל, getBifrostRoutingConfig() (בתוך routingBackend.ts) משתמש אוטומטית
ב־http://127.0.0.1:{port} ככתובת ה־URL הבסיסית של הממסר. משתנה הסביבה BIFROST_BASE_URL שמוגדר במפורש
תמיד מקבל עדיפות.
אותו מבנה מחזור חיים כמו בשירותים האחרים (install, start, stop, restart,
update, status, auto-start, auto-restart-adopted), ובנוסף מישור בקרה של OAuth
המוגן באסימון תחת admin/: admin/accounts, admin/import-from-omniroute,
admin/login-start, admin/login-complete (כולם מוגנים באמצעות DARIO_ADMIN_TOKEN).
open-wa (@open-wa/wa-automate) מפעיל מופע Chromium ללא ממשק גרפי (באמצעות
Puppeteer) כדי לבצע אוטומציה של WhatsApp Web. הוא משתמש באותו מבנה נקודות קצה כמו Mux (עדיין אין
נתיב rotate-key). הוא מנוהל רק ברמת מחזור החיים — אינו יעד ניתוב,
ואין לו רשומת מבצע/ספק בשכבה 4.
| מתודה | נתיב | תיאור |
|---|---|---|
POST |
/api/services/openwa/install |
התקנת open-wa מ־npm (@open-wa/wa-automate) |
POST |
/api/services/openwa/start |
הפעלת open-wa בפורט 8323 (ברירת המחדל) |
POST |
/api/services/openwa/stop |
עצירת open-wa |
POST |
/api/services/openwa/restart |
הפעלה מחדש של open-wa |
POST |
/api/services/openwa/update |
עדכון לגרסה חדשה יותר |
GET |
/api/services/openwa/status |
מצב בזמן אמת + מצב מסד הנתונים |
POST |
/api/services/openwa/auto-start |
הפעלה או השבתה של הפעלה אוטומטית |
GET |
/api/services/openwa/logs |
זנב יומן דרך SSE (באמצעות הנתיב הדינמי המשותף [name]/logs) |
מפתח API: מוזרק בתור WA_KEY — דריסת משתני הסביבה הגנרית של open-wa
עם הקידומת WA_* ממפה אותו לאפשרות ה־CLI --key/-k
(dist/cli/setup.js::envArgs(), אומת מול החבילה המותקנת בגרסה 4.76.0).
בעת יצירתו באמצעות generateServiceApiKey(), נוספת לו הקידומת ow_. open-wa
קורא את המפתח בחזרה מכותרת HTTP בשם key/api_key (ולא Authorization: Bearer); /api-docs* מוחרג במפורש מהבדיקה
(setupAuthenticationLayer בתוך dist/cli/server.js), ולכן בדיקת התקינות
אינה זקוקה לכותרת אימות.
צימוד: open-wa אינו רשמי ואינו משויך ל־WhatsApp — המספר המחובר חשוף לסכנת חסימה עקב מנגנון זיהוי האוטומציה של WhatsApp. בהפעלה הראשונה, קוד ה־QR לצימוד מודפס ל־stdout ומוצג דרך לוח היומנים/זרם ה־SSE הקיים — עדיין אין נקודת קצה ייעודית לתמונת QR באינטגרציה זו.
לוח הבקרה מטמיע את ממשק האינטרנט של 9Router בתוך iframe באמצעות פרוקסי הפוך פנימי בכתובת:
GET|POST|... /dashboard/providers/services/9router/embed/[...path]
פרוקסי זה:
- מעביר את הבקשה אל
http://127.0.0.1:{port}/{path}(לולאת משוב בלבד) - מסיר את הכותרות הנכנסות
cookieו־authorization(ללא דליפה של הפעלת OmniRoute) - מזריק
Authorization: Bearer {apiKey}לצורך אימות מול 9Router - מסיר מהתגובה את
set-cookie,content-security-policy,x-frame-options,cross-origin-* - משכתב תגובות HTML כדי להזריק
<base href>ולנרמל נתיבים מוחלטים (/foo←/dashboard/.../embed/foo)
שדרוגי WebSocket עבור לוח הבקרה המוטמע מטופלים על ידי שרת נלווה
בפורט ייעודי (ראו src/lib/services/embedWsProxy.ts).
אבטחה: נתיבי פרוקסי ההטמעה מסווגים תחת LOCAL_ONLY_API_PREFIXES
וניתן לגשת אליהם רק מלולאת המשוב. תוקף שמשיג JWT דרך
מנהרת Cloudflare/Ngrok אינו יכול להשתמש בפרוקסי כדי לגשת לשירותים המוטמעים.
כל הנתיבים תחת /api/services/ ו-/dashboard/providers/services/*/embed/
מסווגים כ-LOCAL_ONLY ב-src/server/authz/routeGuard.ts. בדיקת ה-loopback
מתבצעת ללא תנאי לפני כל ענף אימות:
הבקשה מגיעה
→ isLocalOnlyPath(path)?
→ לא-loopback → 403 LOCAL_ONLY (תמיד, לפני בדיקת האימות)
→ loopback → המשך לאימות הרגיל
כך נמנע מ-JWT שדלף (למשל, דרך מנהרה) להפעיל npm install או
יצירת תהליכים. ראו docs/security/ROUTE_GUARD_TIERS.md למטריצת הרמות
המלאה.
9Router ו-Mux דורשים מפתח API/אסימון bearer עבור נקודות הקצה של HTTP שלהם. OmniRoute:
- יוצר מפתח באמצעות
crypto.randomBytes(32).toString("base64url")עם קידומת ייעודית לשירות (nr_עבור 9Router,mx_עבור Mux). - מצפין אותו במנוחה באמצעות AES-256-GCM (אותו צופן המשמש לפרטי הגישה של הספק).
- מפענח ומזריק אותו כמשתנה סביבה בעת יצירת התהליך —
NINEROUTER_API_KEYעבור 9Router,MUX_SERVER_AUTH_TOKENעבור Mux (לעולם לא כדגל CLI, כדי שהאסימון לא יופיע לעולם ב-ps/רשימות תהליכים). - לעולם אינו מחזיר את המפתח הגלוי בתגובת HTTP כלשהי.
CLIProxyAPI מקבל מפתח ייעודי למישור הנתונים, המוזרק בעת יצירת התהליך
(needsApiKey: true — משמש לסנכרון מודלים מול המתאם).
פרוקסי ה-HTTP ההפוך (/dashboard/.../embed/[...path]) מוגדר באופן קשיח להעביר
רק אל http://127.0.0.1:{port}. הוא לעולם אינו עוקב אחר הפניות ליעדים
שאינם loopback. הספרייה ssrf-req-filter משמשת לדחיית כל כתובת URL של upstream
שנפתרת לכתובת מחוץ לטווח ה-loopback.
npm install מופעל באמצעות execFile('npm', ['install', pkg, '--prefix', dir]) —
ללא תבניות מחרוזת, ללא מעטפת וללא אינטרפולציה של נתיבים חיצוניים לתוך מחרוזת
הפקודה. ערכי זמן ריצה (יציאות, מפתחות API) מועברים דרך אובייקט ה-env של תהליך הבן.
כל תגובות השגיאה מ-/api/services/* עוברות דרך buildErrorBody() או
sanitizeErrorMessage(). הערכים הגולמיים של err.stack ו-err.message לעולם אינם מוחזרים
כלשונם למבצע הקריאה.
בצעו את 8 השלבים הבאים. קראו את המימושים הקיימים ב-src/lib/services/installers/
וב-src/app/api/services/ כמקור המוסמך.
צרו את src/lib/services/installers/{name}.ts לפי התבנית של ninerouter.ts:
export const NAME_PACKAGE = "your-npm-package";
export const NAME_DEFAULT_PORT = 20132; // בחרו יציאה פנויה
export async function install(version = "latest"): Promise<InstallResult> { ... }
export async function update(version = "latest"): Promise<InstallResult> { ... }
export async function uninstall(): Promise<void> { ... }
export function resolveSpawnArgs(apiKey: string, port: number): SpawnArgs { ... }
export async function getInstalledVersion(): Promise<string | null> { ... }
export async function getLatestVersion(): Promise<string | null> { ... }השתמשו ב-runNpm(['install', NAME_PACKAGE, '--prefix', dir]) מתוך installers/utils.ts
— לעולם אל תשתמשו ב-execSync או באינטרפולציה של מעטפת.
הוסיפו ServiceEntry למערך SERVICES ב-src/lib/services/bootstrap.ts:
{
tool: "myservice",
port: NAME_DEFAULT_PORT,
healthPath: "/health",
healthIntervalMs: 5_000,
stopTimeoutMs: 15_000,
logsBufferBytes: 5_242_880,
needsApiKey: true, // false אם אין צורך במפתח API
}הרחיבו את buildSpawnArgsFactory() כדי לטפל ב-cfg.tool === "myservice".
src/lib/services/providerPlugins/ מציג חוזה ServiceProviderPlugin
המאחד את שדות ה-ServiceEntry של bootstrap.ts בצד השרת ואת שדות תבנית המניפסט של
serviceBackends.ts לאובייקט אחד, במקום שהמבנה של אותו צד שרת יוגדר
בנפרד בשני קבצים שאינם קשורים זה לזה. נכון למועד כתיבה זה, רק 9router
הועבר — bootstrap.ts גוזר את הרשומה שלו ב-SERVICES[] מתוך
getServiceProviderPlugin("9router") (src/lib/services/providerPlugins/registry.ts),
וזורק שגיאת הפעלה אם התוסף חסר אי פעם. cliproxy, mux ו-bifrost
ממשיכים להשתמש בליטרלים הקיימים של SERVICES[] ללא שינוי.
ל-open-sse/config/providerPluginManifest.ts נוסף גם helper מצטבר בשם
createServiceBackendManifestEntry(pluginId, template), שבונה
ProviderPluginManifestEntry תקין מתוך רשומת SERVICE_BACKEND_MANIFEST_TEMPLATE — הוא
עדיין אינו מחובר לאף נתיב בקשות פעיל (לא ל-generateProviderPluginManifestFromRegistry()
ולא ל-/v1/providers/[provider]/models); הדבר נותר להמשך לאחר שהחוזה
יוכח עבור צד שרת שני.
נדחה ל-PR-י המשך, במעקב תחת issue #7333: העברת cliproxyapi דרך אותו
registry, הכללת mux/bifrost ב-union של ServiceBackendPluginId,
שילוב הטיפול המיוחד בניתוב המבצעים (open-sse/executors/index.ts,
open-sse/handlers/chatCore/executorProxy.ts) בחוזה התוסף, וחיבור
createServiceBackendManifestEntry() לנתיב קוד פעיל של מניפסט/מודלים.
ודאו שלשירות קיימת שורה ב-version_manager באמצעות מיגרציה תחת
src/lib/db/migrations/. השורה צריכה לכלול:
INSERT OR IGNORE INTO version_manager (tool, status, auto_start, provider_expose)
VALUES ('myservice', 'not_installed', 0, 0);תחת src/app/api/services/{name}/:
_lib.ts helper מסוג getOrInitSupervisor()
install/route.ts POST — קורא ל-installer.install()
start/route.ts POST — קורא ל-supervisor.start()
stop/route.ts POST — קורא ל-supervisor.stop()
restart/route.ts POST — קורא ל-supervisor.restart()
update/route.ts POST — קורא ל-installer.update()
status/route.ts GET — ממזג סטטוס חי + סטטוס ממסד הנתונים
auto-start/route.ts POST — מחליף את מצב הדגל auto_start
נתיב ה־GET /api/services/[name]/logs המשותף כבר מחובר — אין צורך
בשינויים שם.
יש להעביר את כל תגובות השגיאה דרך createErrorResponse() / buildErrorBody().
בקובץ src/server/authz/routeGuard.ts, יש לוודא כי /api/services/ כבר מופיע ברשימה.
אם נוסף prefix חדש (למשל, /api/tools/), יש להוסיף אותו גם אל
LOCAL_ONLY_API_PREFIXES, ואם הוא מפעיל תהליכים, גם אל SPAWN_CAPABLE_PREFIXES.
יש להוסיף בדיקה בקובץ tests/unit/authz/routeGuard.test.ts.
יש ליצור את src/app/(dashboard)/dashboard/providers/services/tabs/{Name}ServiceTab.tsx.
יש לעשות שימוש חוזר ברכיבים המשותפים:
ServiceStatusCard— מצב בזמן אמת + תג מצב תקינותServiceLifecycleButtons— הפעלה / עצירה / הפעלה מחדש / עדכוןServiceLogsPanel— זנב יומן באמצעות SSE (מתחבר אל/api/services/{name}/logs)ApiKeyCard— חשיפת מפתח + החלפה (אםneedsApiKey: true)
יש לרשום את הלשונית ב־ServicesPageShell.tsx.
אם השירות המוטמע חושף נקודת קצה /v1/chat/completions התואמת ל־OpenAI:
- יש להוסיף רשומת ספק ב־
src/shared/constants/providers.tsעםisEmbeddedService: true. - יש ליצור את
open-sse/executors/{name}.tsשמרחיב אתBaseExecutor. יש לאחזר מחדש את הפורט ואת מפתח ה־API בכל בקשה (לעולם אין לשמור אותם במטמון בבנאי). יש להחזיר תגובת503 service_not_runningכאשר מצב המפקח אינו"running". - יש לרשום מודלים ב־
open-sse/config/providerRegistry.tsעם ה־prefix של השירות (למשל,myservice/sub/model). הקובץmodelSync.tsישמור אותם מעודכנים.
- יש לעדכן את
docs/frameworks/EMBEDDED-SERVICES.md(קובץ זה) — להוסיף את השירות אל הטבלה בסעיף 1 ואת כל נקודות הקצה החדשות לסעיף 4. - יש להוסיף בדיקות יחידה ב־
tests/unit/services/(מחזור חיים, מתקין, מבנה API). - יש להוסיף בדיקת אינטגרציה ב־
tests/integration/services/(מאחוריRUN_SERVICES_INT=1). - יש לעדכן את
docs/openapi.yamlבנקודות הקצה החדשות.
תסמינים: לחצן ההפעלה מחזיר 503, והמצב נשאר "error" או "starting".
רשימת בדיקה:
- בדקו את
GET /api/services/{name}/logs(או את חלונית היומנים בלוח הבקרה). חפשו שורות כמוError: ENOENT,address already in useאוCannot find module. - ודאו ש-
npmנמצא ב-PATH: הריצוwhich npmמאותו חשבון משתמש שמפעיל את OmniRoute. - ודאו שהשירות מותקן: בדקו ב-
GET /api/services/{name}/statusאתinstalledVersion. אם הערך הואnull, בצעו תחילה התקנה. - ודאו שהנתיב
DATA_DIR/services/{name}/node_modules/קיים ואינו ריק. - בדקו את השדה
lastErrorבתגובת הסטטוס כדי לראות את סיבת היציאה המסוננת.
תסמינים: המצב נשאר "starting" במשך זמן רב לפני שהוא משתנה ל-"running" או ל-"error".
הסבר: ההפעלה הקרה של 9Router כוללת ייבוא של עצי תלויות גדולים (מודולי DNS, מנהרה ו-MITM). מרווח ברירת המחדל לבדיקת תקינות הוא 2 שניות, עם 3 ניסיונות לפני שהמפקח מכריז על פסק זמן (אך ממשיך לבצע תשאול).
פתרון: ניתן להגדיר את healthIntervalMs ואת פסק הזמן של waitForHealthy
(healthIntervalMs * 3) ב-bootstrap.ts. עבור שירותים שזמן ההפעלה שלהם ארוך יותר,
הגדילו את healthIntervalMs ל-5000 ואת stopTimeoutMs ל-30 000.
תסמינים: ביומנים מופיע address already in use :::20130.
גורמים:
- תהליך אחר כבר משתמש ביציאה 20130.
- תהליך קודם של 9Router לא נעצר לחלוטין (PID זומבי).
פתרון:
- שנו את יציאת ברירת המחדל באמצעות משתנה הסביבה
NINEROUTER_PORTבקובץ.env. - אתרו וסיימו את התהליך המתנגש:
lsof -ti :20130 | xargs kill -9. - ניתן להגדיר את היציאה לכל שירות בנפרד ב-
bootstrap.tsבאמצעות השדהport.
הערה: 9Router משתמש כברירת מחדל ביציאה 20130 במיוחד כדי למנוע התנגשות עם יציאת ברירת המחדל 20128 של OmniRoute.
תסמינים: ההתקנה מחזירה 500, וביומנים מופיע EACCES או permission denied.
גורמים:
DATA_DIRאו תיקיית האב שלה אינם ניתנים לכתיבה על ידי התהליך של OmniRoute.- הפעלה בתוך Docker ללא הרשאות root וללא גישת כתיבה לאמצעי האחסון הממופה.
פתרון:
- בדקו את
DATA_DIR(ברירת מחדל:~/.omniroute/):ls -la ~/.omniroute/ - ודאו שמשתמש התהליך של OmniRoute הוא הבעלים של התיקייה:
chown -R $USER ~/.omniroute/ - ב-Docker, ודאו שלעיגון אמצעי האחסון יש את ההרשאות הנכונות עבור המשתמש בקונטיינר.
תסמינים: העדכון מחזיר 500 עם InstallError, וביומנים מופיע פסק זמן של הרשת.
רשימת בדיקה:
- ודאו שניתן לגשת למאגר npm:
npm ping. - בדקו אם קיים פרוקסי ארגוני:
npm config get proxy,npm config get https-proxy. - נסו לבצע את ההתקנה ידנית:
npm install {package}@latest --prefix ~/.omniroute/services/{name}/. - בסביבה מבודדת מהרשת, הורידו מראש את קובץ ה-tarball והשתמשו ב-
npm install /path/to/tarball.tgz.
תסמינים: המצב משתנה מ-"starting" ל-"error" בתוך פחות מ-5 שניות.
lastError מציג "Fast crash (exited with code 1)".
רשימת בדיקה:
- קראו את מלוא קטע הסיום של היומן:
GET /api/services/{name}/logs?tail=500. - גורם נפוץ: חסרים משתני סביבה שהשירות מצפה להם.
- עבור 9Router: ודאו ש-
NINEROUTER_DISABLE_MITM=trueו-NINEROUTER_DISABLE_TUNNEL=trueנמצאים בסביבה המועברת בעת יצירת התהליך (ראוinstallers/ninerouter.tsresolveSpawnArgs).
ש: האם ניתן לחשוף את נקודות הקצה של השירותים המוטמעים ללקוחות שאינם loopback?
לא. רמת LOCAL_ONLY היא מכוונת (כלל קשיח מס' 17). נתיבים שיכולים להפעיל
npm install או ליצור תהליכי node אינם יכולים להיות נגישים מתעבורה שאינה loopback,
מכיוון ש-JWT שדלף דרך מנהרה (Cloudflare, Ngrok, Tailscale) היה מאפשר אחרת
יצירה שרירותית של תהליכים. אין החרגת ביטול עבור
/api/services/ — בניגוד ל-/api/mcp/, הוא אינו נכלל ברשימת המעקפים של תחום הניהול.
ראו docs/security/ROUTE_GUARD_TIERS.md.
ש: האם 9Router ו-CLIProxyAPI יהיו זמינים בפריסות ייצור/ענן?
כן. שני השירותים פועלים לפי אותו מודל מקומי-תחילה כמו OmniRoute עצמו. הם פועלים באותו מחשב ומתקשרים באמצעות loopback. "ייצור" כאן פירושו ה-VPS או השרת המקומי שבו OmniRoute פרוס, ולא ספק ענן מרוחק.
ש: כיצד ניתן לנפות שגיאות במפקח?
- עקבו אחר זרם יומני ה-SSE:
curl -N http://localhost:20128/api/services/9router/logs. - בדקו את היומנים המובנים בפלט ה-pino של OmniRoute, המסוננים לפי
מרחב השמות
service:supervisor. - בדקו את הרשומה במסד הנתונים:
sqlite3 ~/.omniroute/omniroute.db "SELECT * FROM version_manager WHERE tool='9router'". - השתמשו ב-
GET /api/services/9router/statusכדי לראות בקריאה אחת את המצב הפעיל הנוכחי, ה-PID, התקינות ו-lastError.
ש: המפקח מציג health: "degraded" או health: "unknown", אך המצב הוא "running". האם זו בעיה?
"degraded" פירושו שבדיקת התקינות החזירה תשובה שאינה 200. "unknown" פירושו שעדיין לא
הושלמה בדיקה (מצב תחרות עם התשאול הראשון). שני המצבים זמניים במהלך האתחול.
אם התקינות נשארת במצב "degraded" במשך יותר מ-healthIntervalMs * 3 אלפיות שנייה לאחר
"running", השירות המוטמע פועל, אך API ה-HTTP שלו אינו מגיב. בדקו
אם הפורט נכון בתגובת הסטטוס ואם השירות אכן
מאזין בפורט זה.
ש: האם ניתן לשנות את מפתח ה-API של 9Router ללא הפעלה מחדש מלאה?
לא. מפתח ה-API מועבר אל 9Router באמצעות משתנה סביבה בזמן יצירת התהליך.
לא ניתן לשנות משתני סביבה בתהליך פועל. POST .../rotate-key
עוצר ומפעיל מחדש את השירות באופן אוטומטי כדי להחיל את המפתח החדש. סבב המפתח
נכנס לתוקף בתוך stopTimeoutMs של השירות (ברירת המחדל היא 15 שניות), בתוספת זמן
האתחול שלו.
ש: מהי מגבלת המאגר המעגלי ומה קורה כשהוא מתמלא?
לכל שירות יש מאגר מעגלי ייעודי בגודל 5 MB. כאשר המאגר מלא, שורות
היומן הישנות ביותר מוסרות כדי לפנות מקום לחדשות. אירוע ה-SSE מסוג snapshot מחזיר
את השורות העדכניות ביותר במסגרת מגבלת tail. היומנים אינם נשמרים בדיסק, אלא אם
logsBufferPath מוגדר ברשומת מסד הנתונים.
docs/security/ROUTE_GUARD_TIERS.md— פרטים על רמת LOCAL_ONLYdocs/architecture/CODEBASE_DOCUMENTATION.md— §3.2 מיפוי מודול השירותים המוטמעיםdocs/architecture/ARCHITECTURE.md— הקשר ברמת המערכתdocs/openapi.yaml— הגדרות נקודות קצה הניתנות לקריאה על ידי מכונהCLAUDE.md§"הוספת שירות מוטמע חדש" — רשימת תיוג לעיון מהיר