# Resilience Guide (मराठी) 🌐 **Languages:** 🇺🇸 [English](../../../../architecture/RESILIENCE_GUIDE.md) · 🇪🇹 [am](../../../am/docs/architecture/RESILIENCE_GUIDE.md) · 🇸🇦 [ar](../../../ar/docs/architecture/RESILIENCE_GUIDE.md) · 🇦🇿 [az](../../../az/docs/architecture/RESILIENCE_GUIDE.md) · 🇧🇬 [bg](../../../bg/docs/architecture/RESILIENCE_GUIDE.md) · 🇧🇩 [bn](../../../bn/docs/architecture/RESILIENCE_GUIDE.md) · 🇧🇦 [bs](../../../bs/docs/architecture/RESILIENCE_GUIDE.md) · 🇨🇿 [cs](../../../cs/docs/architecture/RESILIENCE_GUIDE.md) · 🇩🇰 [da](../../../da/docs/architecture/RESILIENCE_GUIDE.md) · 🇩🇪 [de](../../../de/docs/architecture/RESILIENCE_GUIDE.md) · 🇬🇷 [el](../../../el/docs/architecture/RESILIENCE_GUIDE.md) · 🇪🇸 [es](../../../es/docs/architecture/RESILIENCE_GUIDE.md) · 🇪🇪 [et](../../../et/docs/architecture/RESILIENCE_GUIDE.md) · 🇮🇷 [fa](../../../fa/docs/architecture/RESILIENCE_GUIDE.md) · 🇫🇮 [fi](../../../fi/docs/architecture/RESILIENCE_GUIDE.md) · 🇫🇷 [fr](../../../fr/docs/architecture/RESILIENCE_GUIDE.md) · 🇮🇪 [ga](../../../ga/docs/architecture/RESILIENCE_GUIDE.md) · 🇮🇳 [gu](../../../gu/docs/architecture/RESILIENCE_GUIDE.md) · 🇳🇬 [ha](../../../ha/docs/architecture/RESILIENCE_GUIDE.md) · 🇮🇱 [he](../../../he/docs/architecture/RESILIENCE_GUIDE.md) · 🇮🇳 [hi](../../../hi/docs/architecture/RESILIENCE_GUIDE.md) · 🇭🇷 [hr](../../../hr/docs/architecture/RESILIENCE_GUIDE.md) · 🇭🇺 [hu](../../../hu/docs/architecture/RESILIENCE_GUIDE.md) · 🇦🇲 [hy](../../../hy/docs/architecture/RESILIENCE_GUIDE.md) · 🇮🇩 [id](../../../id/docs/architecture/RESILIENCE_GUIDE.md) · 🇳🇬 [ig](../../../ig/docs/architecture/RESILIENCE_GUIDE.md) · 🇮🇹 [it](../../../it/docs/architecture/RESILIENCE_GUIDE.md) · 🇯🇵 [ja](../../../ja/docs/architecture/RESILIENCE_GUIDE.md) · 🇬🇪 [ka](../../../ka/docs/architecture/RESILIENCE_GUIDE.md) · 🇰🇭 [km](../../../km/docs/architecture/RESILIENCE_GUIDE.md) · 🇮🇳 [kn](../../../kn/docs/architecture/RESILIENCE_GUIDE.md) · 🇰🇷 [ko](../../../ko/docs/architecture/RESILIENCE_GUIDE.md) · 🇱🇹 [lt](../../../lt/docs/architecture/RESILIENCE_GUIDE.md) · 🇱🇻 [lv](../../../lv/docs/architecture/RESILIENCE_GUIDE.md) · 🇮🇳 [ml](../../../ml/docs/architecture/RESILIENCE_GUIDE.md) · 🇲🇾 [ms](../../../ms/docs/architecture/RESILIENCE_GUIDE.md) · 🇲🇹 [mt](../../../mt/docs/architecture/RESILIENCE_GUIDE.md) · 🇲🇲 [my](../../../my/docs/architecture/RESILIENCE_GUIDE.md) · 🇳🇵 [ne](../../../ne/docs/architecture/RESILIENCE_GUIDE.md) · 🇳🇱 [nl](../../../nl/docs/architecture/RESILIENCE_GUIDE.md) · 🇳🇴 [no](../../../no/docs/architecture/RESILIENCE_GUIDE.md) · 🇮🇳 [or](../../../or/docs/architecture/RESILIENCE_GUIDE.md) · 🇮🇳 [pa](../../../pa/docs/architecture/RESILIENCE_GUIDE.md) · 🇵🇭 [phi](../../../phi/docs/architecture/RESILIENCE_GUIDE.md) · 🇵🇱 [pl](../../../pl/docs/architecture/RESILIENCE_GUIDE.md) · 🇵🇹 [pt](../../../pt/docs/architecture/RESILIENCE_GUIDE.md) · 🇧🇷 [pt-BR](../../../pt-BR/docs/architecture/RESILIENCE_GUIDE.md) · 🇷🇴 [ro](../../../ro/docs/architecture/RESILIENCE_GUIDE.md) · 🇷🇺 [ru](../../../ru/docs/architecture/RESILIENCE_GUIDE.md) · 🇱🇰 [si](../../../si/docs/architecture/RESILIENCE_GUIDE.md) · 🇸🇰 [sk](../../../sk/docs/architecture/RESILIENCE_GUIDE.md) · 🇸🇮 [sl](../../../sl/docs/architecture/RESILIENCE_GUIDE.md) · 🇷🇸 [sr](../../../sr/docs/architecture/RESILIENCE_GUIDE.md) · 🇸🇪 [sv](../../../sv/docs/architecture/RESILIENCE_GUIDE.md) · 🇰🇪 [sw](../../../sw/docs/architecture/RESILIENCE_GUIDE.md) · 🇮🇳 [ta](../../../ta/docs/architecture/RESILIENCE_GUIDE.md) · 🇮🇳 [te](../../../te/docs/architecture/RESILIENCE_GUIDE.md) · 🇹🇭 [th](../../../th/docs/architecture/RESILIENCE_GUIDE.md) · 🇹🇷 [tr](../../../tr/docs/architecture/RESILIENCE_GUIDE.md) · 🇺🇦 [uk-UA](../../../uk-UA/docs/architecture/RESILIENCE_GUIDE.md) · 🇵🇰 [ur](../../../ur/docs/architecture/RESILIENCE_GUIDE.md) · 🇺🇿 [uz](../../../uz/docs/architecture/RESILIENCE_GUIDE.md) · 🇻🇳 [vi](../../../vi/docs/architecture/RESILIENCE_GUIDE.md) · 🇳🇬 [yo](../../../yo/docs/architecture/RESILIENCE_GUIDE.md) · 🇨🇳 [zh-CN](../../../zh-CN/docs/architecture/RESILIENCE_GUIDE.md) · 🇹🇼 [zh-TW](../../../zh-TW/docs/architecture/RESILIENCE_GUIDE.md) --- OmniRoute मध्ये तीन स्वतंत्र परंतु परस्परसंबंधित लवचिकता यंत्रणा आहेत. प्रत्येकाची व्याप्ती आणि उद्देश वेगळा आहे. रूटिंग वर्तन डीबग करताना त्या स्वतंत्र ठेवा. ![3-स्तरीय लवचिकता मॉडेल](../diagrams/exported/resilience-3layers.svg) > स्रोत: [diagrams/resilience-3layers.mmd](../diagrams/resilience-3layers.mmd) ## 1. प्रदाता सर्किट ब्रेकर **व्याप्ती:** संपूर्ण प्रदाता (उदा., `glm`, `openai`, `anthropic`). **उद्देश:** अपस्ट्रीम/सेवा स्तरावर वारंवार अपयशी ठरणाऱ्या प्रदात्याकडे ट्रॅफिक पाठवणे थांबवणे. **अंमलबजावणी:** - मुख्य क्लास: `src/shared/utils/circuitBreaker.ts` - जोडणी: `src/sse/handlers/chatHelpers.ts`, `src/sse/handlers/chat.ts` - स्थिती API: `GET /api/monitoring/health` - रीसेट API: `POST /api/resilience/reset` - रॅपर्स: `open-sse/services/accountFallback.ts` - DB टेबल: `domain_circuit_breakers` **स्थिती:** - `CLOSED` — सामान्य ट्रॅफिकला परवानगी - `DEGRADED` — ट्रॅफिकला अजूनही परवानगी, परंतु प्रदात्याच्या वाढलेल्या अपयशांचा मागोवा घेतला जातो - `OPEN` — प्रदाता तात्पुरता अवरोधित; कॉम्बो रूटिंग त्याला वगळते - `HALF_OPEN` — रीसेट कालमर्यादा संपली; तपासणी विनंतीला परवानगी **कॉन्फिगर करता येणारी डीफॉल्ट मूल्ये (`open-sse/config/constants.ts`, Dashboard → Settings → Resilience मध्ये उपलब्ध):** | वर्ग | डिग्रेड होण्याची मर्यादा | उघडण्याची मर्यादा | रीसेट कालमर्यादा | | ------- | ------------------------ | ----------------- | ---------------- | | OAuth | 5 अपयश | 8 अपयश | 60s | | API-key | 7 अपयश | 12 अपयश | 30s | | स्थानिक | व्युत्पन्न | 2 अपयश | 15s | प्रदाता `DEGRADED` स्थितीत कधी प्रवेश करतो हे `degradationThreshold` नियंत्रित करते; तो कधी उघडतो आणि वगळला जातो हे `failureThreshold` नियंत्रित करते. स्थानिक प्रदाता प्रोफाइल्स अद्याप Resilience सेटिंग्ज पृष्ठावर उपलब्ध नाहीत. **ट्रिप कोड्स:** केवळ प्रदाता-स्तरीय स्थिती `[408, 500, 502, 503, 504]`. खाते-स्तरीय त्रुटींसाठी ट्रिप करू नका (बहुतेक 401/403/429 — त्या कूलडाउन किंवा लॉकआउटशी संबंधित आहेत). **लेझी पुनर्प्राप्ती:** `OPEN` ची मुदत संपल्यावर, `getStatus()`, `canExecute()`, `getRetryAfterMs()` स्थिती `HALF_OPEN` वर रीफ्रेश करतात. पार्श्वभूमी टाइमरची आवश्यकता नाही. --- ### ऐच्छिक जागतिक प्रदाता कूलडाउन (विंडो गेट) चौथा, **ऐच्छिक** स्तर (`PROVIDER_COOLDOWN_ENABLED`, डीफॉल्टनुसार **बंद**) अपयशी प्रदात्यांची क्रॉस-रिक्वेस्ट स्मृती `open-sse/services/providerCooldownTracker.ts` मध्ये ठेवतो; कॉम्बो लक्ष्य निश्चित करताना तिचा संदर्भ घेतला जातो, जेणेकरून सलग कॉम्बो विनंत्या नुकत्याच अपयशी ठरलेल्या प्रदात्याकडे पुन्हा-पुन्हा जात नाहीत. प्रदाता-स्तरीय नोंदी `PROVIDER_PROFILES` विंडो गेटचे पालन करतात: | प्रोफाइल | इतक्यानंतर ट्रिप होते (`providerFailureThreshold`) | या कालावधीत (`providerFailureWindowMs`) | इतक्या काळासाठी कूल होते (`providerCooldownMs`) | | -------- | -------------------------------------------------: | --------------------------------------: | ----------------------------------------------: | | OAuth | `10` | `15min` | `5min` | | API key | `15` | `30min` | `10min` | मर्यादेपेक्षा कमी असल्यास प्रदाता **कूल होत आहे** असे मानले जात नाही; यशस्वी विनंती विंडो साफ करते. त्याऐवजी, कनेक्शन-स्तरीय नोंदी (`provider:connectionId`) घातांकीय `minRetryCooldownMs → maxRetryCooldownMs` बॅकऑफ कायम ठेवतात. ओव्हरराइड्स: `OMNIROUTE_PROVIDER_BREAKER_{OAUTH,API_KEY}_{FAILURE_THRESHOLD,FAILURE_WINDOW_MS,COOLDOWN_MS}`. रिग्रेशन गार्ड: `tests/unit/provider-cooldown-window-gate.test.ts`. ## 2. कनेक्शन कूलडाउन **व्याप्ती:** एकच प्रदाता कनेक्शन/खाते/की. **उद्देश:** त्याच प्रदात्याची इतर कनेक्शने सेवा देत असताना एक खराब की वगळणे. **अंमलबजावणी:** - अनुपलब्ध म्हणून चिन्हांकित करणे: `src/sse/services/auth.ts::markAccountUnavailable()` - निवड: त्याच फाइलमधील `getProviderCredentials*` - कूलडाउन गणना: `open-sse/services/accountFallback.ts::checkFallbackError()` - सेटिंग्ज: `src/lib/resilience/settings.ts` **प्रत्येक कनेक्शनसाठी फील्ड्स:** - `rateLimitedUntil` — कूलडाउन संपेपर्यंतचा टाइमस्टॅम्प - `testStatus: "unavailable"` - `lastError`, `lastErrorType`, `errorCode` - `backoffLevel` — एक्स्पोनेन्शियल बॅकऑफ काउंटर **डीफॉल्ट कूलडाउन:** - OAuth बेस: 5s - API-key बेस: 3s - API-key 429: अपस्ट्रीम `Retry-After`/रीसेट हेडर्स/पार्स करता येण्याजोग्या रीसेट मजकुराला प्राधान्य - बॅकऑफ: `baseCooldownMs * 2 ** failureIndex` **अँटी-थंडरिंग-हर्ड संरक्षण:** समकालीन अपयशांमुळे कूलडाउन अनावश्यकपणे वाढवला जाणे किंवा `backoffLevel` दोनदा वाढवला जाणे प्रतिबंधित करते. **स्ट्रीममधील कंटेंट स्टॉलमुळे खाते कूलडाउनमध्ये जात नाही.** वेळेत कोणतेही मॉडेल आउटपुट न पाठवणाऱ्या स्ट्रीमवर कंटेंट-स्टॉल वॉचडॉग (`open-sse/utils/streamHandler.ts`) प्रयत्न सोडून देतो, तेव्हा `markAccountUnavailable()` कनेक्शनवर त्रुटी नोंदवते, परंतु कोणताही कूलडाउन सेट करत नाही: स्टॉल हा त्या विनंतीशी संबंधित असतो आणि बहुतेकदा तो अद्याप कोणतेही आउटपुट नसलेल्या दीर्घ रीझनिंग टर्नमुळे होतो. ऑपरेटर `resilienceSettings.streamStallCooldown.enabled` (डीफॉल्ट `false`) वापरून हे पुन्हा सक्षम करू शकतात. **रीझनिंग फ्रेम्स कंटेंट-स्टॉल बजेट पुन्हा सुरू करतात.** रीझनिंग मॉडेल आपला पहिला दृश्यमान टोकन देण्यापूर्वी कित्येक मिनिटे विचार करू शकते: Claude अशा `thinking_delta` फ्रेम्स स्ट्रीम करते ज्यांमधील विचारांचा मजकूर रिकामा असू शकतो, आणि Responses API एकामागून एक रीझनिंग आयटम स्ट्रीम करते. `isReasoningProgressFrame()` (`open-sse/utils/streamReadiness.ts`) या फ्रेम्स ओळखते, आणि वॉचडॉग टर्न रद्द करण्याऐवजी प्रत्येक फ्रेमवर आपले बजेट पुन्हा सुरू करतो. तरीही त्या मॉडेल आउटपुट नसतात, त्यामुळे केवळ रीझनिंगसह संपणारा टर्न अजूनही रिकामा म्हणून नोंदवला जातो, आणि रीझनिंग थांबवून केवळ हार्टबीट्स पाठवणारा टर्न अजूनही वॉचडॉग सक्रिय करतो. रिकामी नसलेली स्वाक्षरी असलेल्या Kiro च्या बायनरी `reasoningContentEvent` फ्रेम्स ही रीझनिंग अॅक्टिव्हिटी एक रिकामा `reasoning_content` डेल्टा म्हणून एक्झिक्युटरमधून जतन करतात. स्वाक्षरी फॉरवर्ड केली जात नाही. मेटाडेटा, अपूर्ण फ्रेम्स आणि रिकाम्या स्वाक्षऱ्या कंटेंट बजेट पुन्हा सुरू करत नाहीत; स्वतंत्र सक्रिय-स्ट्रीम टाइमआउट आणि क्लायंट रद्दीकरण अद्याप लागू होतात (`open-sse/executors/kiro/reasoning.ts`). **टर्मिनल स्थिती (कूलडाउन नाहीत):** - `banned` — बंदी-कीवर्ड / खाते-बंदी शोधाद्वारे सेट केली जाते ([BAN_DETECTION](../security/BAN_DETECTION.md) पहा), तसेच सलग तीन अपस्ट्रीम प्रति-विनंती नकारांद्वारे (`request_rejected`, उदा. Anthropic OAuth 403 "Request not allowed" — `open-sse/services/requestRejectedStreak.ts`); एकच नकार फक्त कनेक्शनला कूलडाउनमध्ये ठेवतो - `expired` (मर्यादित पुनर्प्रयत्नांनंतर टर्मिनल स्थितीत जाते — एक्स्पोनेन्शियल बॅकऑफसह `EXPIRED_RETRY_MAX = 3` — त्यामुळे खाते कायमचे निष्क्रिय होण्यापूर्वी तात्पुरत्या OAuth त्रुटी स्वतःहून दूर होऊ शकतात) - `credits_exhausted` क्रेडेन्शियल्स बदलत नाहीत किंवा ऑपरेटर त्यांना रीसेट करत नाही तोपर्यंत या स्थिती कायम राहतात. टर्मिनल स्थितींवर तात्पुरती कूलडाउन स्थिती अधिलिखित करू नका. **लेझी रिकव्हरी:** `rateLimitedUntil` ची वेळ उलटून गेल्यावर कनेक्शन पुन्हा पात्र होते. यशस्वी वापरानंतर `clearAccountError()` सर्व त्रुटी फील्ड्स साफ करते. ### Claude OAuth वापर मर्यादा: कमी-प्राधान्य लेन + सत्र-मर्यादा रीसेट **व्याप्ती:** एक Claude सदस्यता (OAuth) कनेक्शन. दोन्ही वैशिष्ट्ये **प्रत्येक कनेक्शनसाठी स्वतंत्रपणे ऑप्ट-इन** आहेत (कनेक्शन संपादित करा → Claude विभाग → `providerSpecificData` मधील `lowPriorityMode` / `autoLimitReset`, दोन्ही डीफॉल्टनुसार बंद) आणि Claude Code च्या `/low-priority` व `/limit-reset` कमांड्सशी सुसंगत आहेत (वायर कॉन्ट्रॅक्ट Claude Code 2.1.263 मधून नोंदवलेला आहे). **अंमलबजावणी:** - स्टेट मशीन + प्रतिसाद वर्गीकरण: `open-sse/services/claudeLowPriority.ts` - रीसेट स्थिती/क्लेम क्लायंट: `open-sse/services/claudeLimitReset.ts` - एक्झिक्युटर हुक (हेडर इंजेक्शन + त्याच खात्यावरील पुनर्प्रयत्न): `open-sse/executors/base.ts::execute()` - ऑप्ट-इन सातत्य: `src/lib/providers/requestDefaults.ts::normalizeProviderSpecificData()` **ट्रिगर:** 5-तासांची वापर मर्यादा — असा `429` ज्याच्या हेडर्समध्ये `anthropic-ratelimit-unified-status: rejected` असते आणि खाते पात्र असल्यास, `anthropic-ratelimit-unified-slow-offer: treatment` असते. त्या पहिल्या मर्यादा 429 पूर्वी काहीही पाठवले जात नाही; युनिफाइड हेडर्स नसलेला बर्स्ट 429 सामान्य कूलडाउन मार्गाने हाताळला जातो. **कमी-प्राधान्य लेन** (`lowPriorityMode`): - वॉल 429 आल्यावर executor ऑफर स्वीकारतो आणि `anthropic-usage-limit: slow` सह **त्याच** खात्यावर त्वरित पुन्हा प्रयत्न करतो; घोषित केलेल्या `anthropic-ratelimit-unified-reset` पर्यंत (+60s अतिरिक्त अवधी) लेन सक्रिय राहते आणि त्या कालावधीतील प्रत्येक विनंतीमध्ये हा header असतो. intercept केलेला 429 कधीही `handleChatCore` पर्यंत पोहोचत नाही, त्यामुळे connection cooldown मध्ये टाकले जात **नाही** आणि त्यापासून दूर rotate केले जात नाही. - नंतरच्या responses वरील `anthropic-ratelimit-unified-slow-status`: `active` / `not_needed` लेन कायम ठेवतात; `slot_busy` (429) किंवा `529` आल्यास server च्या `anthropic-ratelimit-unified-slow-retry-after` इतका वेळ प्रतीक्षा केली जाते (default 20s, clamp 5–600s, ±30% jitter) आणि पुन्हा प्रयत्न केला जातो; हे `anthropic-ratelimit-unified-slow-max-wait` ने मर्यादित असते (default 20 min, clamp 1 min–6 h) — त्यापुढे लेन समाप्त होते आणि 10-minute cool-off पुन्हा स्वीकारण्यास प्रतिबंध करतो. ही प्रतीक्षा विनंतीच्या स्वतःच्या upstream-start timeout मधील उर्वरित वेळेने (`resolveFetchStartTimeout`, default ने 10 min) वजा 5 s margin इतकी अतिरिक्तरीत्या मर्यादित केली जाते: त्या cap शिवाय default 20-minute max-wait विनंतीपेक्षा अधिक काळ टिकेल आणि प्रतीक्षा सुरू असतानाच sleep abort होईल, ज्यामुळे सुरळीत `max_wait` समाप्ती + cool-off ऐवजी `TimeoutError` समोर येईल. - `weekly_limit` / `budget_exhausted` / `off` / `ineligible`, 5h-window rollover, किंवा `ineligible` + `anthropic-ratelimit-unified-overage-in-use: true` (जे कोणत्याही status वर `extra_usage` म्हणून समाप्त करते, कारण paid overage आता वॉल व्यापते) लेन समाप्त करतात; त्यानंतर response सामान्य cooldown मार्गाकडे जाते. `budget_exhausted` घोषित केलेल्या budget reset पर्यंत (≤ 8 days) लक्षात ठेवले जाते. - वॉल तपासणी executor च्या स्वतःच्या 400-प्रेरित intra-attempt retries नंतर चालते (context editing, thinking/effort clamps, param auto-learn), त्यामुळे केवळ अशा एखाद्या retry वर दिसणारा wall 429 देखील cooldown मार्गापर्यंत पोहोचण्याऐवजी intercept केला जातो. - State प्रत्येक connection साठी in-memory असते (restart केल्यास पुन्हा स्वीकारण्यासाठी एक अतिरिक्त wall 429 लागतो). **Session-limit reset** (`autoLimitReset`, दोन्ही सुरू असताना लेनच्या आधी प्रयत्न केला जातो): - `GET https://api.anthropic.com/api/oauth/usage?at_wall=1&skip_spend=1` → `juniper_tide` block; `arm: "reset"` आणि `available: true` असताना, `POST https://api.anthropic.com/api/organizations/{orgUUID}/reset_rate_limits` सह `{ "program": "juniper_tide" }` (`providerSpecificData.organizationUUID` मधून organization UUID, bootstrap fallback). - `result: reset|not_limited` → विनंतीचा पूर्ण वेगाने पुन्हा प्रयत्न केला जातो (slow header नाही). `already_used` / `not_offered` हे `next_available_at` लक्षात ठेवतात (default एक आठवडा); कोणतेही अपयश 15 minutes साठी back off करते. reset आठवड्यातून एकदा होते आणि तरीही weekly limit मध्ये मोजले जाते. Regression guards: `tests/unit/claude-low-priority-mode.test.ts`, `tests/unit/claude-limit-reset.test.ts`, `tests/unit/claude-low-priority-executor.test.ts`. ### Session affinity (#7274) **व्याप्ती:** एक client session (`X-Session-Id` / `x-codex-session-id` / `x-omniroute-session` header) **कोणत्याही** provider साठी एका connection ला pin केले जाते. **उद्देश:** multi-turn agent (Claude Code, aider, custom agents) सर्व विनंत्यांमध्ये त्याच account वर ठेवणे, ज्यामुळे cross-account context loss आणि per-account session state असलेल्या providers वरील वारंवार होणारे cold-start 429s कमी होतात. **अंमलबजावणी:** - TTL resolution: `src/sse/services/sessionAffinityPin.ts::resolveSessionAffinityTtlMs()` - Pin selection/creation: `src/sse/services/sessionAffinityPin.ts::selectSessionAffinityConnection()` - Header extraction (generic, कोणताही provider): `src/sse/services/auth.ts::extractSessionAffinityKey()` - Persisted pin table: `sessionAccountAffinity` (`src/lib/db/sessionAccountAffinity.ts`) - Setting: `sessionAffinityTtlMs` (ms मधील global TTL, `0` केल्यास बंद होते) — `src/lib/db/settings.ts`. migration `124_generic_session_affinity_ttl.sql` द्वारे Codex-only `codexSessionAffinityTtlMs` वरून नाव बदलले आहे; हे आधी configure केलेले कोणतेही Codex TTL नवीन default म्हणून पुढे नेते. #7274 पूर्वी, `resolveSessionAffinityTtlMs()` हे `codex` वगळता प्रत्येक provider साठी थेट `0` परत करत होते, त्यामुळे pinning mechanism आणि header extraction आधीपासून provider-agnostic असूनही TTL setting (आणि session headers) चा इतरत्र कुठेही परिणाम होत नव्हता. या दुरुस्तीने तो early-return काढून टाकला; आता TTL globally `0` पेक्षा जास्त सेट केल्यानंतर प्रत्येक provider वर एकसमान लागू होते. तीन session-affinity headers कधीही upstream forward केले जात नाहीत — client headers pass through करण्याऐवजी executors स्वतःचे upstream headers सुरुवातीपासून तयार करतात, त्यामुळे हे केवळ internal correlation id राहते. ### Exclusive managed session connection leases **व्याप्ती:** एक सक्रिय managed HTTP client/session एका पात्र OmniRoute connection ची मालकी घेतो. **उद्देश:** सर्व विनंत्यांमध्ये कठोर routing fence आवश्यक असलेल्या clients साठी टिकाऊ exclusive connection ownership प्रदान करणे. हे session affinity पेक्षा वेगळे आहे, कारण session affinity ही soft continuity preference आहे: exclusive lease lifecycle state SQLite मध्ये persist करते, global active-owner आणि active-connection uniqueness लागू करते आणि provider dispatch पूर्वी stale generation नाकारते. हे feature प्रत्येक API key साठी opt-in आहे. managed key कडे `lease:exclusive` scope आणि स्पष्टपणे non-empty `allowedConnections` list असणे आवश्यक आहे. कोणताही HTTP client lifecycle endpoint वापरू शकतो; कोणतेही client name, user-agent, provider, OAuth method किंवा model आवश्यक नाही. lease ची मालकी connection वर असते, model वर नाही, त्यामुळे connection सामान्यतः पात्र असेपर्यंत model बदलले तरी binding कायम राहते. सामान्य model, quota, health, cooldown आणि allowlist नियम अधिकृत राहतात आणि त्याच generation ला दुसऱ्या मोकळ्या पात्र connection वर transition करू शकतात. जीवनचक्र म्हणजे JSON क्रिया `acquire`, `renew`, आणि `release` यांसह `POST /api/v1/session-leases`. व्यवस्थापित अनुमान विनंत्या अपारदर्शक `X-OmniRoute-Lease-Owner` मूल्य आणि अचूक `X-OmniRoute-Lease-Generation` सादर करतात. मालकासाठी `vlo_` आणि त्यापुढे 43 base64url वर्ण वापरले जातात; केवळ त्याचा SHA-256 हॅश संग्रहित केला जातो. प्रत्येक अंतिम डिस्पॅच फेन्स प्रमाणीकृत API की ID आणि सक्रिय कनेक्शन ID यांनाही बांधून ठेवतो. लीज नियंत्रण हेडर्स लॉग्ज, जतन केलेले विनंती स्नॅपशॉट्स, आणि अपस्ट्रीम एक्झिक्युटर हेडर्समधून काढून टाकले जातात. सामान्य रूटिंगमध्ये पात्र व्यवस्थापित उमेदवार असतील, परंतु प्रत्येक मोकळा उमेदवार एखाद्या परकीय सक्रिय लीजने व्यापलेला असेल, तर OmniRoute HTTP `429`, lease-capacity-unavailable कोड, क्षमता उपलब्ध होण्याची प्रतीक्षा करणारी स्थिती, आणि सर्वांत लवकर संबंधित कालबाह्यतेवरून निर्धारित केलेले मर्यादित `Retry-After` परत करतो. सामान्य रिकामी पात्रता ही लीज स्पर्धा नसते आणि तिचे विद्यमान रूटिंग त्रुटी अर्थ कायम ठेवते. संबंधित यंत्रणा स्वतंत्र राहतात: - OAuth सत्र व्यापलेपण हे OAuth खात्यांसाठी प्रक्रिया-स्थानिक सॉफ्ट वितरण आहे. - खाते सेमाफोर्स विनंती-समवर्तीतेसाठी परवानग्या देतात आणि विनंती पूर्ण झाल्यावर समाप्त होतात. - विशेष व्यवस्थापित सत्र लीज म्हणजे जनरेशन फेन्ससह टिकाऊ जीवनचक्र मालकी. --- ## 3. मॉडेल लॉकआउट **व्याप्ती:** प्रोव्हायडर + कनेक्शन + मॉडेल हे त्रिकूट. **स्थितीनुसार कीची व्याप्ती:** अयशस्वी स्थिती कोणत्या कीवर लॉकआउट लिहायचा हे ठरवते (`open-sse/services/accountFallback/exactModelLock.ts` मधील `resolveLockoutScope()`): - `429` / `403` / `402` — कोटा किंवा हक्कदारीचा संकेत — **कोटा फॅमिली** लॉक करतात: codex साठी संपूर्ण `codex` / `spark` व्याप्ती (कनेक्शनचे प्रत्येक `gpt-5*` मॉडेल), इतर प्रोव्हायडरसाठी `getQuotaScopedModelForProvider()`. - `404` मूळ मॉडेलला लॉक करते (`getModelLockKey()` हे `not_found` ची व्याप्ती कमी करते). - इतर कोणतीही स्थिती — `5xx` ट्रान्सपोर्ट/सर्व्हर अपयश आणि गुणवत्ता प्रमाणीकरणातून OmniRoute ने स्वतः निर्माण केलेली `502` स्थिती — फक्त **अचूक** प्रोव्हायडर/कनेक्शन/मॉडेल त्रिकूट लॉक करते. एका मॉडेलवरील खराब स्ट्रीम हा खात्याच्या कोट्याबाबतचा पुरावा नसतो; या नियमापूर्वी `codex/gpt-5.6-luna` वरील एका रिकाम्या प्रतिसादामुळे त्या कनेक्शनची प्रत्येक `gpt-5*` मॉडेल राउटिंगमधून 2–30 मिनिटांसाठी (वाढत्या कालावधीसह) काढली जात होती, जरी त्याचा कोटा वापरला गेला नव्हता. - कॉलरचा स्पष्ट `scope` पर्याय नेहमी प्राधान्य घेतो (Antigravity `"exact"` पाठवते). **उद्देश:** केवळ एक मॉडेल अनुपलब्ध किंवा कोटा-मर्यादित असताना संपूर्ण कनेक्शन अक्षम करणे टाळणे. **उदाहरणे:** - प्रत्येक मॉडेलनुसार कोटा असलेले प्रोव्हायडर `429` परत करतात - एका गहाळ मॉडेलसाठी स्थानिक प्रोव्हायडर `404` परत करतात - प्रोव्हायडर-विशिष्ट मोड/मॉडेल परवानगी अपयशे (उदा., Grok मोड्स) **अंमलबजावणी:** `open-sse/services/accountFallback.ts` — `lockModel()`, `clearModelLock()`, `getAllModelLockouts()`. ### मॉडेल कूलडाउन डॅशबोर्ड (v3.8.0) UI: Settings → Model Cooldowns (`src/app/(dashboard)/dashboard/settings/components/ModelCooldownsCard.tsx`) प्रोव्हायडर, कनेक्शन, मॉडेल, कारण आणि expiresAt यांसह सक्रिय लॉकआउटची सूची दाखवते. ऑपरेटर कार्डमधून एखादे मॉडेल स्वहस्ते पुन्हा सक्षम करू शकतात. **REST API:** - `GET /api/resilience/model-cooldowns` — सक्रिय लॉकआउटची सूची - `DELETE /api/resilience/model-cooldowns` — स्वहस्ते पुन्हा सक्षम करणे. बॉडी: `{provider, connection, model}`. प्रमाणीकरण: व्यवस्थापन. ### कूलडाउन व्यवस्थापक UI: Monitoring → Cooldown Manager (`src/app/(dashboard)/dashboard/resilience/cooldowns/`). क्षणिक कारणामुळे राउटिंगमधून बाहेर असलेल्या प्रत्येक कनेक्शनसाठी, प्रत्येक प्रोव्हायडरचे पृष्ठ उघडण्याऐवजी एकच पृष्ठ. हे कनेक्शन कूलडाउन, मॉडेल लॉकआउट आणि अंतिम स्थितींची सूची दाखवते; प्रत्येक कनेक्शनसाठी, निवडलेल्या कनेक्शनसाठी किंवा एखाद्या प्रोव्हायडरच्या सर्व कनेक्शनसाठी त्या साफ करते; तसेच सर्वाधिक समायोजित केले जाणारे कूलडाउन नियम संपादित करते: `streamStallCooldown.enabled` आणि OAuth / API-key `connectionCooldown` चा मूळ कूलडाउन व कमाल बॅकऑफ पायऱ्या (`PATCH /api/resilience` द्वारे जतन केल्या जातात). अंतिम स्थिती (`banned`, `expired`, `credits_exhausted`) येथे सूचीबद्ध केल्या जातात, पण कधीही साफ केल्या जात नाहीत. **REST API** (`src/lib/resilience/cooldownManager.ts`, प्रमाणीकरण: व्यवस्थापन): - `GET /api/resilience/cooldowns[?provider=]` — स्थिती, उर्वरित कूलडाउन, बॅकऑफ पातळी, शेवटच्या त्रुटीचा प्रकार आणि मॉडेल लॉकआउटसह कनेक्शन (क्रेडेन्शियल्सशिवाय) - `POST /api/resilience/cooldowns` — बॉडी `{connectionIds: string[]}` किंवा `{all: true, provider?}`; `{cleared, unchanged, skippedTerminal, lockoutsCleared}` परत करते ### लॉकआउट सेटिंग्ज UI + यश-क्षय पुनर्प्राप्ती (v3.8.23) मॉडेल लॉकआउट हे नेहमी सुरू असलेल्या हार्डकोडेड वर्तनावरून, स्वतःचे सेटिंग्ज कार्ड आणि स्वयंपुनर्प्राप्तीचा मार्ग असलेल्या पूर्णपणे कॉन्फिगर करता येणाऱ्या, निवडून सुरू कराव्या लागणाऱ्या वैशिष्ट्यात बदलले. **सेटिंग्ज कार्ड:** Settings → Model Lockout (`src/app/(dashboard)/dashboard/settings/components/ModelLockoutCard.tsx`). हे वरील केवळ-वाचनीय `ModelCooldownsCard` पेक्षा **वेगळे** आहे (जे फक्त सक्रिय लॉकआउटची _सूची दाखवते_) — नवीन कार्ड _पॅरामीटर्स कॉन्फिगर करते_. डीफॉल्ट मूल्ये `DEFAULT_MODEL_LOCKOUT_SETTINGS` (`src/lib/resilience/modelLockoutSettings.ts`) मध्ये आहेत: | सेटिंग | डीफॉल्ट | अर्थ | | ----------------------- | -------------------------------- | -------------------------------------------------------------- | | `enabled` | `false` | मुख्य टॉगल — मॉडेल लॉकआउट **डीफॉल्टनुसार बंद** असते. | | `errorCodes` | `[403, 404, 429, 502, 503, 504]` | मॉडेल-व्याप्त अपयश म्हणून गणल्या जाणाऱ्या अपस्ट्रीम स्थिती. | | `baseCooldownMs` | `120_000` (120 से.) | पहिल्या अपयशासाठी प्रारंभिक लॉकआउट कालावधी. | | `maxCooldownMs` | `1_800_000` (30 मिनिटे) | वाढवलेल्या कूलडाउनची कमाल मर्यादा. | | `maxBackoffSteps` | `10` | कमाल एक्स्पोनेन्शियल-बॅकऑफ वाढीच्या पायऱ्या. | | `useExponentialBackoff` | `true` | वारंवार होणारी अपयशे कूलडाउन एक्स्पोनेन्शियली वाढवतात की नाही. | सेटिंग्ज नेहमीच्या सेटिंग्ज स्टोअरद्वारे कायम ठेवली जातात आणि रेझिलियन्स सेटिंग्ज स्कीमाद्वारे प्रमाणित केली जातात; कार्ड `baseCooldownMs`/`maxCooldownMs` (`maxCooldownMs ≥ baseCooldownMs` सह) आणि `maxBackoffSteps` यांना मर्यादेत ठेवते. **यश-क्षय पुनर्प्राप्ती:** पुनर्प्राप्ती ही **केवळ** टाइमरची मुदत संपण्यावर अवलंबून नसते. निरोगी प्रतिसाद मॉडेलची अपयश-संख्या कमी करतो, त्यामुळे विंडोच्या मध्यात पुनर्प्राप्त झालेले मॉडेल त्याचा टाइमर संपण्यापूर्वी वाढणे थांबवते (आणि लॉकआउट साफ करते). यशस्वी कॉम्बो टार्गेटवर, `open-sse/services/combo.ts` हे `decayModelFailureCount()` (`open-sse/services/accountFallback.ts`) कॉल करते, जे संग्रहित `failureCount` **अर्धा** करते (`Math.floor(failureCount / 2)`); ते `0` वर पोहोचल्यावर लॉकआउट नोंद पूर्णपणे हटवली जाते. याला अनुरूप असलेले `recordModelLockoutFailure()` एस्कलेशन विंडोमधील अपयशांवर संख्या वाढवते (आणि कूलडाउन वाढवते). हा यश-क्षय साध्या टाइमर समाप्तीच्या अतिरिक्त आहे — दोन्हीपैकी कोणताही मार्ग मॉडेल पुन्हा सक्षम करू शकतो. **स्थिती:** लॉकआउट **इन-मेमरी** ठेवले जातात (प्रत्येक प्रक्रियेसाठी `provider:connectionId:model` ने की केलेले `ModelLockoutEntry` चे `Map`, तर अचूक-व्याप्ती लॉक `provider:connectionId:exact:model` ने की केलेले असतात), DB मध्ये कायम ठेवले जात नाहीत — रीस्टार्ट केल्यावर ते नष्ट होतात. _सेटिंग्ज_ कायम ठेवल्या जातात; सक्रिय लॉकआउटची _स्थिती_ तात्पुरती असते. --- ## 4. कोटा-शेअर समवर्तीता नियंत्रण (v3.8.36) सदस्यता खाती (GLM, MiniMax इ.) सहसा केवळ ~1–3 समवर्ती विनंत्या स्वीकारतात; ही मर्यादा ओलांडल्यास 429 प्रतिसाद आणि कूलडाउन सुरू होतात. ही समस्या विशेषतः **quota-share** (`qtSd/…`) कॉम्बोमध्ये तीव्र होते, जिथे अनेक API की एकच अपस्ट्रीम खाते सामायिक करतात. तीन स्तर सामायिक खात्यावर विनंत्यांचा पूर येण्यापासून प्रतिबंध करतात. ### प्रत्येक कनेक्शनसाठी समवर्तीता मर्यादा (`max_concurrent`) प्रत्येक प्रोव्हायडर कनेक्शन `max_concurrent`ची कमाल मर्यादा घोषित करू शकते (`provider_connections.max_concurrent`, जी कनेक्शन मोडल / API / DB मध्ये सेट केली जाते). कोणतीही मर्यादा नको असल्यास ती रिकामी ठेवा. खालील सिरियलायझेशन स्तर नियंत्रित करणारे हे एकमेव सेटिंग आहे — ते खात्याच्या वास्तविक समवर्तीतेनुसार सेट करा (उदा. GLM ~1, MiniMax ~2). ### प्रत्येक मॉडेलसाठी समवर्तीता मर्यादा (`modelConcurrency`) कनेक्शन त्याच्या `rateLimitOverrides` मॅपमध्ये प्रत्येक मॉडेलसाठी अचूक समवर्तीता मर्यादादेखील घोषित करू शकते: ```json { "rateLimitOverrides": { "maxConcurrent": 4, "modelConcurrency": { "glm-5": 1, "glm-4.7": 3 } } } ``` ती कनेक्शन मोडलमध्ये (**Rate limit overrides → Per-model concurrency caps**, प्रत्येक ओळीवर एक `model=cap`) किंवा समान JSON रचनेसह `PATCH /api/providers/[id]` वापरून सेट करा. कीची अर्थव्यवस्था: - **संपूर्ण कनेक्शनसाठी विरुद्ध मॉडेल-विशिष्ट:** `maxConcurrent` ही संपूर्ण सामायिक कनेक्शनची कमाल मर्यादा राहते. दोन्ही लागू होत असताना, दोन्ही गेट एकाच संयुक्त गेटमध्ये अणुस्वरूपात प्राप्त केले जातात (`global → provider → account → model`); प्रत्यक्ष वर्तन अधिक कठोर लागू मर्यादेनुसार ठरते. - **मॉडेल-कीची अचूक जुळणी:** की ही रूटिंग रिझोल्यूशननंतर एक्झिक्युटरकडे पाठवलेली मॉडेल स्ट्रिंग असते — सामान्यतः मूळ अपस्ट्रीम मॉडेल आयडी (`glm-5`), क्लायंट-साइड `provider/model` उपनाम नव्हे (`zai/glm-5` हे `glm-5`शी जुळत नाही). मूल्ये धन पूर्णांकातील समवर्ती-विनंती मर्यादा असतात. - **स्थानिक क्यूइंग, कोणताही स्वयंचलित शोध नाही:** अतिरिक्त विनंत्या विद्यमान क्यू/टाइमआउट अर्थव्यवस्थेसह स्थानिकरीत्या क्यू केल्या जातात (टाइप केलेल्या `SEMAPHORE_TIMEOUT` / `SEMAPHORE_QUEUE_FULL` प्रवेश त्रुटी). OmniRoute अपस्ट्रीम धोरण शोधत किंवा अनुमानित करत नाही — ते ऑपरेटरने कॉन्फिगर केलेल्या अचूक मर्यादा लागू करते. पूर्ण क्षमतेवर असलेले मॉडेल गेट कधीही प्रोव्हायडर अक्षम करत नाही आणि कायमस्वरूपी मॉडेल लॉकआउट तयार करत नाही; अपस्ट्रीम 429/कूलडाउन/फॉलबॅक वर्तन हे त्रुटींसाठी अंतिम संरक्षण म्हणून कायम राहते. - **प्रत्येक कनेक्शन आणि प्रत्येक प्रक्रियेची व्याप्ती:** मर्यादा प्रत्येक डेटाबेस कनेक्शनसाठी स्वतंत्र असतात आणि मेमरीमध्ये ठेवल्या जातात, त्यामुळे समान अपस्ट्रीम API की पुन्हा वापरणारी दोन कनेक्शन्स एकमेकांशी समन्वय साधत नाहीत. - **कॉन्फिगर न केल्यास कोणताही बदल नाही:** मॅप वगळल्यास (किंवा डॅशबोर्ड फील्ड रिकामे ठेवल्यास) कोणतेही मॉडेल गेट जोडले जात नाही. कोणतीही सार्वत्रिक प्रोव्हायडर मर्यादा गृहीत न धरता केलेले उदाहरण कॉन्फिगरेशन: ```text glm-5=1 glm-4.7=3 ``` ### कोटा-शेअर विनंती सिरियलायझेशन कोटा-शेअर डिस्पॅच सकारात्मक `max_concurrent` घोषित करणाऱ्या कनेक्शनला लक्ष्य करत असताना, त्या **खात्याकडे** जाणाऱ्या समवर्ती विनंत्या प्रत्येक कनेक्शनच्या सेमाफोरद्वारे (की `qsconn:`) क्रमवार केल्या जातात: अतिरिक्त विनंत्या खात्यावर पूर आणण्याऐवजी **क्यूमध्ये प्रतीक्षा करतात**. हे **fail-open** आहे — पूर्ण क्षमतेवरील क्यू किंवा टाइमआउटमुळे डिस्पॅच करता येणारी विनंती नाकारण्याऐवजी ती स्लॉटशिवाय पुढे जाते. हे **Settings → Resilience → Quota-share per-connection concurrency** (`resilienceSettings.quotaShareConcurrencyLimit.enabled`, डीफॉल्टनुसार सुरू) मध्ये टॉगल करा. `max_concurrent` मर्यादा नसल्यास वर्तन बदलत नाही. > कोटा-शेअर रूटिंग गेट (`selectQuotaShareTarget`, DRR + P2C) स्वतः > fail-open आहे आणि मर्यादेवर पोहोचलेल्या कनेक्शनला केवळ _कमी प्राधान्य_ > देते — एकाच कनेक्शनच्या पूलमध्ये ते कठोर मर्यादा लागू करू शकत नाही, त्यामुळे > प्रत्यक्षात विनंत्यांचा पूर नियंत्रित करणारा हा सेमाफोर आहे. ### कॉम्बो कूलडाउन-जागरूक पुनःप्रयत्न प्रत्येक कॉम्बो धोरणासाठी (सक्षम असताना), SHORT तात्पुरत्या कूलडाउनमुळे निश्चितपणे 429 निर्माण करणारी विनंती 429 परत करण्याऐवजी कूलडाउन संपेपर्यंत प्रतीक्षा करते आणि पुन्हा डिस्पॅच होते — यामध्ये बहु-मॉडेल कॉम्बोवरील Gemini-श्रेणीच्या TPM/RPM विंडो (~60s retry-after) समाविष्ट आहेत, उदा. 2-मॉडेल कॉम्बोची दोन्ही लक्ष्ये प्रत्येक मॉडेलच्या दरमर्यादेला पोहोचणे. हे **Settings → Resilience** मधील `comboCooldownWait` (`enabled`, `maxWaitMs`, `maxAttempts`, `budgetMs`) द्वारे मर्यादित केले जाते. ते `quota_exhausted` (मध्यरात्रीपर्यंत लॉक केलेले) किंवा प्रमाणीकरण/न सापडल्याच्या कारणांसाठी कधीही प्रतीक्षा करत नाही. हेच गेट प्री-डिस्पॅच प्रकरण (#16319) हाताळते: कोणताही प्रयत्न करण्यापूर्वी कॉम्बोची सर्व लक्ष्ये वगळली गेली, कारण त्यांचे मॉडेल अल्पकालीन तात्पुरत्या लॉकआउटमध्ये होते, तर शून्य-प्रयत्न मार्ग संकेत नसलेला `503 ALL_TARGETS_SKIPPED` प्रतिसाद देण्याऐवजी त्याच मर्यादांअंतर्गत लॉक संपेपर्यंत प्रतीक्षा करतो आणि पुन्हा डिस्पॅच करतो. प्रतीक्षेला परवानगी नसताना (मर्यादा, प्रयत्न किंवा बजेट संपलेले असताना किंवा वैशिष्ट्य बंद असताना), अंतिम प्रतिसाद सर्वांत लवकर संपणाऱ्या तात्पुरत्या लॉकमधील `retry_after` / `reset_at` समाविष्ट असलेल्या `429 rate_limit_error`मध्ये अपग्रेड होतो; कोटा, प्रमाणीकरण आणि न सापडल्याचे लॉक संकेत नसलेला 503 कायम ठेवतात. ```text #16319 · comboCooldownWait · pre-dispatch (attempts = 0) all targets skipped + transient model lockout: enabled ∧ 0 < waitMs ≤ maxWaitMs ∧ attempt < maxAttempts ∧ budgetLeftMs ≥ waitMs → wait → re-dispatch wait = false → HTTP 429 rate_limit_error retry_after / reset_at ← earliest transient lock expiry quota_exhausted / auth_error / not_found / not_found_local: no transient hint → HTTP 503 ALL_TARGETS_SKIPPED ``` --- ## 5. विनंती रांग प्रवेश नियंत्रण (v3.8.49 · issue #6593) **व्याप्ती**: स्थानिक, प्रत्येक provider+connection साठी असलेली rate-limit रांग (`open-sse/services/rateLimitManager.ts`, Bottleneck द्वारे समर्थित), वरील तीन यंत्रणांच्या एक स्तर खाली. **`maxWaitMs` रांगेतील प्रतीक्षेला मर्यादित करते; `executionMaxWaitMs` अंमलबजावणीला मर्यादित करते.** या दोन्ही मर्यादा जाणीवपूर्वक स्वतंत्र आहेत आणि त्यांपैकी कोणतीही दुसरीवर परिणाम करत नाही. `resilienceSettings.requestQueue.maxWaitMs` हे **रांग-प्रतीक्षा बजेट** आहे: यात provider स्लॉटसाठी प्रतीक्षा करणे आणि त्यानंतर QUEUED स्थितीत राहणे समाविष्ट आहे; जॉबने QUEUED स्थिती सोडून अंमलबजावणी सुरू करताच त्याचा टाइमर साफ केला जातो (`rateLimitManager.ts`, `wrappedFn`). ही मर्यादा ओलांडणारी विनंती upstream पर्यंत कधीही पोहोचत नाही. डीफॉल्ट 30000ms आहे, जो `src/lib/resilience/settings.ts` मधील `DEFAULT_REQUEST_QUEUE_MAX_WAIT_MS` द्वारे पुरवला जातो आणि `tests/unit/ratelimit-admission-control-6593.test.ts` द्वारे निश्चित केला आहे; त्यामुळे त्यात बदल केल्यास हा परिच्छेद नकळत कालबाह्य राहण्याऐवजी ती चाचणी अपयशी ठरते. `resilienceSettings.requestQueue.executionMaxWaitMs` हे Bottleneck ला जॉबचे `expiration` म्हणून मिळते आणि त्याचा टाइमर dispatch झाल्यानंतरच सुरू होतो. स्वतःचा upstream timeout नसलेल्या executors साठी ही एक सुरक्षितता मर्यादा आहे; executor चा स्वतःचा fetch-start timeout अधिक असल्यास ही मर्यादा त्यापर्यंत वाढवली जाते, त्यामुळे निरोगी in-flight प्रतिसाद मध्येच बंद केला जाऊ शकत नाही. डीफॉल्ट 600000ms (10 मिनिटे). रांग बजेट `expiration` मध्ये देण्यामुळे पूर्वी non-incremental gateways अंमलबजावणीच्या मध्यातच बंद होत असत — पहिले bytes पाठवण्यापूर्वी ते वैधरीत्या अनेक मिनिटे चालतात — आणि म्हणूनच expiration हे `code: "RATE_LIMIT_EXECUTION_TIMEOUT"` (HTTP 504) म्हणून दर्शवले जाते, तर रांग बजेटसोबत queue-timeout code असतो. यापैकी कोणतेही `RATE_LIMIT_MAX_WAIT_MS` / `RATE_LIMIT_EXECUTION_MAX_WAIT_MS` (env) किंवा dashboard (**Settings → Resilience**) द्वारे override करा. normalise करताना दोन्ही मूल्ये 1ms–24h या मर्यादेत बांधली जातात. **दोन्हींसाठी प्राधान्यक्रम:** env var फक्त _डीफॉल्ट_ पुरवतो. `resilienceSettings.requestQueue` मध्ये कायमस्वरूपी जतन केलेले मूल्य (dashboard / API patch, `key_value` मध्ये संग्रहित) त्यापेक्षा प्राधान्य घेते आणि प्रत्येक connection साठीचे `rateLimitOverrides.maxWaitMs` / `.executionMaxWaitMs` त्यापेक्षा प्राधान्य घेते. त्यामुळे आधीच कायमस्वरूपी जतन केलेले मूल्य असलेल्या deployment वर env var सेट केल्याने काहीही बदलत नाही — त्याऐवजी कायमस्वरूपी जतन केलेले setting साफ करा किंवा अद्ययावत करा. रांगेतील वास्तव्य `maxWaitMs` द्वारे मर्यादित केले जाते; खालील `maxQueueDepth` एकावेळी किती callers रांगेत असू शकतात हे मर्यादित करते. **`maxQueueDepth` — वैकल्पिक प्रवेश मर्यादा (नवीन).** `resilienceSettings.requestQueue.maxQueueDepth` एका provider+connection साठी एकावेळी किती विनंत्या रांगेत (अद्याप dispatch न झालेल्या) राहू शकतात हे मर्यादित करते. रांगेत आधीपासूनच `maxQueueDepth` विनंत्या असतील, तर नवीन विनंती `limiter.schedule()` पर्यंत पोहोचण्यापूर्वीच typed `code: "RATE_LIMIT_QUEUE_FULL"` त्रुटीसह त्वरित नाकारली जाते — त्यामुळे नकार देण्याची प्रक्रिया कमी खर्चिक असते आणि त्या विनंतीसाठीच्या कोणत्याही downstream prompt-compression / translation कामाच्या आधीच होते. डीफॉल्ट `0` = अक्षम, ज्यामुळे विद्यमान अमर्यादित-रांग वर्तन कायम राहते; मर्यादा 0–100000. `RATE_LIMIT_MAX_QUEUE_DEPTH` (env) किंवा `resilienceSettings.requestQueue.maxQueueDepth` (dashboard/API patch) द्वारे override करा. प्रवेश तपासणी स्वतः एक pure function आहे (`open-sse/services/rateLimitManager/admission.ts::checkQueueAdmission`), त्यामुळे वास्तविक Bottleneck limiter शिवाय तिची unit test करता येते. > #6593 सुरू करणाऱ्या RFC मध्ये `bypassCompressionOnRateLimit` > flag देखील प्रस्तावित केला होता. या repo ची `open-sse/services/compression/` > pipeline ही outbound LLM विनंतीवरील prompt/context compression आहे > (`chatCore.ts`, `resolveCompressionSettings`/`selectCompressionStrategy` > block च्या आसपास), संश्लेषित 429 bodies वरील HTTP response compression > नाही — प्रत्यक्ष bypass flag शी जुळणारा कोणताही code path नाही. ती > prompt-compression पायरी सध्या request pipeline मध्ये `withRateLimit()` > च्या _आधी_ देखील चालते, त्यामुळे queue-full नकाराच्या वेळी ती वगळण्यासाठी > क्रम बदलणे हा या issue च्या व्याप्तीपेक्षा वेगळा आणि मोठा बदल आहे; तो > जाणीवपूर्वक येथे अंमलात आणलेला **नाही** आणि CPU-बचतीचा फायदा क्रम > बदलण्याच्या जोखमीइतका उपयुक्त असल्यास follow-up म्हणून ठेवला आहे. --- ## 6. मंद-स्ट्रीम थ्रूपुट वॉचडॉग (#9709) पर्यायी `resilienceSettings.streamRecovery.throughputWatchdog` संरक्षक अशा अपस्ट्रीमचा शोध घेतो, जो अजूनही चंक्स पाठवत असतो पण कॉन्फिगर केलेल्या उपयुक्त-आउटपुट दरापेक्षा कमी असिस्टंट आउटपुट निर्माण करत असतो. हा मुद्दाम निष्क्रियता टाइमआउटपेक्षा वेगळा आहे: हार्टबीट्स आणि मेटाडेटा यांपैकी कोणतेही टायमर रीसेट करत नाहीत आणि त्यांना प्रगती म्हणून मोजले जात नाही. तो कठोर प्रयत्न डेडलाइनपेक्षाही (#9153) वेगळा आहे; आउटपुटच्या गुणवत्तेची पर्वा न करता ती निरपेक्ष सुरक्षा कमाल मर्यादा राहते. वॉचडॉग प्रयत्न रद्द करू शकण्यापूर्वी वॉर्म-अप कालावधी आणि त्यानंतरची संपूर्ण रोलिंग विंडो आवश्यक असते. तो Chat Completions आणि Responses API आउटपुट इव्हेंट्समधील मजकूर डेल्टांची गणना करतो (UTF-8 बाइट्सचा सावधगिरीचा प्रॉक्सी), केवळ वापर-माहिती असलेले आणि रिकामे इव्हेंट्स दुर्लक्षित करतो आणि टूल-कॉल किंवा रीझनिंग इव्हेंट्स प्रक्रियेत असताना मूल्यमापन स्थगित करतो. तो डीफॉल्टनुसार अक्षम असतो आणि `STREAM_THROUGHPUT_WATCHDOG_ENABLED=true` वापरून सक्षम करता येतो; विंडो, वॉर्म-अप, किमान दर आणि किमान मोजता येण्याजोगे आउटपुट यांना सामान्य resilience-settings normalization स्तराद्वारे मर्यादा घातल्या जातात. सक्षम केल्यावर, वॉचडॉगद्वारे केलेला रद्दबातल परिणाम फक्त सक्रिय अपस्ट्रीम प्रयत्नावर लागू केला जातो. क्लायंटला दृश्यमान होणारे कोणतेही बाइट्स पाठवण्यापूर्वी, विद्यमान समान-अकाउंट प्रारंभिक-रिकव्हरी मार्ग तो प्रयत्न पुन्हा उघडू शकतो. कमिटनंतर, स्ट्रीम कधीही विचार न करता पुन्हा प्ले केला जात नाही; फक्त विद्यमान सुरक्षित मिड-स्ट्रीम कंटिन्युएशन करार प्रत्यय जोडू शकतो. अंतिमीकरण एकदाच होते, त्यामुळे वापराचे लेखांकन आणि semaphore मुक्त करणे यांची पुनरावृत्ती होत नाही. --- ## 7. अपस्ट्रीम स्थितीचे पुनर्निर्धारण (चुकीने नमूद केलेल्या कोटा त्रुटी) **व्याप्ती:** तात्पुरता कोटा संपल्याचे चुकीच्या HTTP स्थितीद्वारे कळवणारा एक अपस्ट्रीम गेटवे. **उद्देश:** वर्गीकरणापूर्वी दिशाभूल करणारी स्थिती दुरुस्त करणे, जेणेकरून डाउनस्ट्रीम ग्राहकांना (फॉलबॅक इंजिन, कॉम्बो एकत्रीकरण, क्लायंटसमोरील प्रतिसाद) अपयशाचे खरे, पुन्हा प्रयत्न करण्यायोग्य स्वरूप दिसेल. काही गेटवे तात्पुरता कोटा संपल्याचे पुन्हा प्रयत्न न करण्यायोग्य HTTP स्थितीद्वारे सूचित करतात. `agentrouter.org` मानक `429` ऐवजी चिनी मजकूरासह (`用户额度不足` / `额度不足`) `403` (कधीकधी `400`) परत करतो. Claude Code सारखे क्लायंट `403` ला कायमस्वरूपी मानतात आणि सत्र रद्द करतात; तसेच, दुरुस्ती न केल्यास फॉलबॅक इंजिन त्याचे कोटा इव्हेंटऐवजी `AUTH_ERROR` म्हणून वर्गीकरण करेल. **अंमलबजावणी:** - रजिस्ट्री + मॅचर: `open-sse/config/upstreamStatusRestatement.ts` — प्रत्येक प्रदात्यासाठी नियमांची सूची (`{id, fromStatuses, toStatus, textMarkers, excludeMarkers, defaultRetryAfterMs}`), जी `applyStatusRestatement()` द्वारे जुळवली जाते. - कॉल स्थळ: `open-sse/handlers/chatCore.ts` मधील `providerFailure:` ब्लॉक (ओळ 3654 च्या आसपास), `parseUpstreamError()` ने त्रुटीयुक्त HTTP स्थितीसह (`!providerResponse.ok`) अपस्ट्रीम प्रतिसाद पार्स केल्यानंतर लगेच आणि कोणतेही वर्गीकरण चालण्यापूर्वी, जेणेकरून प्रत्येक डाउनस्ट्रीम ग्राहकाला दुरुस्त केलेली स्थिती दिसेल. `200` SSE स्ट्रीममध्ये अंतर्भूत असलेल्या त्रुटी वेगळ्या, नंतरच्या स्ट्रीम-पार्सिंग मार्गाचे अनुसरण करतात आणि त्या आज या हुकच्या कक्षेत **येत नाहीत** — ही ज्ञात मर्यादा आहे, पण agentrouter च्या चुकीच्या स्थितीसाठी अद्याप तिची गरज नाही (कारण ती त्रुटीयुक्त HTTP स्थिती म्हणून समोर येते). - पुनर्प्रयत्न पात्रता: `429` हे `RETRY_AFTER_ELIGIBLE_STATUSES` (`open-sse/services/combo/unavailableRetryGate.ts`) मध्ये आहे, त्यामुळे पुनर्निर्धारित त्रुटी निष्क्रिय `403` म्हणून समोर येण्याऐवजी खरी पुनर्प्रयत्न विंडो घेऊन येते. - कृत्रिम `60s` `defaultRetryAfterMs` (`upstreamStatusRestatement.ts`) हे पुनर्निर्धारित प्रतिसाद **क्लायंटला** काय सांगतो तेवढेच आहे; ते स्वतः कनेक्शनच्या अंतर्गत cooldown/lockout चा कालावधी नाही — तो पुनर्निर्धारित त्रुटी प्रत्यक्षात हाताळणाऱ्या स्वतंत्र यंत्रणेद्वारे नियंत्रित केला जातो (Connection Cooldown चे वाढते backoff, §2, API-key प्रदात्यांसाठी आधार `3s`; किंवा agentrouter सारख्या प्रत्येक-मॉडेल-कोटा प्रदात्यांसाठी Model Lockout, §3). राउटरने क्लायंटला जाहिरात केलेल्या 60s विंडोपेक्षा अंतर्गत पुनर्प्रयत्नासाठी लवकर पात्र होणे शक्य आहे — ही हेतुपुरस्सर अतिरिक्त मोकळीक आहे, बग नाही. कायमस्वरूपी त्रुटी (agentrouter चे `无权访问模型` — या मॉडेलमध्ये प्रवेश नाही) कधीही पुनर्निर्धारित केल्या जात **नाहीत**: `textMarkers` जुळले तरी `excludeMarkers` नियमाला नाकारते, त्यामुळे त्रुटी तिची मूळ स्थिती कायम ठेवते आणि कोणतीही यंत्रणा तिच्यावर अनंतकाळ पुनर्प्रयत्न करत नाही. संबंधित प्रदाता वर्गीकरण नियम (`open-sse/config/providerErrorRules.ts` मधील `agentrouter-model-access-denied`: `reason: "auth_error"`, `scope: "model"`, घोषित `6h` आधार cooldown) याचा `checkFallbackError` (`open-sse/services/accountFallback.ts`) द्वारे सर्वसाधारण apikey-श्रेणीच्या `FORBIDDEN` प्रारंभिक-परतीच्या _आधी_ विचार केला जातो; यासाठी `honorsRuleLockScope(provider)` चे गेटिंग लागू होते (#10334 — सध्या `providerErrorRules.ts` मधील `HONORS_RULE_LOCK_SCOPE_PROVIDERS` अनुमतीसूचीद्वारे फक्त agentrouter साठी). नियमाचा घोषित 6h cooldown `fallbackResult.baseCooldownMs` म्हणून पुढे जातो, पण तरीही तो आधीपासून अस्तित्वात असलेल्या प्रत्येक-मॉडेल-कोटा lockout मार्गालाच पुरवला जातो (`lockModelIfPerModelQuota()` / `recordModelLockoutFailure()`, cooldown च्या स्रोताव्यतिरिक्त #10334 मुळे अपरिवर्तित): इतर प्रत्येक मॉडेल lockout प्रमाणे तो ऑपरेटरच्या `mlSettings.maxCooldownMs` (डीफॉल्ट `1_800_000ms` / 30min) पर्यंत खाली मर्यादित केला जातो आणि _संचयित lockout कारण_ नियमातील `"auth_error"` नसून आधीपासून हार्डकोड केलेले `"forbidden"` हेच राहते — सुरुवातीपासून शेवटपर्यंत फक्त cooldown कालावधीचा आदर केला जातो, कारण स्ट्रिंगचा नाही. कनेक्शन स्वतः सक्रिय राहते; त्याच कनेक्शनवरील इतर समकक्ष मॉडेल्सवर परिणाम होत नाही. पुनर्निर्धारित कोटा त्रुटी (`额度不足`) उत्पादनामध्ये provider नियमापर्यंत पोहोचतात (`agentrouter-user-quota-exhausted`: `reason: "quota_exhausted"`, `scope: "connection"`, स्वतःचा कोणताही घोषित cooldown नाही — persistence layer चा scaled backoff default लागू होतो). #10334 पासून, `ProviderErrorRuleMatch` वरील `scope` संपूर्ण प्रवाहात वापरला जातो, पण **फक्त** `HONORS_RULE_LOCK_SCOPE_PROVIDERS` allowlist मधील providers साठी (`providerErrorRules.ts` — सध्या फक्त `"agentrouter"`, `honorsRuleLockScope()` द्वारे gated). इतर प्रत्येक provider साठी `scope` हा #10334 पूर्वीप्रमाणेच केवळ माहितीपर राहतो. `checkFallbackError` जुळलेल्या नियमाचा scope `fallbackResult.ruleScope` म्हणून उघड करतो; `isAgentrouterConnectionQuotaScope()` (`src/sse/services/auth.ts`) हा सामायिक guard आहे, जो `ruleScope` हा connection-व्यापी, स्वयं-पुनर्प्राप्त होणारा signal म्हणून मानणे खरोखर सुरक्षित आहे याची पुष्टी करतो (scope `"connection"`, reason `quota_exhausted`, कधीही `permanent` नाही, कधीही `creditsExhausted` नाही — भविष्यातील एखादा नियम scope `"connection"` ला कायमस्वरूपी account state सोबत जोडण्याविरुद्धचा बचाव). दोन consumers त्याला call करतात: - **Persistence** (`markAccountUnavailable()`, `src/sse/services/auth.ts`): passthrough-provider च्या **per-model** lockout branch मध्ये जाण्याऐवजी (agentrouter हा `passthroughModels: true` आहे → `hasPerModelQuota()` `true` परत करतो), तो **तात्पुरता connection cooldown** लागू करतो — `testStatus: "unavailable"` + `rateLimitedUntil`, कधीही terminal status (`credits_exhausted`/`banned`/`expired`) नाही — त्यामुळे cooldown संपल्यानंतर connection स्वयं-पुनर्प्राप्त होतो आणि manual credential reset ची गरज पडत नाही. `disableCooling: true` असलेल्या connections साठी हे वगळले जाते (#2997): त्याऐवजी ते opt-out per-model lockout मध्ये जाते (हा दस्तऐवजीकृत trade-off आहे — branch वरील code comment पहा). - **Same-request combo routing** (`applyComboTargetExhaustion()`, `open-sse/services/combo/targetExhaustion.ts`): हाच guard connection ला in-memory `exhaustedConnections` set मध्ये `${provider}:${connectionId}` या key ने चिन्हांकित करतो. यामुळे फक्त उर्वरित SAME-REQUEST target वगळला जातो, ज्याच्या स्वतःच्या target object वर _आधीपासूनच तोच अचूक `connectionId`_ आहे (`getExhaustedTargetSkipReason()`, `open-sse/services/combo/comboPredicates.ts`, `if (provider && connectionId)` हे `exhaustedConnections` lookup करण्यापूर्वी) — साधा model-list combo, ज्यामध्ये sibling targets कडे स्वतःचा pinned `connectionId` नसतो आणि प्रत्येक dispatch साठी response च्या `X-OmniRoute-Selected-Connection-Id` header मधून एक resolve केला जातो, त्या key शी कधीही जुळत नाही. त्या सामान्य बाबतीत, उर्वरित leg ने नुकतेच exhausted झालेले account पुन्हा वापरण्यापासूनचे खरे संरक्षण हा Set **नाही** — तर वरील persistence layer (connection चा `rateLimitedUntil` आता भविष्यातील आहे) आणि failure साठी हाच guard `transientRateLimitedProviders` ला suppress करतो, यांचे संयोजन आहे (`targetExhaustion.ts` मधील `isAgentrouterConnectionQuotaScope` branch वरील "दोन-टप्प्यांची रचना" आणि code comment पहा): तो Set unmarked ठेवल्यामुळे, provider च्या उर्वरित legs साठी `combo.ts` चा `allowRateLimitedConnection` force-allow (`open-sse/services/combo.ts:1005-1013`, `:2734-2738`) सक्रिय होत **नाही**, त्यामुळे credential selection चा `rateLimitedUntil` filter (`src/sse/services/auth.ts:1238`) नेहमीप्रमाणे पाळला जातो आणि उर्वरित leg एकतर वेगळा, अजूनही eligible असलेला agentrouter connection निवडतो किंवा credentials उपलब्ध नसल्याने अयशस्वी होतो — हा branch ने नुकतेच cooldown केलेल्या connection वर तो जबरदस्तीने परत जात नाही. ### दोन-टप्प्यांची रचना: status restatement, त्यानंतर classification Status restatement (`upstreamStatusRestatement.ts`) आणि provider classification rules (`open-sse/config/providerErrorRules.ts`, `providerRuleRegistry`) या स्वतंत्र registries आहेत. दोन्ही provider id आणि text markers वर key करतात, परंतु त्या वेगवेगळ्या ठिकाणी चालतात आणि वेगवेगळे उद्देश साध्य करतात: restatement `chatCore.ts` मध्ये HTTP status लवकर rewrite करते; classification rules `checkFallbackError()` मध्ये fallback `reason` आणि lock `scope` (`model` / `provider` / `connection`) निवडतात (`open-sse/services/accountFallback.ts`). Classification rules ना संपूर्ण error **text** (उदा. `额度不足` सारख्या body markers शी जुळवण्यासाठी आवश्यक) फक्त `providerErrorRules.ts` मधील `FULL_TEXT_RULE_PROVIDERS` allowlist मध्ये सूचीबद्ध providers साठी दिसतो — सध्या फक्त `"agentrouter"`. इतर प्रत्येक **built-in catalog** provider साठी, `checkFallbackError` हा `getProviderErrorRuleMatch` ला फक्त structured error (`{code, type}`) देतो, जो header/status/code-आधारित rules साठी पुरेसा असतो, परंतु body-text markers पाहू शकत नाही. `resolveRuleMatchBody()` helper ही निवड करतो: allowlisted providers साठी संपूर्ण error text, अन्यथा structured error. `FULL_TEXT_RULE_PROVIDERS` मध्ये एखादा **built-in** provider जोडणे हे स्पष्ट per-provider opt-in आहे — list मध्ये नसलेल्या प्रत्येक provider साठी default path byte-for-byte अपरिवर्तित राहावा म्हणून ते अस्तित्वात आहे. एखाद्या नियमाचा `scope` (`model` / `provider` / `connection`) हा `FULL_TEXT_RULE_PROVIDERS` पासून स्वतंत्र opt-in आहे: `checkFallbackError` तो फक्त `fallbackResult.ruleScope` म्हणून उघड करतो आणि downstream consumers त्याला केवळ माहितीपर label पेक्षा वेगळे महत्त्व फक्त त्याच file मधील `HONORS_RULE_LOCK_SCOPE_PROVIDERS` allowlist मध्ये असलेल्या providers साठी देतात (`honorsRuleLockScope()` द्वारे gated — सध्या फक्त `"agentrouter"`). एखादा provider त्या allowlist मध्ये आल्यानंतर `scope: "connection"` match प्रत्यक्षात काय करतो यासाठी वरील "पुनर्निर्धारित कोटा त्रुटी" पहा. **#11104 — ऑपरेटरने घोषित केलेले नियम दोन्ही अनुमतीसूचींना वळसा घालतात.** ऑपरेटर `settings.providerErrorRules` (`open-sse/config/providerErrorRules.ts::setOperatorProviderErrorRules`) द्वारे रनटाइमवर प्रत्येक प्रदात्यासाठी स्वतंत्र नियम घोषित करू शकतो, त्यासाठी ही फाइल संपादित करण्याची गरज नाही. बिल्ट-इन कॅटलॉग नियमांच्या **डीफॉल्ट** वर्तनाचे संरक्षण करण्यासाठी असलेल्या `FULL_TEXT_RULE_PROVIDERS`/`HONORS_RULE_LOCK_SCOPE_PROVIDERS` — अनुमतीसूचींमागे ऑपरेटर नियमाला गेट केल्यास, आधीच सूचीबद्ध असलेले प्रदाते वगळता इतर प्रत्येक प्रदात्यासाठी सेटिंग्ज यंत्रणा निष्क्रिय होईल, कारण नियम घोषित करणे हे आधीच ऑपरेटरचे स्पष्ट निवड-सक्षमकरण आहे. `resolveRuleMatchBody()` आणि `honorsRuleLockScope()` हे दोन्ही प्रथम `hasOperatorRuleForProvider()` तपासतात: ऑपरेटर नियम असलेल्या प्रदात्याला मूळ त्रुटी मजकूर मिळतो आणि त्याने घोषित केलेल्या `scope` चा आदर केला जातो, तो कोणत्याही अनुमतीसूचीत समाविष्ट आहे की नाही याची पर्वा न करता. **ज्ञात उणीव — HTTP 400 साठी `providerRuleRegistry` चा कधीही सल्ला घेतला जात नाही.** `checkFallbackError` ची `BAD_REQUEST` शाखा status 400 चे वर्गीकरण पूर्णपणे तिच्या स्वतःच्या पॅटर्न अॅरेद्वारे (`MODEL_ACCESS_DENIED_PATTERNS`, `CONTEXT_OVERFLOW_PATTERNS`, इत्यादी `accountFallback.ts` मध्ये) करते आणि तिच्यावरील `configuredRule`/`getProviderErrorRuleMatch` शाखेपर्यंत पोहोचण्यापूर्वीच परत येते. `status: 400` असलेला बिल्ट-इन कॅटलॉग नियम (किंवा ऑपरेटर नियम) वाक्यरचनात्मकदृष्ट्या वैध असतो, पण तो कधीही सक्रिय होणार नाही. सध्या कोणताही विद्यमान नियम 400 ला लक्ष्य करत नाही, त्यामुळे उत्पादनातील कशावरही परिणाम होत नाही — परंतु भविष्यातील 400 नियमासाठी प्रथम ही शाखा बदलावी लागेल; हा बदल केवळ नियम जोडण्यापेक्षा मोठा आहे (तो आधीच पॅटर्न-अॅरे वर्तनावर अवलंबून असलेल्या प्रत्येक प्रदात्यासाठी 400 चे पुनर्वर्गीकरण करतो) आणि एकाच प्रदात्याचा नियम जोडण्याच्या व्याप्तीबाहेर आहे. ### कोट्याचे चुकीचे वर्णन करणारा नवीन गेटवे जोडणे 1. `statusRestatementRegistry` (`open-sse/config/upstreamStatusRestatement.ts`) मध्ये एक नियम अॅरे नोंदवा. `textMarkers` प्रदाता-विशिष्ट ठेवा; `CREDITS_EXHAUSTED_SIGNALS` (`open-sse/services/accountFallback.ts`) शी टक्कर होणारी सर्वसाधारण इंग्रजी वाक्ये कधीही पुन्हा वापरू नका. 2. योग्य लॉक व्याप्ती निवडण्यासाठी (`connection` खाते-व्यापी कोट्यासाठी, `model` प्रत्येक मॉडेलच्या त्रुट्यांसाठी) पर्यायाने `open-sse/config/providerErrorRules.ts` (`providerRuleRegistry`) मध्ये वर्गीकरण नियम नोंदवा. पूर्ण त्रुटी मजकुराची (बॉडी मार्कर्सची) आवश्यकता असलेल्या प्रदात्यांच्या नियमांसाठीच ही पायरी उत्पादनात प्रभावी होते: त्याच फाइलमधील `FULL_TEXT_RULE_PROVIDERS` मध्ये प्रदाता id जोडा — अन्यथा `checkFallbackError` नियमाला केवळ संरचित `{code, type}` त्रुटीच देते आणि बॉडी-मजकुराचा नियम थेट ट्रॅफिकशी कधीही जुळणार नाही. केवळ `status`/`headers` वर जुळणाऱ्या नियमांना (जसे Opencode किंवा Minimax चे) या निवड-सक्षमकरणाची आवश्यकता नाही. स्वतंत्रपणे, जर नियम `scope: "connection"` घोषित करत असेल आणि प्रत्यक्ष connection-व्यापी cooldown तसेच त्याच विनंतीतील combo skip (केवळ माहितीपर लेबल नव्हे) हा उद्देश असेल, तर त्याच फाइलमधील `HONORS_RULE_LOCK_SCOPE_PROVIDERS` मध्ये प्रदाता id जोडा — हेच `markAccountUnavailable()` (`src/sse/services/auth.ts`) आणि `applyComboTargetExhaustion()` (`open-sse/services/combo/targetExhaustion.ts`) मधील `isAgentrouterConnectionQuotaScope()`-शैलीतील वापर गेट करते; त्याशिवाय `scope` अजूनही `fallbackResult.ruleScope` मधून पुढे जातो, पण त्यावर कोणतीही कृती होत नाही. 3. `tests/unit/upstream-status-restatement.test.ts` आणि `tests/unit/agentrouter-error-rules.test.ts` यांचे प्रतिबिंब असलेल्या युनिट चाचण्या जोडा (`not-permanent` / `not-creditsExhausted` गार्ड्ससह, आणि — प्रदात्याला अनुमतीसूचीची आवश्यकता असल्यास — `resolveRuleMatchBody()` केवळ त्या प्रदात्यासाठी पूर्ण मजकूर परत करते हे सिद्ध करणारी चाचणी). `chatCore.ts`, `classifyError` किंवा combo मध्ये कोणतेही बदल आवश्यक नाहीत. #### एग्रेस-बकेटेड लॉक (#10880) `EGRESS_BUCKETED_LOCK_PROVIDERS` (opencode फॅमिली) मधील प्रदात्यांना IP-बकेटेड upstream म्हणून हाताळले जाते (opencode चा फ्री टियर account-बकेटेड नसून IP-बकेटेड आहे — #9611 पहा): `quota_exhausted` **किंवा** `rate_limit_exceeded` म्हणून वर्गीकृत केलेला status-429, रोटेशनने त्यांना वापरून पाहण्यापूर्वी, ज्यांचा शेवटचा ज्ञात एग्रेस IP अयशस्वी connection च्या IP शी जुळतो अशा अनुमतीसूचीत समाविष्ट फॅमिलीतील प्रत्येक connection ला cooldown मध्ये टाकतो — त्यामुळे N-1 हमखास अयशस्वी होणारे upstream कॉल्स टाळले जातात (#10460/#10525 सारखीच रचना). `rate_limit_exceeded` मुद्दाम समाविष्ट केले आहे: `markAccountUnavailable` मार्गावर opencode-विशिष्ट नियम कधीही जुळत नाहीत (`checkFallbackError` ला headers/body दिले जात नाहीत, opencode हा `FULL_TEXT_RULE_PROVIDERS` मध्ये नाही), त्यामुळे ज्याच्या body मध्ये subscription-quota मजकूर ("monthly usage limit reached") आहे असा 429, `status_429` नियमापर्यंत पोहोचण्यापूर्वीच quota-text fallback (`buildSubscriptionQuotaFallback`, `accountFallback.ts`; 1h cooldown) द्वारे `quota_exhausted` म्हणून वर्गीकृत होतो — तर quota-text नसलेला 429 (साधे rate limiting) `status_429` नियमाद्वारे `rate_limit_exceeded` म्हणून वर्गीकृत होतो आणि तरीही IP फॅमिलीला cooldown मध्ये टाकतो. अनुमतीसूचीत असलेल्या प्रदात्यासाठी IP-बकेटेड rate limit हा संपलेल्या कोट्यासारखाच सिग्नल आहे. वास्तविक मर्यादा: - **सर्वोत्तम-प्रयत्न**: लॉक `proxy_logs` मधून कनेक्शनचा शेवटचा ज्ञात `egress_ip` शोधतो (24h विंडो, समकालिक, कॅश नाही). कोल्ड कॅश (egress IP ची कधीही तपासणी झालेली नाही) किंवा कोणतीही रांग नाही → अयशस्वी कनेक्शनला तरीही या शाखेद्वारे कूलडाउनमध्ये ठेवले जाते (आजच्याप्रमाणे नोंदवले जाते), फक्त कोणतेही सहोदर कनेक्शन लॉक केले जात नाही. - **कधीही अंतिम नाही**: कूलडाउन ही नूतनीकरण होणारी कोटा विंडो आहे (`testStatus: "unavailable"`); IP-स्तरीय सिग्नलवरून कधीही कायमस्वरूपी स्थिती निर्धारित केली जात नाही. `disableCooling` कनेक्शन्स ही शाखा पूर्णपणे वगळतात. - **अनुमतिसूचीतील फॅमिलीसाठी लॉकची ग्रॅन्युलॅरिटी बदलते**: हा केवळ सहोदर-संबंधित ऑप्टिमायझेशन नसून स्कोपमधील बदल आहे. opencode हा `passthroughModels` प्रोव्हायडर आहे, त्यामुळे या शाखेपूर्वी 429 मुळे प्रति-MODEL लॉकआउट होत असे; आता त्यामुळे कनेक्शन कूलडाउन होते — यात कोणतेही सहोदर कनेक्शन नसताना एकच कनेक्शन चालवणारा ऑपरेटरही समाविष्ट आहे. हीच ग्रॅन्युलॅरिटी opencode च्या नियम तक्त्याने आधीच योग्य म्हणून घोषित केली आहे (`scope: "connection"`, `providerErrorRules.ts`), परंतु opencode हा `HONORS_RULE_LOCK_SCOPE_PROVIDERS` मध्ये नसल्यामुळे आजवर तिचे पालन झालेले नाही. ही शाखा, कनेक्शन-स्कोप असलेल्या agentrouter शाखेचे प्रतिबिंब ठेवत, अयशस्वी कनेक्शनचा कूलडाउन + `backoffLevel` स्वतः लिहिते आणि परतते — खालील प्रति-मॉडेल ब्लॉक आणि सर्वसाधारण मार्गापर्यंत कधीही पोहोचले जात नाही. - **कॉम्बो समाविष्ट**: agentrouter शाखेप्रमाणेच, एखादा कॉम्बो कॉलर 429 वर लागू करत असलेल्या `persistUnavailableState`/`isCombo` डाउनग्रेडकडे हा स्कोप जाणीवपूर्वक दुर्लक्ष करतो. प्रति-मॉडेल लॉकआउट हा या स्कोपचा कमकुवत प्रकार नसून ते चुकीचे एकक आहे: तो संपलेल्या IP बद्दल काहीही सांगत नाही, त्यामुळे कॉम्बो रोटेशन प्रत्येक सहोदर कनेक्शनमागे एक हमखास-अयशस्वी कॉल करत राहील. - **सहोदर सुरक्षितता**: आधीच अंतिम स्थितीत असलेले (banned/credits_exhausted) किंवा आधीच अधिक दीर्घ कूलडाउनमध्ये असलेले सहोदर कनेक्शन कधीही अधिलिखित केले जात नाही. - **विशिष्ट अनुमतिसूची**: `EGRESS_BUCKETED_LOCK_PROVIDERS` विस्तारणे हा मालकाचा स्पष्ट निर्णय आहे; कोणतेही सर्वसाधारण वायरिंग नाही (पॅटर्न #10334/#10419). सहोदर क्वेरी तीच अनुमतिसूची SQL लिटरल म्हणून पुन्हा लिहिण्याऐवजी बाइंड करते, त्यामुळे ती विस्तारणे हा एकाच ओळीतील बदल राहतो. - **Egress IP रोटेशन, दोन्ही दिशांनी**: लुकअप विंडो (24h) ही egress-IP कॅश TTL (5 min) पेक्षा बरीच मोठी आहे, त्यामुळे "शेवटचा ज्ञात IP" हा इतिहास आहे, सद्यस्थिती नाही. एखाद्या कनेक्शनचा प्रॉक्सी या विंडोमध्ये रोटेट झाला असेल, तर लॉककडून खरोखर सामायिक असलेला IP **सुटू** शकतो (नोंदवलेला IP हा नवीन, न संपलेला IP असतो) — आणि त्याचप्रमाणे, तो संपलेल्या IP पासून त्यानंतर दूर रोटेट झालेल्या सहोदर कनेक्शनला **कूलडाउनमध्ये ठेवू** शकतो. दुसऱ्या प्रकरणात त्या सहोदर कनेक्शनला एका कूलडाउन विंडोची किंमत मोजावी लागते; इतिहास-आधारित लुकअपच्या सर्वोत्तम-प्रयत्न मर्यादा म्हणून दोन्ही स्वीकारल्या आहेत. - **खर्च**: `proxy_logs` चे दोन मर्यादित स्कॅन (`idx_pl_timestamp` द्वारे विंडो-फिल्टर केलेले), फक्त 429 च्या वारंवारतेवर. कोणताही नवीन इंडेक्स नाही (मायग्रेशन 134 YAGNI). मध्यम आकाराच्या प्रत्यक्ष-ट्रॅफिक DB प्रतीवर मोजलेले; उच्च-थ्रूपुट इन्स्टन्समध्ये त्याच विंडोमध्ये प्रमाणानुसार अधिक रांगा असतात. --- ## इतर लवचिकता वैशिष्ट्ये - **19 रूटिंग धोरणे** (प्राधान्य, भारित, राउंड-रॉबिन, कॉन्टेक्स्ट-रिले, फिल-फर्स्ट, p2c, रँडम, सर्वात-कमी-वापरलेले, खर्च-अनुकूलित, रीसेट-जागरूक, रीसेट-विंडो, हेडरूम, स्ट्रिक्ट-रँडम, ऑटो, lkgp, कॉन्टेक्स्ट-अनुकूलित, कॅश-अनुकूलित, फ्यूजन, पाइपलाइन) — [AUTO-COMBO.md](../routing/AUTO-COMBO.md) पहा. - **रीसेट-जागरूक रूटिंग** (v3.8.0) — कोटा रीसेट वेळेनुसार कनेक्शन्सना प्राधान्य देते. - **बॅकग्राउंड मोड अवनती** — Responses API `background: true` चेतावणीसह सिंक मोडमध्ये अवनत केले जाते. - **डायनॅमिक टूल मर्यादा शोध** — टूल संख्येची मर्यादा गाठल्यास प्रदात्यांकडून माघार घेतो. - **आपत्कालीन फॉलबॅक** — `OMNIROUTE_EMERGENCY_FALLBACK` द्वारे नियंत्रित; ऑपरेटर रीस्टार्ट न करता Feature Flags पृष्ठावरून ते ओव्हरराइड करू शकतात. --- ## डीबगिंग - भारित कॉम्बोचे उत्तर `503 all_targets_cooling_down` असे असल्यास (`Retry-After` सेट केलेले असते आणि `diagnostics.excluded` मध्ये प्रत्येक लक्ष्याची नोंद `model_lockout` / `circuit_open` / `provider_cooldown` / `unavailable` यांसह असते) → पूल कॉन्फिगर केलेला आणि कनेक्ट केलेला आहे; प्रत्येक लक्ष्य केवळ लवचिकता टाइमरमुळे वगळलेले आहे. `[COMBO] Weighted selection: every target excluded before dispatch — …` या चेतावणीमध्ये कारणे आणि उर्वरित सेकंद नमूद केलेले असतात. त्याच कॉम्बोकडून मिळणारे `404 no_executable_targets` म्हणजे कोणताही लवचिकता टाइमर संबंधित नव्हता (चालवण्यासाठी काहीही नाही किंवा प्रत्येक खात्याची उपलब्धता तपासणी अयशस्वी झाली). `targetResolution.ts` मध्ये गोळा केलेल्या वगळण्यांच्या आधारे `open-sse/services/combo/pinRecovery.ts` मध्ये अंगभूत केलेले आहे. - एखाद्या प्रदात्याच्या सर्व की वगळल्या गेल्या → सर्किट ब्रेकरची स्थिती आणि प्रत्येक कनेक्शनचे `rateLimitedUntil`/`testStatus` हे दोन्ही तपासा. - रीसेट विंडोनंतरही प्रदाता कायमस्वरूपी वगळलेला आहे → कोड `getStatus()`/`canExecute()` ऐवजी थेट `state` वाचत आहे. - एक की अयशस्वी झाली, तरी इतरांनी कार्य केले पाहिजे → सर्किट ब्रेकरपेक्षा कनेक्शन कूलडाउनला प्राधान्य द्या. - केवळ एक मॉडेल अयशस्वी होते → कनेक्शन कूलडाउनपेक्षा मॉडेल लॉकआउटला प्राधान्य द्या. - स्थिती स्वतःहून पूर्ववत व्हायला हवी, पण होत नाही → भविष्यातील टाइमस्टॅम्प आणि कालबाह्य स्थिती रीफ्रेश करणारा रीड पाथ तपासा. कायमस्वरूपी स्थितींसाठी मॅन्युअल बदल आवश्यक असतात. --- ## TLS फिंगरप्रिंटिंग आणि स्टेल्थ प्रदाता-विशिष्ट स्टेल्थ (JA3/JA4, CCH, ऑब्फस्केशन) स्वतंत्रपणे दस्तऐवजीकृत आहे — `docs/security/STEALTH_GUIDE.md` पहा (git; `/docs` मध्ये कम्पाइल केलेले नाही). --- ## लवचिकता चाचणी (Phase 8 · Block C) लवचिकता लॉजिकच्या युनिट चाचण्यांव्यतिरिक्त, तीन चाचण्या प्रत्यक्ष ताण/अपयश परिस्थितींमध्ये रनटाइमची चाचणी करतात (सर्व इंटिग्रेशन/नाइटली — कोणतीही PRs अवरोधित करत नाही): | चाचणी | काय | चालवण्याची पद्धत | | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------- | | केऑस | बनावट-अपस्ट्रीम नोड प्रत्यक्ष विलंब/रीसेट/टाइमआउट/503 इंजेक्ट करतो; सर्किट ब्रेकर उघडतो/पूर्ववत होतो आणि `checkFallbackError` 503 ला पुनर्प्राप्त करण्यायोग्य फॉलबॅक म्हणून वर्गीकृत करतो याची पडताळणी करतो. | `RUN_CHAOS_INT=1 npm run test:chaos` | | हीप-वाढ | `--expose-gc` अंतर्गत प्रत्येक `createSSEStream` साठी ~500 स्ट्रीम्स; हीप कमाल मर्यादेपेक्षा वाढल्यास चाचणी अयशस्वी होते (OOM गार्ड #3069). | `npm run test:heap` | | k6 सोक | `/api/monitoring/health` वर सातत्यपूर्ण लोड; p95/त्रुटी थ्रेशोल्ड्स. | `k6 run tests/load/k6-soak.js` (नाइटली) | `.github/workflows/nightly-resilience.yml` (cron + dispatch) द्वारे समन्वयित. डीफॉल्ट `test:integration` मध्ये, केऑस आणि हीप चाचण्या स्वतःहून वगळल्या जातात (`RUN_CHAOS_INT`/`--expose-gc` शिवाय). --- ## हे देखील पहा - [आर्किटेक्चर मार्गदर्शक](./ARCHITECTURE.md) — प्रणालीची आर्किटेक्चर आणि अंतर्गत रचना - [वापरकर्ता मार्गदर्शक](../guides/USER_GUIDE.md) — प्रदाते, कॉम्बो, CLI एकत्रीकरण - [ऑटो-कॉम्बो इंजिन](../routing/AUTO-COMBO.md) — 16-घटक गुणांकन, मोड पॅक्स