From 67246273fce05053f99b6bff00c1da68884beabc Mon Sep 17 00:00:00 2001 From: Mike Tuev R9FT Date: Tue, 27 Oct 2026 15:43:48 +0500 Subject: [PATCH 01/20] fix(sse): make mid-conversation system turns a per-connection opt-in without the client beta #16236 kept mid-conversation role:system turns in messages[] for plain anthropic-compatible-* relays only when the global ANTHROPIC_COMPATIBLE_MID_CONVERSATION_SYSTEM flag was on AND the client sent the mid-conversation-system-2026-04-07 anthropic-beta. Several Claude Code builds (cli and claude-desktop-3p) keep those turns in the body but never send the beta, so with the flag on they were still hoisted into the top-level system: system grew by one line each turn and the cache read stayed flat at ~30K while ~110K was re-written every turn. Whether the shape is accepted is a property of the relay, not of the client, so the decision moves to the connection: providerSpecificData .keepMidConversationSystem (strict boolean, default off, toggle in the edit-connection modal for plain anthropic-compatible-*, en/ru strings, validated and normalized like the other knobs). The client's own beta is still forwarded when present and never invented. The global feature flag, its definition, i18n description and FEATURE_FLAGS.md rows are removed. --- ...-mid-conversation-system-per-connection.md | 1 + docs/i18n/am/docs/reference/FEATURE_FLAGS.md | 7 +- docs/i18n/ar/docs/reference/FEATURE_FLAGS.md | 7 +- docs/i18n/az/docs/reference/FEATURE_FLAGS.md | 7 +- docs/i18n/bg/docs/reference/FEATURE_FLAGS.md | 7 +- docs/i18n/bn/docs/reference/FEATURE_FLAGS.md | 5 +- docs/i18n/bs/docs/reference/FEATURE_FLAGS.md | 7 +- docs/i18n/cs/docs/reference/FEATURE_FLAGS.md | 7 +- docs/i18n/da/docs/reference/FEATURE_FLAGS.md | 7 +- docs/i18n/de/docs/reference/FEATURE_FLAGS.md | 7 +- docs/i18n/el/docs/reference/FEATURE_FLAGS.md | 7 +- docs/i18n/es/docs/reference/FEATURE_FLAGS.md | 7 +- docs/i18n/et/docs/reference/FEATURE_FLAGS.md | 7 +- docs/i18n/fa/docs/reference/FEATURE_FLAGS.md | 7 +- docs/i18n/fi/docs/reference/FEATURE_FLAGS.md | 7 +- docs/i18n/fr/docs/reference/FEATURE_FLAGS.md | 7 +- docs/i18n/ga/docs/reference/FEATURE_FLAGS.md | 7 +- docs/i18n/gu/docs/reference/FEATURE_FLAGS.md | 7 +- docs/i18n/ha/docs/reference/FEATURE_FLAGS.md | 7 +- docs/i18n/he/docs/reference/FEATURE_FLAGS.md | 7 +- docs/i18n/hi/docs/reference/FEATURE_FLAGS.md | 7 +- docs/i18n/hr/docs/reference/FEATURE_FLAGS.md | 7 +- docs/i18n/hu/docs/reference/FEATURE_FLAGS.md | 7 +- docs/i18n/hy/docs/reference/FEATURE_FLAGS.md | 7 +- docs/i18n/id/docs/reference/FEATURE_FLAGS.md | 7 +- docs/i18n/ig/docs/reference/FEATURE_FLAGS.md | 7 +- docs/i18n/it/docs/reference/FEATURE_FLAGS.md | 7 +- docs/i18n/ja/docs/reference/FEATURE_FLAGS.md | 7 +- docs/i18n/ka/docs/reference/FEATURE_FLAGS.md | 7 +- docs/i18n/km/docs/reference/FEATURE_FLAGS.md | 7 +- docs/i18n/kn/docs/reference/FEATURE_FLAGS.md | 7 +- docs/i18n/ko/docs/reference/FEATURE_FLAGS.md | 7 +- docs/i18n/lt/docs/reference/FEATURE_FLAGS.md | 7 +- docs/i18n/lv/docs/reference/FEATURE_FLAGS.md | 7 +- docs/i18n/ml/docs/reference/FEATURE_FLAGS.md | 7 +- docs/i18n/mr/docs/reference/FEATURE_FLAGS.md | 7 +- docs/i18n/ms/docs/reference/FEATURE_FLAGS.md | 7 +- docs/i18n/mt/docs/reference/FEATURE_FLAGS.md | 7 +- docs/i18n/my/docs/reference/FEATURE_FLAGS.md | 7 +- docs/i18n/ne/docs/reference/FEATURE_FLAGS.md | 7 +- docs/i18n/nl/docs/reference/FEATURE_FLAGS.md | 7 +- docs/i18n/no/docs/reference/FEATURE_FLAGS.md | 7 +- docs/i18n/or/docs/reference/FEATURE_FLAGS.md | 7 +- docs/i18n/pa/docs/reference/FEATURE_FLAGS.md | 7 +- docs/i18n/phi/docs/reference/FEATURE_FLAGS.md | 7 +- docs/i18n/pl/docs/reference/FEATURE_FLAGS.md | 7 +- .../pt-BR/docs/reference/FEATURE_FLAGS.md | 7 +- docs/i18n/pt/docs/reference/FEATURE_FLAGS.md | 7 +- docs/i18n/ro/docs/reference/FEATURE_FLAGS.md | 7 +- docs/i18n/ru/docs/reference/FEATURE_FLAGS.md | 7 +- docs/i18n/si/docs/reference/FEATURE_FLAGS.md | 7 +- docs/i18n/sk/docs/reference/FEATURE_FLAGS.md | 7 +- docs/i18n/sl/docs/reference/FEATURE_FLAGS.md | 7 +- docs/i18n/sr/docs/reference/FEATURE_FLAGS.md | 7 +- docs/i18n/sv/docs/reference/FEATURE_FLAGS.md | 7 +- docs/i18n/sw/docs/reference/FEATURE_FLAGS.md | 7 +- docs/i18n/ta/docs/reference/FEATURE_FLAGS.md | 7 +- docs/i18n/te/docs/reference/FEATURE_FLAGS.md | 7 +- docs/i18n/th/docs/reference/FEATURE_FLAGS.md | 7 +- docs/i18n/tr/docs/reference/FEATURE_FLAGS.md | 7 +- .../uk-UA/docs/reference/FEATURE_FLAGS.md | 7 +- docs/i18n/ur/docs/reference/FEATURE_FLAGS.md | 7 +- docs/i18n/uz/docs/reference/FEATURE_FLAGS.md | 7 +- docs/i18n/vi/docs/reference/FEATURE_FLAGS.md | 7 +- docs/i18n/yo/docs/reference/FEATURE_FLAGS.md | 7 +- .../zh-CN/docs/reference/FEATURE_FLAGS.md | 7 +- .../zh-TW/docs/reference/FEATURE_FLAGS.md | 7 +- docs/reference/FEATURE_FLAGS.md | 7 +- open-sse/config/anthropicHeaders.ts | 12 +- open-sse/handlers/chatCore.ts | 9 +- .../handlers/chatCore/claudeSystemRole.ts | 11 +- .../services/claudeMidConversationSystem.ts | 54 +++----- open-sse/services/systemPrompt.ts | 2 +- .../components/modals/EditConnectionModal.tsx | 25 ++++ src/i18n/messages/am.json | 1 - src/i18n/messages/ar.json | 1 - src/i18n/messages/az.json | 1 - src/i18n/messages/bg.json | 1 - src/i18n/messages/bn.json | 1 - src/i18n/messages/bs.json | 1 - src/i18n/messages/cs.json | 1 - src/i18n/messages/da.json | 1 - src/i18n/messages/de.json | 1 - src/i18n/messages/el.json | 1 - src/i18n/messages/en.json | 3 +- src/i18n/messages/es.json | 1 - src/i18n/messages/et.json | 1 - src/i18n/messages/fa.json | 1 - src/i18n/messages/fi.json | 1 - src/i18n/messages/fr.json | 1 - src/i18n/messages/ga.json | 1 - src/i18n/messages/gu.json | 1 - src/i18n/messages/ha.json | 1 - src/i18n/messages/he.json | 1 - src/i18n/messages/hi.json | 1 - src/i18n/messages/hr.json | 1 - src/i18n/messages/hu.json | 1 - src/i18n/messages/hy.json | 1 - src/i18n/messages/id.json | 1 - src/i18n/messages/ig.json | 1 - src/i18n/messages/it.json | 1 - src/i18n/messages/ja.json | 1 - src/i18n/messages/ka.json | 1 - src/i18n/messages/km.json | 1 - src/i18n/messages/kn.json | 1 - src/i18n/messages/ko.json | 1 - src/i18n/messages/lt.json | 1 - src/i18n/messages/lv.json | 1 - src/i18n/messages/ml.json | 1 - src/i18n/messages/mr.json | 1 - src/i18n/messages/ms.json | 1 - src/i18n/messages/mt.json | 1 - src/i18n/messages/my.json | 1 - src/i18n/messages/ne.json | 1 - src/i18n/messages/nl.json | 1 - src/i18n/messages/no.json | 1 - src/i18n/messages/or.json | 1 - src/i18n/messages/pa.json | 1 - src/i18n/messages/phi.json | 1 - src/i18n/messages/pl.json | 1 - src/i18n/messages/pt-BR.json | 1 - src/i18n/messages/pt.json | 1 - src/i18n/messages/ro.json | 1 - src/i18n/messages/ru.json | 3 +- src/i18n/messages/si.json | 1 - src/i18n/messages/sk.json | 1 - src/i18n/messages/sl.json | 1 - src/i18n/messages/sr.json | 1 - src/i18n/messages/sv.json | 1 - src/i18n/messages/sw.json | 1 - src/i18n/messages/ta.json | 1 - src/i18n/messages/te.json | 1 - src/i18n/messages/th.json | 1 - src/i18n/messages/tr.json | 1 - src/i18n/messages/uk-UA.json | 1 - src/i18n/messages/ur.json | 1 - src/i18n/messages/uz.json | 1 - src/i18n/messages/vi.json | 1 - src/i18n/messages/yo.json | 1 - src/i18n/messages/zh-CN.json | 1 - src/i18n/messages/zh-TW.json | 1 - src/lib/providers/requestDefaults.ts | 8 ++ .../constants/featureFlagDefinitions.ts | 12 -- src/shared/validation/providerSpecificData.ts | 9 ++ ...tion-system-compatible-passthrough.test.ts | 131 +++++++++++------- tests/unit/feature-flags-settings.test.ts | 4 +- .../unit/server-owned-tool-loop-flag.test.ts | 4 +- 147 files changed, 362 insertions(+), 458 deletions(-) create mode 100644 changelog.d/fixes/anthropic-compatible-keep-mid-conversation-system-per-connection.md diff --git a/changelog.d/fixes/anthropic-compatible-keep-mid-conversation-system-per-connection.md b/changelog.d/fixes/anthropic-compatible-keep-mid-conversation-system-per-connection.md new file mode 100644 index 000000000000..4cdb1272ccb3 --- /dev/null +++ b/changelog.d/fixes/anthropic-compatible-keep-mid-conversation-system-per-connection.md @@ -0,0 +1 @@ +- **fix(sse):** the in-place mid-conversation system turns for plain `anthropic-compatible-*` relays are now a per-connection setting (`providerSpecificData.keepMidConversationSystem`, toggle in the edit-connection modal) instead of the global `ANTHROPIC_COMPATIBLE_MID_CONVERSATION_SYSTEM` feature flag, and no longer require the client to send the `mid-conversation-system-2026-04-07` beta — Claude Code builds that keep such turns without negotiating the beta were hoisted again and re-wrote ~110K tokens of cache on every turn. The beta is still forwarded when the client sends it, never invented diff --git a/docs/i18n/am/docs/reference/FEATURE_FLAGS.md b/docs/i18n/am/docs/reference/FEATURE_FLAGS.md index af7ccd88bc9a..d3550c92a86b 100644 --- a/docs/i18n/am/docs/reference/FEATURE_FLAGS.md +++ b/docs/i18n/am/docs/reference/FEATURE_FLAGS.md @@ -44,7 +44,7 @@ ## የፍላግ ካታሎግ -በ6 ምድቦች የተከፋፈሉ 111 ፍላጎች። **ነባሪ** ማለት በትርጉሙ የተወሰነው ነባሪ ነው — ይህም +በ6 ምድቦች የተከፋፈሉ 110 ፍላጎች። **ነባሪ** ማለት በትርጉሙ የተወሰነው ነባሪ ነው — ይህም የDB መሻሪያም ሆነ የአካባቢ ተለዋዋጭ በማይኖርበት ጊዜ ጥቅም ላይ የሚውለው እሴት ነው። ### ደህንነት (10) @@ -109,7 +109,7 @@ | `RADAR_ENABLED` | ቡሊያን | `false` | የOmniRoute Radar ሞጁሉን (የካታሎግ ምግብ ማሳያዎችን እና ማመሳሰልን) ያንቁ። በነባሪ ጠፍቷል፤ ማንቃት UIውን ብቻ ይከፍታል—የውሂብ ማመሳሰል የተለየ በፈቃድ የሚነቃ ባህሪ ሆኖ ይቆያል። | | `MODEL_ENDPOINT_CAPABILITY_GATE_ENABLED` | ቡሊያን | `false` | የታመኑ የሞዴል መጨረሻ ነጥብ መለያዎችን (በአቅራቢው የተገለጹ፣ ከልዩ የሞዴል id የተገመቱ፣ ወይም በከዋኙ የተዋቀሩ) ያስፈጽሙ፦ በ`/v1/chat/completions`፣ `/v1/embeddings`፣ `/v1/rerank`፣ `/v1/images/generations`፣ `/v1/videos/generations`፣ `/v1/music/generations` እና `/v1/audio/*` ላይ ሞዴሉ ወደማያገለግልበት መጨረሻ ነጥብ የተላከ ጥያቄን ከማስተላለፍ በፊት በHTTP 400 ውድቅ ያድርጉ፤ መለያ የተሰጣቸው መጨረሻ ነጥቦች የOmniRoute አስማሚ የሌላቸውን ሞዴሎች ከ`/v1/models` ደብቁ፤ ለእነዚህም የዳሽቦርዱን የሞዴል ሙከራ በ422 ይመልሱ (#16929)። በነባሪ ጠፍቷል፦ የመጨረሻ ነጥብ መለያዎች ለመረጃ ብቻ ሆነው ይቆያሉ፣ ማስተላለፉም አይለወጥም። | -### የአሂድ ጊዜ (53) +### የአሂድ ጊዜ (52) | ቁልፍ | ዓይነት | ነባሪ | ዳግም ማስጀመር | መግለጫ | | ---------------------------------------------- | ------- | ------- | --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -156,7 +156,6 @@ | `KIRO_TOOL_CALL_REPAIR_ENABLED` | boolean | `true` | | የሚለቀቅ Kiro ምላሽ ማንኛውም የሚታይ ውጤት ከመስጠቱ በፊት የተበላሸ `tool_call` መጠቅለያ (`input.name` ወይም `input.arguments` የጎደለው) ካወጣ፣ ዙሩን በ`invalid_kiro_tool_call` ከማጠናቀቅ ይልቅ ያፍኑት እና ጥያቄውን ከማስተካከያ መመሪያ ጋር አንድ ጊዜ እንደገና ይላኩ። ዳግም ሙከራው አንድ ተጨማሪ Kiro ጥያቄ ያስከፍላል፤ ሁለተኛ ውድቀት ዙሩን በ`kiro_tool_call_repair_retry_failed` ያጠናቅቃል። በነባሪ በርቷል። ዙሩ በመጀመሪያው የተበላሸ መጠቅለያ ላይ እንዲወድቅ `false` ያድርጉት። | | `CATALOG_MIRROR_REFINEMENTS_ENABLED` | boolean | `false` | | በ`/v1/models` ላይ ያሉትን የ`claude/` ማግኛ እና የተግባራዊ-ጌትዌይ መስተዋቶች ያጥሩ። በነባሪ ጠፍቷል፦ ሁለቱም የመስተዋት ሂደቶች ልክ እንደበፊቱ ይሠራሉ። ሲበራ፦ የ`claude/` ማግኛ መስተዋቶች በዝርዝር የቀረቡ የreasoning-effort ልዩነቶችን (`-low`/`-medium`/`-high`/`-xhigh`) ጭምር ይሸፍናሉ፣ የጥያቄው መንገድ ሊፈጥራቸው የማይችላቸውን አብሮገነብ `auto/*` ids እና አስቀድሞ የተዘረዘረ alias ይዘላሉ፤ የተግባራዊ-ጌትዌይ መስተዋቶች ጥምረቶችን እና በOmniRoute የተፈጠሩ ids (`claude/`፣ `no-think/`) ይዘላሉ፣ ባለቤቱን ከ`owned_by` ይወስዳሉ፣ ባለቤቱን እንደ ራሱ ጌትዌይ በፍጹም አይጠቀሙም፣ በ`blockedProviders` ውስጥ የተሰናከሉ ጌትዌዮችን እና ከአንድ ጌትዌይ ሥልጣን ካለው የቀጥታ ካታሎግ ውጭ ያሉ ሞዴሎችን ይዘላሉ። | | ---------------------------------------------- | ------- | ------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `ANTHROPIC_COMPATIBLE_MID_CONVERSATION_SYSTEM` | boolean | `false` | | በቀጥታ `anthropic-compatible-*` ሪሌዮች (claude → claude passthrough) ላይ፣ በውይይት መሐል ያሉ `role: "system"` ዙሮችን ወደ ከፍተኛ-ደረጃ `system` መለኪያ ከማውጣት ይልቅ በ`messages[]` ውስጥ ያቆዩ፤ ይህም በcache የተቀመጠው የprompt ቅድመ-ቅጥያ በዙሮች መካከል የተረጋጋ ሆኖ እንዲቆይ ያደርጋል። ይህ የሚተገበረው ደንበኛው ራሱ `mid-conversation-system-2026-04-07` `anthropic-beta` ሲልክ ብቻ ነው (ሳይቀየር ወደ upstream ይተላለፋል)። በነባሪ ጠፍቷል፦ ቤታው የሌላቸው ሪሌዮች `role: "system"` መልዕክቶችን በ400 ስለሚቃወሙ፣ እያንዳንዱ የsystem ዙር ልክ እንደበፊቱ ወደ ላይ ይወጣል። | | `MODELS_DEV_EFFORT_TIERS_ENABLED` | boolean | `false` | | የኦፕሬተር override ወይም የአቅራቢ መዝገብ ምንም ደረጃዎችን ካላወጀ፣ models.dev የሚያትማቸውን የreasoning effort ደረጃዎች (ወደ የታወቁት effort ቃላት የተገደቡ) ይጠቀሙ፤ እንዲሁም ባዶ ነው ተብሎ የታወጀ የደረጃዎች ዝርዝር በ`/v1/models` `effort_tiers` እና በጥምረት ደረጃ መገናኛዎች ውስጥ "ምንም ደረጃዎች የሉም" ተብሎ እንዲወሰድ ያድርጉ። በነባሪ ጠፍቷል፦ `effort_tiers` እና የተፈታው `supportedThinkingEfforts` ልክ እንደበፊቱ ይቆያሉ። | | `AUTO_REQUEST_RESOLVED_CAPABILITIES` | boolean | `false` | | auto/* ጥያቄዎች የዕጩዎች ስብስቡን በተፈቱ የሞዴል ብቃቶች (ምስል፣ reasoning፣ thinking) እና በታዩ የጥራት ውጤቶች በአንድ bulk read ይገነባሉ (#16419)። በነባሪ ጠፍቷል፦ በጥያቄ-ጊዜ ያሉ የsnapshot ክብደቶች ልክ እንደበፊቱ ይቆያሉ። ሲበራ፦ እነዚያ ምልክቶች የsnapshot ክብደቶቹን ይመግባሉ፣ ስለዚህ የዕጩዎች ክብደቶች እና ምርጫው ሊቀየሩ ይችላሉ። | | `AUTO_COMBO_KEYLESS_TRANSPORT_GATE` | boolean | `false` | | ትራንስፖርታቸው የአካባቢ አሳሽ (`browserBacked`) ወይም በአካባቢው የተጫነ CLI (`isLocalCli`) የሚፈልግ keyless (no-auth) አቅራቢዎችን ከ`auto/*` ዕጩ ስብስብ ውጭ ያቆያል፤ በዚህም የተንጠለጠለ አባል አንድን auto ጥያቄ እስከ የእያንዳንዱ ዒላማ ጊዜ ገደብ ድረስ እንዳያቆም ያደርጋል (#17036)። በነባሪነት ጠፍቷል፦ የkeyless ስብስቡ ሳይለወጥ ይቆያል። ቀጥተኛ የ`/` ጥሪዎች በሁለቱም ሁኔታዎች ይሠራሉ። | @@ -252,7 +251,7 @@ "requiresRestart": false, "warningLevel": "caution", }, - // ... ሁሉም 111 ፍላጎች + // ... ሁሉም 110 ፍላጎች ], "summary": { "total": 56, diff --git a/docs/i18n/ar/docs/reference/FEATURE_FLAGS.md b/docs/i18n/ar/docs/reference/FEATURE_FLAGS.md index 96e3962ee256..d5769e2e095c 100644 --- a/docs/i18n/ar/docs/reference/FEATURE_FLAGS.md +++ b/docs/i18n/ar/docs/reference/FEATURE_FLAGS.md @@ -44,7 +44,7 @@ ## دليل العلامات -111 علامة موزعة على 6 فئات. **القيمة الافتراضية** هي القيمة الافتراضية للتعريف — أي القيمة +110 علامة موزعة على 6 فئات. **القيمة الافتراضية** هي القيمة الافتراضية للتعريف — أي القيمة المستخدمة عند عدم وجود تجاوز في قاعدة البيانات أو متغير بيئة. ### الأمان (10) @@ -109,7 +109,7 @@ | `RADAR_ENABLED` | منطقي | `false` | تمكين وحدة OmniRoute Radar (شاشات موجز الدليل والمزامنة). تكون معطّلة افتراضيًا؛ ولا يؤدي تمكينها إلا إلى إتاحة واجهة المستخدم — إذ تظل مزامنة البيانات خيارًا منفصلًا يتطلب الاشتراك الصريح. | | `MODEL_ENDPOINT_CAPABILITY_GATE_ENABLED` | منطقي | `false` | فرض وسوم موثوقة لنقاط نهاية النموذج (يصرّح بها المزوّد، أو تُستنتج من معرّف نموذج متخصص، أو يعيّنها المشغّل): رفض الطلب المرسل إلى نقطة نهاية لا يخدمها النموذج باستخدام HTTP 400 قبل إرساله عبر `/v1/chat/completions` و`/v1/embeddings` و`/v1/rerank` و`/v1/images/generations` و`/v1/videos/generations` و`/v1/music/generations` و`/v1/audio/*`؛ وإخفاء النماذج التي لا تمتلك نقاط نهايتها الموسومة محوّل OmniRoute من `/v1/models`؛ والرد على اختبار النموذج في لوحة المعلومات بالحالة 422 لها (#16929). يكون معطّلًا افتراضيًا: تظل وسوم نقاط النهاية معلوماتية ولا تتغير عملية الإرسال. | -### وقت التشغيل (53) +### وقت التشغيل (52) | المفتاح | النوع | القيمة الافتراضية | إعادة التشغيل | الوصف | | ---------------------------------------------- | ------- | ----------------- | ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -156,7 +156,6 @@ | `KIRO_TOOL_CALL_REPAIR_ENABLED` | boolean | `true` | | عندما تُصدر استجابة Kiro متدفقة غلاف `tool_call` غير صحيح (يفتقد `input.name` أو `input.arguments`) قبل أي مخرجات مرئية، فتجاهله وأعِد إرسال الطلب مرة واحدة مع تعليمة تصحيحية بدلًا من إنهاء الدور بالحالة `invalid_kiro_tool_call`. تكلف إعادة المحاولة طلب Kiro إضافيًا واحدًا؛ ويؤدي الفشل الثاني إلى إنهاء الدور بالحالة `kiro_tool_call_repair_retry_failed`. يكون هذا مفعّلًا افتراضيًا. اضبطه على `false` لإنهاء الدور بالفشل عند أول غلاف غير صحيح. | | `CATALOG_MIRROR_REFINEMENTS_ENABLED` | boolean | `false` | | حسّن نسخ الاكتشاف `claude/` ونسخ البوابة الوظيفية المتطابقة على `/v1/models`. يكون هذا معطلًا افتراضيًا: تعمل مرحلتا النسخ المتطابق تمامًا كما كانتا من قبل. عند التفعيل: تشمل نسخ اكتشاف `claude/` أيضًا متغيرات جهد الاستدلال المدرجة (`-low`/`-medium`/`-high`/`-xhigh`)، وتتخطى معرّفات `auto/*` المدمجة التي لا يمكن لمسار الطلب إنشاء مثيل منها، وتتخطى أي اسم مستعار مدرج بالفعل؛ وتتخطى نسخ البوابة الوظيفية التركيبات والمعرّفات التي أنشأها OmniRoute (`claude/`، و`no-think/`)، وتأخذ المالك من `owned_by`، ولا تستخدم المالك مطلقًا بوصفه بوابته الخاصة، وتتخطى البوابات المعطلة في `blockedProviders` والنماذج الواقعة خارج الكتالوج المباشر الموثوق للبوابة. | | ---------------------------------------------- | ------- | ------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `ANTHROPIC_COMPATIBLE_MID_CONVERSATION_SYSTEM` | boolean | `false` | | في مرحّلات `anthropic-compatible-*` العادية (تمرير claude → claude)، احتفظ بأدوار `role: "system"` الواقعة في منتصف المحادثة داخل `messages[]` بدلًا من رفعها إلى معامل `system` ذي المستوى الأعلى، بحيث تظل بادئة المطالبة المخزنة مؤقتًا مستقرة عبر الأدوار. ينطبق هذا فقط عندما يكون العميل نفسه قد أرسل إصدار `mid-conversation-system-2026-04-07` التجريبي من `anthropic-beta` (ويُمرر إلى المنبع كما هو). يكون هذا معطلًا افتراضيًا: يُرفع كل دور نظام تمامًا كما كان من قبل، لأن المرحّلات التي لا تدعم الإصدار التجريبي ترفض رسائل `role: "system"` باستجابة 400. | | `MODELS_DEV_EFFORT_TIERS_ENABLED` | boolean | `false` | | استخدم مستويات جهد الاستدلال التي ينشرها models.dev (بعد حصرها في مفردات الجهد المعروفة) عندما لا يصرّح أي من تجاوزات المشغّل أو سجل المزوّد بأي منها، وتعامل مع قائمة مستويات فارغة مصرّح بها على أنها "لا توجد مستويات" في `effort_tiers` ضمن `/v1/models` وفي تقاطعات مستويات التركيبات. يكون هذا معطلًا افتراضيًا: تظل `effort_tiers` و`supportedThinkingEfforts` المحسومة تمامًا كما كانتا من قبل. | | `AUTO_REQUEST_RESOLVED_CAPABILITIES` | boolean | `false` | | تنشئ طلبات auto/* مجموعة المرشحين باستخدام إمكانات النموذج المحسومة (الرؤية، والاستدلال، والتفكير) ودرجات الجودة المرصودة ضمن قراءة مجمعة واحدة (#16419). يكون هذا معطلًا افتراضيًا: تظل أوزان اللقطة وقت الطلب تمامًا كما كانت من قبل. عند التفعيل: تُغذي هذه الإشارات أوزان اللقطة، لذا يمكن أن تتغير أوزان المرشحين وعملية الاختيار. | | `AUTO_COMBO_KEYLESS_TRANSPORT_GATE` | boolean | `false` | | استبعد المزوّدين بلا مفاتيح (من دون مصادقة) الذين يتطلب نقلهم متصفحًا محليًا (`browserBacked`) أو واجهة سطر أوامر مثبّتة محليًا (`isLocalCli`) من مجموعة مرشحي `auto/*`، كي لا يتسبب عضو عالق في تعطيل طلب تلقائي حتى انقضاء مهلة كل هدف (#17036). معطّل افتراضيًا: تظل مجموعة المزوّدين بلا مفاتيح من دون تغيير. تعمل استدعاءات `/` المباشرة في الحالتين. | @@ -253,7 +252,7 @@ "requiresRestart": false, "warningLevel": "caution", }, - // ... جميع العلامات الـ 111 + // ... جميع العلامات الـ 110 ], "summary": { "total": 56, diff --git a/docs/i18n/az/docs/reference/FEATURE_FLAGS.md b/docs/i18n/az/docs/reference/FEATURE_FLAGS.md index 3ebcd7fed8dc..96ae2ab9ce90 100644 --- a/docs/i18n/az/docs/reference/FEATURE_FLAGS.md +++ b/docs/i18n/az/docs/reference/FEATURE_FLAGS.md @@ -44,7 +44,7 @@ o, **aktiv** hesab edilir (`isFeatureFlagEnabled()` funksiyasına baxın). ## Bayraq Kataloqu -6 kateqoriya üzrə 111 bayraq. **Defolt** tərifdəki standart dəyərdir — nə DB əvəzləməsi, +6 kateqoriya üzrə 110 bayraq. **Defolt** tərifdəki standart dəyərdir — nə DB əvəzləməsi, nə də mühit dəyişəni mövcud olduqda istifadə edilən dəyərdir. ### Təhlükəsizlik (10) @@ -109,7 +109,7 @@ nə də mühit dəyişəni mövcud olduqda istifadə edilən dəyərdir. | `RADAR_ENABLED` | boolean | `false` | OmniRoute Radar modulunu (kataloq lenti ekranları və sinxronizasiya) aktivləşdirir. Defolt olaraq deaktivdir; aktivləşdirmə yalnız istifadəçi interfeysini əlçatan edir — məlumatların sinxronizasiyası üçün ayrıca açıq seçim tələb olunur. | | `MODEL_ENDPOINT_CAPABILITY_GATE_ENABLED` | boolean | `false` | Etibarlı model son nöqtəsi teqlərini (provayder tərəfindən bəyan edilmiş, ixtisaslaşdırılmış model identifikatorundan müəyyənləşdirilmiş və ya operator tərəfindən təyin edilmiş) tətbiq edir: `/v1/chat/completions`, `/v1/embeddings`, `/v1/rerank`, `/v1/images/generations`, `/v1/videos/generations`, `/v1/music/generations` və `/v1/audio/*` ünvanlarında modelin xidmət göstərmədiyi son nöqtəyə göndərilən sorğunu yönləndirmədən əvvəl HTTP 400 ilə rədd edir; teqlənmiş son nöqtələri üçün OmniRoute adapteri olmayan modelləri `/v1/models` siyahısında gizlədir; onlar üçün idarəetmə panelindəki model testinə 422 cavabı verir (#16929). Defolt olaraq deaktivdir: son nöqtə teqləri yalnız məlumat xarakteri daşıyır və yönləndirmə dəyişməz qalır. | -### İcra mühiti (53) +### İcra mühiti (52) | Açar | Növ | Standart | Yenidən başlatma | Təsvir | | ---------------------------------------------- | ------- | -------- | ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -156,7 +156,6 @@ nə də mühit dəyişəni mövcud olduqda istifadə edilən dəyərdir. | `KIRO_TOOL_CALL_REPAIR_ENABLED` | boolean | `true` | | Axınla ötürülən Kiro cavabı hər hansı görünən çıxışdan əvvəl xətalı `tool_call` qablaşdırıcısı (`input.name` və ya `input.arguments` yoxdur) yaratdıqda, onu basdırın və söhbət növbəsini `invalid_kiro_tool_call` ilə bitirmək əvəzinə düzəldici təlimatla sorğunu bir dəfə yenidən göndərin. Təkrar cəhd bir əlavə Kiro sorğusu tələb edir; ikinci uğursuzluq söhbət növbəsini `kiro_tool_call_repair_retry_failed` ilə bitirir. Defolt olaraq aktivdir. İlk xətalı qablaşdırıcıda söhbət növbəsini uğursuz bitirmək üçün `false` təyin edin. | | `CATALOG_MIRROR_REFINEMENTS_ENABLED` | boolean | `false` | | `/v1/models` üzərində `claude/` aşkarlama və funksional şlüz güzgülərini təkmilləşdirin. Defolt olaraq deaktivdir: hər iki güzgüləmə mərhələsi əvvəlki kimi işləyir. Aktiv olduqda: `claude/` aşkarlama güzgüləri siyahıya alınmış mühakimə səyi variantlarını (`-low`/`-medium`/`-high`/`-xhigh`) da əhatə edir, sorğu yolunun yarada bilmədiyi daxili `auto/*` identifikatorlarını və artıq siyahıda olan aliası ötürür; funksional şlüz güzgüləri kombinasiyaları və OmniRoute tərəfindən sintez edilmiş identifikatorları (`claude/`, `no-think/`) ötürür, sahib məlumatını `owned_by` sahəsindən götürür, heç vaxt sahibin özünü öz şlüzü kimi istifadə etmir, `blockedProviders` daxilində deaktiv edilmiş şlüzləri və şlüzün mötəbər canlı kataloqundan kənar modelləri ötürür. | | ---------------------------------------------- | ------- | ------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `ANTHROPIC_COMPATIBLE_MID_CONVERSATION_SYSTEM` | boolean | `false` | | Adi `anthropic-compatible-*` relelərində (claude → claude birbaşa ötürməsi) söhbətin ortasındakı `role: "system"` növbələrini üst səviyyəli `system` parametrinə qaldırmaq əvəzinə `messages[]` daxilində saxlayın ki, keşlənmiş sorğu prefiksi növbələr boyunca sabit qalsın. Yalnız müştərinin özü `mid-conversation-system-2026-04-07` `anthropic-beta` parametrini göndərdikdə tətbiq olunur (yuxarı axına dəyişdirilmədən ötürülür). Defolt olaraq deaktivdir: beta dəstəyi olmayan relelər `role: "system"` mesajlarını 400 cavabı ilə rədd etdiyinə görə hər bir sistem növbəsi əvvəlki kimi üst səviyyəyə qaldırılır. | | `MODELS_DEV_EFFORT_TIERS_ENABLED` | boolean | `false` | | Nə operator tərəfindən təyin edilmiş əvəzləmə, nə də provayder reyestri hər hansı səviyyə elan etdikdə, models.dev tərəfindən dərc olunan mühakimə səyi səviyyələrindən (məlum səy terminologiyası ilə məhdudlaşdırılmış) istifadə edin və elan edilmiş boş səviyyə siyahısını `/v1/models` daxilindəki `effort_tiers` və kombinasiya səviyyələrinin kəsişmələrində «səviyyə yoxdur» kimi qəbul edin. Defolt olaraq deaktivdir: `effort_tiers` və hesablanmış `supportedThinkingEfforts` əvvəlki kimi qalır. | | `AUTO_REQUEST_RESOLVED_CAPABILITIES` | boolean | `false` | | auto/* sorğuları namizəd hovuzunu hesablanmış model imkanlarından (görüntü, mühakimə, düşünmə) və müşahidə edilmiş keyfiyyət göstəricilərindən istifadə edərək vahid toplu oxuma əməliyyatında yaradır (#16419). Defolt olaraq deaktivdir: sorğu vaxtındakı ani vəziyyət çəkiləri əvvəlki kimi qalır. Aktiv olduqda: həmin siqnallar ani vəziyyət çəkilərinə daxil edilir, buna görə namizədlərin çəkiləri və seçim dəyişə bilər. | | `AUTO_COMBO_KEYLESS_TRANSPORT_GATE` | boolean | `false` | | Daşıma mexanizmi yerli brauzer (`browserBacked`) və ya yerli quraşdırılmış CLI (`isLocalCli`) tələb edən açarsız (autentifikasiyasız) provayderləri `auto/*` namizəd hovuzundan kənarda saxlayır ki, cavab verməyən üzv hər hədəf üçün nəzərdə tutulan vaxt limiti bitənədək avtomatik sorğunu dayandırmasın (#17036). Defolt olaraq söndürülüb: açarsız hovuz dəyişməz qalır. Birbaşa `/` çağırışları hər iki halda işləyir. | @@ -253,7 +252,7 @@ Hər bir bayrağı onun qüvvədə olan dəyəri, mənbəyi və xülasəsi ilə "requiresRestart": false, "warningLevel": "caution", }, - // ... 111 bayrağın hamısı + // ... 110 bayrağın hamısı ], "summary": { "total": 56, diff --git a/docs/i18n/bg/docs/reference/FEATURE_FLAGS.md b/docs/i18n/bg/docs/reference/FEATURE_FLAGS.md index 5063c2ac7805..44051e72454c 100644 --- a/docs/i18n/bg/docs/reference/FEATURE_FLAGS.md +++ b/docs/i18n/bg/docs/reference/FEATURE_FLAGS.md @@ -44,7 +44,7 @@ ## Каталог на флаговете -111 флага в 6 категории. **По подразбиране** е стойността по подразбиране от дефиницията — стойността, +110 флага в 6 категории. **По подразбиране** е стойността по подразбиране от дефиницията — стойността, използвана, когато няма нито заместваща стойност в БД, нито променлива на средата. ### Сигурност (10) @@ -109,7 +109,7 @@ | `RADAR_ENABLED` | boolean | `false` | Активиране на модула OmniRoute Radar (екрани за каталожния поток и синхронизиране). Изключено по подразбиране; активирането отключва само потребителския интерфейс — синхронизирането на данни остава отделна опция, която трябва да бъде изрично включена. | | `MODEL_ENDPOINT_CAPABILITY_GATE_ENABLED` | boolean | `false` | Прилагане на доверени етикети за крайните точки на модела (декларирани от доставчика, изведени от идентификатор на специализиран модел или зададени от оператор): заявка, изпратена до крайна точка, която моделът не обслужва, се отхвърля с HTTP 400 преди изпращането за `/v1/chat/completions`, `/v1/embeddings`, `/v1/rerank`, `/v1/images/generations`, `/v1/videos/generations`, `/v1/music/generations` и `/v1/audio/*`; моделите, чиито маркирани крайни точки нямат адаптер на OmniRoute, се скриват от `/v1/models`; тестът на модела в таблото за управление връща 422 за тях (#16929). Изключено по подразбиране: етикетите на крайните точки остават само информативни и изпращането не се променя. | -### Среда за изпълнение (53) +### Среда за изпълнение (52) | Ключ | Тип | По подразбиране | Рестартиране | Описание | | ---------------------------------------------- | ------- | --------------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -156,7 +156,6 @@ | `KIRO_TOOL_CALL_REPAIR_ENABLED` | boolean | `true` | | Когато поточен отговор от Kiro изведе неправилно формирана обвивка `tool_call` (с липсващо `input.name` или `input.arguments`) преди какъвто и да е видим изход, тя се потиска и заявката се изпраща повторно веднъж с коригираща инструкция, вместо ходът да приключи с `invalid_kiro_tool_call`. Повторният опит изисква една допълнителна заявка към Kiro; при втори неуспех ходът приключва с `kiro_tool_call_repair_retry_failed`. Включено по подразбиране. Задайте `false`, за да завършите хода с грешка още при първата неправилно формирана обвивка. | | `CATALOG_MIRROR_REFINEMENTS_ENABLED` | boolean | `false` | | Прецизира огледалните записи за откриване чрез `claude/` и функционалния шлюз в `/v1/models`. Изключено по подразбиране: и двата етапа на огледално отразяване работят точно както преди. Когато е включено: огледалните записи за откриване чрез `claude/` обхващат и изброените варианти за интензивност на разсъжденията (`-low`/`-medium`/`-high`/`-xhigh`), пропускат вградените идентификатори `auto/*`, които пътят на заявката не може да материализира, и пропускат вече посочен псевдоним; огледалните записи на функционалния шлюз пропускат комбинациите и синтезираните от OmniRoute идентификатори (`claude/`, `no-think/`), вземат собственика от `owned_by`, никога не използват собственика като негов собствен шлюз и пропускат шлюзовете, деактивирани в `blockedProviders`, както и моделите извън авторитетния актуален каталог на даден шлюз. | | ---------------------------------------------- | ------- | ------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `ANTHROPIC_COMPATIBLE_MID_CONVERSATION_SYSTEM` | boolean | `false` | | При обикновени препредаващи услуги `anthropic-compatible-*` (директно предаване от claude към claude) запазва ходовете по средата на разговора с `role: "system"` в `messages[]`, вместо да ги издига в параметъра `system` от най-горно ниво, така че кешираният префикс на подканата да остане стабилен между ходовете. Прилага се само когато самият клиент е изпратил `mid-conversation-system-2026-04-07` в `anthropic-beta` (препратено нагоре без промени). Изключено по подразбиране: всеки системен ход се издига точно както преди, тъй като препредаващите услуги без тази бета версия отхвърлят съобщенията с `role: "system"` с код 400. | | `MODELS_DEV_EFFORT_TIERS_ENABLED` | boolean | `false` | | Използва нивата на интензивност на разсъжденията, публикувани от models.dev (ограничени до познатия набор от стойности за интензивност), когато нито заместване от оператор, нито регистърът на доставчика декларира такива, и третира деклариран празен списък с нива като „без нива“ в `effort_tiers` на `/v1/models` и при пресичанията на нивата за комбинации. Изключено по подразбиране: `effort_tiers` и разрешените `supportedThinkingEfforts` остават точно както преди. | | `AUTO_REQUEST_RESOLVED_CAPABILITIES` | boolean | `false` | | Заявките auto/* изграждат пула с кандидати чрез разрешените възможности на моделите (зрение, разсъждение, мислене) и наблюдаваните оценки за качество в едно пакетно прочитане (#16419). Изключено по подразбиране: теглата на моментната снимка по време на заявката остават точно както преди. Когато е включено: тези сигнали се използват при изчисляването на теглата на моментната снимка, така че теглата и изборът на кандидатите могат да се променят. | | `AUTO_COMBO_KEYLESS_TRANSPORT_GATE` | boolean | `false` | | Изключва от набора с кандидати `auto/*` доставчиците без ключ (без удостоверяване), чийто транспорт изисква локален браузър (`browserBacked`) или локално инсталиран CLI (`isLocalCli`), така че блокирал член да не може да забави автоматична заявка до изтичането на времето за изчакване за съответната цел (#17036). Изключено по подразбиране: наборът без ключ остава непроменен. Директните извиквания на `/` работят и в двата случая. | @@ -253,7 +252,7 @@ "requiresRestart": false, "warningLevel": "caution", }, - // ... всичките 111 флага + // ... всичките 110 флага ], "summary": { "total": 56, diff --git a/docs/i18n/bn/docs/reference/FEATURE_FLAGS.md b/docs/i18n/bn/docs/reference/FEATURE_FLAGS.md index b1ea4969b9c0..9c9127fe3703 100644 --- a/docs/i18n/bn/docs/reference/FEATURE_FLAGS.md +++ b/docs/i18n/bn/docs/reference/FEATURE_FLAGS.md @@ -111,7 +111,7 @@ | `RADAR_ENABLED` | বুলিয়ান | `false` | OmniRoute Radar মডিউল (ক্যাটালগ ফিড স্ক্রিন ও সিঙ্ক) সক্রিয় করুন। ডিফল্টরূপে বন্ধ; এটি সক্রিয় করলে শুধু UI উন্মুক্ত হয়—ডেটা সিঙ্ক একটি পৃথক ঐচ্ছিক সম্মতি হিসেবেই থাকে। | | `MODEL_ENDPOINT_CAPABILITY_GATE_ENABLED` | বুলিয়ান | `false` | বিশ্বস্ত মডেল এন্ডপয়েন্ট ট্যাগ (প্রোভাইডার-ঘোষিত, বিশেষায়িত মডেল আইডি থেকে অনুমান করা, অথবা অপারেটর-নির্ধারিত) প্রয়োগ করুন: `/v1/chat/completions`, `/v1/embeddings`, `/v1/rerank`, `/v1/images/generations`, `/v1/videos/generations`, `/v1/music/generations` এবং `/v1/audio/*`-এ মডেলটি যে এন্ডপয়েন্ট পরিবেশন করে না, সেখানে পাঠানো অনুরোধ ডিসপ্যাচের আগে HTTP 400 দিয়ে প্রত্যাখ্যান করুন; `/v1/models` থেকে এমন মডেল লুকান, যেগুলোর ট্যাগযুক্ত এন্ডপয়েন্টের জন্য কোনো OmniRoute অ্যাডাপ্টার নেই; এবং সেগুলোর ক্ষেত্রে ড্যাশবোর্ড মডেল পরীক্ষায় 422 দিয়ে উত্তর দিন (#16929)। ডিফল্টরূপে বন্ধ: এন্ডপয়েন্ট ট্যাগগুলো কেবল তথ্যমূলক থাকে এবং ডিসপ্যাচ অপরিবর্তিত থাকে। | -### রানটাইম (53) +### রানটাইম (52) | কী | ধরন | ডিফল্ট | রিস্টার্ট | বিবরণ | | ---------------------------------------------- | -------- | ------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | @@ -158,7 +158,6 @@ | `KIRO_TOOL_CALL_REPAIR_ENABLED` | boolean | `true` | | কোনো স্ট্রিমিং Kiro প্রতিক্রিয়া দৃশ্যমান আউটপুটের আগে একটি ত্রুটিপূর্ণ `tool_call` র্যাপার (`input.name` বা `input.arguments` অনুপস্থিত) নির্গত করলে, টার্নটি `invalid_kiro_tool_call` দিয়ে শেষ করার পরিবর্তে সেটি দমন করুন এবং একটি সংশোধনমূলক নির্দেশনাসহ অনুরোধটি একবার পুনরায় পাঠান। পুনঃচেষ্টায় একটি অতিরিক্ত Kiro অনুরোধের খরচ হয়; দ্বিতীয়বার ব্যর্থ হলে টার্নটি `kiro_tool_call_repair_retry_failed` দিয়ে শেষ হয়। ডিফল্টভাবে চালু। প্রথম ত্রুটিপূর্ণ র্যাপারেই টার্ন ব্যর্থ করতে `false` সেট করুন। | | `CATALOG_MIRROR_REFINEMENTS_ENABLED` | boolean | `false` | | `/v1/models`-এ `claude/` ডিসকভারি এবং ফাংশনাল-গেটওয়ে মিররগুলো পরিমার্জন করুন। ডিফল্টভাবে বন্ধ: উভয় মিরর পাস ঠিক আগের মতোই আচরণ করে। চালু থাকলে: `claude/` ডিসকভারি মিররগুলো তালিকাভুক্ত রিজনিং-এফোর্ট ভ্যারিয়েন্টও (`-low`/`-medium`/`-high`/`-xhigh`) অন্তর্ভুক্ত করে, রিকোয়েস্ট পাথ বাস্তবায়ন করতে পারে না এমন বিল্ট-ইন `auto/*` আইডি এবং ইতিমধ্যে তালিকাভুক্ত অ্যালিয়াস বাদ দেয়; ফাংশনাল-গেটওয়ে মিররগুলো কম্বো এবং OmniRoute-সিন্থেসাইজড আইডি (`claude/`, `no-think/`) বাদ দেয়, `owned_by` থেকে মালিক নেয়, কখনো মালিককেই তার নিজের গেটওয়ে হিসেবে ব্যবহার করে না, `blockedProviders`-এ নিষ্ক্রিয় করা গেটওয়ে এবং কোনো গেটওয়ের কর্তৃত্বপূর্ণ লাইভ ক্যাটালগের বাইরের মডেল বাদ দেয়। | | ---------------------------------------------- | ------- | ------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `ANTHROPIC_COMPATIBLE_MID_CONVERSATION_SYSTEM` | boolean | `false` | | সাধারণ `anthropic-compatible-*` রিলেতে (claude → claude পাসথ্রু), কথোপকথনের মাঝের `role: "system"` টার্নগুলোকে শীর্ষ-স্তরের `system` প্যারামিটারে তুলে নেওয়ার পরিবর্তে `messages[]`-এর মধ্যে রাখুন, যাতে ক্যাশ করা প্রম্পট প্রিফিক্স টার্নগুলোর মধ্যে স্থিতিশীল থাকে। এটি কেবল তখনই প্রযোজ্য, যখন ক্লায়েন্ট নিজেই `mid-conversation-system-2026-04-07` `anthropic-beta` পাঠিয়েছে (যা অপরিবর্তিতভাবে আপস্ট্রিমে ফরওয়ার্ড করা হয়)। ডিফল্টভাবে বন্ধ: প্রতিটি সিস্টেম টার্ন ঠিক আগের মতোই তুলে নেওয়া হয়, কারণ বিটা ছাড়া রিলেগুলো `role: "system"` মেসেজ 400 দিয়ে প্রত্যাখ্যান করে। | | `MODELS_DEV_EFFORT_TIERS_ENABLED` | boolean | `false` | | কোনো অপারেটর ওভাররাইড বা প্রোভাইডার রেজিস্ট্রি কোনো স্তর ঘোষণা না করলে models.dev প্রকাশিত রিজনিং এফোর্ট স্তরগুলো (পরিচিত এফোর্ট শব্দভান্ডারে সীমিত) ব্যবহার করুন এবং ঘোষিত খালি স্তর-তালিকাকে `/v1/models`-এর `effort_tiers` ও কম্বো স্তর ইন্টারসেকশনে "কোনো স্তর নেই" হিসেবে বিবেচনা করুন। ডিফল্টভাবে বন্ধ: `effort_tiers` এবং সমাধানকৃত `supportedThinkingEfforts` ঠিক আগের মতোই থাকে। | | `AUTO_REQUEST_RESOLVED_CAPABILITIES` | boolean | `false` | | auto/* অনুরোধগুলো একবারের বাল্ক রিডে সমাধানকৃত মডেল সক্ষমতা (ভিশন, রিজনিং, থিংকিং) এবং পর্যবেক্ষিত মানের স্কোর ব্যবহার করে প্রার্থী পুল তৈরি করে (#16419)। ডিফল্টভাবে বন্ধ: অনুরোধকালীন স্ন্যাপশট ওয়েট ঠিক আগের মতোই থাকে। চালু থাকলে: ওই সংকেতগুলো স্ন্যাপশট ওয়েটে যুক্ত হয়, ফলে প্রার্থীর ওয়েট ও নির্বাচন পরিবর্তিত হতে পারে। | | `AUTO_COMBO_KEYLESS_TRANSPORT_GATE` | boolean | `false` | | যেসব keyless (no-auth) প্রদানকারীর transport-এর জন্য একটি স্থানীয় browser (`browserBacked`) বা স্থানীয়ভাবে ইনস্টল করা CLI (`isLocalCli`) প্রয়োজন, সেগুলোকে `auto/*` candidate pool-এর বাইরে রাখে, যাতে কোনো অচল member প্রতি-target timeout পর্যন্ত একটি auto request আটকে রাখতে না পারে (#17036)। ডিফল্টভাবে বন্ধ: keyless pool অপরিবর্তিত থাকে। সরাসরি `/` call উভয় ক্ষেত্রেই কাজ করে। | @@ -255,7 +254,7 @@ "requiresRestart": false, "warningLevel": "caution", }, - // ... মোট 111টি ফ্ল্যাগ + // ... মোট 110টি ফ্ল্যাগ ], "summary": { "total": 56, diff --git a/docs/i18n/bs/docs/reference/FEATURE_FLAGS.md b/docs/i18n/bs/docs/reference/FEATURE_FLAGS.md index de8141ca0b30..944e5269cace 100644 --- a/docs/i18n/bs/docs/reference/FEATURE_FLAGS.md +++ b/docs/i18n/bs/docs/reference/FEATURE_FLAGS.md @@ -42,7 +42,7 @@ Boolean zastavica se smatra **omogućenom** kada je njena efektivna vrijednost ` ## Katalog zastavica -111 zastavica u 6 kategorija. **Zadano** je zadana vrijednost definicije — vrijednost +110 zastavica u 6 kategorija. **Zadano** je zadana vrijednost definicije — vrijednost koja se koristi kada nisu prisutni ni nadjačavanje iz baze podataka ni varijabla okruženja. ### Sigurnost (10) @@ -107,7 +107,7 @@ koja se koristi kada nisu prisutni ni nadjačavanje iz baze podataka ni varijabl | `RADAR_ENABLED` | boolean | `false` | Omogućite modul OmniRoute Radar (ekrane kataloškog izvora i sinhronizaciju). Zadano je isključeno; omogućavanje samo otključava korisničko sučelje — sinhronizacija podataka ostaje zasebna opcija koju je potrebno izričito uključiti. | | `MODEL_ENDPOINT_CAPABILITY_GATE_ENABLED` | boolean | `false` | Nametnite pouzdane oznake krajnjih tačaka modela (koje je deklarirao pružatelj, koje su izvedene iz ID-a specijaliziranog modela ili koje je postavio operater): odbijte zahtjev poslan krajnjoj tački koju model ne opslužuje odgovorom HTTP 400 prije slanja na `/v1/chat/completions`, `/v1/embeddings`, `/v1/rerank`, `/v1/images/generations`, `/v1/videos/generations`, `/v1/music/generations` i `/v1/audio/*`; sakrijte iz `/v1/models` modele čije označene krajnje tačke nemaju OmniRoute adapter; za njih odgovorite na test modela na kontrolnoj ploči statusom 422 (#16929). Zadano je isključeno: oznake krajnjih tačaka ostaju informativne, a slanje ostaje nepromijenjeno. | -### Izvršno okruženje (53) +### Izvršno okruženje (52) | Ključ | Tip | Zadano | Ponovno pokretanje | Opis | | ---------------------------------------------- | ------- | ------- | ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -154,7 +154,6 @@ koja se koristi kada nisu prisutni ni nadjačavanje iz baze podataka ni varijabl | `KIRO_TOOL_CALL_REPAIR_ENABLED` | boolean | `true` | | Kada Kiro odgovor koji se prenosi kontinuirano pošalje neispravan omotač `tool_call` (nedostaje `input.name` ili `input.arguments`) prije bilo kakvog vidljivog izlaza, potisnite ga i jednom ponovo pošaljite zahtjev s korektivnom uputom umjesto da završite potez greškom `invalid_kiro_tool_call`. Ponovni pokušaj zahtijeva jedan dodatni Kiro zahtjev; drugi neuspjeh završava potez greškom `kiro_tool_call_repair_retry_failed`. Podrazumijevano je uključeno. Postavite na `false` da bi se potez prekinuo pri prvom neispravnom omotaču. | | `CATALOG_MIRROR_REFINEMENTS_ENABLED` | boolean | `false` | | Poboljšajte ogledala za otkrivanje `claude/` i funkcionalna pristupna ogledala na `/v1/models`. Podrazumijevano je isključeno: oba prolaza za stvaranje ogledala ponašaju se potpuno kao i ranije. Kada je uključeno: ogledala za otkrivanje `claude/` obuhvataju i navedene varijante intenziteta zaključivanja (`-low`/`-medium`/`-high`/`-xhigh`), preskaču ugrađene `auto/*` identifikatore koje putanja zahtjeva ne može materijalizirati i preskaču alias koji je već naveden; funkcionalna pristupna ogledala preskaču kombinacije i identifikatore koje je sintetizirao OmniRoute (`claude/`, `no-think/`), preuzimaju vlasnika iz `owned_by`, nikada ne koriste vlasnika kao njegov vlastiti pristupnik, preskaču pristupnike onemogućene u `blockedProviders` i modele izvan mjerodavnog aktivnog kataloga pristupnika. | | ---------------------------------------------- | ------- | ------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `ANTHROPIC_COMPATIBLE_MID_CONVERSATION_SYSTEM` | boolean | `false` | | Na običnim `anthropic-compatible-*` relejima (claude → claude direktno prosljeđivanje), zadržite poteze s `role: "system"` usred razgovora unutar `messages[]` umjesto da ih premještate u parametar najvišeg nivoa `system`, kako bi keširani prefiks upita ostao stabilan između poteza. Primjenjuje se samo kada je sam klijent poslao `mid-conversation-system-2026-04-07` `anthropic-beta` (prosljeđuje se uzvodno bez izmjena). Podrazumijevano je isključeno: svaki sistemski potez premješta se kao i ranije jer releji bez te bete odbijaju poruke s `role: "system"` odgovorom 400. | | `MODELS_DEV_EFFORT_TIERS_ENABLED` | boolean | `false` | | Koristite nivoe intenziteta zaključivanja koje objavljuje models.dev (ograničene na poznati skup vrijednosti intenziteta) kada ih ne navodi ni premošćivanje operatora ni registar pružalaca, a deklariranu praznu listu nivoa tretirajte kao "bez nivoa" u `/v1/models` `effort_tiers` i presjecima nivoa kombinacija. Podrazumijevano je isključeno: `effort_tiers` i razriješeni `supportedThinkingEfforts` ostaju potpuno isti kao i ranije. | | `AUTO_REQUEST_RESOLVED_CAPABILITIES` | boolean | `false` | | Zahtjevi auto/* izgrađuju skup kandidata pomoću razriješenih mogućnosti modela (vid, zaključivanje, razmišljanje) i zabilježenih ocjena kvaliteta u jednom skupnom čitanju (#16419). Podrazumijevano je isključeno: težine snimka u trenutku zahtjeva ostaju potpuno iste kao i ranije. Kada je uključeno: ti signali ulaze u težine snimka, pa se težine kandidata i odabir mogu promijeniti. | | `AUTO_COMBO_KEYLESS_TRANSPORT_GATE` | boolean | `false` | | Izostavite pružaoce bez ključa (bez autentifikacije) čiji transport zahtijeva lokalni preglednik (`browserBacked`) ili lokalno instalirani CLI (`isLocalCli`) iz skupa kandidata `auto/*`, tako da zaglavljeni član ne može zadržati automatski zahtjev do isteka vremena za pojedinačno odredište (#17036). Podrazumijevano je isključeno: skup bez ključa ostaje nepromijenjen. Direktni pozivi `/` rade u oba slučaja. | @@ -251,7 +250,7 @@ Vraća svaku zastavicu s njenom efektivnom vrijednošću, izvorom i sažetkom. "requiresRestart": false, "warningLevel": "caution", }, - // ... svih 111 zastavica + // ... svih 110 zastavica ], "summary": { "total": 56, diff --git a/docs/i18n/cs/docs/reference/FEATURE_FLAGS.md b/docs/i18n/cs/docs/reference/FEATURE_FLAGS.md index a60f929f29c8..c30e69e219e6 100644 --- a/docs/i18n/cs/docs/reference/FEATURE_FLAGS.md +++ b/docs/i18n/cs/docs/reference/FEATURE_FLAGS.md @@ -44,7 +44,7 @@ Příznak typu boolean se považuje za **povolený**, pokud je jeho efektivní h ## Katalog příznaků -111 příznaků v 6 kategoriích. **Výchozí hodnota** je výchozí hodnota definice — hodnota +110 příznaků v 6 kategoriích. **Výchozí hodnota** je výchozí hodnota definice — hodnota použitá v případě, že není přítomno přepsání z databáze ani proměnná prostředí. ### Zabezpečení (10) @@ -109,7 +109,7 @@ použitá v případě, že není přítomno přepsání z databáze ani proměn | `RADAR_ENABLED` | logická hodnota | `false` | Povolí modul OmniRoute Radar (obrazovky katalogových kanálů a synchronizaci). Ve výchozím nastavení je vypnutý; povolením se pouze zpřístupní uživatelské rozhraní — synchronizace dat nadále vyžaduje samostatné explicitní povolení. | | `MODEL_ENDPOINT_CAPABILITY_GATE_ENABLED` | logická hodnota | `false` | Vynutí důvěryhodné značky koncových bodů modelu (deklarované poskytovatelem, odvozené z ID specializovaného modelu nebo nastavené operátorem): před odesláním odmítne s HTTP 400 požadavek odeslaný na koncový bod, který model neposkytuje, pro `/v1/chat/completions`, `/v1/embeddings`, `/v1/rerank`, `/v1/images/generations`, `/v1/videos/generations`, `/v1/music/generations` a `/v1/audio/*`; skryje v `/v1/models` modely, jejichž označené koncové body nemají adaptér OmniRoute; a pro tyto modely vrátí v testu modelu na řídicím panelu odpověď 422 (#16929). Ve výchozím nastavení je tato možnost vypnutá: značky koncových bodů zůstávají pouze informativní a odesílání požadavků se nemění. | -### Běhové prostředí (53) +### Běhové prostředí (52) | Klíč | Typ | Výchozí | Restart | Popis | | ---------------------------------------------- | ------- | ------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -156,7 +156,6 @@ použitá v případě, že není přítomno přepsání z databáze ani proměn | `KIRO_TOOL_CALL_REPAIR_ENABLED` | boolean | `true` | | Když streamovaná odpověď Kiro před jakýmkoli viditelným výstupem odešle chybně vytvořenou obálku `tool_call` (chybí `input.name` nebo `input.arguments`), potlačí ji a jednou znovu odešle požadavek s opravnou instrukcí namísto ukončení tahu chybou `invalid_kiro_tool_call`. Opakovaný pokus stojí jeden požadavek Kiro navíc; druhé selhání ukončí tah chybou `kiro_tool_call_repair_retry_failed`. Ve výchozím nastavení zapnuto. Nastavením na `false` tah selže už při první chybně vytvořené obálce. | | `CATALOG_MIRROR_REFINEMENTS_ENABLED` | boolean | `false` | | Zpřesní zrcadla zjišťování `claude/` a funkčních bran na `/v1/models`. Ve výchozím nastavení vypnuto: oba průchody zrcadlení se chovají přesně jako dříve. Při zapnutí: zrcadla zjišťování `claude/` zahrnou také uvedené varianty intenzity uvažování (`-low`/`-medium`/`-high`/`-xhigh`), přeskočí vestavěná ID `auto/*`, která cesta požadavku nedokáže vytvořit, a přeskočí již uvedený alias; zrcadla funkčních bran přeskočí kombinace a ID syntetizovaná službou OmniRoute (`claude/`, `no-think/`), převezmou vlastníka z `owned_by`, nikdy nepoužijí vlastníka jako jeho vlastní bránu a přeskočí brány zakázané v `blockedProviders` i modely mimo autoritativní živý katalog dané brány. | | ---------------------------------------------- | ------- | ------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `ANTHROPIC_COMPATIBLE_MID_CONVERSATION_SYSTEM` | boolean | `false` | | U prostých přenosových služeb `anthropic-compatible-*` (předávání claude → claude beze změny) ponechá tahy `role: "system"` uprostřed konverzace v `messages[]` namísto jejich přesunutí do parametru `system` nejvyšší úrovně, takže prefix výzvy uložený v mezipaměti zůstane mezi tahy stabilní. Použije se pouze tehdy, když klient sám odeslal `mid-conversation-system-2026-04-07` v hlavičce `anthropic-beta` (předané upstreamu beze změny). Ve výchozím nastavení vypnuto: každý systémový tah se přesune přesně jako dříve, protože přenosové služby bez této beta funkce odmítají zprávy `role: "system"` chybou 400. | | `MODELS_DEV_EFFORT_TIERS_ENABLED` | boolean | `false` | | Použije úrovně intenzity uvažování publikované službou models.dev (omezené na známý slovník intenzit), pokud žádné nedeklaruje ani přepsání operátorem, ani registr poskytovatelů, a deklarovaný prázdný seznam úrovní bude v `effort_tiers` koncového bodu `/v1/models` a v průnicích úrovní kombinací považovat za „žádné úrovně“. Ve výchozím nastavení vypnuto: `effort_tiers` a vyhodnocené `supportedThinkingEfforts` zůstanou přesně stejné jako dříve. | | `AUTO_REQUEST_RESOLVED_CAPABILITIES` | boolean | `false` | | Požadavky auto/* sestaví fond kandidátů pomocí vyhodnocených schopností modelů (vidění, uvažování, přemýšlení) a zaznamenaných skóre kvality v jednom hromadném čtení (#16419). Ve výchozím nastavení vypnuto: váhy snímku při zpracování požadavku zůstanou přesně stejné jako dříve. Při zapnutí: tyto signály ovlivní váhy snímku, takže se mohou změnit váhy kandidátů i výběr. | | `AUTO_COMBO_KEYLESS_TRANSPORT_GATE` | boolean | `false` | | Vyřadí z fondu kandidátů `auto/*` poskytovatele bez klíče (bez autentizace), jejichž transport vyžaduje místní prohlížeč (`browserBacked`) nebo místně nainstalované CLI (`isLocalCli`), aby zaseknutý člen nemohl pozastavit automatický požadavek až do vypršení časového limitu pro daný cíl (#17036). Ve výchozím nastavení vypnuto: fond poskytovatelů bez klíče zůstává beze změny. Přímá volání `/` fungují v obou případech. | @@ -253,7 +252,7 @@ Vrátí všechny příznaky s jejich výslednou hodnotou, zdrojem a souhrnem. "requiresRestart": false, "warningLevel": "caution", }, - // ... všech 111 příznaků + // ... všech 110 příznaků ], "summary": { "total": 56, diff --git a/docs/i18n/da/docs/reference/FEATURE_FLAGS.md b/docs/i18n/da/docs/reference/FEATURE_FLAGS.md index 475224c99c74..49919e3971fc 100644 --- a/docs/i18n/da/docs/reference/FEATURE_FLAGS.md +++ b/docs/i18n/da/docs/reference/FEATURE_FLAGS.md @@ -44,7 +44,7 @@ Et boolean-flag betragtes som **aktiveret**, når dets effektive værdi er `"tru ## Flagkatalog -111 flag på tværs af 6 kategorier. **Standardværdi** er definitionens standardværdi — den værdi, +110 flag på tværs af 6 kategorier. **Standardværdi** er definitionens standardværdi — den værdi, der bruges, når der hverken findes en tilsidesættelse i databasen eller en miljøvariabel. ### Sikkerhed (10) @@ -109,7 +109,7 @@ der bruges, når der hverken findes en tilsidesættelse i databasen eller en mil | `RADAR_ENABLED` | boolean | `false` | Aktivér OmniRoute Radar-modulet (visninger af katalogfeeds og synkronisering). Deaktiveret som standard; aktivering låser kun brugergrænsefladen op — datasynkronisering kræver stadig særskilt tilvalg. | | `MODEL_ENDPOINT_CAPABILITY_GATE_ENABLED` | boolean | `false` | Håndhæv betroede tags for modelendepunkter (angivet af udbyderen, udledt fra et specialiseret model-id eller angivet af operatøren): Afvis før videresendelse med HTTP 400 en anmodning, der sendes til et endepunkt, som modellen ikke betjener, på `/v1/chat/completions`, `/v1/embeddings`, `/v1/rerank`, `/v1/images/generations`, `/v1/videos/generations`, `/v1/music/generations` og `/v1/audio/*`; skjul modeller, hvis taggede endepunkter ikke har en OmniRoute-adapter, fra `/v1/models`; besvar dashboardets modeltest med 422 for disse (#16929). Deaktiveret som standard: Endepunktstags forbliver informative, og videresendelsen er uændret. | -### Kørselstid (53) +### Kørselstid (52) | Nøgle | Type | Standardværdi | Genstart | Beskrivelse | | ---------------------------------------------- | ------- | ------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -156,7 +156,6 @@ der bruges, når der hverken findes en tilsidesættelse i databasen eller en mil | `KIRO_TOOL_CALL_REPAIR_ENABLED` | boolean | `true` | | Når et streamet Kiro-svar udsender en ugyldigt udformet `tool_call`-wrapper (manglende `input.name` eller `input.arguments`) før noget synligt output, skal den undertrykkes, og anmodningen sendes igen én gang med en korrigerende instruktion i stedet for at afslutte turen med `invalid_kiro_tool_call`. Det nye forsøg koster én ekstra Kiro-anmodning; endnu en fejl afslutter turen med `kiro_tool_call_repair_retry_failed`. Aktiveret som standard. Indstil til `false` for at lade turen mislykkes ved den første ugyldigt udformede wrapper. | | `CATALOG_MIRROR_REFINEMENTS_ENABLED` | boolean | `false` | | Finjuster spejlene for `claude/`-registrering og funktionelle gateways på `/v1/models`. Deaktiveret som standard: Begge spejlingsgennemløb fungerer præcis som før. Når aktiveret: `claude/`-registreringsspejle omfatter også de angivne varianter for ræsonneringsindsats (`-low`/`-medium`/`-high`/`-xhigh`), springer indbyggede `auto/*`-id'er over, som anmodningsstien ikke kan materialisere, og springer et alias over, der allerede er angivet; funktionelle gatewayspejle springer kombinationer og OmniRoute-syntetiserede id'er (`claude/`, `no-think/`) over, henter ejeren fra `owned_by`, bruger aldrig ejeren som sin egen gateway, springer gateways over, der er deaktiveret i `blockedProviders`, samt modeller uden for en gateways autoritative livekatalog. | | ---------------------------------------------- | ------- | ------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `ANTHROPIC_COMPATIBLE_MID_CONVERSATION_SYSTEM` | boolean | `false` | | På almindelige `anthropic-compatible-*`-relæer (claude → claude-gennemløb) skal `role: "system"`-ture midt i samtalen beholdes i `messages[]` i stedet for at blive flyttet op i parameteren `system` på øverste niveau, så den cachelagrede promptpræfiks forbliver stabil på tværs af ture. Gælder kun, når klienten selv har sendt `mid-conversation-system-2026-04-07`-`anthropic-beta` (videresendt uændret til upstream). Deaktiveret som standard: Hver systemtur flyttes op præcis som før, fordi relæer uden betaen afviser meddelelser med `role: "system"` med 400. | | `MODELS_DEV_EFFORT_TIERS_ENABLED` | boolean | `false` | | Brug de niveauer for ræsonneringsindsats, som models.dev udgiver (begrænset til det kendte ordforråd for indsats), når hverken en operatørtilsidesættelse eller udbyderregistret angiver nogen, og behandl en angivet tom niveauliste som "ingen niveauer" i `/v1/models`-`effort_tiers` og kombinationers niveausnitmængder. Deaktiveret som standard: `effort_tiers` og de opløste `supportedThinkingEfforts` forbliver præcis som før. | | `AUTO_REQUEST_RESOLVED_CAPABILITIES` | boolean | `false` | | auto/*-anmodninger opbygger kandidatpuljen med opløste modelfunktioner (billedforståelse, ræsonnering, tænkning) og observerede kvalitetsscorer i én samlet læsning (#16419). Deaktiveret som standard: Vægtene i øjebliksbilledet på anmodningstidspunktet forbliver præcis som før. Når aktiveret: Disse signaler indgår i øjebliksbilledets vægte, så kandidatvægte og valg kan ændre sig. | | `AUTO_COMBO_KEYLESS_TRANSPORT_GATE` | boolean | `false` | | Hold nøgleløse udbydere (uden godkendelse), hvis transport kræver en lokal browser (`browserBacked`) eller en lokalt installeret CLI (`isLocalCli`), ude af kandidatpuljen `auto/*`, så et medlem, der hænger, ikke kan blokere en automatisk anmodning indtil timeouten pr. mål (#17036). Slået fra som standard: Den nøgleløse pulje er uændret. Direkte kald til `/` fungerer i begge tilfælde. | @@ -253,7 +252,7 @@ Returnerer alle flag med deres effektive værdi, kilde og en oversigt. "requiresRestart": false, "warningLevel": "caution", }, - // ... alle 111 flag + // ... alle 110 flag ], "summary": { "total": 56, diff --git a/docs/i18n/de/docs/reference/FEATURE_FLAGS.md b/docs/i18n/de/docs/reference/FEATURE_FLAGS.md index e6f86e9d61a5..13b6e2f1cc52 100644 --- a/docs/i18n/de/docs/reference/FEATURE_FLAGS.md +++ b/docs/i18n/de/docs/reference/FEATURE_FLAGS.md @@ -45,7 +45,7 @@ Ein boolesches Flag gilt als **aktiviert**, wenn sein effektiver Wert `"true"`, ## Flag-Katalog -111 Flags in 6 Kategorien. **Standardwert** ist der in der Definition festgelegte Standardwert — der Wert, +110 Flags in 6 Kategorien. **Standardwert** ist der in der Definition festgelegte Standardwert — der Wert, der verwendet wird, wenn weder eine DB-Überschreibung noch eine Umgebungsvariable vorhanden ist. ### Sicherheit (10) @@ -110,7 +110,7 @@ der verwendet wird, wenn weder eine DB-Überschreibung noch eine Umgebungsvariab | `RADAR_ENABLED` | boolean | `false` | Aktiviert das OmniRoute-Radar-Modul (Ansichten und Synchronisierung des Katalog-Feeds). Standardmäßig deaktiviert; die Aktivierung schaltet lediglich die Benutzeroberfläche frei — die Datensynchronisierung muss weiterhin separat aktiviert werden. | | `MODEL_ENDPOINT_CAPABILITY_GATE_ENABLED` | boolean | `false` | Erzwingt vertrauenswürdige Modell-Endpunkt-Tags (vom Anbieter deklariert, aus einer spezialisierten Modell-ID abgeleitet oder vom Betreiber festgelegt): Lehnt eine an einen vom Modell nicht unterstützten Endpunkt gesendete Anfrage vor der Weiterleitung mit HTTP 400 ab, und zwar für `/v1/chat/completions`, `/v1/embeddings`, `/v1/rerank`, `/v1/images/generations`, `/v1/videos/generations`, `/v1/music/generations` und `/v1/audio/*`; blendet Modelle, für deren markierte Endpunkte kein OmniRoute-Adapter vorhanden ist, unter `/v1/models` aus; beantwortet den Modelltest im Dashboard für diese Modelle mit 422 (#16929). Standardmäßig deaktiviert: Endpunkt-Tags bleiben rein informativ und die Weiterleitung bleibt unverändert. | -### Laufzeit (53) +### Laufzeit (52) | Schlüssel | Typ | Standard | Neustart | Beschreibung | | ---------------------------------------------- | ------- | -------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -157,7 +157,6 @@ der verwendet wird, wenn weder eine DB-Überschreibung noch eine Umgebungsvariab | `KIRO_TOOL_CALL_REPAIR_ENABLED` | boolean | `true` | | Wenn eine streamende Kiro-Antwort vor jeglicher sichtbaren Ausgabe einen fehlerhaften `tool_call`-Wrapper ausgibt (`input.name` oder `input.arguments` fehlt), diesen unterdrücken und die Anfrage einmal mit einer Korrekturanweisung erneut senden, anstatt den Turn mit `invalid_kiro_tool_call` zu beenden. Der Wiederholungsversuch verursacht eine zusätzliche Kiro-Anfrage; ein zweiter Fehler beendet den Turn mit `kiro_tool_call_repair_retry_failed`. Standardmäßig aktiviert. Auf `false` setzen, um den Turn bereits beim ersten fehlerhaften Wrapper abzubrechen. | | `CATALOG_MIRROR_REFINEMENTS_ENABLED` | boolean | `false` | | Die `claude/`-Discovery- und Functional-Gateway-Spiegelungen unter `/v1/models` verfeinern. Standardmäßig deaktiviert: Beide Spiegelungsdurchläufe verhalten sich exakt wie zuvor. Wenn aktiviert: `claude/`-Discovery-Spiegelungen umfassen außerdem aufgeführte Varianten des Reasoning-Aufwands (`-low`/`-medium`/`-high`/`-xhigh`), überspringen integrierte `auto/*`-IDs, die der Anfragepfad nicht materialisieren kann, und überspringen bereits aufgeführte Aliasse; Functional-Gateway-Spiegelungen überspringen Kombinationen und von OmniRoute synthetisierte IDs (`claude/`, `no-think/`), übernehmen den Eigentümer aus `owned_by`, verwenden den Eigentümer niemals als sein eigenes Gateway, überspringen in `blockedProviders` deaktivierte Gateways sowie Modelle außerhalb des maßgeblichen Live-Katalogs eines Gateways. | | ---------------------------------------------- | ------- | ------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `ANTHROPIC_COMPATIBLE_MID_CONVERSATION_SYSTEM` | boolean | `false` | | Bei einfachen `anthropic-compatible-*`-Relays (claude → claude-Durchleitung) `role: "system"`-Turns innerhalb einer laufenden Konversation in `messages[]` belassen, anstatt sie in den übergeordneten Parameter `system` zu verschieben, damit das zwischengespeicherte Prompt-Präfix über mehrere Turns hinweg stabil bleibt. Gilt nur, wenn der Client selbst die `anthropic-beta` `mid-conversation-system-2026-04-07` gesendet hat (wird unverändert an den Upstream weitergeleitet). Standardmäßig deaktiviert: Jeder System-Turn wird exakt wie zuvor verschoben, da Relays ohne diese Beta Nachrichten mit `role: "system"` mit dem Statuscode 400 ablehnen. | | `MODELS_DEV_EFFORT_TIERS_ENABLED` | boolean | `false` | | Die von models.dev veröffentlichten Stufen für den Reasoning-Aufwand verwenden (beschränkt auf das bekannte Aufwandsvokabular), wenn weder eine Betreiberüberschreibung noch die Provider-Registry welche deklariert, und eine deklarierte leere Stufenliste in `/v1/models` `effort_tiers` sowie bei Schnittmengen von Kombinationsstufen als „keine Stufen“ behandeln. Standardmäßig deaktiviert: `effort_tiers` und das aufgelöste `supportedThinkingEfforts` bleiben exakt wie zuvor. | | `AUTO_REQUEST_RESOLVED_CAPABILITIES` | boolean | `false` | | auto/*-Anfragen erstellen den Kandidatenpool mit aufgelösten Modellfähigkeiten (Bildverarbeitung, Reasoning, Gedankengänge) und beobachteten Qualitätswerten in einem einzigen Sammellesevorgang (#16419). Standardmäßig deaktiviert: Die Gewichtungen des Snapshots zur Anfragezeit bleiben exakt wie zuvor. Wenn aktiviert: Diese Signale fließen in die Snapshot-Gewichtungen ein, sodass sich Kandidatengewichtungen und Auswahl ändern können. | | `AUTO_COMBO_KEYLESS_TRANSPORT_GATE` | boolean | `false` | | Schließt schlüssellose Anbieter (ohne Authentifizierung), deren Transport einen lokalen Browser (`browserBacked`) oder eine lokal installierte CLI (`isLocalCli`) benötigt, aus dem Kandidatenpool `auto/*` aus, damit ein hängendes Mitglied eine automatische Anfrage nicht bis zum zielbezogenen Timeout blockieren kann (#17036). Standardmäßig deaktiviert: Der schlüssellose Pool bleibt unverändert. Direkte Aufrufe von `/` funktionieren in beiden Fällen. | @@ -256,7 +255,7 @@ Gibt jedes Flag mit seinem effektiven Wert, seiner Quelle und einer Zusammenfass "requiresRestart": false, "warningLevel": "caution", }, - // ... alle 111 Flags + // ... alle 110 Flags ], "summary": { "total": 56, diff --git a/docs/i18n/el/docs/reference/FEATURE_FLAGS.md b/docs/i18n/el/docs/reference/FEATURE_FLAGS.md index 7437fcdbcf21..da1dbe31c82d 100644 --- a/docs/i18n/el/docs/reference/FEATURE_FLAGS.md +++ b/docs/i18n/el/docs/reference/FEATURE_FLAGS.md @@ -44,7 +44,7 @@ ## Κατάλογος σημαιών -111 σημαίες σε 6 κατηγορίες. Η **Προεπιλογή** είναι η προεπιλεγμένη τιμή του ορισμού — η τιμή +110 σημαίες σε 6 κατηγορίες. Η **Προεπιλογή** είναι η προεπιλεγμένη τιμή του ορισμού — η τιμή που χρησιμοποιείται όταν δεν υπάρχει ούτε παράκαμψη στη DB ούτε μεταβλητή περιβάλλοντος. ### Ασφάλεια (10) @@ -109,7 +109,7 @@ | `RADAR_ENABLED` | boolean | `false` | Ενεργοποιεί τη λειτουργική μονάδα OmniRoute Radar (οθόνες ροής καταλόγου και συγχρονισμός). Είναι απενεργοποιημένη από προεπιλογή· η ενεργοποίηση ξεκλειδώνει μόνο το περιβάλλον εργασίας χρήστη — ο συγχρονισμός δεδομένων εξακολουθεί να απαιτεί ξεχωριστή ρητή συγκατάθεση. | | `MODEL_ENDPOINT_CAPABILITY_GATE_ENABLED` | boolean | `false` | Επιβάλλει αξιόπιστες ετικέτες τελικών σημείων μοντέλων (δηλωμένες από τον πάροχο, συναγόμενες από το αναγνωριστικό ενός εξειδικευμένου μοντέλου ή ορισμένες από τον διαχειριστή): απορρίπτει με HTTP 400, πριν από την αποστολή, ένα αίτημα που έχει σταλεί σε τελικό σημείο το οποίο δεν εξυπηρετεί το μοντέλο, στα `/v1/chat/completions`, `/v1/embeddings`, `/v1/rerank`, `/v1/images/generations`, `/v1/videos/generations`, `/v1/music/generations` και `/v1/audio/*`· αποκρύπτει από το `/v1/models` τα μοντέλα των οποίων τα τελικά σημεία με ετικέτες δεν διαθέτουν προσαρμογέα OmniRoute· απαντά με 422 στη δοκιμή μοντέλου του πίνακα ελέγχου για αυτά (#16929). Είναι απενεργοποιημένο από προεπιλογή: οι ετικέτες τελικών σημείων παραμένουν ενημερωτικές και η αποστολή δεν αλλάζει. | -### Περιβάλλον εκτέλεσης (53) +### Περιβάλλον εκτέλεσης (52) | Κλειδί | Τύπος | Προεπιλογή | Επανεκκίνηση | Περιγραφή | | ---------------------------------------------- | ------- | ---------- | ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -156,7 +156,6 @@ | `KIRO_TOOL_CALL_REPAIR_ENABLED` | boolean | `true` | | Όταν μια απόκριση ροής του Kiro εκπέμπει ένα εσφαλμένα διαμορφωμένο περίβλημα `tool_call` (λείπει το `input.name` ή το `input.arguments`) πριν από οποιαδήποτε ορατή έξοδο, το καταστέλλει και αποστέλλει ξανά το αίτημα μία φορά με μια διορθωτική οδηγία, αντί να τερματίσει τη σειρά με `invalid_kiro_tool_call`. Η επανάληψη κοστίζει ένα επιπλέον αίτημα Kiro· μια δεύτερη αποτυχία τερματίζει τη σειρά με `kiro_tool_call_repair_retry_failed`. Ενεργοποιημένο από προεπιλογή. Ορίστε το σε `false` για να αποτυγχάνει η σειρά με το πρώτο εσφαλμένα διαμορφωμένο περίβλημα. | | `CATALOG_MIRROR_REFINEMENTS_ENABLED` | boolean | `false` | | Βελτιώνει τα είδωλα εντοπισμού `claude/` και λειτουργικών πυλών στο `/v1/models`. Απενεργοποιημένο από προεπιλογή: και τα δύο περάσματα ειδώλων συμπεριφέρονται ακριβώς όπως πριν. Όταν είναι ενεργοποιημένο: τα είδωλα εντοπισμού `claude/` καλύπτουν επίσης τις παραλλαγές έντασης συλλογιστικής που παρατίθενται (`-low`/`-medium`/`-high`/`-xhigh`), παραλείπουν τα ενσωματωμένα αναγνωριστικά `auto/*` που η διαδρομή αιτήματος δεν μπορεί να υλοποιήσει και παραλείπουν ένα ψευδώνυμο που παρατίθεται ήδη· τα είδωλα λειτουργικών πυλών παραλείπουν συνδυασμούς και αναγνωριστικά που συντίθενται από το OmniRoute (`claude/`, `no-think/`), λαμβάνουν τον κάτοχο από το `owned_by`, δεν χρησιμοποιούν ποτέ τον κάτοχο ως δική του πύλη, παραλείπουν πύλες που είναι απενεργοποιημένες στο `blockedProviders` και μοντέλα εκτός του έγκυρου ζωντανού καταλόγου μιας πύλης. | | ---------------------------------------------- | ------- | ------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `ANTHROPIC_COMPATIBLE_MID_CONVERSATION_SYSTEM` | boolean | `false` | | Σε απλούς αναμεταδότες `anthropic-compatible-*` (διέλευση claude → claude), διατηρεί τις ενδιάμεσες σειρές `role: "system"` της συνομιλίας μέσα στο `messages[]`, αντί να τις ανυψώνει στην παράμετρο ανώτατου επιπέδου `system`, ώστε το αποθηκευμένο στην κρυφή μνήμη πρόθεμα της προτροπής να παραμένει σταθερό μεταξύ των σειρών. Εφαρμόζεται μόνο όταν ο ίδιος ο πελάτης έχει στείλει το `mid-conversation-system-2026-04-07` `anthropic-beta` (προωθείται αυτούσιο προς τα ανάντη). Απενεργοποιημένο από προεπιλογή: κάθε σειρά συστήματος ανυψώνεται ακριβώς όπως πριν, επειδή οι αναμεταδότες χωρίς την έκδοση beta απορρίπτουν τα μηνύματα `role: "system"` με 400. | | `MODELS_DEV_EFFORT_TIERS_ENABLED` | boolean | `false` | | Χρησιμοποιεί τα επίπεδα έντασης συλλογιστικής που δημοσιεύει το models.dev (περιορισμένα στο γνωστό λεξιλόγιο έντασης), όταν ούτε μια παράκαμψη χειριστή ούτε το μητρώο παρόχων δηλώνει κάποιο, και αντιμετωπίζει μια δηλωμένη κενή λίστα επιπέδων ως «χωρίς επίπεδα» στο `effort_tiers` του `/v1/models` και στις τομές επιπέδων των συνδυασμών. Απενεργοποιημένο από προεπιλογή: το `effort_tiers` και τα επιλυμένα `supportedThinkingEfforts` παραμένουν ακριβώς όπως πριν. | | `AUTO_REQUEST_RESOLVED_CAPABILITIES` | boolean | `false` | | Τα αιτήματα auto/* δημιουργούν τη δεξαμενή υποψηφίων με επιλυμένες δυνατότητες μοντέλου (όραση, συλλογιστική, σκέψη) και παρατηρούμενες βαθμολογίες ποιότητας σε μία μαζική ανάγνωση (#16419). Απενεργοποιημένο από προεπιλογή: τα βάρη στιγμιότυπου κατά τον χρόνο του αιτήματος παραμένουν ακριβώς όπως πριν. Όταν είναι ενεργοποιημένο: αυτά τα σήματα τροφοδοτούν τα βάρη του στιγμιότυπου, επομένως τα βάρη και η επιλογή των υποψηφίων μπορούν να αλλάξουν. | | `AUTO_COMBO_KEYLESS_TRANSPORT_GATE` | boolean | `false` | | Εξαιρεί από τη δεξαμενή υποψηφίων `auto/*` τους παρόχους χωρίς κλειδί (χωρίς έλεγχο ταυτότητας) των οποίων η μεταφορά απαιτεί τοπικό πρόγραμμα περιήγησης (`browserBacked`) ή τοπικά εγκατεστημένο CLI (`isLocalCli`), ώστε ένα μέλος που έχει κολλήσει να μην μπορεί να καθυστερήσει ένα αυτόματο αίτημα μέχρι τη λήξη του χρονικού ορίου ανά προορισμό (#17036). Απενεργοποιημένο από προεπιλογή: η δεξαμενή χωρίς κλειδί παραμένει αμετάβλητη. Οι άμεσες κλήσεις `/` λειτουργούν και στις δύο περιπτώσεις. | @@ -253,7 +252,7 @@ "requiresRestart": false, "warningLevel": "caution", }, - // ... και οι 111 σημαίες + // ... και οι 110 σημαίες ], "summary": { "total": 56, diff --git a/docs/i18n/es/docs/reference/FEATURE_FLAGS.md b/docs/i18n/es/docs/reference/FEATURE_FLAGS.md index 427df2011476..0c8b71b44d2f 100644 --- a/docs/i18n/es/docs/reference/FEATURE_FLAGS.md +++ b/docs/i18n/es/docs/reference/FEATURE_FLAGS.md @@ -44,7 +44,7 @@ Un indicador booleano se considera **habilitado** cuando su valor efectivo es `" ## Catálogo de indicadores -111 indicadores en 6 categorías. El valor **Predeterminado** es el definido de forma predeterminada: el valor +110 indicadores en 6 categorías. El valor **Predeterminado** es el definido de forma predeterminada: el valor utilizado cuando no existe una anulación en la BD ni una variable de entorno. ### Seguridad (10) @@ -109,7 +109,7 @@ utilizado cuando no existe una anulación en la BD ni una variable de entorno. | `RADAR_ENABLED` | boolean | `false` | Activa el módulo Radar de OmniRoute (pantallas del canal del catálogo y sincronización). Está desactivado de forma predeterminada; activarlo solo desbloquea la interfaz de usuario, mientras que la sincronización de datos sigue requiriendo una activación independiente. | | `MODEL_ENDPOINT_CAPABILITY_GATE_ENABLED` | boolean | `false` | Aplica etiquetas de endpoints de modelos de confianza (declaradas por el proveedor, inferidas a partir del identificador de un modelo especializado o establecidas por el operador): rechaza con HTTP 400, antes de enviarla, una solicitud dirigida a un endpoint que el modelo no admite en `/v1/chat/completions`, `/v1/embeddings`, `/v1/rerank`, `/v1/images/generations`, `/v1/videos/generations`, `/v1/music/generations` y `/v1/audio/*`; oculta en `/v1/models` los modelos cuyos endpoints etiquetados no tienen un adaptador de OmniRoute; y responde con 422 a la prueba de modelos del panel para dichos modelos (#16929). Desactivado de forma predeterminada: las etiquetas de endpoints siguen siendo informativas y el envío no cambia. | -### Entorno de ejecución (53) +### Entorno de ejecución (52) | Clave | Tipo | Predeterminado | Reinicio | Descripción | | ---------------------------------------------- | -------- | -------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | @@ -156,7 +156,6 @@ utilizado cuando no existe una anulación en la BD ni una variable de entorno. | `KIRO_TOOL_CALL_REPAIR_ENABLED` | boolean | `true` | | Cuando una respuesta en streaming de Kiro emite un contenedor `tool_call` mal formado (sin `input.name` o `input.arguments`) antes de cualquier salida visible, lo suprime y vuelve a enviar la solicitud una vez con una instrucción correctiva en lugar de finalizar el turno con `invalid_kiro_tool_call`. El reintento cuesta una solicitud adicional a Kiro; un segundo fallo finaliza el turno con `kiro_tool_call_repair_retry_failed`. Activado de forma predeterminada. Establécelo en `false` para que el turno falle con el primer contenedor mal formado. | | `CATALOG_MIRROR_REFINEMENTS_ENABLED` | boolean | `false` | | Perfecciona los espejos de descubrimiento `claude/` y de puertas de enlace funcionales en `/v1/models`. Desactivado de forma predeterminada: ambas pasadas de replicación se comportan exactamente como antes. Si está activado: los espejos de descubrimiento `claude/` también abarcan las variantes de esfuerzo de razonamiento enumeradas (`-low`/`-medium`/`-high`/`-xhigh`), omiten los identificadores integrados `auto/*` que la ruta de solicitud no puede materializar y omiten los alias que ya están enumerados; los espejos de puertas de enlace funcionales omiten los combos y los identificadores sintetizados por OmniRoute (`claude/`, `no-think/`), toman el propietario de `owned_by`, nunca usan al propietario como su propia puerta de enlace, omiten las puertas de enlace deshabilitadas en `blockedProviders` y los modelos que no estén en el catálogo activo autoritativo de una puerta de enlace. | | ---------------------------------------------- | ------- | ------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `ANTHROPIC_COMPATIBLE_MID_CONVERSATION_SYSTEM` | boolean | `false` | | En relés `anthropic-compatible-*` simples (paso directo de claude → claude), mantiene los turnos `role: "system"` a mitad de conversación dentro de `messages[]` en lugar de elevarlos al parámetro `system` de nivel superior, para que el prefijo del prompt almacenado en caché permanezca estable entre turnos. Solo se aplica cuando el propio cliente envió la beta de Anthropic `mid-conversation-system-2026-04-07` `anthropic-beta` (reenviada al origen sin cambios). Desactivado de forma predeterminada: cada turno del sistema se eleva exactamente como antes, porque los relés sin la beta rechazan los mensajes `role: "system"` con un error 400. | | `MODELS_DEV_EFFORT_TIERS_ENABLED` | boolean | `false` | | Usa los niveles de esfuerzo de razonamiento que publica models.dev (limitados al vocabulario de esfuerzo conocido) cuando ni una sobrescritura del operador ni el registro del proveedor declaran alguno, y trata una lista de niveles declarada pero vacía como "sin niveles" en `effort_tiers` de `/v1/models` y en las intersecciones de niveles de los combos. Desactivado de forma predeterminada: `effort_tiers` y los valores resueltos de `supportedThinkingEfforts` permanecen exactamente como antes. | | `AUTO_REQUEST_RESOLVED_CAPABILITIES` | boolean | `false` | | Las solicitudes auto/* crean el grupo de candidatos con las capacidades resueltas de los modelos (visión, razonamiento, pensamiento) y las puntuaciones de calidad observadas mediante una única lectura masiva (#16419). Desactivado de forma predeterminada: las ponderaciones de las instantáneas durante la solicitud permanecen exactamente como antes. Si está activado: esas señales alimentan las ponderaciones de las instantáneas, por lo que las ponderaciones de los candidatos y la selección pueden cambiar. | | `AUTO_COMBO_KEYLESS_TRANSPORT_GATE` | boolean | `false` | | Mantiene fuera del conjunto de candidatos `auto/*` a los proveedores sin clave (sin autenticación) cuyo transporte necesita un navegador local (`browserBacked`) o una CLI instalada localmente (`isLocalCli`), para que un miembro bloqueado no pueda detener una solicitud automática hasta que se agote el tiempo de espera por destino (#17036). Desactivado de forma predeterminada: el conjunto sin clave no cambia. Las llamadas directas a `/` funcionan de cualquier manera. | @@ -253,7 +252,7 @@ Devuelve todos los indicadores con su valor efectivo, origen y un resumen. "requiresRestart": false, "warningLevel": "caution", }, - // ... los 111 indicadores + // ... los 110 indicadores ], "summary": { "total": 56, diff --git a/docs/i18n/et/docs/reference/FEATURE_FLAGS.md b/docs/i18n/et/docs/reference/FEATURE_FLAGS.md index 066f1f3c827c..a3516a9ba267 100644 --- a/docs/i18n/et/docs/reference/FEATURE_FLAGS.md +++ b/docs/i18n/et/docs/reference/FEATURE_FLAGS.md @@ -44,7 +44,7 @@ Tõeväärtusega lipp loetakse **lubatuks**, kui selle tegelik väärtus on `"tr ## Lippude kataloog -111 lippu kuues kategoorias. **Vaikeväärtus** on definitsioonis määratud vaikeväärtus — väärtus, +110 lippu kuues kategoorias. **Vaikeväärtus** on definitsioonis määratud vaikeväärtus — väärtus, mida kasutatakse, kui puuduvad nii andmebaasi ülekirjutus kui ka keskkonnamuutuja. ### Turve (10) @@ -109,7 +109,7 @@ mida kasutatakse, kui puuduvad nii andmebaasi ülekirjutus kui ka keskkonnamuutu | `RADAR_ENABLED` | boolean | `false` | Lubab OmniRoute Radari mooduli (kataloogivoo kuvad ja sünkroonimine). Vaikimisi välja lülitatud; lubamine avab ainult kasutajaliidese — andmete sünkroonimine tuleb eraldi sisse lülitada. | | `MODEL_ENDPOINT_CAPABILITY_GATE_ENABLED` | boolean | `false` | Jõustab usaldusväärsed mudeli lõpp-punktide sildid (teenusepakkuja deklareeritud, spetsiaalse mudeli ID põhjal tuletatud või operaatori määratud): lükkab lõpp-punktile saadetud päringu, mida mudel ei teeninda, enne edastamist HTTP 400 vastusega tagasi lõpp-punktides `/v1/chat/completions`, `/v1/embeddings`, `/v1/rerank`, `/v1/images/generations`, `/v1/videos/generations`, `/v1/music/generations` ja `/v1/audio/*`; peidab lõpp-punktist `/v1/models` mudelid, mille sildistatud lõpp-punktidel puudub OmniRoute'i adapter; vastab nende puhul juhtpaneeli mudelitestile olekuga 422 (#16929). Vaikimisi välja lülitatud: lõpp-punktide sildid jäävad informatiivseks ja edastamine ei muutu. | -### Käituskeskkond (53) +### Käituskeskkond (52) | Võti | Tüüp | Vaikimisi | Taaskäivitus | Kirjeldus | | ---------------------------------------------- | ---------- | --------- | ------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -156,7 +156,6 @@ mida kasutatakse, kui puuduvad nii andmebaasi ülekirjutus kui ka keskkonnamuutu | `KIRO_TOOL_CALL_REPAIR_ENABLED` | boolean | `true` | | Kui Kiro voogedastusvastus väljastab enne mis tahes nähtavat väljundit vigase `tool_call` ümbrise (puudub `input.name` või `input.arguments`), jäta see välja ja saada päring üks kord koos parandava juhisega uuesti, selle asemel et lõpetada voor veaga `invalid_kiro_tool_call`. Korduskatse maksab ühe täiendava Kiro päringu; teine nurjumine lõpetab vooru veaga `kiro_tool_call_repair_retry_failed`. Vaikimisi sees. Määra väärtuseks `false`, et lõpetada voor kohe esimese vigase ümbrise korral. | | `CATALOG_MIRROR_REFINEMENTS_ENABLED` | boolean | `false` | | Täpsusta `/v1/models` lõpp-punktis `claude/` tuvastuspeegleid ja funktsionaalsete lüüside peegleid. Vaikimisi väljas: mõlemad peegeldusetapid toimivad täpselt nagu varem. Kui sees: `claude/` tuvastuspeeglid hõlmavad ka loetletud arutluspingutuse variante (`-low`/`-medium`/`-high`/`-xhigh`), jätavad vahele sisseehitatud `auto/*` ID-d, mida päringutee ei saa materialiseerida, ning juba loetletud aliased; funktsionaalsete lüüside peeglid jätavad vahele kombinatsioonid ja OmniRoute'i sünteesitud ID-d (`claude/`, `no-think/`), võtavad omaniku väljast `owned_by`, ei kasuta omanikku kunagi tema enda lüüsina, jätavad vahele atribuudis `blockedProviders` keelatud lüüsid ning mudelid, mis ei kuulu lüüsi autoriteetsesse aktiivsesse kataloogi. | | ---------------------------------------------- | ------- | ------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `ANTHROPIC_COMPATIBLE_MID_CONVERSATION_SYSTEM` | boolean | `false` | | Tavalistes `anthropic-compatible-*` releedes (claude → claude otseedastus) hoia vestluse keskel olevad `role: "system"` voorud massiivis `messages[]`, selle asemel et tõsta need ülataseme parameetrisse `system`, et vahemällu salvestatud viiba prefiks püsiks voorude vahel stabiilsena. Rakendub ainult siis, kui klient saatis ise `mid-conversation-system-2026-04-07` `anthropic-beta` väärtuse (edastatakse ülesvoolu muutmata kujul). Vaikimisi väljas: iga süsteemivoor tõstetakse täpselt nagu varem, sest beetafunktsioonita releed lükkavad `role: "system"` sõnumid tagasi veakoodiga 400. | | `MODELS_DEV_EFFORT_TIERS_ENABLED` | boolean | `false` | | Kasuta models.dev-i avaldatud arutluspingutuse tasemeid (piiratuna teadaoleva pingutussõnavaraga), kui ei operaatori alistus ega teenusepakkuja register ühtegi taset ei deklareeri, ning käsitle deklareeritud tühja tasemeloendit `/v1/models` väljal `effort_tiers` ja kombinatsioonide tasemete ühisosades kui „tasemeid pole”. Vaikimisi väljas: `effort_tiers` ja lahendatud `supportedThinkingEfforts` jäävad täpselt samaks nagu varem. | | `AUTO_REQUEST_RESOLVED_CAPABILITIES` | boolean | `false` | | auto/* päringud koostavad kandidaatide kogumi lahendatud mudelivõimekuste (nägemine, arutlemine, mõtlemine) ja vaadeldud kvaliteediskooride põhjal ühe hulgilugemisega (#16419). Vaikimisi väljas: päringuaegse hetktõmmise kaalud jäävad täpselt samaks nagu varem. Kui sees: need signaalid mõjutavad hetktõmmise kaale, mistõttu võivad kandidaatide kaalud ja valik muutuda. | | `AUTO_COMBO_KEYLESS_TRANSPORT_GATE` | boolean | `false` | | Jäta võtmeta (autentimiseta) teenusepakkujad, kelle transport vajab kohalikku brauserit (`browserBacked`) või kohalikult installitud CLI-d (`isLocalCli`), `auto/*` kandidaatide kogumist välja, et hangunud liige ei saaks automaatset päringut peatada kuni sihtkohapõhise ajalõpu saabumiseni (#17036). Vaikimisi väljas: võtmeta teenusepakkujate kogum ei muutu. Otsesed `/` kutsed töötavad mõlemal juhul. | @@ -253,7 +252,7 @@ Tagastab kõik lipud koos nende kehtiva väärtuse, allika ja kokkuvõttega. "requiresRestart": false, "warningLevel": "caution", }, - // ... kõik 111 lippu + // ... kõik 110 lippu ], "summary": { "total": 56, diff --git a/docs/i18n/fa/docs/reference/FEATURE_FLAGS.md b/docs/i18n/fa/docs/reference/FEATURE_FLAGS.md index 1beddad9bdf1..c2d2e9552923 100644 --- a/docs/i18n/fa/docs/reference/FEATURE_FLAGS.md +++ b/docs/i18n/fa/docs/reference/FEATURE_FLAGS.md @@ -44,7 +44,7 @@ runtime تغییر داد و در پایگاه داده ذخیره کرد، بد ## فهرست پرچمها -111 پرچم در 6 دسته. **پیشفرض**، مقدار پیشفرضِ تعریفشده است — مقداری که +110 پرچم در 6 دسته. **پیشفرض**، مقدار پیشفرضِ تعریفشده است — مقداری که وقتی نه بازنویسی DB وجود داشته باشد و نه متغیر محیطی، استفاده میشود. ### امنیت (10) @@ -109,7 +109,7 @@ runtime تغییر داد و در پایگاه داده ذخیره کرد، بد | `RADAR_ENABLED` | بولی | `false` | ماژول Radar در OmniRoute (صفحههای خوراک فهرست و همگامسازی) را فعال میکند. بهطور پیشفرض خاموش است؛ فعالسازی فقط رابط کاربری را در دسترس قرار میدهد — همگامسازی داده همچنان نیازمند موافقت جداگانه است. | | `MODEL_ENDPOINT_CAPABILITY_GATE_ENABLED` | بولی | `false` | برچسبهای قابلاعتماد نقطهٔ پایانی مدل (اعلامشده توسط ارائهدهنده، استنباطشده از شناسهٔ یک مدل تخصصی، یا تنظیمشده توسط اپراتور) را اعمال میکند: درخواست ارسالشده به نقطهٔ پایانیای را که مدل پشتیبانی نمیکند، پیش از ارسال با HTTP 400 در `/v1/chat/completions`، `/v1/embeddings`، `/v1/rerank`، `/v1/images/generations`، `/v1/videos/generations`، `/v1/music/generations` و `/v1/audio/*` رد میکند؛ مدلهایی را که نقاط پایانی برچسبخوردهٔ آنها آداپتور OmniRoute ندارند از `/v1/models` پنهان میکند؛ و برای آنها به آزمون مدل داشبورد با 422 پاسخ میدهد (#16929). بهطور پیشفرض خاموش است: برچسبهای نقطهٔ پایانی صرفاً اطلاعاتی باقی میمانند و ارسال بدون تغییر است. | -### زمان اجرا (53) +### زمان اجرا (52) | کلید | نوع | پیشفرض | راهاندازی مجدد | توضیحات | | ---------------------------------------------- | ------- | ------- | -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -156,7 +156,6 @@ runtime تغییر داد و در پایگاه داده ذخیره کرد، بد | `KIRO_TOOL_CALL_REPAIR_ENABLED` | boolean | `true` | | وقتی پاسخ جریانی Kiro پیش از هر خروجی قابل مشاهده، یک پوشش نادرست `tool_call` (فاقد `input.name` یا `input.arguments`) تولید میکند، بهجای پایاندادن نوبت با `invalid_kiro_tool_call`، آن را نادیده گرفته و درخواست را یکبار همراه با دستور اصلاحی دوباره ارسال کنید. این تلاش مجدد هزینه یک درخواست اضافی Kiro را دارد؛ شکست دوم نوبت را با `kiro_tool_call_repair_retry_failed` پایان میدهد. بهصورت پیشفرض روشن است. برای شکستدادن نوبت در نخستین پوشش نادرست، آن را روی `false` تنظیم کنید. | | `CATALOG_MIRROR_REFINEMENTS_ENABLED` | boolean | `false` | | آینههای کشف `claude/` و دروازه کاربردی را در `/v1/models` اصلاح کنید. بهصورت پیشفرض خاموش است: هر دو مرحله آینهسازی دقیقاً مانند قبل عمل میکنند. در حالت روشن: آینههای کشف `claude/` گونههای فهرستشده سطح تلاش استدلال (`-low`/`-medium`/`-high`/`-xhigh`) را نیز پوشش میدهند، شناسههای داخلی `auto/*` را که مسیر درخواست نمیتواند ایجاد کند کنار میگذارند و از نام مستعاری که از قبل فهرست شده است عبور میکنند؛ آینههای دروازه کاربردی، ترکیبها و شناسههای تولیدشده توسط OmniRoute (`claude/`، `no-think/`) را کنار میگذارند، مالک را از `owned_by` میگیرند، هرگز از مالک بهعنوان دروازه خودش استفاده نمیکنند، و دروازههای غیرفعالشده در `blockedProviders` و مدلهای خارج از کاتالوگ زنده و مرجع یک دروازه را کنار میگذارند. | | ---------------------------------------------- | ------- | ------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `ANTHROPIC_COMPATIBLE_MID_CONVERSATION_SYSTEM` | boolean | `false` | | در رلههای ساده `anthropic-compatible-*` (عبور مستقیم claude → claude)، نوبتهای میانه مکالمه با `role: "system"` را بهجای انتقال به پارامتر سطحبالای `system`، درون `messages[]` نگه دارید تا پیشوند ذخیرهشده پرامپت در نوبتهای مختلف پایدار بماند. این فقط زمانی اعمال میشود که خود کلاینت `anthropic-beta` با نام `mid-conversation-system-2026-04-07` را ارسال کرده باشد (که بدون تغییر به بالادست فرستاده میشود). بهصورت پیشفرض خاموش است: هر نوبت سیستم دقیقاً مانند قبل به سطح بالا منتقل میشود، زیرا رلههای فاقد این نسخه بتا، پیامهای `role: "system"` را با خطای 400 رد میکنند. | | `MODELS_DEV_EFFORT_TIERS_ENABLED` | boolean | `false` | | وقتی نه تنظیم بازنویسی اپراتور و نه رجیستری ارائهدهنده هیچ سطحی را اعلام نکردهاند، از سطوح تلاش استدلالی منتشرشده توسط models.dev (محدودشده به واژگان شناختهشده تلاش) استفاده کنید و فهرست سطوح خالی اعلامشده را در `effort_tiers` مربوط به `/v1/models` و اشتراک سطوح ترکیبها بهمعنای «بدون سطح» در نظر بگیرید. بهصورت پیشفرض خاموش است: `effort_tiers` و `supportedThinkingEfforts` حلشده دقیقاً مانند قبل باقی میمانند. | | `AUTO_REQUEST_RESOLVED_CAPABILITIES` | boolean | `false` | | درخواستهای auto/* استخر نامزدها را با قابلیتهای حلشده مدل (بینایی، استدلال، تفکر) و امتیازهای کیفیت مشاهدهشده، طی یک خواندن دستهای میسازند (#16419). بهصورت پیشفرض خاموش است: وزنهای اسنپشات در زمان درخواست دقیقاً مانند قبل باقی میمانند. در حالت روشن: این سیگنالها به وزنهای اسنپشات وارد میشوند، بنابراین وزن نامزدها و انتخاب میتواند تغییر کند. | | `AUTO_COMBO_KEYLESS_TRANSPORT_GATE` | boolean | `false` | | ارائهدهندگان بدون کلید (بدون احراز هویت) را که انتقال آنها به مرورگر محلی (`browserBacked`) یا CLI نصبشده بهصورت محلی (`isLocalCli`) نیاز دارد، از مجموعه نامزدهای `auto/*` خارج نگه میدارد تا یک عضو متوقفشده نتواند درخواست خودکار را تا پایان مهلت زمانی هر مقصد معطل کند (#17036). بهطور پیشفرض غیرفعال است: مجموعه بدون کلید بدون تغییر باقی میماند. فراخوانیهای مستقیم `/` در هر دو حالت کار میکنند. | @@ -253,7 +252,7 @@ runtime تغییر داد و در پایگاه داده ذخیره کرد، بد "requiresRestart": false, "warningLevel": "caution", }, - // ... همهٔ 111 پرچم + // ... همهٔ 110 پرچم ], "summary": { "total": 56, diff --git a/docs/i18n/fi/docs/reference/FEATURE_FLAGS.md b/docs/i18n/fi/docs/reference/FEATURE_FLAGS.md index fc52afa80c1c..33714c4fd4ea 100644 --- a/docs/i18n/fi/docs/reference/FEATURE_FLAGS.md +++ b/docs/i18n/fi/docs/reference/FEATURE_FLAGS.md @@ -44,7 +44,7 @@ Totuusarvoinen lippu katsotaan **käytössä olevaksi**, kun sen voimassa oleva ## Valitsinluettelo -111 valitsinta 6 kategoriassa. **Oletusarvo** on määrityksen oletusarvo — arvo, jota +110 valitsinta 6 kategoriassa. **Oletusarvo** on määrityksen oletusarvo — arvo, jota käytetään, kun tietokantaohitusta tai ympäristömuuttujaa ei ole määritetty. ### Tietoturva (10) @@ -109,7 +109,7 @@ käytetään, kun tietokantaohitusta tai ympäristömuuttujaa ei ole määritett | `RADAR_ENABLED` | totuusarvo | `false` | Ota OmniRoute Radar -moduuli käyttöön (luettelosyötteen näytöt ja synkronointi). Oletuksena pois käytöstä; käyttöönotto avaa vain käyttöliittymän — tietojen synkronointi on edelleen otettava erikseen käyttöön. | | `MODEL_ENDPOINT_CAPABILITY_GATE_ENABLED` | totuusarvo | `false` | Valvo luotettuja mallin päätepistetunnisteita (palveluntarjoajan ilmoittamia, erikoismallin tunnisteesta pääteltyjä tai operaattorin asettamia): hylkää päätepisteeseen lähetetty pyyntö HTTP 400 -vastauksella ennen välitystä, jos malli ei palvele kyseistä päätepistettä reiteillä `/v1/chat/completions`, `/v1/embeddings`, `/v1/rerank`, `/v1/images/generations`, `/v1/videos/generations`, `/v1/music/generations` ja `/v1/audio/*`; piilota reitiltä `/v1/models` mallit, joiden tunnisteella merkityille päätepisteille ei ole OmniRoute-sovitinta; vastaa niiden hallintapaneelin mallitestiin tilakoodilla 422 (#16929). Oletuksena pois käytöstä: päätepistetunnisteet säilyvät informatiivisina eikä välitys muutu. | -### Ajonaika (53) +### Ajonaika (52) | Avain | Tyyppi | Oletusarvo | Uudelleenkäynnistys | Kuvaus | | ---------------------------------------------- | ---------- | ---------- | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -156,7 +156,6 @@ käytetään, kun tietokantaohitusta tai ympäristömuuttujaa ei ole määritett | `KIRO_TOOL_CALL_REPAIR_ENABLED` | boolean | `true` | | Kun suoratoistettava Kiro-vastaus tuottaa virheellisen `tool_call`-kääreen (josta puuttuu `input.name` tai `input.arguments`) ennen näkyvää tulostetta, estä se ja lähetä pyyntö uudelleen kerran korjaavan ohjeen kanssa sen sijaan, että vuoro päätettäisiin virheeseen `invalid_kiro_tool_call`. Uudelleenyritys maksaa yhden ylimääräisen Kiro-pyynnön; toinen epäonnistuminen päättää vuoron virheeseen `kiro_tool_call_repair_retry_failed`. Oletusarvoisesti käytössä. Aseta arvoksi `false`, jos haluat vuoron epäonnistuvan ensimmäiseen virheelliseen kääreeseen. | | `CATALOG_MIRROR_REFINEMENTS_ENABLED` | boolean | `false` | | Tarkenna `claude/`-löytötoiminnon ja toiminnallisten yhdyskäytävien peilauksia kohteessa `/v1/models`. Oletusarvoisesti pois käytöstä: molemmat peilausvaiheet toimivat täsmälleen kuten ennen. Käytössä: `claude/`-löytötoiminnon peilaukset kattavat myös luetellut päättelypanoksen variantit (`-low`/`-medium`/`-high`/`-xhigh`), ohittavat sisäänrakennetut `auto/*`-tunnukset, joita pyyntöpolku ei voi toteuttaa, sekä jo luetellut aliakset; toiminnallisten yhdyskäytävien peilaukset ohittavat yhdistelmät ja OmniRouten muodostamat tunnukset (`claude/`, `no-think/`), ottavat omistajan kentästä `owned_by`, eivät koskaan käytä omistajaa sen omana yhdyskäytävänä ja ohittavat kohdassa `blockedProviders` käytöstä poistetut yhdyskäytävät sekä mallit, jotka eivät kuulu yhdyskäytävän ensisijaiseen reaaliaikaiseen luetteloon. | | ---------------------------------------------- | ------- | ------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `ANTHROPIC_COMPATIBLE_MID_CONVERSATION_SYSTEM` | boolean | `false` | | Säilytä tavallisissa `anthropic-compatible-*`-välityksissä (claude → claude -läpivienti) keskustelun keskellä olevat `role: "system"` -vuorot taulukossa `messages[]` sen sijaan, että ne nostettaisiin ylätason `system`-parametriin, jotta välimuistiin tallennettu kehotteen etuliite pysyy vakaana vuorojen välillä. Käytetään vain, kun asiakas itse lähetti `mid-conversation-system-2026-04-07`-arvon `anthropic-beta`-kentässä (välitetään ylävirtaan sellaisenaan). Oletusarvoisesti pois käytöstä: jokainen järjestelmävuoro nostetaan täsmälleen kuten ennen, koska välityspalvelut, joilla ei ole kyseistä betaa, hylkäävät `role: "system"` -viestit 400-virheellä. | | `MODELS_DEV_EFFORT_TIERS_ENABLED` | boolean | `false` | | Käytä models.dev-palvelun julkaisemia päättelypanoksen tasoja (rajattu tunnettuihin panosarvoihin), kun operaattorin ohitus tai palveluntarjoajarekisteri ei määritä tasoja, ja käsittele nimenomaisesti määritettyä tyhjää tasoluetteloa merkityksessä "ei tasoja" kohteen `/v1/models` kentässä `effort_tiers` ja yhdistelmien tasojen leikkauksissa. Oletusarvoisesti pois käytöstä: `effort_tiers` ja ratkaistu `supportedThinkingEfforts` säilyvät täsmälleen ennallaan. | | `AUTO_REQUEST_RESOLVED_CAPABILITIES` | boolean | `false` | | auto/*-pyynnöt muodostavat ehdokasjoukon ratkaistujen malliominaisuuksien (kuvaominaisuudet, päättely, ajattelu) ja havaittujen laatupisteiden perusteella yhdellä massalukutoiminnolla (#16419). Oletusarvoisesti pois käytöstä: pyyntöajankohdan tilannevedoksen painot säilyvät täsmälleen ennallaan. Käytössä: nämä signaalit vaikuttavat tilannevedoksen painoihin, joten ehdokkaiden painot ja valinta voivat muuttua. | | `AUTO_COMBO_KEYLESS_TRANSPORT_GATE` | boolean | `false` | | Jätä avaimettomat (ei todennusta käyttävät) palveluntarjoajat, joiden siirtotapa edellyttää paikallista selainta (`browserBacked`) tai paikallisesti asennettua komentorivityökalua (`isLocalCli`), pois `auto/*`-ehdokasjoukosta, jotta jumiutunut jäsen ei voi viivyttää automaattista pyyntöä kohdekohtaisen aikakatkaisun täyttymiseen asti (#17036). Oletusarvoisesti pois käytöstä: avaimeton joukko säilyy ennallaan. Suorat `/`-kutsut toimivat kummallakin asetuksella. | @@ -254,7 +253,7 @@ Palauttaa jokaisen lipun sekä sen voimassa olevan arvon, lähteen ja yhteenvedo "requiresRestart": false, "warningLevel": "caution", }, - // ... kaikki 111 lippua + // ... kaikki 110 lippua ], "summary": { "total": 56, diff --git a/docs/i18n/fr/docs/reference/FEATURE_FLAGS.md b/docs/i18n/fr/docs/reference/FEATURE_FLAGS.md index 8d4ad813babb..7bf5ef0c986a 100644 --- a/docs/i18n/fr/docs/reference/FEATURE_FLAGS.md +++ b/docs/i18n/fr/docs/reference/FEATURE_FLAGS.md @@ -44,7 +44,7 @@ Un indicateur booléen est considéré comme **activé** lorsque sa valeur effec ## Catalogue des indicateurs -111 indicateurs répartis en 6 catégories. **Par défaut** correspond à la valeur par défaut de la définition — la valeur +110 indicateurs répartis en 6 catégories. **Par défaut** correspond à la valeur par défaut de la définition — la valeur utilisée lorsqu’aucune substitution en base de données ni aucune variable d’environnement n’est présente. ### Sécurité (10) @@ -109,7 +109,7 @@ utilisée lorsqu’aucune substitution en base de données ni aucune variable d | `RADAR_ENABLED` | booléen | `false` | Active le module OmniRoute Radar (écrans du flux du catalogue et synchronisation). Désactivé par défaut ; son activation déverrouille uniquement l’interface utilisateur — la synchronisation des données reste soumise à une activation distincte. | | `MODEL_ENDPOINT_CAPABILITY_GATE_ENABLED` | booléen | `false` | Applique les étiquettes fiables des points de terminaison des modèles (déclarées par le fournisseur, déduites d’un identifiant de modèle spécialisé ou définies par l’opérateur) : rejette avec le statut HTTP 400, avant son envoi, toute requête adressée à un point de terminaison non pris en charge par le modèle sur `/v1/chat/completions`, `/v1/embeddings`, `/v1/rerank`, `/v1/images/generations`, `/v1/videos/generations`, `/v1/music/generations` et `/v1/audio/*` ; masque dans `/v1/models` les modèles dont les points de terminaison étiquetés ne disposent d’aucun adaptateur OmniRoute ; répond avec le statut 422 au test de modèle du tableau de bord pour ces modèles (#16929). Désactivé par défaut : les étiquettes de points de terminaison restent informatives et l’envoi demeure inchangé. | -### Environnement d’exécution (53) +### Environnement d’exécution (52) | Clé | Type | Valeur par défaut | Redémarrage | Description | | ---------------------------------------------- | ------- | ----------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -156,7 +156,6 @@ utilisée lorsqu’aucune substitution en base de données ni aucune variable d | `KIRO_TOOL_CALL_REPAIR_ENABLED` | boolean | `true` | | Lorsqu’une réponse Kiro en streaming émet une enveloppe `tool_call` mal formée (`input.name` ou `input.arguments` manquant) avant toute sortie visible, la supprimer et renvoyer une fois la requête avec une instruction corrective au lieu de terminer le tour avec `invalid_kiro_tool_call`. La nouvelle tentative coûte une requête Kiro supplémentaire ; un second échec termine le tour avec `kiro_tool_call_repair_retry_failed`. Activé par défaut. Définissez cette option sur `false` pour faire échouer le tour dès la première enveloppe mal formée. | | `CATALOG_MIRROR_REFINEMENTS_ENABLED` | boolean | `false` | | Affiner les miroirs de découverte `claude/` et de passerelle fonctionnelle sur `/v1/models`. Désactivé par défaut : les deux passes de mise en miroir se comportent exactement comme auparavant. Lorsque cette option est activée, les miroirs de découverte `claude/` couvrent également les variantes répertoriées d’effort de raisonnement (`-low`/`-medium`/`-high`/`-xhigh`), ignorent les identifiants `auto/*` intégrés que le chemin de requête ne peut pas matérialiser ainsi que les alias déjà répertoriés ; les miroirs de passerelle fonctionnelle ignorent les combos et les identifiants synthétisés par OmniRoute (`claude/`, `no-think/`), récupèrent le propriétaire depuis `owned_by`, n’utilisent jamais le propriétaire comme sa propre passerelle, et ignorent les passerelles désactivées dans `blockedProviders` ainsi que les modèles absents du catalogue actif faisant autorité pour une passerelle. | | ---------------------------------------------- | ------- | ------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `ANTHROPIC_COMPATIBLE_MID_CONVERSATION_SYSTEM` | boolean | `false` | | Sur les relais simples `anthropic-compatible-*` (transmission directe claude → claude), conserver les tours `role: "system"` en milieu de conversation dans `messages[]` au lieu de les remonter dans le paramètre `system` de premier niveau, afin que le préfixe de prompt mis en cache reste stable d’un tour à l’autre. S’applique uniquement lorsque le client a lui-même envoyé la bêta `mid-conversation-system-2026-04-07` dans `anthropic-beta` (transmise en amont telle quelle). Désactivé par défaut : chaque tour système est remonté exactement comme auparavant, car les relais ne prenant pas en charge la bêta rejettent les messages `role: "system"` avec une erreur 400. | | `MODELS_DEV_EFFORT_TIERS_ENABLED` | boolean | `false` | | Utiliser les niveaux d’effort de raisonnement publiés par models.dev (limités au vocabulaire d’effort connu) lorsque ni une substitution définie par un opérateur ni le registre du fournisseur n’en déclare, et traiter une liste de niveaux explicitement vide comme « aucun niveau » dans `effort_tiers` de `/v1/models` et dans les intersections de niveaux des combos. Désactivé par défaut : `effort_tiers` et les valeurs `supportedThinkingEfforts` résolues restent exactement comme auparavant. | | `AUTO_REQUEST_RESOLVED_CAPABILITIES` | boolean | `false` | | Les requêtes auto/* construisent le groupe de candidats à partir des capacités résolues des modèles (vision, raisonnement, réflexion) et des scores de qualité observés, obtenus au moyen d’une seule lecture groupée (#16419). Désactivé par défaut : les pondérations de l’instantané au moment de la requête restent exactement comme auparavant. Lorsque cette option est activée, ces signaux alimentent les pondérations de l’instantané ; les pondérations des candidats et la sélection peuvent donc changer. | | `AUTO_COMBO_KEYLESS_TRANSPORT_GATE` | boolean | `false` | | Exclut du groupe de candidats `auto/*` les fournisseurs sans clé (sans authentification) dont le transport nécessite un navigateur local (`browserBacked`) ou une CLI installée localement (`isLocalCli`), afin qu’un membre bloqué ne puisse pas immobiliser une requête automatique jusqu’à l’expiration du délai par cible (#17036). Désactivé par défaut : le groupe sans clé reste inchangé. Les appels directs `/` fonctionnent dans les deux cas. | @@ -253,7 +252,7 @@ Renvoie chaque indicateur avec sa valeur effective, sa source et un résumé. "requiresRestart": false, "warningLevel": "caution", }, - // ... les 111 indicateurs + // ... les 110 indicateurs ], "summary": { "total": 56, diff --git a/docs/i18n/ga/docs/reference/FEATURE_FLAGS.md b/docs/i18n/ga/docs/reference/FEATURE_FLAGS.md index 272eb1f1dc38..32f6071e9350 100644 --- a/docs/i18n/ga/docs/reference/FEATURE_FLAGS.md +++ b/docs/i18n/ga/docs/reference/FEATURE_FLAGS.md @@ -44,7 +44,7 @@ Meastar bratach bhooleach a bheith **cumasaithe** nuair is é `"true"`, ## Catalóg Bratach -111 bratach i 6 chatagóir. Is é **Réamhshocrú** réamhshocrú an tsainmhínithe — an luach +110 bratach i 6 chatagóir. Is é **Réamhshocrú** réamhshocrú an tsainmhínithe — an luach a úsáidtear nuair nach bhfuil sárú DB ná athróg timpeallachta i láthair. ### Slándáil (10) @@ -109,7 +109,7 @@ a úsáidtear nuair nach bhfuil sárú DB ná athróg timpeallachta i láthair. | `RADAR_ENABLED` | Booleach | `false` | Cumasaigh modúl Radar OmniRoute (scáileáin fotha catalóige agus sioncronú). As de réir réamhshocraithe; ní dhéanann cumasú ach an Chomhéadain a dhíghlasáil — is rogha ar leith fós é sioncronú sonraí nach mór a chumasú go sainráite. | | `MODEL_ENDPOINT_CAPABILITY_GATE_ENABLED` | Booleach | `false` | Forfheidhmigh clibeanna iontaofa chríochphointí samhla (dearbhaithe ag an soláthraí, tátal déanta astu ó ID samhla speisialtachta, nó socraithe ag oibreoir): diúltaigh, le HTTP 400 sula seoltar é, d’iarratas a seoladh chuig críochphointe nach bhfreastalaíonn an tsamhail air ar `/v1/chat/completions`, `/v1/embeddings`, `/v1/rerank`, `/v1/images/generations`, `/v1/videos/generations`, `/v1/music/generations` agus `/v1/audio/*`; folaigh ó `/v1/models` samhlacha nach bhfuil cuibheoir OmniRoute ag a gcríochphointí clibeáilte; freagair tástáil samhla na deaise le 422 dóibh (#16929). As de réir réamhshocraithe: fanann clibeanna críochphointí mar fhaisnéis amháin agus ní athraítear an seoladh. | -### Am rite (53) +### Am rite (52) | Eochair | Cineál | Réamhshocrú | Atosú | Cur síos | | ---------------------------------------------- | -------- | ----------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -156,7 +156,6 @@ a úsáidtear nuair nach bhfuil sárú DB ná athróg timpeallachta i láthair. | `KIRO_TOOL_CALL_REPAIR_ENABLED` | boolean | `true` | | Nuair a astaíonn freagra sruthaithe Kiro fillteán míchumtha `tool_call` (`input.name` nó `input.arguments` ar iarraidh) roimh aon aschur infheicthe, cuir faoi chois é agus seol an t-iarratas arís uair amháin le treoir cheartaitheach in ionad deireadh a chur leis an seal le `invalid_kiro_tool_call`. Cosnaíonn an atriail iarratas breise Kiro amháin; cuireann an dara teip deireadh leis an seal le `kiro_tool_call_repair_retry_failed`. Cumasaithe de réir réamhshocraithe. Socraigh mar `false` é chun go dteipfidh ar an seal ag an gcéad fhillteán míchumtha. | | `CATALOG_MIRROR_REFINEMENTS_ENABLED` | boolean | `false` | | Déan scátháin aimsithe `claude/` agus scátháin geata feidhmiúla ar `/v1/models` a mhionchoigeartú. Díchumasaithe de réir réamhshocraithe: iompraíonn an dá phas scáthánaithe díreach mar a d'iompair siad roimhe seo. Nuair atá sé cumasaithe: clúdaíonn scátháin aimsithe `claude/` leaganacha liostaithe den iarracht réasúnaithe freisin (`-low`/`-medium`/`-high`/`-xhigh`), scipeálann siad aitheantóirí ionsuite `auto/*` nach féidir le conair an iarratais a chruthú agus scipeálann siad ailias atá liostaithe cheana féin; scipeálann scátháin geata feidhmiúla teaglamaí agus aitheantóirí sintéisithe ag OmniRoute (`claude/`, `no-think/`), tógann siad an t-úinéir ó `owned_by`, ní úsáideann siad an t-úinéir mar a gheata féin riamh, agus scipeálann siad geataí atá díchumasaithe in `blockedProviders` agus samhlacha lasmuigh de chatalóg bheo údarásach geata. | | ---------------------------------------------- | ------- | ------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `ANTHROPIC_COMPATIBLE_MID_CONVERSATION_SYSTEM` | boolean | `false` | | Ar athsheachadáin shimplí `anthropic-compatible-*` (claude → claude gan athrú), coimeád sealanna `role: "system"` i lár comhrá laistigh de `messages[]` in ionad iad a ardú isteach sa pharaiméadar barrleibhéil `system`, ionas go bhfanfaidh réimír an leid thaisce cobhsaí thar shealanna. Ní bhaineann sé seo ach amháin nuair a sheol an cliant féin an `anthropic-beta` `mid-conversation-system-2026-04-07` (curtha ar aghaidh réamhtheachtach gan athrú). Díchumasaithe de réir réamhshocraithe: ardaítear gach seal córais díreach mar a rinneadh roimhe seo, toisc go ndiúltaíonn athsheachadáin gan an béite do theachtaireachtaí `role: "system"` le 400. | | `MODELS_DEV_EFFORT_TIERS_ENABLED` | boolean | `false` | | Úsáid na sraitheanna iarrachta réasúnaithe a fhoilsíonn models.dev (cúngaithe don stór focal iarrachta aitheanta) nuair nach ndearbhaíonn sárú oibreora ná clárlann an tsoláthraí aon cheann acu, agus caith le liosta sraitheanna folamh dearbhaithe mar "gan sraitheanna" in `/v1/models` `effort_tiers` agus i dtrasnuithe sraitheanna teaglamaí. Díchumasaithe de réir réamhshocraithe: fanann `effort_tiers` agus `supportedThinkingEfforts` réitithe díreach mar a bhí roimhe seo. | | `AUTO_REQUEST_RESOLVED_CAPABILITIES` | boolean | `false` | | Tógann iarratais auto/* linn na n-iarrthóirí le cumais réitithe samhla (fís, réasúnú, smaointeoireacht) agus scóir cháilíochta bhreathnaithe in aon oll-léamh amháin (#16419). Díchumasaithe de réir réamhshocraithe: fanann meáchain seat ag am an iarratais díreach mar a bhí roimhe seo. Nuair atá sé cumasaithe: cuirtear na comharthaí sin isteach sna meáchain seat, mar sin is féidir le meáchain agus roghnú iarrthóirí athrú. | | `AUTO_COMBO_KEYLESS_TRANSPORT_GATE` | boolean | `false` | | Coinnigh soláthraithe gan eochair (gan fíordheimhniú), a dteastaíonn brabhsálaí áitiúil (`browserBacked`) nó CLI suiteáilte go háitiúil (`isLocalCli`) dá n-iompar, amach as linn iarrthóirí `auto/*`, ionas nach mbeidh ball atá crochta in ann iarratas uathoibríoch a chur i bhfostú go dtí an teorainn ama in aghaidh na sprice (#17036). Díchumasaithe de réir réamhshocraithe: fanann an linn gan eochair gan athrú. Oibríonn glaonna díreacha `/` ar aon nós. | @@ -253,7 +252,7 @@ Filleann sé gach bratach lena luach éifeachtach, lena foinse agus le hachoimre "requiresRestart": false, "warningLevel": "caution", }, - // ... na 111 bhratach ar fad + // ... na 110 bhratach ar fad ], "summary": { "total": 56, diff --git a/docs/i18n/gu/docs/reference/FEATURE_FLAGS.md b/docs/i18n/gu/docs/reference/FEATURE_FLAGS.md index 2a02125c94f0..5db746bf7580 100644 --- a/docs/i18n/gu/docs/reference/FEATURE_FLAGS.md +++ b/docs/i18n/gu/docs/reference/FEATURE_FLAGS.md @@ -45,7 +45,7 @@ ## ફ્લૅગ સૂચિ -6 શ્રેણીઓમાં 111 ફ્લૅગ્સ. **મૂળભૂત** એટલે વ્યાખ્યામાં નિર્ધારિત મૂળભૂત મૂલ્ય — જ્યારે +6 શ્રેણીઓમાં 110 ફ્લૅગ્સ. **મૂળભૂત** એટલે વ્યાખ્યામાં નિર્ધારિત મૂળભૂત મૂલ્ય — જ્યારે DB ઓવરરાઇડ કે પર્યાવરણ ચલ, બંનેમાંથી કોઈ હાજર ન હોય ત્યારે ઉપયોગમાં લેવાતું મૂલ્ય. ### સુરક્ષા (10) @@ -110,7 +110,7 @@ DB ઓવરરાઇડ કે પર્યાવરણ ચલ, બંનેમ | `RADAR_ENABLED` | boolean | `false` | OmniRoute Radar મોડ્યુલ (કેટલોગ ફીડ સ્ક્રીનો અને સિંક) સક્ષમ કરો. ડિફૉલ્ટ રૂપે બંધ; તેને સક્ષમ કરવાથી ફક્ત UI અનલૉક થાય છે — ડેટા સિંક માટે હજી પણ અલગથી ઑપ્ટ-ઇન કરવું જરૂરી છે. | | `MODEL_ENDPOINT_CAPABILITY_GATE_ENABLED` | boolean | `false` | વિશ્વસનીય મોડેલ એન્ડપોઇન્ટ ટૅગ્સ (પ્રોવાઇડર દ્વારા જાહેર કરેલા, વિશિષ્ટ મોડેલ id પરથી અનુમાનિત અથવા ઑપરેટર દ્વારા સેટ કરેલા) લાગુ કરો: `/v1/chat/completions`, `/v1/embeddings`, `/v1/rerank`, `/v1/images/generations`, `/v1/videos/generations`, `/v1/music/generations` અને `/v1/audio/*` પર મોડેલ જે એન્ડપોઇન્ટ સેવા આપતું ન હોય તેને મોકલાયેલી વિનંતીને ડિસ્પૅચ પહેલાં HTTP 400 સાથે નકારો; `/v1/models`માંથી એવા મોડેલ છુપાવો કે જેમના ટૅગ કરેલા એન્ડપોઇન્ટ માટે કોઈ OmniRoute ઍડૅપ્ટર ન હોય; તેમના માટે ડૅશબોર્ડ મોડેલ પરીક્ષણનો જવાબ 422થી આપો (#16929). ડિફૉલ્ટ રૂપે બંધ: એન્ડપોઇન્ટ ટૅગ્સ માહિતીપ્રદ રહે છે અને ડિસ્પૅચમાં કોઈ ફેરફાર થતો નથી. | -### રનટાઇમ (53) +### રનટાઇમ (52) | કી | પ્રકાર | ડિફૉલ્ટ | પુનઃપ્રારંભ | વર્ણન | | ---------------------------------------------- | ------- | ------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -157,7 +157,6 @@ DB ઓવરરાઇડ કે પર્યાવરણ ચલ, બંનેમ | `KIRO_TOOL_CALL_REPAIR_ENABLED` | boolean | `true` | | જ્યારે સ્ટ્રીમિંગ Kiro પ્રતિસાદ કોઈપણ દૃશ્યમાન આઉટપુટ પહેલાં વિકૃત `tool_call` રૅપર (`input.name` અથવા `input.arguments` ગેરહાજર હોય તેવું) ઉત્સર્જિત કરે, ત્યારે તેને દબાવો અને ટર્નને `invalid_kiro_tool_call` સાથે સમાપ્ત કરવાને બદલે સુધારાત્મક સૂચના સાથે વિનંતી ફરી એક વાર મોકલો. પુનઃપ્રયાસ માટે એક વધારાની Kiro વિનંતીનો ખર્ચ થાય છે; બીજી નિષ્ફળતા ટર્નને `kiro_tool_call_repair_retry_failed` સાથે સમાપ્ત કરે છે. ડિફૉલ્ટ રૂપે ચાલુ. પ્રથમ વિકૃત રૅપર પર જ ટર્ન નિષ્ફળ કરવા માટે `false` પર સેટ કરો. | | `CATALOG_MIRROR_REFINEMENTS_ENABLED` | boolean | `false` | | `/v1/models` પર `claude/` ડિસ્કવરી અને ફંક્શનલ-ગેટવે મિરર્સને વધુ ચોક્કસ બનાવો. ડિફૉલ્ટ રૂપે બંધ: બંને મિરર પાસ પહેલાંની જેમ જ બરાબર વર્તે છે. ચાલુ હોય ત્યારે: `claude/` ડિસ્કવરી મિરર્સ સૂચિબદ્ધ રીઝનિંગ-એફર્ટ વેરિઅન્ટ્સ (`-low`/`-medium`/`-high`/`-xhigh`)ને પણ આવરી લે છે, વિનંતી પાથ સાકાર કરી શકતો ન હોય તેવા બિલ્ટ-ઇન `auto/*` idsને છોડી દે છે અને પહેલેથી સૂચિબદ્ધ ઉપનામને છોડી દે છે; ફંક્શનલ-ગેટવે મિરર્સ કૉમ્બો અને OmniRoute દ્વારા સંશ્લેષિત ids (`claude/`, `no-think/`)ને છોડી દે છે, માલિકને `owned_by`માંથી લે છે, માલિકને ક્યારેય તેના પોતાના ગેટવે તરીકે ઉપયોગ કરતા નથી, `blockedProviders`માં અક્ષમ કરાયેલા ગેટવેઝ અને ગેટવેના અધિકૃત લાઇવ કૅટલૉગની બહારનાં મૉડલ્સને છોડી દે છે. | | ---------------------------------------------- | ------- | ------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `ANTHROPIC_COMPATIBLE_MID_CONVERSATION_SYSTEM` | boolean | `false` | | સાદા `anthropic-compatible-*` રિલેઝ (claude → claude પાસથ્રૂ) પર, વાતચીતની વચ્ચે આવતા `role: "system"` ટર્ન્સને ટોચ-સ્તરના `system` પૅરામીટરમાં ઉપર ખસેડવાને બદલે `messages[]`ની અંદર રાખો, જેથી કૅશ કરેલો પ્રૉમ્પ્ટ પ્રીફિક્સ ટર્ન્સ દરમિયાન સ્થિર રહે. ફક્ત ત્યારે લાગુ પડે છે જ્યારે ક્લાયન્ટે પોતે `mid-conversation-system-2026-04-07` `anthropic-beta` મોકલ્યું હોય (અપસ્ટ્રીમમાં યથાવત્ ફૉર્વર્ડ થાય છે). ડિફૉલ્ટ રૂપે બંધ: દરેક સિસ્ટમ ટર્ન પહેલાંની જેમ જ બરાબર ઉપર ખસેડાય છે, કારણ કે બીટા વિનાના રિલેઝ `role: "system"` સંદેશાઓને 400 સાથે નકારે છે. | | `MODELS_DEV_EFFORT_TIERS_ENABLED` | boolean | `false` | | જ્યારે ઑપરેટર ઓવરરાઇડ કે પ્રોવાઇડર રજિસ્ટ્રીમાંથી કોઈ પણ ટિયર્સ જાહેર ન કરે ત્યારે models.dev દ્વારા પ્રકાશિત રીઝનિંગ એફર્ટ ટિયર્સનો ઉપયોગ કરો (જાણીતી એફર્ટ શબ્દાવલી સુધી મર્યાદિત), અને `/v1/models`ના `effort_tiers` તથા કૉમ્બો ટિયર ઇન્ટરસેક્શન્સમાં જાહેર કરેલી ખાલી ટિયર સૂચિને "કોઈ ટિયર્સ નહીં" તરીકે ગણો. ડિફૉલ્ટ રૂપે બંધ: `effort_tiers` અને રિઝૉલ્વ થયેલું `supportedThinkingEfforts` પહેલાંની જેમ જ બરાબર રહે છે. | | `AUTO_REQUEST_RESOLVED_CAPABILITIES` | boolean | `false` | | auto/* વિનંતીઓ એક જ બલ્ક રીડમાં રિઝૉલ્વ થયેલી મૉડલ ક્ષમતાઓ (વિઝન, રીઝનિંગ, થિંકિંગ) અને અવલોકિત ગુણવત્તા સ્કોર્સ સાથે ઉમેદવાર પૂલ બનાવે છે (#16419). ડિફૉલ્ટ રૂપે બંધ: વિનંતી-સમયના સ્નૅપશૉટ વેઇટ્સ પહેલાંની જેમ જ બરાબર રહે છે. ચાલુ હોય ત્યારે: આ સંકેતો સ્નૅપશૉટ વેઇટ્સમાં સામેલ થાય છે, તેથી ઉમેદવાર વેઇટ્સ અને પસંદગી બદલાઈ શકે છે. | | `AUTO_COMBO_KEYLESS_TRANSPORT_GATE` | boolean | `false` | | એવા કીલેસ (નો-ઑથ) પ્રદાતાઓને `auto/*` ઉમેદવાર પૂલની બહાર રાખો, જેમના ટ્રાન્સપોર્ટને સ્થાનિક બ્રાઉઝર (`browserBacked`) અથવા સ્થાનિક રીતે ઇન્સ્ટોલ કરેલ CLI (`isLocalCli`)ની જરૂર પડે છે, જેથી કોઈ અટવાયેલ સભ્ય પ્રતિ-લક્ષ્ય સમયસમાપ્તિ સુધી ઑટો વિનંતીને રોકી ન શકે (#17036). ડિફૉલ્ટ રૂપે બંધ: કીલેસ પૂલ યથાવત્ રહે છે. સીધા `/` કૉલ્સ બંને સ્થિતિમાં કાર્ય કરે છે. | @@ -254,7 +253,7 @@ DB ઓવરરાઇડ કે પર્યાવરણ ચલ, બંનેમ "requiresRestart": false, "warningLevel": "caution", }, - // ... બધા 111 ફ્લૅગ્સ + // ... બધા 110 ફ્લૅગ્સ ], "summary": { "total": 56, diff --git a/docs/i18n/ha/docs/reference/FEATURE_FLAGS.md b/docs/i18n/ha/docs/reference/FEATURE_FLAGS.md index f6e8895fe578..ac619a67a215 100644 --- a/docs/i18n/ha/docs/reference/FEATURE_FLAGS.md +++ b/docs/i18n/ha/docs/reference/FEATURE_FLAGS.md @@ -44,7 +44,7 @@ Ana ɗaukar alamar boolean a matsayin **mai aiki** idan ƙimarta mai aiki ita ce ## Kundin Tutoci -Tutoci 111 a rukunai 6. **Tsoho** shi ne tsohon ƙimar da aka ayyana — ƙimar da ake +Tutoci 110 a rukunai 6. **Tsoho** shi ne tsohon ƙimar da aka ayyana — ƙimar da ake amfani da ita idan babu sauya ƙima ta DB ko canjin muhalli. ### Tsaro (10) @@ -109,7 +109,7 @@ amfani da ita idan babu sauya ƙima ta DB ko canjin muhalli. | `RADAR_ENABLED` | boolean | `false` | Kunna tsarin OmniRoute Radar (fuskokin catalog feed da sync). A kashe yake ta tsohuwa; kunna shi yana buɗe UI ne kawai — sync na bayanai har yanzu yana buƙatar amincewa daban. | | `MODEL_ENDPOINT_CAPABILITY_GATE_ENABLED` | boolean | `false` | Tilasta amintattun alamun endpoint na samfuri (wanda mai bayarwa ya bayyana, wanda aka fahimta daga id na samfuri na musamman, ko wanda mai gudanarwa ya saita): ƙin amincewa da buƙatar da aka aika zuwa endpoint wanda samfurin ba ya tallafawa da HTTP 400 kafin aikawa a kan `/v1/chat/completions`, `/v1/embeddings`, `/v1/rerank`, `/v1/images/generations`, `/v1/videos/generations`, `/v1/music/generations` da `/v1/audio/*`; ɓoye samfuran da endpoint ɗinsu mai alama ba shi da adapter na OmniRoute daga `/v1/models`; mayar da 422 ga gwajin samfuri na dashboard game da su (#16929). A kashe yake ta tsohuwa: alamun endpoint suna ci gaba da zama na bayani kawai kuma yadda ake aikawa ba ya canzawa. | -### Lokacin Gudanarwa (53) +### Lokacin Gudanarwa (52) | Maɓalli | Nau'i | Na asali | Sake farawa | Bayani | | ---------------------------------------------- | ------- | -------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | @@ -156,7 +156,6 @@ amfani da ita idan babu sauya ƙima ta DB ko canjin muhalli. | `KIRO_TOOL_CALL_REPAIR_ENABLED` | boolean | `true` | | Lokacin da amsar Kiro mai gudana ta fitar da gurɓataccen marufin `tool_call` (wanda ba shi da `input.name` ko `input.arguments`) kafin kowace fitowa da ake iya gani, a danne shi sannan a sake aika buƙatar sau ɗaya tare da umarnin gyarawa maimakon ƙare zagayen da `invalid_kiro_tool_call`. Sake gwadawar tana haifar da ƙarin buƙatar Kiro guda ɗaya; gazawa ta biyu tana ƙare zagayen da `kiro_tool_call_repair_retry_failed`. A kunne yake ta tsohuwa. Saita zuwa `false` don a gaza zagayen a gurɓataccen marufi na farko. | | `CATALOG_MIRROR_REFINEMENTS_ENABLED` | boolean | `false` | | Inganta madubin ganowa na `claude/` da na ƙofofin aiki a `/v1/models`. A kashe yake ta tsohuwa: dukkan matakan madubin biyu suna aiki daidai kamar yadda suke a baya. Idan an kunna: madubin ganowa na `claude/` suna kuma rufe jerin bambance-bambancen matakin ƙoƙarin tunani (`-low`/`-medium`/`-high`/`-xhigh`), suna tsallake ginannun id na `auto/*` waɗanda hanyar buƙata ba za ta iya samarwa ba, kuma suna tsallake wani laƙabi da aka riga aka jera; madubin ƙofofin aiki suna tsallake haɗe-haɗe da id da OmniRoute ya ƙirƙira (`claude/`, `no-think/`), suna ɗaukar mai shi daga `owned_by`, ba sa taɓa amfani da mai shi a matsayin ƙofarsa ta kansa, suna tsallake ƙofofin da aka kashe a `blockedProviders` da samfuran da ba sa cikin tabbataccen kundin kai-tsaye na wata ƙofa. | | ---------------------------------------------- | ------- | ------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `ANTHROPIC_COMPATIBLE_MID_CONVERSATION_SYSTEM` | boolean | `false` | | A kan relays na `anthropic-compatible-*` na yau da kullum (wucewar claude → claude), a bar zagayen `role: "system"` na tsakiyar tattaunawa a cikin `messages[]` maimakon ɗaga su zuwa sigar `system` ta matakin sama, domin farkon prompt ɗin da aka adana a cache ya ci gaba da zama daidaitacce tsakanin zagaye. Wannan yana aiki ne kawai idan abokin hulɗar da kansa ya aika `mid-conversation-system-2026-04-07` `anthropic-beta` (ana tura shi zuwa sama yadda yake). A kashe yake ta tsohuwa: ana ɗaga kowane zagayen tsarin daidai kamar yadda ake yi a baya, saboda relays marasa beta suna ƙin saƙonnin `role: "system"` da 400. | | `MODELS_DEV_EFFORT_TIERS_ENABLED` | boolean | `false` | | Yi amfani da matakan ƙoƙarin tunani da models.dev ke wallafawa (an taƙaita su zuwa sanannun kalmomin ƙoƙari) idan babu wani maye gurbin mai gudanarwa ko rajistar mai samarwa da ya ayyana wani, sannan a ɗauki jerin matakan da aka ayyana amma babu komai a ciki a matsayin "babu matakai" a cikin `effort_tiers` na `/v1/models` da maƙalar matakan haɗe-haɗe. A kashe yake ta tsohuwa: `effort_tiers` da `supportedThinkingEfforts` da aka warware suna nan daidai kamar yadda suke a baya. | | `AUTO_REQUEST_RESOLVED_CAPABILITIES` | boolean | `false` | | Buƙatun auto/* suna gina rukunin ’yan takara da damar samfurin da aka warware (gani, yin tunani, tunani) da makin ingancin da aka lura da su a cikin karatun taro guda ɗaya (#16419). A kashe yake ta tsohuwa: nauyin hoton-lokaci na lokacin buƙata yana nan daidai kamar yadda yake a baya. Idan an kunna: waɗannan siginoni suna shiga cikin nauyin hoton-lokaci, saboda haka nauyin ’yan takara da zaɓi na iya canzawa. | | `AUTO_COMBO_KEYLESS_TRANSPORT_GATE` | boolean | `false` | | Ware masu samarwa marasa maɓalli (ba tare da tantancewa ba) waɗanda jigilar su ke buƙatar burauzar gida (`browserBacked`) ko CLI da aka girka a gida (`isLocalCli`) daga tarin ’yan takarar `auto/*`, domin memba da ya makale kada ya tsaida buƙatar auto har sai lokacin jiran kowane maƙasudi ya ƙare (#17036). A kashe yake ta tsohuwa: tarin marasa maɓalli bai canza ba. Kiran `/` kai tsaye yana aiki a kowane yanayi. | @@ -253,7 +252,7 @@ Yana mayar da kowace tuta tare da ƙimar da ake amfani da ita, tushenta, da taƙ "requiresRestart": false, "warningLevel": "caution", }, - // ... dukkan tutoci 111 + // ... dukkan tutoci 110 ], "summary": { "total": 56, diff --git a/docs/i18n/he/docs/reference/FEATURE_FLAGS.md b/docs/i18n/he/docs/reference/FEATURE_FLAGS.md index c018959d9e46..0cd3d40aa0d8 100644 --- a/docs/i18n/he/docs/reference/FEATURE_FLAGS.md +++ b/docs/i18n/he/docs/reference/FEATURE_FLAGS.md @@ -44,7 +44,7 @@ ## קטלוג דגלים -111 דגלים ב-6 קטגוריות. **ברירת המחדל** היא ברירת המחדל שבהגדרה — הערך +110 דגלים ב-6 קטגוריות. **ברירת המחדל** היא ברירת המחדל שבהגדרה — הערך שמשמש כאשר לא קיימת דריסה במסד הנתונים ולא קיים משתנה סביבה. ### אבטחה (10) @@ -109,7 +109,7 @@ | `RADAR_ENABLED` | boolean | `false` | הפעלת מודול Radar של OmniRoute (מסכי פיד הקטלוג וסנכרון). מושבת כברירת מחדל; ההפעלה רק פותחת את ממשק המשתמש — סנכרון הנתונים עדיין דורש הסכמה נפרדת. | | `MODEL_ENDPOINT_CAPABILITY_GATE_ENABLED` | boolean | `false` | אכיפת תגיות מהימנות של נקודות קצה למודלים (שהוצהרו על ידי הספק, הוסקו ממזהה של מודל ייעודי או הוגדרו על ידי המפעיל): דחיית בקשה שנשלחה לנקודת קצה שאינה נתמכת על ידי המודל עם HTTP 400 לפני שליחתה אל `/v1/chat/completions`, `/v1/embeddings`, `/v1/rerank`, `/v1/images/generations`, `/v1/videos/generations`, `/v1/music/generations` ו-`/v1/audio/*`; הסתרת מודלים שלנקודות הקצה המתויגות שלהם אין מתאם OmniRoute מתוך `/v1/models`; ומענה עם 422 בבדיקת המודל בלוח הבקרה עבורם (#16929). מושבת כברירת מחדל: תגיות נקודות הקצה נשארות לצורכי מידע בלבד, ותהליך השליחה אינו משתנה. | -### זמן ריצה (53) +### זמן ריצה (52) | מפתח | סוג | ברירת מחדל | הפעלה מחדש | תיאור | | ---------------------------------------------- | ------- | ---------- | ---------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -156,7 +156,6 @@ | `KIRO_TOOL_CALL_REPAIR_ENABLED` | boolean | `true` | | כאשר תגובת Kiro מוזרמת פולטת מעטפת `tool_call` פגומה (חסר `input.name` או `input.arguments`) לפני פלט גלוי כלשהו, יש לדכא אותה ולשלוח מחדש את הבקשה פעם אחת עם הנחיית תיקון, במקום לסיים את הסבב עם `invalid_kiro_tool_call`. הניסיון החוזר כרוך בעלות של בקשת Kiro נוספת אחת; כשל שני מסיים את הסבב עם `kiro_tool_call_repair_retry_failed`. מופעל כברירת מחדל. הגדירו `false` כדי להכשיל את הסבב עם המעטפת הפגומה הראשונה. | | `CATALOG_MIRROR_REFINEMENTS_ENABLED` | boolean | `false` | | משפר את מראות הגילוי `claude/` ואת מראות השערים הפונקציונליים ב-`/v1/models`. מושבת כברירת מחדל: שני מעברי השיקוף מתנהגים בדיוק כפי שהתנהגו בעבר. כאשר מופעל: מראות הגילוי `claude/` כוללות גם וריאנטים רשומים של מאמץ הסקה (`-low`/`-medium`/`-high`/`-xhigh`), מדלגות על מזהי `auto/*` מובנים שנתיב הבקשה אינו יכול לממש ועל כינוי שכבר מופיע ברשימה; מראות השערים הפונקציונליים מדלגות על צירופים ועל מזהים שנוצרו בידי OmniRoute (`claude/`, `no-think/`), לוקחות את הבעלים מתוך `owned_by`, לעולם אינן משתמשות בבעלים כשער של עצמו, ומדלגות על שערים המושבתים ב-`blockedProviders` ועל מודלים שאינם נמצאים בקטלוג הפעיל והסמכותי של שער. | | ---------------------------------------------- | ------- | ------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `ANTHROPIC_COMPATIBLE_MID_CONVERSATION_SYSTEM` | boolean | `false` | | בממסרי `anthropic-compatible-*` רגילים (העברה ישירה מ-claude אל claude), משאיר תורות `role: "system"` באמצע השיחה בתוך `messages[]` במקום להעביר אותם לפרמטר `system` ברמה העליונה, כך שתחילית ההנחיה שבמטמון נשארת יציבה בין תורות. חל רק כאשר הלקוח עצמו שלח את `mid-conversation-system-2026-04-07` בתוך `anthropic-beta` (ומועבר כלפי מעלה ללא שינוי). מושבת כברירת מחדל: כל תור מערכת מועבר לרמה העליונה בדיוק כפי שהיה בעבר, משום שממסרים ללא גרסת הבטא דוחים הודעות `role: "system"` עם שגיאת 400. | | `MODELS_DEV_EFFORT_TIERS_ENABLED` | boolean | `false` | | משתמש ברמות מאמץ ההסקה שמפרסם models.dev (תוך הגבלה לאוצר המילים המוכר של רמות המאמץ) כאשר לא הוגדרה אף רמה בעקיפה של מפעיל או במרשם הספק, ומתייחס לרשימת רמות ריקה שהוגדרה כאל "ללא רמות" ב-`effort_tiers` של `/v1/models` ובחיתוכים של רמות בצירופים. מושבת כברירת מחדל: `effort_tiers` והערכים שנפתרו עבור `supportedThinkingEfforts` נשארים בדיוק כפי שהיו בעבר. | | `AUTO_REQUEST_RESOLVED_CAPABILITIES` | boolean | `false` | | בקשות auto/* בונות את מאגר המועמדים באמצעות יכולות מודל שנפתרו (ראייה, הסקה, חשיבה) וציוני איכות שנצפו, בקריאה מרוכזת אחת (#16419). מושבת כברירת מחדל: משקלי תמונת המצב בזמן הבקשה נשארים בדיוק כפי שהיו בעבר. כאשר מופעל: אותות אלה מוזנים למשקלי תמונת המצב, ולכן משקלי המועמדים והבחירה עשויים להשתנות. | | `AUTO_COMBO_KEYLESS_TRANSPORT_GATE` | boolean | `false` | | החרגת ספקים ללא מפתח (ללא אימות), שהתעבורה שלהם דורשת דפדפן מקומי (`browserBacked`) או CLI שמותקן מקומית (`isLocalCli`), ממאגר המועמדים של `auto/*`, כדי שחבר שנתקע לא יוכל לעכב בקשת auto עד לפסק הזמן של כל יעד (#17036). מושבת כברירת מחדל: מאגר הספקים ללא מפתח נותר ללא שינוי. קריאות ישירות אל `/` פועלות בכל מקרה. | @@ -253,7 +252,7 @@ "requiresRestart": false, "warningLevel": "caution", }, - // ... כל 111 הדגלים + // ... כל 110 הדגלים ], "summary": { "total": 56, diff --git a/docs/i18n/hi/docs/reference/FEATURE_FLAGS.md b/docs/i18n/hi/docs/reference/FEATURE_FLAGS.md index dffadd834658..3b958f5e1c8f 100644 --- a/docs/i18n/hi/docs/reference/FEATURE_FLAGS.md +++ b/docs/i18n/hi/docs/reference/FEATURE_FLAGS.md @@ -30,7 +30,7 @@ ## फ़्लैग कैटलॉग -6 श्रेणियों में 111 फ़्लैग। **डिफ़ॉल्ट** का अर्थ परिभाषा का डिफ़ॉल्ट मान है — वह मान +6 श्रेणियों में 110 फ़्लैग। **डिफ़ॉल्ट** का अर्थ परिभाषा का डिफ़ॉल्ट मान है — वह मान जिसका उपयोग तब किया जाता है, जब न तो कोई DB ओवरराइड मौजूद हो और न ही कोई एनवायरनमेंट वेरिएबल। ### सुरक्षा (10) @@ -95,7 +95,7 @@ | `RADAR_ENABLED` | boolean | `false` | OmniRoute Radar मॉड्यूल (कैटलॉग फ़ीड स्क्रीन और सिंक) सक्षम करें। डिफ़ॉल्ट रूप से बंद; इसे सक्षम करने से केवल UI अनलॉक होता है—डेटा सिंक के लिए अलग से ऑप्ट-इन करना अभी भी आवश्यक है। | | `MODEL_ENDPOINT_CAPABILITY_GATE_ENABLED` | boolean | `false` | विश्वसनीय मॉडल एंडपॉइंट टैग (प्रदाता द्वारा घोषित, किसी विशेष मॉडल id से अनुमानित, या ऑपरेटर द्वारा सेट) लागू करें: `/v1/chat/completions`, `/v1/embeddings`, `/v1/rerank`, `/v1/images/generations`, `/v1/videos/generations`, `/v1/music/generations` और `/v1/audio/*` पर डिस्पैच से पहले ऐसे एंडपॉइंट को भेजे गए अनुरोध को HTTP 400 के साथ अस्वीकार करें जिसे मॉडल सर्व नहीं करता; `/v1/models` से उन मॉडलों को छिपाएँ जिनके टैग किए गए एंडपॉइंट के लिए कोई OmniRoute अडैप्टर नहीं है; उनके लिए डैशबोर्ड मॉडल परीक्षण का उत्तर 422 से दें (#16929)। डिफ़ॉल्ट रूप से बंद: एंडपॉइंट टैग केवल सूचनात्मक रहते हैं और डिस्पैच अपरिवर्तित रहता है। | -### रनटाइम (53) +### रनटाइम (52) | कुंजी | प्रकार | डिफ़ॉल्ट | पुनः आरंभ | विवरण | | ---------------------------------------------- | ------- | -------- | --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -142,7 +142,6 @@ | `KIRO_TOOL_CALL_REPAIR_ENABLED` | boolean | `true` | | जब कोई स्ट्रीमिंग Kiro प्रतिक्रिया किसी भी दृश्यमान आउटपुट से पहले विकृत `tool_call` रैपर (`input.name` या `input.arguments` अनुपस्थित) उत्सर्जित करती है, तो उसे दबाएँ और टर्न को `invalid_kiro_tool_call` के साथ समाप्त करने के बजाय सुधारात्मक निर्देश के साथ अनुरोध को एक बार फिर भेजें। पुनः प्रयास में एक अतिरिक्त Kiro अनुरोध लगता है; दूसरी विफलता टर्न को `kiro_tool_call_repair_retry_failed` के साथ समाप्त करती है। डिफ़ॉल्ट रूप से चालू। पहले विकृत रैपर पर ही टर्न को विफल करने के लिए इसे `false` पर सेट करें। | | `CATALOG_MIRROR_REFINEMENTS_ENABLED` | boolean | `false` | | `/v1/models` पर `claude/` डिस्कवरी और फ़ंक्शनल-गेटवे मिरर को परिष्कृत करें। डिफ़ॉल्ट रूप से बंद: दोनों मिरर पास पहले की तरह ही व्यवहार करते हैं। चालू होने पर: `claude/` डिस्कवरी मिरर सूचीबद्ध रीजनिंग-एफ़र्ट वेरिएंट (`-low`/`-medium`/`-high`/`-xhigh`) को भी शामिल करते हैं, उन अंतर्निहित `auto/*` आईडी को छोड़ देते हैं जिन्हें अनुरोध पथ साकार नहीं कर सकता और पहले से सूचीबद्ध एलियस को छोड़ देते हैं; फ़ंक्शनल-गेटवे मिरर कॉम्बो और OmniRoute द्वारा संश्लेषित आईडी (`claude/`, `no-think/`) को छोड़ देते हैं, स्वामी को `owned_by` से लेते हैं, स्वामी को कभी उसका अपना गेटवे नहीं बनाते, `blockedProviders` में अक्षम गेटवे और किसी गेटवे के आधिकारिक लाइव कैटलॉग से बाहर के मॉडल छोड़ देते हैं। | | ---------------------------------------------- | ------- | ------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `ANTHROPIC_COMPATIBLE_MID_CONVERSATION_SYSTEM` | boolean | `false` | | सामान्य `anthropic-compatible-*` रिले (claude → claude पासथ्रू) पर, बातचीत के बीच के `role: "system"` टर्न को शीर्ष-स्तरीय `system` पैरामीटर में उठाने के बजाय `messages[]` के भीतर रखें, ताकि कैश किया गया प्रॉम्प्ट प्रीफ़िक्स सभी टर्न में स्थिर रहे। यह केवल तभी लागू होता है, जब क्लाइंट ने स्वयं `mid-conversation-system-2026-04-07` `anthropic-beta` भेजा हो (जैसा है वैसा ही अपस्ट्रीम अग्रेषित किया जाता है)। डिफ़ॉल्ट रूप से बंद: प्रत्येक सिस्टम टर्न को पहले की तरह ही ऊपर उठाया जाता है, क्योंकि बीटा के बिना रिले `role: "system"` संदेशों को 400 के साथ अस्वीकार कर देते हैं। | | `MODELS_DEV_EFFORT_TIERS_ENABLED` | boolean | `false` | | जब न तो कोई ऑपरेटर ओवरराइड और न ही प्रदाता रजिस्ट्री कोई स्तर घोषित करे, तब models.dev द्वारा प्रकाशित रीजनिंग एफ़र्ट स्तरों (ज्ञात एफ़र्ट शब्दावली तक सीमित) का उपयोग करें और घोषित खाली स्तर सूची को `/v1/models` `effort_tiers` तथा कॉम्बो स्तर इंटरसेक्शन में "कोई स्तर नहीं" मानें। डिफ़ॉल्ट रूप से बंद: `effort_tiers` और रिज़ॉल्व किया गया `supportedThinkingEfforts` ठीक पहले जैसा ही रहता है। | | `AUTO_REQUEST_RESOLVED_CAPABILITIES` | boolean | `false` | | auto/* अनुरोध एक बल्क रीड में रिज़ॉल्व की गई मॉडल क्षमताओं (विज़न, रीजनिंग, थिंकिंग) और प्रेक्षित गुणवत्ता स्कोर के साथ उम्मीदवार पूल बनाते हैं (#16419)। डिफ़ॉल्ट रूप से बंद: अनुरोध-समय स्नैपशॉट वेट पहले जैसे ही रहते हैं। चालू होने पर: वे संकेत स्नैपशॉट वेट में शामिल होते हैं, इसलिए उम्मीदवार वेट और चयन बदल सकते हैं। | | `AUTO_COMBO_KEYLESS_TRANSPORT_GATE` | boolean | `false` | | उन keyless (no-auth) providers को `auto/*` उम्मीदवार पूल से बाहर रखें, जिनके transport को किसी स्थानीय browser (`browserBacked`) या स्थानीय रूप से इंस्टॉल किए गए CLI (`isLocalCli`) की आवश्यकता होती है, ताकि कोई अटका हुआ सदस्य प्रति-target timeout तक किसी auto request को बाधित न कर सके (#17036)। डिफ़ॉल्ट रूप से बंद: keyless पूल अपरिवर्तित रहता है। सीधे `/` कॉल दोनों ही स्थितियों में काम करते हैं। | @@ -240,7 +239,7 @@ "requiresRestart": false, "warningLevel": "caution", }, - // ... सभी 111 फ़्लैग + // ... सभी 110 फ़्लैग ], "summary": { "total": 56, diff --git a/docs/i18n/hr/docs/reference/FEATURE_FLAGS.md b/docs/i18n/hr/docs/reference/FEATURE_FLAGS.md index 8fa1e9dd74a9..85b330c71ec9 100644 --- a/docs/i18n/hr/docs/reference/FEATURE_FLAGS.md +++ b/docs/i18n/hr/docs/reference/FEATURE_FLAGS.md @@ -44,7 +44,7 @@ Boolean zastavica smatra se **omogućenom** kada je njezina efektivna vrijednost ## Katalog zastavica -111 zastavica u 6 kategorija. **Zadana vrijednost** jest zadana vrijednost definicije — vrijednost +110 zastavica u 6 kategorija. **Zadana vrijednost** jest zadana vrijednost definicije — vrijednost koja se koristi kada nisu prisutni ni nadjačavanje u bazi podataka ni varijabla okruženja. ### Sigurnost (10) @@ -109,7 +109,7 @@ koja se koristi kada nisu prisutni ni nadjačavanje u bazi podataka ni varijabla | `RADAR_ENABLED` | boolean | `false` | Omogućuje modul OmniRoute Radar (zasloni izvora kataloga i sinkronizacija). Zadano je isključeno; omogućavanje samo otključava korisničko sučelje — sinkronizacija podataka ostaje zasebna opcija koja se mora izričito uključiti. | | `MODEL_ENDPOINT_CAPABILITY_GATE_ENABLED` | boolean | `false` | Provodi pouzdane oznake krajnjih točaka modela (koje je deklarirao pružatelj usluga, koje su izvedene iz ID-a specijaliziranog modela ili ih je postavio operator): prije slanja odbija HTTP statusom 400 zahtjev poslan krajnjoj točki koju model ne poslužuje na `/v1/chat/completions`, `/v1/embeddings`, `/v1/rerank`, `/v1/images/generations`, `/v1/videos/generations`, `/v1/music/generations` i `/v1/audio/*`; na `/v1/models` skriva modele čije označene krajnje točke nemaju prilagodnik za OmniRoute; na test modela na nadzornoj ploči za njih odgovara statusom 422 (#16929). Zadano je isključeno: oznake krajnjih točaka ostaju informativne, a slanje se ne mijenja. | -### Izvršno okruženje (53) +### Izvršno okruženje (52) | Ključ | Vrsta | Zadano | Ponovno pokretanje | Opis | | ---------------------------------------------- | ------- | ------- | ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -156,7 +156,6 @@ koja se koristi kada nisu prisutni ni nadjačavanje u bazi podataka ni varijabla | `KIRO_TOOL_CALL_REPAIR_ENABLED` | boolean | `true` | | Kada Kiro odgovor u strujanju pošalje neispravan omotač `tool_call` (nedostaje `input.name` ili `input.arguments`) prije bilo kakvog vidljivog izlaza, potisni ga i jednom ponovno pošalji zahtjev s uputom za ispravak umjesto završavanja poteza pogreškom `invalid_kiro_tool_call`. Ponovni pokušaj zahtijeva jedan dodatni Kiro zahtjev; drugi neuspjeh završava potez pogreškom `kiro_tool_call_repair_retry_failed`. Prema zadanim postavkama uključeno. Postavite na `false` kako bi potez završio neuspjehom pri prvom neispravnom omotaču. | | `CATALOG_MIRROR_REFINEMENTS_ENABLED` | boolean | `false` | | Poboljšaj zrcala za otkrivanje `claude/` i funkcionalne pristupnike na `/v1/models`. Prema zadanim postavkama isključeno: oba prolaza zrcaljenja ponašaju se potpuno jednako kao prije. Kada je uključeno: zrcala za otkrivanje `claude/` obuhvaćaju i navedene varijante intenziteta zaključivanja (`-low`/`-medium`/`-high`/`-xhigh`), preskaču ugrađene ID-ove `auto/*` koje putanja zahtjeva ne može materijalizirati i preskaču već navedene pseudonime; zrcala funkcionalnih pristupnika preskaču kombinacije i ID-ove koje je sintetizirao OmniRoute (`claude/`, `no-think/`), preuzimaju vlasnika iz `owned_by`, nikada ne koriste vlasnika kao njegov vlastiti pristupnik, preskaču pristupnike onemogućene u `blockedProviders` i modele izvan mjerodavnog aktivnog kataloga pristupnika. | | ---------------------------------------------- | ------- | ------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `ANTHROPIC_COMPATIBLE_MID_CONVERSATION_SYSTEM` | boolean | `false` | | Na običnim relejima `anthropic-compatible-*` (izravno prosljeđivanje claude → claude), zadrži poteze `role: "system"` usred razgovora unutar `messages[]` umjesto njihova premještanja u parametar `system` najviše razine, kako bi predmemorirani prefiks upita ostao stabilan između poteza. Primjenjuje se samo kada je sam klijent poslao `mid-conversation-system-2026-04-07` `anthropic-beta` (prosljeđuje se uzvodno bez izmjena). Prema zadanim postavkama isključeno: svaki sistemski potez premješta se kao i prije jer releji bez bete odbijaju poruke `role: "system"` statusom 400. | | `MODELS_DEV_EFFORT_TIERS_ENABLED` | boolean | `false` | | Upotrijebi razine intenziteta zaključivanja koje objavljuje models.dev (ograničene na poznati rječnik intenziteta) kada ih ne navodi ni nadjačavanje operatora ni registar pružatelja, a deklarirani prazan popis razina tretiraj kao „bez razina” u `/v1/models` `effort_tiers` i presjecima razina kombinacija. Prema zadanim postavkama isključeno: `effort_tiers` i razriješeni `supportedThinkingEfforts` ostaju potpuno jednaki kao prije. | | `AUTO_REQUEST_RESOLVED_CAPABILITIES` | boolean | `false` | | Zahtjevi auto/* izrađuju skup kandidata s razriješenim mogućnostima modela (vid, zaključivanje, razmišljanje) i zabilježenim ocjenama kvalitete u jednom skupnom čitanju (#16419). Prema zadanim postavkama isključeno: težine snimke u trenutku zahtjeva ostaju potpuno jednake kao prije. Kada je uključeno: ti signali utječu na težine snimke, pa se težine kandidata i odabir mogu promijeniti. | | `AUTO_COMBO_KEYLESS_TRANSPORT_GATE` | boolean | `false` | | Iz skupa kandidata `auto/*` izostavite pružatelje bez ključa (bez autentifikacije) čiji prijenos zahtijeva lokalni preglednik (`browserBacked`) ili lokalno instaliran CLI (`isLocalCli`), kako blokirani član ne bi mogao zaustaviti automatski zahtjev sve do isteka vremena za pojedinačni cilj (#17036). Zadano je isključeno: skup bez ključa ostaje nepromijenjen. Izravni pozivi `/` rade u oba slučaja. | @@ -253,7 +252,7 @@ Vraća svaku zastavicu s njezinom efektivnom vrijednošću, izvorom i sažetkom. "requiresRestart": false, "warningLevel": "caution", }, - // ... svih 111 zastavica + // ... svih 110 zastavica ], "summary": { "total": 56, diff --git a/docs/i18n/hu/docs/reference/FEATURE_FLAGS.md b/docs/i18n/hu/docs/reference/FEATURE_FLAGS.md index 6b3d37348666..adb5555fe925 100644 --- a/docs/i18n/hu/docs/reference/FEATURE_FLAGS.md +++ b/docs/i18n/hu/docs/reference/FEATURE_FLAGS.md @@ -45,7 +45,7 @@ Egy logikai jelző akkor tekinthető **engedélyezettnek**, ha tényleges érté ## Jelzőkatalógus -111 jelző 6 kategóriában. Az **Alapértelmezett érték** a definícióban megadott alapérték — ez az érték +110 jelző 6 kategóriában. Az **Alapértelmezett érték** a definícióban megadott alapérték — ez az érték használatos, ha sem adatbázis-felülbírálás, sem környezeti változó nincs megadva. ### Biztonság (10) @@ -110,7 +110,7 @@ használatos, ha sem adatbázis-felülbírálás, sem környezeti változó ninc | `RADAR_ENABLED` | logikai | `false` | Az OmniRoute Radar modul engedélyezése (katalógushírcsatorna-képernyők és szinkronizálás). Alapértelmezés szerint ki van kapcsolva; az engedélyezése csak a felhasználói felületet teszi elérhetővé — az adatszinkronizáláshoz továbbra is külön hozzájárulás szükséges. | | `MODEL_ENDPOINT_CAPABILITY_GATE_ENABLED` | logikai | `false` | A megbízható modellvégpont-címkék kikényszerítése (a szolgáltató által deklarált, speciális modellazonosítóból kikövetkeztetett vagy az üzemeltető által beállított): a továbbítás előtt HTTP 400 válasszal elutasítja az olyan végpontra küldött kéréseket, amelyet a modell nem szolgál ki a következőkön: `/v1/chat/completions`, `/v1/embeddings`, `/v1/rerank`, `/v1/images/generations`, `/v1/videos/generations`, `/v1/music/generations` és `/v1/audio/*`; elrejti a `/v1/models` listájából azokat a modelleket, amelyek címkézett végpontjaihoz nincs OmniRoute-adapter; a vezérlőpult modelltesztjére 422-es választ ad ezek esetében (#16929). Alapértelmezés szerint ki van kapcsolva: a végpontcímkék tájékoztató jellegűek maradnak, a továbbítás pedig változatlan. | -### Futtatókörnyezet (53) +### Futtatókörnyezet (52) | Kulcs | Típus | Alapértelmezett | Újraindítás | Leírás | | ---------------------------------------------- | ------- | --------------- | ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -157,7 +157,6 @@ használatos, ha sem adatbázis-felülbírálás, sem környezeti változó ninc | `KIRO_TOOL_CALL_REPAIR_ENABLED` | boolean | `true` | | Ha egy streamelt Kiro-válasz bármilyen látható kimenet előtt hibás `tool_call` burkolót bocsát ki (hiányzik az `input.name` vagy az `input.arguments`), elnyomja azt, és a kör `invalid_kiro_tool_call` hibával történő befejezése helyett egyszer újraküldi a kérést egy javító utasítással. Az újrapróbálkozás egy további Kiro-kérésbe kerül; egy második hiba `kiro_tool_call_repair_retry_failed` hibával zárja a kört. Alapértelmezés szerint be van kapcsolva. Állítsa `false` értékre, hogy a kör már az első hibás burkolónál sikertelenül végződjön. | | `CATALOG_MIRROR_REFINEMENTS_ENABLED` | boolean | `false` | | Finomítja a `claude/` felderítési és funkcionális átjárótükröket a `/v1/models` végponton. Alapértelmezés szerint ki van kapcsolva: mindkét tükrözési menet pontosan úgy működik, mint korábban. Bekapcsolva: a `claude/` felderítési tükrök a felsorolt következtetésierőfeszítés-változatokra (`-low`/`-medium`/`-high`/`-xhigh`) is kiterjednek, kihagyják a beépített `auto/*` azonosítókat, amelyeket a kérési útvonal nem tud létrehozni, valamint a már felsorolt aliasokat; a funkcionális átjárótükrök kihagyják a kombinációkat és az OmniRoute által szintetizált azonosítókat (`claude/`, `no-think/`), a tulajdonost az `owned_by` mezőből veszik, soha nem használják a tulajdonost saját átjárójaként, továbbá kihagyják a `blockedProviders` alatt letiltott átjárókat és azokat a modelleket, amelyek nem szerepelnek az átjáró mérvadó élő katalógusában. | | ---------------------------------------------- | ------- | ------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `ANTHROPIC_COMPATIBLE_MID_CONVERSATION_SYSTEM` | boolean | `false` | | Egyszerű `anthropic-compatible-*` továbbítókon (claude → claude közvetlen továbbítás) a beszélgetés közbeni `role: "system"` fordulókat a `messages[]` tömbben tartja ahelyett, hogy a legfelső szintű `system` paraméterbe emelné őket, így a gyorsítótárazott promptelőtag stabil marad a fordulók között. Csak akkor érvényes, ha maga a kliens küldte a `mid-conversation-system-2026-04-07` `anthropic-beta` értéket (amely változtatás nélkül továbbítódik a felsőbb szolgáltatásnak). Alapértelmezés szerint ki van kapcsolva: minden rendszerforduló pontosan úgy kerül kiemelésre, mint korábban, mert a béta nélküli továbbítók 400-as hibával utasítják el a `role: "system"` üzeneteket. | | `MODELS_DEV_EFFORT_TIERS_ENABLED` | boolean | `false` | | A models.dev által közzétett következtetésierőfeszítés-szinteket használja (az ismert erőfeszítés-szókészletre szűkítve), ha sem operátori felülbírálás, sem a szolgáltatói nyilvántartás nem határoz meg ilyet, továbbá a deklarált üres szintlistát „nincsenek szintek” jelentéssel kezeli a `/v1/models` `effort_tiers` mezőjében és a kombinációs szintek metszeteiben. Alapértelmezés szerint ki van kapcsolva: az `effort_tiers` és a feloldott `supportedThinkingEfforts` pontosan úgy marad, mint korábban. | | `AUTO_REQUEST_RESOLVED_CAPABILITIES` | boolean | `false` | | Az auto/* kérések a feloldott modellképességek (látás, következtetés, gondolkodás) és a megfigyelt minőségi pontszámok alapján, egyetlen tömeges olvasással építik fel a jelöltkészletet (#16419). Alapértelmezés szerint ki van kapcsolva: a kéréskori pillanatkép súlyai pontosan úgy maradnak, mint korábban. Bekapcsolva: ezek a jelek bekerülnek a pillanatkép súlyaiba, így a jelöltek súlyai és a kiválasztás megváltozhatnak. | | `AUTO_COMBO_KEYLESS_TRANSPORT_GATE` | boolean | `false` | | Kizárja az olyan kulcs nélküli (hitelesítést nem igénylő) szolgáltatókat az `auto/*` jelöltkészletéből, amelyek átvitele helyi böngészőt (`browserBacked`) vagy helyileg telepített CLI-t (`isLocalCli`) igényel, így egy elakadt tag nem tarthatja fel az automatikus kérést a célonkénti időtúllépésig (#17036). Alapértelmezés szerint ki van kapcsolva: a kulcs nélküli készlet változatlan marad. A közvetlen `/` hívások mindkét esetben működnek. | @@ -254,7 +253,7 @@ Visszaad minden jelzőt a tényleges értékével, forrásával és egy összegz "requiresRestart": false, "warningLevel": "caution", }, - // ... mind a 111 jelző + // ... mind a 110 jelző ], "summary": { "total": 56, diff --git a/docs/i18n/hy/docs/reference/FEATURE_FLAGS.md b/docs/i18n/hy/docs/reference/FEATURE_FLAGS.md index d31f007529f3..e68efeb6226e 100644 --- a/docs/i18n/hy/docs/reference/FEATURE_FLAGS.md +++ b/docs/i18n/hy/docs/reference/FEATURE_FLAGS.md @@ -33,7 +33,7 @@ ## Դրոշակների կատալոգ -111 դրոշակ՝ 6 կատեգորիաներում։ **Լռելյայնը** սահմանման լռելյայն արժեքն է՝ այն արժեքը, +110 դրոշակ՝ 6 կատեգորիաներում։ **Լռելյայնը** սահմանման լռելյայն արժեքն է՝ այն արժեքը, որն օգտագործվում է, երբ առկա չէ ո՛չ DB-ի վերասահմանում, ո՛չ միջավայրի փոփոխական։ ### Անվտանգություն (10) @@ -98,7 +98,7 @@ | `RADAR_ENABLED` | բուլյան | `false` | Միացնել OmniRoute Radar մոդուլը (կատալոգային հոսքի էկրաններ և համաժամացում)։ Լռելյայն անջատված է. միացնելը միայն ապակողպում է օգտատիրոջ միջերեսը. տվյալների համաժամացումը շարունակում է պահանջել առանձին համաձայնություն։ | | `MODEL_ENDPOINT_CAPABILITY_GATE_ENABLED` | բուլյան | `false` | Կիրառել մոդելի վստահելի վերջնակետերի պիտակները (հայտարարված մատակարարի կողմից, եզրակացված մասնագիտացված մոդելի նույնացուցիչից կամ սահմանված օպերատորի կողմից). մինչև ուղարկումը HTTP 400-ով մերժել այն վերջնակետին ուղարկված հարցումը, որը մոդելը չի սպասարկում՝ `/v1/chat/completions`, `/v1/embeddings`, `/v1/rerank`, `/v1/images/generations`, `/v1/videos/generations`, `/v1/music/generations` և `/v1/audio/*` հասցեներում. `/v1/models`-ից թաքցնել այն մոդելները, որոնց պիտակավորված վերջնակետերի համար OmniRoute ադապտեր չկա. դրանց համար կառավարման վահանակի մոդելի թեստին պատասխանել 422-ով (#16929)։ Լռելյայն անջատված է. վերջնակետերի պիտակները մնում են տեղեկատվական, իսկ հարցումների ուղարկումը չի փոխվում։ | -### Կատարման միջավայր (53) +### Կատարման միջավայր (52) | Բանալի | Տեսակ | Լռելյայն | Վերագործարկում | Նկարագրություն | | ---------------------------------------------- | ------- | -------- | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | @@ -145,7 +145,6 @@ | `KIRO_TOOL_CALL_REPAIR_ENABLED` | boolean | `true` | | Երբ հոսքային Kiro պատասխանը տեսանելի որևէ ելքից առաջ թողարկում է սխալ ձևավորված `tool_call` փաթաթիչ (`input.name`-ը կամ `input.arguments`-ը բացակայում է), թաքցնել այն և ուղղիչ հրահանգով մեկ անգամ կրկին ուղարկել հարցումը՝ հերթը `invalid_kiro_tool_call`-ով ավարտելու փոխարեն։ Կրկնափորձը պահանջում է մեկ լրացուցիչ Kiro հարցում. երկրորդ ձախողման դեպքում հերթն ավարտվում է `kiro_tool_call_repair_retry_failed`-ով։ Լռելյայն միացված է։ Սահմանեք `false`, որպեսզի հերթն ավարտվի առաջին իսկ սխալ ձևավորված փաթաթիչի դեպքում։ | | `CATALOG_MIRROR_REFINEMENTS_ENABLED` | boolean | `false` | | Ճշգրտել `/v1/models`-ի `claude/` հայտնաբերման և ֆունկցիոնալ gateway-ի հայելիները։ Լռելյայն անջատված է. հայելավորման երկու անցումներն էլ աշխատում են ճիշտ այնպես, ինչպես նախկինում։ Միացված լինելու դեպքում `claude/` հայտնաբերման հայելիները ներառում են նաև թվարկված reasoning-effort տարբերակները (`-low`/`-medium`/`-high`/`-xhigh`), բաց են թողնում ներկառուցված `auto/*` id-ները, որոնք հարցման ուղին չի կարող նյութականացնել, և արդեն թվարկված կեղծանունները. ֆունկցիոնալ gateway-ի հայելիները բաց են թողնում համակցումներն ու OmniRoute-ի սինթեզած id-ները (`claude/`, `no-think/`), սեփականատիրոջը վերցնում են `owned_by`-ից, երբեք սեփականատիրոջը չեն օգտագործում որպես իր իսկ gateway, բաց են թողնում `blockedProviders`-ում անջատված gateway-ները և gateway-ի հեղինակավոր ընթացիկ կատալոգից դուրս գտնվող մոդելները։ | | ---------------------------------------------- | ------- | ------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `ANTHROPIC_COMPATIBLE_MID_CONVERSATION_SYSTEM` | boolean | `false` | | Սովորական `anthropic-compatible-*` ռելեներում (claude → claude անմիջական փոխանցում) զրույցի միջնամասի `role: "system"` հերթերը պահել `messages[]`-ի ներսում՝ դրանք վերին մակարդակի `system` պարամետր տեղափոխելու փոխարեն, որպեսզի քեշավորված հուշման նախածանցը կայուն մնա հերթերի միջև։ Կիրառվում է միայն այն դեպքում, երբ հաճախորդն ինքն է ուղարկել `mid-conversation-system-2026-04-07` `anthropic-beta`-ն (որը վերին հոսք է փոխանցվում անփոփոխ)։ Լռելյայն անջատված է. յուրաքանչյուր համակարգային հերթ տեղափոխվում է ճիշտ այնպես, ինչպես նախկինում, քանի որ beta չունեցող ռելեները մերժում են `role: "system"` հաղորդագրությունները 400 պատասխանով։ | | `MODELS_DEV_EFFORT_TIERS_ENABLED` | boolean | `false` | | Օգտագործել models.dev-ի հրապարակած դատողության ջանքի մակարդակները (սահմանափակված ջանքի հայտնի բառապաշարով), երբ ոչ օպերատորի վերասահմանումը, ոչ էլ մատակարարի գրանցամատյանը որևէ մակարդակ չեն հայտարարում, և հայտարարված դատարկ մակարդակների ցանկը մեկնաբանել որպես «մակարդակներ չկան»՝ `/v1/models`-ի `effort_tiers`-ում և համակցումների մակարդակների հատումներում։ Լռելյայն անջատված է. `effort_tiers`-ը և լուծված `supportedThinkingEfforts`-ը մնում են ճիշտ այնպես, ինչպես նախկինում։ | | `AUTO_REQUEST_RESOLVED_CAPABILITIES` | boolean | `false` | | auto/* հարցումները թեկնածուների հավաքածուն կառուցում են լուծված մոդելային կարողություններով (տեսողություն, դատողություն, մտածողություն) և դիտարկված որակի գնահատականներով՝ մեկ զանգվածային ընթերցմամբ (#16419)։ Լռելյայն անջատված է. հարցման պահին ստեղծված պատկերի կշիռները մնում են ճիշտ այնպես, ինչպես նախկինում։ Միացված լինելու դեպքում այդ ազդակներն ազդում են պատկերի կշիռների վրա, ուստի թեկնածուների կշիռներն ու ընտրությունը կարող են փոխվել։ | | `AUTO_COMBO_KEYLESS_TRANSPORT_GATE` | boolean | `false` | | Առանց բանալու (առանց նույնականացման) այն մատակարարներին, որոնց փոխադրամիջոցին անհրաժեշտ է տեղային դիտարկիչ (`browserBacked`) կամ տեղային տեղադրված CLI (`isLocalCli`), դուրս պահել `auto/*` թեկնածուների հավաքածուից, որպեսզի կախված անդամը չկարողանա կանգնեցնել ավտոմատ հարցումը մինչև յուրաքանչյուր թիրախի ժամանակային սահմանաչափը (#17036)։ Լռելյայն անջատված է. առանց բանալու մատակարարների հավաքածուն մնում է անփոփոխ։ Ուղղակի `/` կանչերն աշխատում են երկու դեպքում էլ։ | @@ -242,7 +241,7 @@ "requiresRestart": false, "warningLevel": "caution", }, - // ... բոլոր 111 դրոշները + // ... բոլոր 110 դրոշները ], "summary": { "total": 56, diff --git a/docs/i18n/id/docs/reference/FEATURE_FLAGS.md b/docs/i18n/id/docs/reference/FEATURE_FLAGS.md index a7c308014feb..d2c6a1a35ef0 100644 --- a/docs/i18n/id/docs/reference/FEATURE_FLAGS.md +++ b/docs/i18n/id/docs/reference/FEATURE_FLAGS.md @@ -44,7 +44,7 @@ Flag boolean dianggap **diaktifkan** ketika nilai efektifnya adalah `"true"`, ## Katalog Flag -111 flag dalam 6 kategori. **Default** adalah nilai default definisi — nilai yang +110 flag dalam 6 kategori. **Default** adalah nilai default definisi — nilai yang digunakan ketika tidak ada penggantian dari DB maupun variabel lingkungan. ### Keamanan (10) @@ -109,7 +109,7 @@ digunakan ketika tidak ada penggantian dari DB maupun variabel lingkungan. | `RADAR_ENABLED` | boolean | `false` | Aktifkan modul OmniRoute Radar (layar umpan katalog dan sinkronisasi). Nonaktif secara default; mengaktifkannya hanya membuka UI — sinkronisasi data tetap memerlukan persetujuan terpisah. | | `MODEL_ENDPOINT_CAPABILITY_GATE_ENABLED` | boolean | `false` | Terapkan tag endpoint model tepercaya (dideklarasikan oleh penyedia, disimpulkan dari ID model khusus, atau ditetapkan oleh operator): tolak permintaan yang dikirim ke endpoint yang tidak dilayani model dengan HTTP 400 sebelum pengiriman pada `/v1/chat/completions`, `/v1/embeddings`, `/v1/rerank`, `/v1/images/generations`, `/v1/videos/generations`, `/v1/music/generations` dan `/v1/audio/*`; sembunyikan model yang endpoint bertagnya tidak memiliki adaptor OmniRoute dari `/v1/models`; jawab pengujian model dasbor dengan 422 untuk model tersebut (#16929). Nonaktif secara default: tag endpoint tetap bersifat informatif dan pengiriman tidak berubah. | -### Runtime (53) +### Runtime (52) | Kunci | Tipe | Default | Mulai Ulang | Deskripsi | | ---------------------------------------------- | ------- | ------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -156,7 +156,6 @@ digunakan ketika tidak ada penggantian dari DB maupun variabel lingkungan. | `KIRO_TOOL_CALL_REPAIR_ENABLED` | boolean | `true` | | Ketika respons streaming Kiro memancarkan wrapper `tool_call` yang salah format (tidak memiliki `input.name` atau `input.arguments`) sebelum keluaran yang terlihat, hentikan wrapper tersebut dan kirim ulang permintaan satu kali dengan instruksi korektif alih-alih mengakhiri giliran dengan `invalid_kiro_tool_call`. Percobaan ulang memerlukan satu permintaan Kiro tambahan; kegagalan kedua mengakhiri giliran dengan `kiro_tool_call_repair_retry_failed`. Diaktifkan secara default. Atur ke `false` untuk menggagalkan giliran pada wrapper salah format pertama. | | `CATALOG_MIRROR_REFINEMENTS_ENABLED` | boolean | `false` | | Sempurnakan mirror penemuan `claude/` dan gateway fungsional di `/v1/models`. Dinonaktifkan secara default: kedua proses mirror berperilaku persis seperti sebelumnya. Jika diaktifkan: mirror penemuan `claude/` juga mencakup varian upaya penalaran yang tercantum (`-low`/`-medium`/`-high`/`-xhigh`), melewati id `auto/*` bawaan yang tidak dapat diwujudkan oleh jalur permintaan, dan melewati alias yang sudah tercantum; mirror gateway fungsional melewati combo dan id yang disintesis OmniRoute (`claude/`, `no-think/`), mengambil pemilik dari `owned_by`, tidak pernah menggunakan pemilik sebagai gateway-nya sendiri, melewati gateway yang dinonaktifkan di `blockedProviders`, serta model di luar katalog live otoritatif gateway. | | ---------------------------------------------- | ------- | ------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `ANTHROPIC_COMPATIBLE_MID_CONVERSATION_SYSTEM` | boolean | `false` | | Pada relay `anthropic-compatible-*` biasa (passthrough claude → claude), pertahankan giliran `role: "system"` di tengah percakapan di dalam `messages[]` alih-alih mengangkatnya ke parameter `system` tingkat teratas, agar prefiks prompt yang di-cache tetap stabil di antara giliran. Hanya berlaku ketika klien sendiri mengirimkan `mid-conversation-system-2026-04-07` `anthropic-beta` (diteruskan ke upstream tanpa perubahan). Dinonaktifkan secara default: setiap giliran sistem diangkat persis seperti sebelumnya, karena relay tanpa beta tersebut menolak pesan `role: "system"` dengan 400. | | `MODELS_DEV_EFFORT_TIERS_ENABLED` | boolean | `false` | | Gunakan tingkat upaya penalaran yang dipublikasikan models.dev (dibatasi pada kosakata upaya yang dikenal) ketika override operator maupun registri penyedia tidak mendeklarasikan apa pun, dan perlakukan daftar tingkat kosong yang dideklarasikan sebagai "tanpa tingkat" dalam `effort_tiers` di `/v1/models` serta irisan tingkat combo. Dinonaktifkan secara default: `effort_tiers` dan `supportedThinkingEfforts` yang telah diselesaikan tetap persis seperti sebelumnya. | | `AUTO_REQUEST_RESOLVED_CAPABILITIES` | boolean | `false` | | Permintaan auto/* membangun kumpulan kandidat menggunakan kapabilitas model yang telah diselesaikan (visi, penalaran, pemikiran) dan skor kualitas yang diamati dalam satu pembacaan massal (#16419). Dinonaktifkan secara default: bobot snapshot pada waktu permintaan tetap persis seperti sebelumnya. Jika diaktifkan: sinyal tersebut digunakan dalam bobot snapshot, sehingga bobot kandidat dan pemilihan dapat berubah. | | `AUTO_COMBO_KEYLESS_TRANSPORT_GATE` | boolean | `false` | | Kecualikan penyedia keyless (tanpa autentikasi) yang transportnya memerlukan browser lokal (`browserBacked`) atau CLI yang terinstal secara lokal (`isLocalCli`) dari kumpulan kandidat `auto/*`, agar anggota yang macet tidak menahan permintaan otomatis hingga batas waktu per target tercapai (#17036). Nonaktif secara default: kumpulan keyless tidak berubah. Panggilan langsung `/` tetap berfungsi dalam kedua kondisi. | @@ -253,7 +252,7 @@ Mengembalikan setiap flag beserta nilai efektif, sumber, dan ringkasannya. "requiresRestart": false, "warningLevel": "caution", }, - // ... semua 111 flag + // ... semua 110 flag ], "summary": { "total": 56, diff --git a/docs/i18n/ig/docs/reference/FEATURE_FLAGS.md b/docs/i18n/ig/docs/reference/FEATURE_FLAGS.md index 7ee0d8e33c1d..5085d7f08279 100644 --- a/docs/i18n/ig/docs/reference/FEATURE_FLAGS.md +++ b/docs/i18n/ig/docs/reference/FEATURE_FLAGS.md @@ -43,7 +43,7 @@ A na-ewere boolean flag dị ka nke **agbanyere** mgbe uru ya dị irè bụ `"t ## Katalọgụ Ọkọlọtọ -Ọkọlọtọ 111 n’ime ngalaba 6. **Ndabara** bụ ndabara nke nkọwa — uru a +Ọkọlọtọ 110 n’ime ngalaba 6. **Ndabara** bụ ndabara nke nkọwa — uru a na-eji mgbe enweghị ma mgbanwe DB ma ọ bụ mgbanwe gburugburu ebe obibi. ### Nchebe (10) @@ -108,7 +108,7 @@ na-eji mgbe enweghị ma mgbanwe DB ma ọ bụ mgbanwe gburugburu ebe obibi. | `RADAR_ENABLED` | boolean | `false` | Gbanye modulu OmniRoute Radar (ihuenyo feed katalọgụ na mmekọrịta data). Ọ na-agbanyụ na ndabara; ịgbanye ya na-emeghe naanị UI — mmekọrịta data ka chọrọ nkwenye dị iche. | | `MODEL_ENDPOINT_CAPABILITY_GATE_ENABLED` | boolean | `false` | Manye tags endpoint model ndị a tụkwasịrị obi (nke provider kwupụtara, nke a chọpụtara site na id model pụrụ iche, ma ọ bụ nke operator setịpụrụ): jụ arịrịọ ezigara endpoint nke model ahụ anaghị akwado site na HTTP 400 tupu izipu ya na `/v1/chat/completions`, `/v1/embeddings`, `/v1/rerank`, `/v1/images/generations`, `/v1/videos/generations`, `/v1/music/generations` na `/v1/audio/*`; zoo model ndị endpoint ha nwere tag na-enweghị adapter OmniRoute na `/v1/models`; jiri 422 zaghachi ule model dashboard maka ha (#16929). Ọ na-agbanyụ na ndabara: tags endpoint na-anọgide naanị dị ka ozi, izipu arịrịọ anaghịkwa agbanwe. | -### Oge ịrụ ọrụ (53) +### Oge ịrụ ọrụ (52) | Igodo | Ụdị | Ndabara | Malitegharịa | Nkọwa | | ---------------------------------------------- | ------- | ------- | ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -155,7 +155,6 @@ na-eji mgbe enweghị ma mgbanwe DB ma ọ bụ mgbanwe gburugburu ebe obibi. | `KIRO_TOOL_CALL_REPAIR_ENABLED` | boolean | `true` | | Mgbe nzaghachi Kiro a na-enuba wepụtara ihe mkpuchi `tool_call` mebiri emebi (nke `input.name` ma ọ bụ `input.arguments` na-adịghị na ya) tupu mmepụta ọ bụla a na-ahụ anya, gbochie ya ma zipụgharịa arịrịọ ahụ otu ugboro yana ntuziaka mmezi kama iji `invalid_kiro_tool_call` mechie ntụgharị ahụ. Nnwale ọzọ ahụ na-eri otu arịrịọ Kiro ọzọ; ọdịda nke ugboro abụọ na-eji `kiro_tool_call_repair_retry_failed` mechie ntụgharị ahụ. Agbanyere ya na ndabara. Tọọ ya ka ọ bụrụ `false` iji mee ka ntụgharị ahụ daa ozugbo ihe mkpuchi mbụ mebiri emebi pụtara. | | `CATALOG_MIRROR_REFINEMENTS_ENABLED` | boolean | `false` | | Meziwanye enyo nchọpụta `claude/` na enyo functional-gateway dị na `/v1/models`. Agbanyụrụ ya na ndabara: usoro enyo abụọ ahụ na-arụ ọrụ kpọmkwem dịka ha si arụbu. Mgbe agbanyere ya: enyo nchọpụta `claude/` na-ekpuchikwa ụdị reasoning-effort ndị edepụtara (`-low`/`-medium`/`-high`/`-xhigh`), na-amafere id `auto/*` arụnyere n'ime sistemụ nke ụzọ arịrịọ enweghị ike ịmepụta, ma na-amafere alias e depụtarala; enyo functional-gateway na-amafere ngwakọta na id ndị OmniRoute mepụtara (`claude/`, `no-think/`), na-ewere onye nwe ya site na `owned_by`, anaghị eji onye nwe ya dịka gateway nke onwe ya, ma na-amafere gateway ndị agbanyụrụ na `blockedProviders` na model ndị na-anọghị na katalọgụ dị ndụ nke gateway ahụ nwere ikike ikpeazụ na ya. | | ---------------------------------------------- | ------- | ------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `ANTHROPIC_COMPATIBLE_MID_CONVERSATION_SYSTEM` | boolean | `false` | | Na relay nkịtị `anthropic-compatible-*` (claude → claude passthrough), debe ntụgharị `role: "system"` ndị dị n'etiti mkparịta ụka n'ime `messages[]` kama ibuli ha gaa na parameter `system` nke ọkwa kachasị elu, ka prefix prompt echekwara na cache wee kwụsie ike n'etiti ntụgharị. Nke a na-emetụta naanị mgbe client n'onwe ya zitere `mid-conversation-system-2026-04-07` `anthropic-beta` (a na-ebuga ya upstream otu ọ dị). Agbanyụrụ ya na ndabara: a na-ebuli ntụgharị system ọ bụla kpọmkwem dịka ọ dị na mbụ, n'ihi na relay ndị na-enweghị beta ahụ na-eji 400 jụ ozi `role: "system"`. | | `MODELS_DEV_EFFORT_TIERS_ENABLED` | boolean | `false` | | Jiri ọkwa mgbalị echiche models.dev na-ebipụta (nke e mere ka ọ bụrụ naanị mkpụrụokwu mgbalị ndị amaara) mgbe ma nhazi operator dochiri anya ma ndekọ provider ekwupụtaghị nke ọ bụla, ma were ndepụta ọkwa tọgbọrọ chakoo e kwupụtara dịka "enweghị ọkwa" na `/v1/models` `effort_tiers` na njikọ ọkwa nke ngwakọta. Agbanyụrụ ya na ndabara: `effort_tiers` na `supportedThinkingEfforts` e kpebiri na-anọ kpọmkwem dịka ha dị na mbụ. | | `AUTO_REQUEST_RESOLVED_CAPABILITIES` | boolean | `false` | | Arịrịọ auto/* na-eji ikike model e kpebiri (ọhụụ, reasoning, thinking) na akara ogo a hụrụ anya n'otu ọgụgụ mkpokọta wuo ọdọ ndị ndọrọndọrọ (#16419). Agbanyụrụ ya na ndabara: ịdị arọ snapshot n'oge arịrịọ na-anọ kpọmkwem dịka ọ dị na mbụ. Mgbe agbanyere ya: akara ndị ahụ na-abanye n'ịdị arọ snapshot, ya mere ịdị arọ na nhọpụta ndị ndọrọndọrọ nwere ike ịgbanwe. | | `AUTO_COMBO_KEYLESS_TRANSPORT_GATE` | boolean | `false` | | Wepụ ndị na-eweta ọrụ na-enweghị igodo (enweghị nyocha-njirimara) ndị njem-data ha chọrọ ihe nchọgharị mpaghara (`browserBacked`) ma ọ bụ CLI arụnyere na mpaghara (`isLocalCli`) na nchịkọta ndị a ga-ahọrọ `auto/*`, ka onye otu kwụsịrị ịrụ ọrụ ghara igbochi arịrịọ akpaaka ruo mgbe oge nchere nke ebumnuche ọ bụla gwụsịrị (#17036). Agbanyụghị ya na ndabara: nchịkọta na-enweghị igodo agbanwebeghị. Oku `/` kpọmkwem na-arụ ọrụ n'ụzọ ọ bụla. | @@ -252,7 +251,7 @@ Na-eweghachi ọkọlọtọ ọ bụla tinyere ụkpụrụ ya dị ire, isi mm "requiresRestart": false, "warningLevel": "caution", }, - // ... ọkọlọtọ 111 niile + // ... ọkọlọtọ 110 niile ], "summary": { "total": 56, diff --git a/docs/i18n/it/docs/reference/FEATURE_FLAGS.md b/docs/i18n/it/docs/reference/FEATURE_FLAGS.md index 094efd963746..861db164d7c3 100644 --- a/docs/i18n/it/docs/reference/FEATURE_FLAGS.md +++ b/docs/i18n/it/docs/reference/FEATURE_FLAGS.md @@ -44,7 +44,7 @@ Un flag booleano è considerato **abilitato** quando il suo valore effettivo è ## Catalogo dei flag -111 flag in 6 categorie. **Predefinito** indica il valore predefinito della definizione, utilizzato +110 flag in 6 categorie. **Predefinito** indica il valore predefinito della definizione, utilizzato quando non è presente né una sostituzione nel DB né una variabile d'ambiente. ### Sicurezza (10) @@ -109,7 +109,7 @@ quando non è presente né una sostituzione nel DB né una variabile d'ambiente. | `RADAR_ENABLED` | boolean | `false` | Abilita il modulo OmniRoute Radar (schermate dei feed del catalogo e sincronizzazione). Disattivato per impostazione predefinita; l'abilitazione rende disponibile solo l'interfaccia utente, mentre la sincronizzazione dei dati richiede un consenso separato. | | `MODEL_ENDPOINT_CAPABILITY_GATE_ENABLED` | boolean | `false` | Applica i tag attendibili degli endpoint del modello (dichiarati dal provider, dedotti dall'ID di un modello specializzato oppure impostati dall'operatore): rifiuta con HTTP 400, prima dell'inoltro, una richiesta inviata a un endpoint non supportato dal modello su `/v1/chat/completions`, `/v1/embeddings`, `/v1/rerank`, `/v1/images/generations`, `/v1/videos/generations`, `/v1/music/generations` e `/v1/audio/*`; nasconde da `/v1/models` i modelli i cui endpoint contrassegnati non dispongono di un adattatore OmniRoute; per tali modelli, risponde con 422 al test del modello nel dashboard (#16929). Disattivato per impostazione predefinita: i tag degli endpoint restano informativi e l'inoltro rimane invariato. | -### Runtime (53) +### Runtime (52) | Chiave | Tipo | Predefinito | Riavvio | Descrizione | | ---------------------------------------------- | ------- | ----------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -156,7 +156,6 @@ quando non è presente né una sostituzione nel DB né una variabile d'ambiente. | `KIRO_TOOL_CALL_REPAIR_ENABLED` | boolean | `true` | | Quando una risposta Kiro in streaming emette un wrapper `tool_call` non valido (privo di `input.name` o `input.arguments`) prima di qualsiasi output visibile, lo elimina e invia nuovamente la richiesta una volta con un'istruzione correttiva, anziché terminare il turno con `invalid_kiro_tool_call`. Il nuovo tentativo comporta il costo di una richiesta Kiro aggiuntiva; un secondo errore termina il turno con `kiro_tool_call_repair_retry_failed`. Attivato per impostazione predefinita. Impostare su `false` per far fallire il turno al primo wrapper non valido. | | `CATALOG_MIRROR_REFINEMENTS_ENABLED` | boolean | `false` | | Migliora i mirror di individuazione `claude/` e del gateway funzionale su `/v1/models`. Disattivato per impostazione predefinita: entrambi i passaggi di mirroring si comportano esattamente come prima. Quando è attivo: i mirror di individuazione `claude/` includono anche le varianti elencate dell'intensità di ragionamento (`-low`/`-medium`/`-high`/`-xhigh`), ignorano gli ID `auto/*` integrati che il percorso della richiesta non può materializzare e ignorano gli alias già elencati; i mirror del gateway funzionale ignorano le combo e gli ID sintetizzati da OmniRoute (`claude/`, `no-think/`), ricavano il proprietario da `owned_by`, non utilizzano mai il proprietario come gateway di se stesso, ignorano i gateway disabilitati in `blockedProviders` e i modelli esterni al catalogo live autorevole di un gateway. | | ---------------------------------------------- | ------- | ------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `ANTHROPIC_COMPATIBLE_MID_CONVERSATION_SYSTEM` | boolean | `false` | | Sui relay semplici `anthropic-compatible-*` (pass-through claude → claude), mantiene i turni `role: "system"` a metà conversazione all'interno di `messages[]` anziché spostarli nel parametro di primo livello `system`, in modo che il prefisso del prompt memorizzato nella cache rimanga stabile tra i turni. Si applica solo quando il client ha inviato direttamente `mid-conversation-system-2026-04-07` in `anthropic-beta` (inoltrato a monte senza modifiche). Disattivato per impostazione predefinita: ogni turno di sistema viene spostato esattamente come prima, poiché i relay privi della beta rifiutano con un errore 400 i messaggi `role: "system"`. | | `MODELS_DEV_EFFORT_TIERS_ENABLED` | boolean | `false` | | Utilizza i livelli di intensità del ragionamento pubblicati da models.dev (limitati al vocabolario noto delle intensità) quando né una sostituzione definita dall'operatore né il registro del provider ne dichiara alcuno, e considera un elenco di livelli dichiarato ma vuoto come "nessun livello" in `effort_tiers` di `/v1/models` e nelle intersezioni dei livelli delle combo. Disattivato per impostazione predefinita: `effort_tiers` e `supportedThinkingEfforts` risolto rimangono esattamente come prima. | | `AUTO_REQUEST_RESOLVED_CAPABILITIES` | boolean | `false` | | Le richieste auto/* creano il pool di candidati usando le funzionalità risolte dei modelli (visione, ragionamento, pensiero) e i punteggi di qualità osservati tramite un'unica lettura in blocco (#16419). Disattivato per impostazione predefinita: i pesi dell'istantanea al momento della richiesta rimangono esattamente come prima. Quando è attivo: questi segnali alimentano i pesi dell'istantanea, pertanto i pesi dei candidati e la selezione possono cambiare. | | `AUTO_COMBO_KEYLESS_TRANSPORT_GATE` | boolean | `false` | | Esclude dal pool di candidati `auto/*` i provider keyless (senza autenticazione) il cui trasporto richiede un browser locale (`browserBacked`) o una CLI installata localmente (`isLocalCli`), in modo che un membro bloccato non possa sospendere una richiesta automatica fino al timeout per destinazione (#17036). Disattivato per impostazione predefinita: il pool keyless rimane invariato. Le chiamate dirette `/` funzionano in entrambi i casi. | @@ -253,7 +252,7 @@ Restituisce ogni flag con il relativo valore effettivo, l'origine e un riepilogo "requiresRestart": false, "warningLevel": "caution", }, - // ... tutti i 111 flag + // ... tutti i 110 flag ], "summary": { "total": 56, diff --git a/docs/i18n/ja/docs/reference/FEATURE_FLAGS.md b/docs/i18n/ja/docs/reference/FEATURE_FLAGS.md index 5c17f2a471ac..098bdb741505 100644 --- a/docs/i18n/ja/docs/reference/FEATURE_FLAGS.md +++ b/docs/i18n/ja/docs/reference/FEATURE_FLAGS.md @@ -37,7 +37,7 @@ boolean フラグは、実効値が `"true"`、`"1"`、または `"yes"` の場 ## フラグカタログ -6つのカテゴリにわたる111個のフラグ。**デフォルト**は定義上のデフォルト値です。DBによる上書きも環境変数も存在しない場合に +6つのカテゴリにわたる110個のフラグ。**デフォルト**は定義上のデフォルト値です。DBによる上書きも環境変数も存在しない場合に 使用される値です。 ### セキュリティ(10) @@ -102,7 +102,7 @@ boolean フラグは、実効値が `"true"`、`"1"`、または `"yes"` の場 | `RADAR_ENABLED` | boolean | `false` | OmniRoute Radar モジュール(カタログフィード画面と同期)を有効にします。デフォルトではオフです。有効にしても UI が利用可能になるだけで、データ同期には別途オプトインが必要です。 | | `MODEL_ENDPOINT_CAPABILITY_GATE_ENABLED` | boolean | `false` | 信頼できるモデルエンドポイントタグ(プロバイダーによる宣言、特殊用途モデル ID からの推論、または運用者による設定)を適用します。`/v1/chat/completions`、`/v1/embeddings`、`/v1/rerank`、`/v1/images/generations`、`/v1/videos/generations`、`/v1/music/generations`、`/v1/audio/*` において、モデルが提供していないエンドポイントへ送信されたリクエストをディスパッチ前に HTTP 400 で拒否し、タグ付けされたエンドポイントに対応する OmniRoute アダプターがないモデルを `/v1/models` から非表示にし、それらに対するダッシュボードのモデルテストには 422 で応答します(#16929)。デフォルトではオフです。エンドポイントタグは情報提供のみのままで、ディスパッチは変更されません。 | -### ランタイム(53) +### ランタイム(52) | キー | 型 | デフォルト | 再起動 | 説明 | | ---------------------------------------------- | ------- | ---------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -149,7 +149,6 @@ boolean フラグは、実効値が `"true"`、`"1"`、または `"yes"` の場 | `KIRO_TOOL_CALL_REPAIR_ENABLED` | boolean | `true` | | ストリーミング Kiro レスポンスが、可視出力の前に不正な形式の `tool_call` ラッパー(`input.name` または `input.arguments` が欠落)を送出した場合、それを抑制し、ターンを `invalid_kiro_tool_call` で終了する代わりに、修正指示を付けてリクエストを一度再送します。再試行では Kiro リクエストが 1 回余分に発生します。2 回目も失敗した場合、ターンは `kiro_tool_call_repair_retry_failed` で終了します。デフォルトではオンです。最初の不正なラッパーでターンを失敗させるには、`false` に設定します。 | | `CATALOG_MIRROR_REFINEMENTS_ENABLED` | boolean | `false` | | `/v1/models` 上の `claude/` ディスカバリーミラーと機能ゲートウェイミラーを改良します。デフォルトではオフです。オフの場合、両方のミラー処理は従来とまったく同じように動作します。オンの場合、`claude/` ディスカバリーミラーは、一覧にある推論エフォートのバリアント(`-low`/`-medium`/`-high`/`-xhigh`)も対象にします。また、リクエストパスで実体化できない組み込みの `auto/*` ID と、すでに一覧にあるエイリアスをスキップします。機能ゲートウェイミラーは、コンボおよび OmniRoute が合成した ID(`claude/`、`no-think/`)をスキップし、所有者を `owned_by` から取得し、所有者自体をゲートウェイとして使用せず、`blockedProviders` で無効化されたゲートウェイと、ゲートウェイの信頼できるライブカタログに含まれないモデルをスキップします。 | | ---------------------------------------------- | ------- | ------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `ANTHROPIC_COMPATIBLE_MID_CONVERSATION_SYSTEM` | boolean | `false` | | 通常の `anthropic-compatible-*` リレー(claude → claude パススルー)で、会話途中の `role: "system"` ターンをトップレベルの `system` パラメーターへ移動せず、`messages[]` 内に保持します。これにより、キャッシュされたプロンプトのプレフィックスがターン間で安定します。クライアント自体が `mid-conversation-system-2026-04-07` `anthropic-beta` を送信した場合にのみ適用されます(そのままアップストリームへ転送されます)。デフォルトではオフです。このベータに対応していないリレーは `role: "system"` メッセージを 400 で拒否するため、オフの場合は従来どおりすべての system ターンが移動されます。 | | `MODELS_DEV_EFFORT_TIERS_ENABLED` | boolean | `false` | | オペレーターによるオーバーライドにもプロバイダーレジストリにも定義がない場合、models.dev が公開する推論エフォート階層(既知のエフォート語彙に限定)を使用します。また、空の階層リストが明示されている場合、`/v1/models` の `effort_tiers` およびコンボの階層積集合では「階層なし」として扱います。デフォルトではオフです。オフの場合、`effort_tiers` と解決済みの `supportedThinkingEfforts` は従来とまったく同じ状態に維持されます。 | | `AUTO_REQUEST_RESOLVED_CAPABILITIES` | boolean | `false` | | auto/* リクエストでは、解決済みのモデル機能(ビジョン、推論、思考)と観測された品質スコアを使用し、1 回の一括読み取りで候補プールを構築します(#16419)。デフォルトではオフです。オフの場合、リクエスト時のスナップショット重みは従来とまったく同じ状態に維持されます。オンの場合、これらのシグナルがスナップショットの重みに反映されるため、候補の重みと選択が変わる可能性があります。 | | `AUTO_COMBO_KEYLESS_TRANSPORT_GATE` | boolean | `false` | | トランスポートにローカルブラウザー (`browserBacked`) またはローカルにインストールされた CLI (`isLocalCli`) が必要なキーレス(認証不要)プロバイダーを `auto/*` 候補プールから除外し、応答しないメンバーによってターゲットごとのタイムアウトまで自動リクエストが停止するのを防ぎます (#17036)。デフォルトではオフで、キーレスプールは変更されません。直接の `/` 呼び出しは、どちらの設定でも機能します。 | @@ -246,7 +245,7 @@ boolean フラグは、実効値が `"true"`、`"1"`、または `"yes"` の場 "requiresRestart": false, "warningLevel": "caution", }, - // ... 全111個のフラグ + // ... 全110個のフラグ ], "summary": { "total": 56, diff --git a/docs/i18n/ka/docs/reference/FEATURE_FLAGS.md b/docs/i18n/ka/docs/reference/FEATURE_FLAGS.md index ca5f6715fc40..de736bdab72b 100644 --- a/docs/i18n/ka/docs/reference/FEATURE_FLAGS.md +++ b/docs/i18n/ka/docs/reference/FEATURE_FLAGS.md @@ -44,7 +44,7 @@ boolean დროშა **ჩართულად** ითვლება, რ ## ფლაგების კატალოგი -111 ფლაგი 6 კატეგორიაში. **ნაგულისხმევი მნიშვნელობა** არის განსაზღვრებაში მითითებული ნაგულისხმევი მნიშვნელობა — მნიშვნელობა, +110 ფლაგი 6 კატეგორიაში. **ნაგულისხმევი მნიშვნელობა** არის განსაზღვრებაში მითითებული ნაგულისხმევი მნიშვნელობა — მნიშვნელობა, რომელიც გამოიყენება, როდესაც არც DB-ის ჩანაცვლება და არც გარემოს ცვლადი არ არის მითითებული. ### უსაფრთხოება (10) @@ -109,7 +109,7 @@ boolean დროშა **ჩართულად** ითვლება, რ | `RADAR_ENABLED` | boolean | `false` | OmniRoute Radar მოდულის (კატალოგის არხის ეკრანებისა და სინქრონიზაციის) ჩართვა. ნაგულისხმევად გამორთულია; ჩართვა მხოლოდ მომხმარებლის ინტერფეისს განბლოკავს — მონაცემთა სინქრონიზაციისთვის კვლავ ცალკე თანხმობაა საჭირო. | | `MODEL_ENDPOINT_CAPABILITY_GATE_ENABLED` | boolean | `false` | სანდო მოდელის საბოლოო წერტილის ტეგების (პროვაიდერის მიერ გამოცხადებული, სპეციალიზებული მოდელის id-დან მიღებული ან ოპერატორის მიერ დაყენებული) აღსრულება: `/v1/chat/completions`, `/v1/embeddings`, `/v1/rerank`, `/v1/images/generations`, `/v1/videos/generations`, `/v1/music/generations` და `/v1/audio/*` მისამართებზე გაგზავნამდე HTTP 400-ით უარყოს მოთხოვნა, რომელიც ისეთ საბოლოო წერტილზეა გაგზავნილი, რომელსაც მოდელი არ ემსახურება; `/v1/models`-დან დამალოს მოდელები, რომელთა ტეგირებულ საბოლოო წერტილებს OmniRoute-ის ადაპტერი არ გააჩნია; მათთვის მართვის პანელის მოდელის ტესტს 422-ით უპასუხოს (#16929). ნაგულისხმევად გამორთულია: საბოლოო წერტილის ტეგები მხოლოდ საინფორმაციოდ რჩება და გაგზავნის პროცესი უცვლელია. | -### შესრულების გარემო (53) +### შესრულების გარემო (52) | გასაღები | ტიპი | ნაგულისხმევი მნიშვნელობა | გადატვირთვა | აღწერა | | ---------------------------------------------- | ------- | ------------------------ | ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -156,7 +156,6 @@ boolean დროშა **ჩართულად** ითვლება, რ | `KIRO_TOOL_CALL_REPAIR_ENABLED` | boolean | `true` | | როდესაც Kiro-ს ნაკადური პასუხი ხილული გამომავალი მონაცემების გამოჩენამდე გასცემს არასწორად ფორმირებულ `tool_call` გარსს (აკლია `input.name` ან `input.arguments`), ჩაახშეთ ის და მოთხოვნა ერთხელ ხელახლა გაგზავნეთ მაკორექტირებელი ინსტრუქციით, ნაცვლად იმისა, რომ სვლა `invalid_kiro_tool_call`-ით დასრულდეს. განმეორებითი მცდელობა მოითხოვს Kiro-ს ერთ დამატებით მოთხოვნას; მეორე წარუმატებლობა სვლას `kiro_tool_call_repair_retry_failed`-ით ასრულებს. ნაგულისხმევად ჩართულია. დააყენეთ `false`, რათა სვლა პირველივე არასწორად ფორმირებული გარსისას წარუმატებლად დასრულდეს. | | `CATALOG_MIRROR_REFINEMENTS_ENABLED` | boolean | `false` | | დახვეწეთ `claude/` აღმოჩენისა და ფუნქციური კარიბჭის სარკეები `/v1/models`-ში. ნაგულისხმევად გამორთულია: ორივე სარკისებური დამუშავება ზუსტად ისე მუშაობს, როგორც ადრე. ჩართვისას: `claude/` აღმოჩენის სარკეები ასევე მოიცავს ჩამოთვლილ მსჯელობის ძალისხმევის ვარიანტებს (`-low`/`-medium`/`-high`/`-xhigh`), გამოტოვებს ჩაშენებულ `auto/*` ID-ებს, რომელთა მატერიალიზებაც მოთხოვნის გზას არ შეუძლია, და უკვე ჩამოთვლილ ფსევდონიმებს; ფუნქციური კარიბჭის სარკეები გამოტოვებს კომბინაციებსა და OmniRoute-ის მიერ სინთეზირებულ ID-ებს (`claude/`, `no-think/`), მფლობელს იღებს `owned_by`-დან, არასოდეს იყენებს მფლობელს საკუთარ კარიბჭედ, გამოტოვებს `blockedProviders`-ში გამორთულ კარიბჭეებსა და კარიბჭის ავტორიტეტულ აქტიურ კატალოგში არშემავალ მოდელებს. | | ---------------------------------------------- | ------- | ------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `ANTHROPIC_COMPATIBLE_MID_CONVERSATION_SYSTEM` | boolean | `false` | | ჩვეულებრივ `anthropic-compatible-*` რელეებზე (claude → claude გამჭოლი გადაცემა), საუბრის შუაში არსებული `role: "system"` სვლები დატოვეთ `messages[]`-ში, მათი ზედა დონის `system` პარამეტრში ატანის ნაცვლად, რათა დაქეშილი მოთხოვნის პრეფიქსი სვლებს შორის სტაბილური დარჩეს. გამოიყენება მხოლოდ მაშინ, როდესაც კლიენტმა თავად გაგზავნა `mid-conversation-system-2026-04-07` `anthropic-beta` (ზედა დინებაში უცვლელად გადაიგზავნება). ნაგულისხმევად გამორთულია: ყველა სისტემური სვლა ზუსტად ისე აიტანება, როგორც ადრე, რადგან ბეტის არმქონე რელეები `role: "system"` შეტყობინებებს 400 პასუხით უარყოფს. | | `MODELS_DEV_EFFORT_TIERS_ENABLED` | boolean | `false` | | გამოიყენეთ models.dev-ის მიერ გამოქვეყნებული მსჯელობის ძალისხმევის დონეები (შეზღუდული ძალისხმევის ცნობილ ლექსიკონამდე), როდესაც არც ოპერატორის გადაფარვა და არც პროვაიდერის რეესტრი არ განსაზღვრავს რომელიმე მათგანს, ხოლო დონეების ცხადად გამოცხადებული ცარიელი სია `/v1/models`-ის `effort_tiers`-სა და კომბინაციის დონეთა თანაკვეთებში განიხილეთ როგორც „დონეების გარეშე“. ნაგულისხმევად გამორთულია: `effort_tiers` და ამოხსნილი `supportedThinkingEfforts` ზუსტად ისეთივე რჩება, როგორიც ადრე იყო. | | `AUTO_REQUEST_RESOLVED_CAPABILITIES` | boolean | `false` | | auto/* მოთხოვნები კანდიდატთა პულს აგებს ამოხსნილი მოდელის შესაძლებლობებით (ხედვა, მსჯელობა, აზროვნება) და დაკვირვებული ხარისხის ქულებით, ერთი ჯგუფური წაკითხვის ფარგლებში (#16419). ნაგულისხმევად გამორთულია: მოთხოვნის დროინდელი სნეპშოტის წონები ზუსტად ისეთივე რჩება, როგორიც ადრე იყო. ჩართვისას: ეს სიგნალები სნეპშოტის წონებზე მოქმედებს, ამიტომ კანდიდატთა წონები და არჩევანი შეიძლება შეიცვალოს. | | `AUTO_COMBO_KEYLESS_TRANSPORT_GATE` | boolean | `false` | | გამორიცხეთ გასაღების არმქონე (ავტორიზაციის გარეშე მომუშავე) პროვაიდერები, რომელთა ტრანსპორტსაც ადგილობრივი ბრაუზერი (`browserBacked`) ან ადგილობრივად დაყენებული CLI (`isLocalCli`) სჭირდება, `auto/*` კანდიდატების ნაკრებიდან, რათა გაჭედილმა წევრმა ავტომატური მოთხოვნა თითოეული სამიზნისთვის განსაზღვრული დროის ამოწურვამდე არ შეაყოვნოს (#17036). ნაგულისხმევად გამორთულია: გასაღების არმქონე პროვაიდერების ნაკრები უცვლელია. პირდაპირი `/` გამოძახებები ორივე შემთხვევაში მუშაობს. | @@ -253,7 +252,7 @@ boolean დროშა **ჩართულად** ითვლება, რ "requiresRestart": false, "warningLevel": "caution", }, - // ... სულ 111 დროშა + // ... სულ 110 დროშა ], "summary": { "total": 56, diff --git a/docs/i18n/km/docs/reference/FEATURE_FLAGS.md b/docs/i18n/km/docs/reference/FEATURE_FLAGS.md index b5f1c342e35c..3810892cb42c 100644 --- a/docs/i18n/km/docs/reference/FEATURE_FLAGS.md +++ b/docs/i18n/km/docs/reference/FEATURE_FLAGS.md @@ -43,7 +43,7 @@ Boolean flag ត្រូវបានចាត់ទុកថា **បានប ## បញ្ជី Flag -មាន flag ចំនួន 111 ក្នុង 6 ប្រភេទ។ **លំនាំដើម** គឺជាតម្លៃលំនាំដើមក្នុងការកំណត់និយមន័យ — តម្លៃដែលត្រូវបានប្រើ នៅពេលគ្មានទាំងការកំណត់ជាន់លើពី DB និងអថេរបរិស្ថាន។ +មាន flag ចំនួន 110 ក្នុង 6 ប្រភេទ។ **លំនាំដើម** គឺជាតម្លៃលំនាំដើមក្នុងការកំណត់និយមន័យ — តម្លៃដែលត្រូវបានប្រើ នៅពេលគ្មានទាំងការកំណត់ជាន់លើពី DB និងអថេរបរិស្ថាន។ ### សុវត្ថិភាព (10) @@ -107,7 +107,7 @@ Boolean flag ត្រូវបានចាត់ទុកថា **បានប | `RADAR_ENABLED` | boolean | `false` | បើកម៉ូឌុល OmniRoute Radar (អេក្រង់ប្រភពទិន្នន័យកាតាឡុក និងការធ្វើសមកាលកម្ម)។ បិទតាមលំនាំដើម៖ ការបើកវាគ្រាន់តែដោះសោចំណុចប្រទាក់អ្នកប្រើប៉ុណ្ណោះ — ការធ្វើសមកាលកម្មទិន្នន័យនៅតែត្រូវជ្រើសរើសបើកដោយឡែក។ | | `MODEL_ENDPOINT_CAPABILITY_GATE_ENABLED` | boolean | `false` | អនុវត្តស្លាកចំណុចចុងរបស់ម៉ូដែលដែលអាចទុកចិត្តបាន (ប្រកាសដោយអ្នកផ្តល់សេវា សន្និដ្ឋានពីលេខសម្គាល់ម៉ូដែលឯកទេស ឬកំណត់ដោយប្រតិបត្តិករ)៖ បដិសេធសំណើដែលផ្ញើទៅចំណុចចុងដែលមិនផ្តល់សេវាដោយម៉ូដែល ជាមួយ HTTP 400 មុនពេលបញ្ជូននៅលើ `/v1/chat/completions`, `/v1/embeddings`, `/v1/rerank`, `/v1/images/generations`, `/v1/videos/generations`, `/v1/music/generations` និង `/v1/audio/*`; លាក់ម៉ូដែលដែលចំណុចចុងមានស្លាករបស់វាមិនមានអាដាប់ទ័រ OmniRoute ពី `/v1/models`; ឆ្លើយតបការសាកល្បងម៉ូដែលនៅលើផ្ទាំងគ្រប់គ្រងដោយ 422 សម្រាប់ម៉ូដែលទាំងនោះ (#16929)។ បិទតាមលំនាំដើម៖ ស្លាកចំណុចចុងនៅតែមានគោលបំណងផ្តល់ព័ត៌មាន ហើយការបញ្ជូនមិនផ្លាស់ប្តូរ។ | -### ពេលដំណើរការ (53) +### ពេលដំណើរការ (52) | សោ | ប្រភេទ | លំនាំដើម | ចាប់ផ្ដើមឡើងវិញ | សេចក្ដីពិពណ៌នា | | ---------------------------------------------- | ------- | -------- | --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -154,7 +154,6 @@ Boolean flag ត្រូវបានចាត់ទុកថា **បានប | `KIRO_TOOL_CALL_REPAIR_ENABLED` | boolean | `true` | | នៅពេលការឆ្លើយតបជាស្ទ្រីមរបស់ Kiro បញ្ចេញ wrapper `tool_call` ដែលមានទម្រង់មិនត្រឹមត្រូវ (ខ្វះ `input.name` ឬ `input.arguments`) មុនពេលមានលទ្ធផលណាមួយដែលអាចមើលឃើញ ត្រូវទប់ស្កាត់វា ហើយផ្ញើសំណើឡើងវិញម្តងជាមួយសេចក្តីណែនាំកែតម្រូវ ជំនួសឱ្យការបញ្ចប់វេនដោយ `invalid_kiro_tool_call`។ ការព្យាយាមឡើងវិញត្រូវចំណាយសំណើ Kiro បន្ថែមមួយ។ ការបរាជ័យលើកទីពីរនឹងបញ្ចប់វេនដោយ `kiro_tool_call_repair_retry_failed`។ បើកតាមលំនាំដើម។ កំណត់ទៅជា `false` ដើម្បីឱ្យវេនបរាជ័យភ្លាមៗនៅពេលជួប wrapper មិនត្រឹមត្រូវលើកដំបូង។ | | `CATALOG_MIRROR_REFINEMENTS_ENABLED` | boolean | `false` | | កែលម្អ mirror សម្រាប់ការរកឃើញ `claude/` និង functional-gateway នៅលើ `/v1/models`។ បិទតាមលំនាំដើម៖ ដំណើរការ mirror ទាំងពីរមានឥរិយាបថដូចមុនទាំងស្រុង។ នៅពេលបើក៖ mirror សម្រាប់ការរកឃើញ `claude/` ក៏គ្របដណ្តប់លើវ៉ារ្យ៉ង់ reasoning-effort ដែលបានរាយបញ្ជី (`-low`/`-medium`/`-high`/`-xhigh`) រំលង ID `auto/*` ដែលមានស្រាប់ក្នុងប្រព័ន្ធ ហើយផ្លូវសំណើមិនអាចបង្កើតបាន និងរំលង alias ដែលបានរាយបញ្ជីរួចហើយ។ mirror របស់ functional-gateway រំលង combo និង ID ដែល OmniRoute បានសំយោគ (`claude/`, `no-think/`) យកម្ចាស់ពី `owned_by` មិនប្រើម្ចាស់ជ gateway របស់ខ្លួនឯង រំលង gateway ដែលត្រូវបានបិទក្នុង `blockedProviders` និងរំលងម៉ូដែលដែលនៅក្រៅកាតាឡុកបច្ចុប្បន្នជាផ្លូវការរបស់ gateway។ | | ---------------------------------------------- | ------- | ------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `ANTHROPIC_COMPATIBLE_MID_CONVERSATION_SYSTEM` | boolean | `false` | | នៅលើ relay `anthropic-compatible-*` ធម្មតា (ការបញ្ជូនបន្តពី claude → claude) រក្សាវេន `role: "system"` ដែលនៅកណ្តាលការសន្ទនាឱ្យនៅក្នុង `messages[]` ជំនួសឱ្យការលើកវាទៅក្នុងប៉ារ៉ាម៉ែត្រ `system` កម្រិតកំពូល ដើម្បីឱ្យបុព្វបទ prompt ដែលបាន cache នៅមានស្ថិរភាពឆ្លងកាត់វេននានា។ អនុវត្តតែនៅពេល client ផ្ទាល់បានផ្ញើ `mid-conversation-system-2026-04-07` `anthropic-beta` (ត្រូវបានបញ្ជូនបន្តទៅ upstream ដូចដើម)។ បិទតាមលំនាំដើម៖ រាល់វេន system ត្រូវបានលើកទៅកម្រិតកំពូលដូចមុនទាំងស្រុង ព្រោះ relay ដែលគ្មាន beta នេះបដិសេធសារ `role: "system"` ដោយឆ្លើយតប 400។ | | `MODELS_DEV_EFFORT_TIERS_ENABLED` | boolean | `false` | | ប្រើកម្រិត reasoning effort ដែល models.dev បោះពុម្ពផ្សាយ (កំណត់ត្រឹមសំណុំពាក្យ effort ដែលស្គាល់) នៅពេលគ្មានទាំងការកំណត់ជាន់លើដោយប្រតិបត្តិករ និងការប្រកាសពី registry របស់ provider ហើយចាត់ទុកបញ្ជីកម្រិតទទេដែលបានប្រកាសថាជា "គ្មានកម្រិត" ក្នុង `/v1/models` `effort_tiers` និងប្រសព្វកម្រិតរបស់ combo។ បិទតាមលំនាំដើម៖ `effort_tiers` និង `supportedThinkingEfforts` ដែលបានដោះស្រាយនៅដូចមុនទាំងស្រុង។ | | `AUTO_REQUEST_RESOLVED_CAPABILITIES` | boolean | `false` | | សំណើ auto/* បង្កើតបណ្តុំបេក្ខភាពដោយប្រើសមត្ថភាពម៉ូដែលដែលបានដោះស្រាយ (ចក្ខុវិស័យ ការវែកញែក និងការគិត) ព្រមទាំងពិន្ទុគុណភាពដែលបានសង្កេត ក្នុងការអានជាបាច់តែមួយ (#16419)។ បិទតាមលំនាំដើម៖ ទម្ងន់ snapshot នៅពេលស្នើសុំនៅដូចមុនទាំងស្រុង។ នៅពេលបើក៖ សញ្ញាទាំងនោះត្រូវបានបញ្ចូលទៅក្នុងទម្ងន់ snapshot ដូច្នេះទម្ងន់បេក្ខភាព និងការជ្រើសរើសអាចផ្លាស់ប្តូរ។ | | `AUTO_COMBO_KEYLESS_TRANSPORT_GATE` | boolean | `false` | | មិនបញ្ចូលអ្នកផ្តល់សេវាដែលមិនប្រើសោ (គ្មានការផ្ទៀងផ្ទាត់) ដែលការដឹកជញ្ជូនរបស់ពួកវាត្រូវការកម្មវិធីរុករកតាមអ៊ីនធឺណិតក្នុងម៉ាស៊ីនមូលដ្ឋាន (`browserBacked`) ឬ CLI ដែលបានដំឡើងក្នុងម៉ាស៊ីនមូលដ្ឋាន (`isLocalCli`) ទៅក្នុងក្រុមបេក្ខជន `auto/*` ដើម្បីកុំឱ្យសមាជិកដែលជាប់គាំងអាចធ្វើឱ្យសំណើដោយស្វ័យប្រវត្តិជាប់គាំងរហូតដល់អស់ពេលកំណត់សម្រាប់គោលដៅនីមួយៗ (#17036)។ តាមលំនាំដើម មុខងារនេះត្រូវបានបិទ៖ ក្រុមដែលមិនប្រើសោនៅតែមិនផ្លាស់ប្តូរ។ ការហៅដោយផ្ទាល់ទៅកាន់ `/` ដំណើរការក្នុងករណីទាំងពីរ។ | @@ -251,7 +250,7 @@ method នីមួយៗតម្រូវឱ្យមានសម័យផ្ "requiresRestart": false, "warningLevel": "caution", }, - // ... flags ទាំង 111 + // ... flags ទាំង 110 ], "summary": { "total": 56, diff --git a/docs/i18n/kn/docs/reference/FEATURE_FLAGS.md b/docs/i18n/kn/docs/reference/FEATURE_FLAGS.md index 6d296dcbd053..992477012fe4 100644 --- a/docs/i18n/kn/docs/reference/FEATURE_FLAGS.md +++ b/docs/i18n/kn/docs/reference/FEATURE_FLAGS.md @@ -44,7 +44,7 @@ boolean ಫ್ಲ್ಯಾಗ್ನ ಪರಿಣಾಮಕಾರಿ ಮೌಲ್ ## ಫ್ಲ್ಯಾಗ್ ಕ್ಯಾಟಲಾಗ್ -6 ವರ್ಗಗಳಲ್ಲಿ 111 ಫ್ಲ್ಯಾಗ್ಗಳು. **ಡೀಫಾಲ್ಟ್** ಎಂದರೆ ವ್ಯಾಖ್ಯಾನದ ಡೀಫಾಲ್ಟ್ — DB ಓವರ್ರೈಡ್ ಅಥವಾ ಪರಿಸರ ವೇರಿಯಬಲ್ ಎರಡೂ ಇಲ್ಲದಿರುವಾಗ ಬಳಸಲಾಗುವ ಮೌಲ್ಯ. +6 ವರ್ಗಗಳಲ್ಲಿ 110 ಫ್ಲ್ಯಾಗ್ಗಳು. **ಡೀಫಾಲ್ಟ್** ಎಂದರೆ ವ್ಯಾಖ್ಯಾನದ ಡೀಫಾಲ್ಟ್ — DB ಓವರ್ರೈಡ್ ಅಥವಾ ಪರಿಸರ ವೇರಿಯಬಲ್ ಎರಡೂ ಇಲ್ಲದಿರುವಾಗ ಬಳಸಲಾಗುವ ಮೌಲ್ಯ. ### ಭದ್ರತೆ (10) @@ -108,7 +108,7 @@ boolean ಫ್ಲ್ಯಾಗ್ನ ಪರಿಣಾಮಕಾರಿ ಮೌಲ್ | `RADAR_ENABLED` | boolean | `false` | OmniRoute Radar ಮಾಡ್ಯೂಲ್ ಅನ್ನು (ಕ್ಯಾಟಲಾಗ್ ಫೀಡ್ ಪರದೆಗಳು ಮತ್ತು ಸಿಂಕ್) ಸಕ್ರಿಯಗೊಳಿಸಿ. ಡೀಫಾಲ್ಟ್ ಆಗಿ ಆಫ್; ಸಕ್ರಿಯಗೊಳಿಸುವುದರಿಂದ UI ಮಾತ್ರ ಅನ್ಲಾಕ್ ಆಗುತ್ತದೆ — ಡೇಟಾ ಸಿಂಕ್ ಪ್ರತ್ಯೇಕ ಆಯ್ಕೆಯಾಗಿಯೇ ಉಳಿಯುತ್ತದೆ. | | `MODEL_ENDPOINT_CAPABILITY_GATE_ENABLED` | boolean | `false` | ವಿಶ್ವಾಸಾರ್ಹ ಮಾದರಿ ಎಂಡ್ಪಾಯಿಂಟ್ ಟ್ಯಾಗ್ಗಳನ್ನು (ಪೂರೈಕೆದಾರರು ಘೋಷಿಸಿದ, ವಿಶೇಷ ಮಾದರಿ id ಯಿಂದ ಊಹಿಸಿದ ಅಥವಾ ಆಪರೇಟರ್ ಹೊಂದಿಸಿದ) ಜಾರಿಗೊಳಿಸಿ: `/v1/chat/completions`, `/v1/embeddings`, `/v1/rerank`, `/v1/images/generations`, `/v1/videos/generations`, `/v1/music/generations` ಮತ್ತು `/v1/audio/*` ನಲ್ಲಿ ಮಾದರಿಯು ಸೇವೆ ನೀಡದ ಎಂಡ್ಪಾಯಿಂಟ್ಗೆ ಕಳುಹಿಸಲಾದ ವಿನಂತಿಯನ್ನು ರವಾನಿಸುವ ಮೊದಲು HTTP 400 ನೊಂದಿಗೆ ತಿರಸ್ಕರಿಸಿ; ಟ್ಯಾಗ್ ಮಾಡಲಾದ ಎಂಡ್ಪಾಯಿಂಟ್ಗಳಿಗೆ OmniRoute ಅಡಾಪ್ಟರ್ ಇಲ್ಲದ ಮಾದರಿಗಳನ್ನು `/v1/models` ನಿಂದ ಮರೆಮಾಡಿ; ಅವುಗಳಿಗಾಗಿ ಡ್ಯಾಶ್ಬೋರ್ಡ್ ಮಾದರಿ ಪರೀಕ್ಷೆಗೆ 422 ನೊಂದಿಗೆ ಪ್ರತಿಕ್ರಿಯಿಸಿ (#16929). ಡೀಫಾಲ್ಟ್ ಆಗಿ ಆಫ್: ಎಂಡ್ಪಾಯಿಂಟ್ ಟ್ಯಾಗ್ಗಳು ಮಾಹಿತಿಗಾಗಿ ಮಾತ್ರ ಉಳಿಯುತ್ತವೆ ಮತ್ತು ರವಾನಿಸುವಿಕೆ ಬದಲಾಗುವುದಿಲ್ಲ. | -### ರನ್ಟೈಮ್ (53) +### ರನ್ಟೈಮ್ (52) | ಕೀ | ಪ್ರಕಾರ | ಡೀಫಾಲ್ಟ್ | ಮರುಪ್ರಾರಂಭ | ವಿವರಣೆ | | ---------------------------------------------- | ------- | -------- | ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -155,7 +155,6 @@ boolean ಫ್ಲ್ಯಾಗ್ನ ಪರಿಣಾಮಕಾರಿ ಮೌಲ್ | `KIRO_TOOL_CALL_REPAIR_ENABLED` | boolean | `true` | | ಯಾವುದೇ ಗೋಚರ ಔಟ್ಪುಟ್ಗಿಂತ ಮೊದಲು ಸ್ಟ್ರೀಮಿಂಗ್ Kiro ಪ್ರತಿಕ್ರಿಯೆಯು ದೋಷಪೂರಿತ `tool_call` ರ್ಯಾಪರ್ ಅನ್ನು (`input.name` ಅಥವಾ `input.arguments` ಇಲ್ಲದೆ) ಹೊರಸೂಸಿದಾಗ, ಅದನ್ನು ನಿಗ್ರಹಿಸಿ ಮತ್ತು ಟರ್ನ್ ಅನ್ನು `invalid_kiro_tool_call` ನೊಂದಿಗೆ ಕೊನೆಗೊಳಿಸುವ ಬದಲು ಸರಿಪಡಿಸುವ ಸೂಚನೆಯೊಂದಿಗೆ ವಿನಂತಿಯನ್ನು ಒಮ್ಮೆ ಮರುಕಳುಹಿಸಿ. ಮರುಪ್ರಯತ್ನಕ್ಕೆ ಒಂದು ಹೆಚ್ಚುವರಿ Kiro ವಿನಂತಿಯ ವೆಚ್ಚ ತಗಲುತ್ತದೆ; ಎರಡನೇ ವೈಫಲ್ಯವು ಟರ್ನ್ ಅನ್ನು `kiro_tool_call_repair_retry_failed` ನೊಂದಿಗೆ ಕೊನೆಗೊಳಿಸುತ್ತದೆ. ಡೀಫಾಲ್ಟ್ ಆಗಿ ಆನ್ ಆಗಿರುತ್ತದೆ. ಮೊದಲ ದೋಷಪೂರಿತ ರ್ಯಾಪರ್ನಲ್ಲಿಯೇ ಟರ್ನ್ ವಿಫಲಗೊಳಿಸಲು `false` ಗೆ ಹೊಂದಿಸಿ. | | `CATALOG_MIRROR_REFINEMENTS_ENABLED` | boolean | `false` | | `/v1/models` ನಲ್ಲಿನ `claude/` ಅನ್ವೇಷಣೆ ಮತ್ತು ಕಾರ್ಯಾತ್ಮಕ-ಗೇಟ್ವೇ ಮಿರರ್ಗಳನ್ನು ಪರಿಷ್ಕರಿಸಿ. ಡೀಫಾಲ್ಟ್ ಆಗಿ ಆಫ್ ಆಗಿರುತ್ತದೆ: ಎರಡೂ ಮಿರರ್ ಪಾಸ್ಗಳು ಮೊದಲಿನಂತೆಯೇ ನಿಖರವಾಗಿ ವರ್ತಿಸುತ್ತವೆ. ಆನ್ ಇದ್ದಾಗ: `claude/` ಅನ್ವೇಷಣಾ ಮಿರರ್ಗಳು ಪಟ್ಟಿ ಮಾಡಲಾದ ರೀಸನಿಂಗ್-ಎಫರ್ಟ್ ರೂಪಾಂತರಗಳನ್ನೂ (`-low`/`-medium`/`-high`/`-xhigh`) ಒಳಗೊಳ್ಳುತ್ತವೆ, ವಿನಂತಿ ಪಥವು ಸಾಕಾರಗೊಳಿಸಲಾಗದ ಅಂತರ್ನಿರ್ಮಿತ `auto/*` id ಗಳನ್ನು ಬಿಟ್ಟುಬಿಡುತ್ತವೆ ಮತ್ತು ಈಗಾಗಲೇ ಪಟ್ಟಿ ಮಾಡಲಾದ ಅಲಿಯಾಸ್ ಅನ್ನು ಬಿಟ್ಟುಬಿಡುತ್ತವೆ; ಕಾರ್ಯಾತ್ಮಕ-ಗೇಟ್ವೇ ಮಿರರ್ಗಳು ಕಾಂಬೊಗಳು ಮತ್ತು OmniRoute-ಸಂಶ್ಲೇಷಿತ id ಗಳನ್ನು (`claude/`, `no-think/`) ಬಿಟ್ಟುಬಿಡುತ್ತವೆ, ಮಾಲೀಕರನ್ನು `owned_by` ನಿಂದ ತೆಗೆದುಕೊಳ್ಳುತ್ತವೆ, ಮಾಲೀಕರನ್ನೇ ಅದರ ಸ್ವಂತ ಗೇಟ್ವೇ ಆಗಿ ಎಂದಿಗೂ ಬಳಸುವುದಿಲ್ಲ, `blockedProviders` ನಲ್ಲಿ ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಲಾದ ಗೇಟ್ವೇಗಳನ್ನು ಮತ್ತು ಗೇಟ್ವೇಯ ಅಧಿಕೃತ ಲೈವ್ ಕ್ಯಾಟಲಾಗ್ನ ಹೊರಗಿನ ಮಾಡೆಲ್ಗಳನ್ನು ಬಿಟ್ಟುಬಿಡುತ್ತವೆ. | | ---------------------------------------------- | ------- | ------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `ANTHROPIC_COMPATIBLE_MID_CONVERSATION_SYSTEM` | boolean | `false` | | ಸಾಮಾನ್ಯ `anthropic-compatible-*` ರಿಲೇಗಳಲ್ಲಿ (claude → claude ಪಾಸ್ಥ್ರೂ), ಸಂಭಾಷಣೆಯ ಮಧ್ಯದ `role: "system"` ಟರ್ನ್ಗಳನ್ನು ಮೇಲ್ಮಟ್ಟದ `system` ಪ್ಯಾರಾಮೀಟರ್ಗೆ ಮೇಲಕ್ಕೆತ್ತುವ ಬದಲು `messages[]` ಒಳಗೇ ಉಳಿಸಿ, ಇದರಿಂದ ಕ್ಯಾಶ್ ಮಾಡಿದ ಪ್ರಾಂಪ್ಟ್ ಪ್ರಿಫಿಕ್ಸ್ ಟರ್ನ್ಗಳಾದ್ಯಂತ ಸ್ಥಿರವಾಗಿರುತ್ತದೆ. ಕ್ಲೈಂಟ್ ಸ್ವತಃ `mid-conversation-system-2026-04-07` `anthropic-beta` ಅನ್ನು ಕಳುಹಿಸಿದ್ದಾಗ ಮಾತ್ರ ಇದು ಅನ್ವಯಿಸುತ್ತದೆ (ಯಾವುದೇ ಬದಲಾವಣೆಯಿಲ್ಲದೆ ಅಪ್ಸ್ಟ್ರೀಮ್ಗೆ ಫಾರ್ವರ್ಡ್ ಮಾಡಲಾಗುತ್ತದೆ). ಡೀಫಾಲ್ಟ್ ಆಗಿ ಆಫ್ ಆಗಿರುತ್ತದೆ: beta ಇಲ್ಲದ ರಿಲೇಗಳು `role: "system"` ಸಂದೇಶಗಳನ್ನು 400 ನೊಂದಿಗೆ ತಿರಸ್ಕರಿಸುವುದರಿಂದ ಪ್ರತಿಯೊಂದು ಸಿಸ್ಟಮ್ ಟರ್ನ್ ಅನ್ನು ಮೊದಲಿನಂತೆಯೇ ನಿಖರವಾಗಿ ಮೇಲಕ್ಕೆತ್ತಲಾಗುತ್ತದೆ. | | `MODELS_DEV_EFFORT_TIERS_ENABLED` | boolean | `false` | | ಆಪರೇಟರ್ ಓವರ್ರೈಡ್ ಅಥವಾ ಪ್ರೊವೈಡರ್ ರಿಜಿಸ್ಟ್ರಿ ಯಾವುದನ್ನೂ ಘೋಷಿಸದಿದ್ದಾಗ, models.dev ಪ್ರಕಟಿಸುವ ರೀಸನಿಂಗ್ ಎಫರ್ಟ್ ಟಿಯರ್ಗಳನ್ನು (ತಿಳಿದಿರುವ ಎಫರ್ಟ್ ಪದಸಂಪತ್ತಿಗೆ ಸೀಮಿತಗೊಳಿಸಿ) ಬಳಸಿ ಮತ್ತು ಘೋಷಿಸಲಾದ ಖಾಲಿ ಟಿಯರ್ ಪಟ್ಟಿಯನ್ನು `/v1/models` `effort_tiers` ಹಾಗೂ ಕಾಂಬೊ ಟಿಯರ್ ಇಂಟರ್ಸೆಕ್ಷನ್ಗಳಲ್ಲಿ "ಯಾವುದೇ ಟಿಯರ್ಗಳಿಲ್ಲ" ಎಂದು ಪರಿಗಣಿಸಿ. ಡೀಫಾಲ್ಟ್ ಆಗಿ ಆಫ್ ಆಗಿರುತ್ತದೆ: `effort_tiers` ಮತ್ತು ಪರಿಹರಿಸಲಾದ `supportedThinkingEfforts` ಮೊದಲಿನಂತೆಯೇ ನಿಖರವಾಗಿ ಉಳಿಯುತ್ತವೆ. | | `AUTO_REQUEST_RESOLVED_CAPABILITIES` | boolean | `false` | | auto/* ವಿನಂತಿಗಳು ಒಂದೇ ಬಲ್ಕ್ ರೀಡ್ನಲ್ಲಿ ಪರಿಹರಿಸಲಾದ ಮಾಡೆಲ್ ಸಾಮರ್ಥ್ಯಗಳು (ವಿಷನ್, ರೀಸನಿಂಗ್, ಥಿಂಕಿಂಗ್) ಮತ್ತು ಗಮನಿಸಲಾದ ಗುಣಮಟ್ಟದ ಸ್ಕೋರ್ಗಳೊಂದಿಗೆ ಅಭ್ಯರ್ಥಿ ಪೂಲ್ ಅನ್ನು ನಿರ್ಮಿಸುತ್ತವೆ (#16419). ಡೀಫಾಲ್ಟ್ ಆಗಿ ಆಫ್ ಆಗಿರುತ್ತದೆ: ವಿನಂತಿ-ಸಮಯದ ಸ್ನ್ಯಾಪ್ಶಾಟ್ ತೂಕಗಳು ಮೊದಲಿನಂತೆಯೇ ನಿಖರವಾಗಿ ಉಳಿಯುತ್ತವೆ. ಆನ್ ಇದ್ದಾಗ: ಆ ಸಂಕೇತಗಳು ಸ್ನ್ಯಾಪ್ಶಾಟ್ ತೂಕಗಳಿಗೆ ಇನ್ಪುಟ್ ಆಗುತ್ತವೆ, ಆದ್ದರಿಂದ ಅಭ್ಯರ್ಥಿ ತೂಕಗಳು ಮತ್ತು ಆಯ್ಕೆ ಬದಲಾಗಬಹುದು. | | `AUTO_COMBO_KEYLESS_TRANSPORT_GATE` | boolean | `false` | | ಸ್ಥಳೀಯ ಬ್ರೌಸರ್ (`browserBacked`) ಅಥವಾ ಸ್ಥಳೀಯವಾಗಿ ಸ್ಥಾಪಿಸಲಾದ CLI (`isLocalCli`) ಅಗತ್ಯವಿರುವ transport ಹೊಂದಿರುವ keyless (no-auth) ಪೂರೈಕೆದಾರರನ್ನು `auto/*` ಅಭ್ಯರ್ಥಿ poolನಿಂದ ಹೊರಗಿಡುತ್ತದೆ; ಇದರಿಂದ ಸ್ಥಗಿತಗೊಂಡ ಸದಸ್ಯನು ಪ್ರತಿ-target timeout ಮುಗಿಯುವವರೆಗೆ auto ವಿನಂತಿಯನ್ನು ತಡೆಹಿಡಿಯುವುದನ್ನು ತಪ್ಪಿಸಬಹುದು (#17036). ಪೂರ್ವನಿಯೋಜಿತವಾಗಿ ನಿಷ್ಕ್ರಿಯವಾಗಿದೆ: keyless pool ಬದಲಾಗುವುದಿಲ್ಲ. ನೇರ `/` ಕರೆಗಳು ಎರಡೂ ಸಂದರ್ಭಗಳಲ್ಲಿ ಕಾರ್ಯನಿರ್ವಹಿಸುತ್ತವೆ. | @@ -252,7 +251,7 @@ boolean ಫ್ಲ್ಯಾಗ್ನ ಪರಿಣಾಮಕಾರಿ ಮೌಲ್ "requiresRestart": false, "warningLevel": "caution", }, - // ... ಎಲ್ಲಾ 111 ಫ್ಲ್ಯಾಗ್ಗಳು + // ... ಎಲ್ಲಾ 110 ಫ್ಲ್ಯಾಗ್ಗಳು ], "summary": { "total": 56, diff --git a/docs/i18n/ko/docs/reference/FEATURE_FLAGS.md b/docs/i18n/ko/docs/reference/FEATURE_FLAGS.md index a8296c67f854..1ca19d44dd06 100644 --- a/docs/i18n/ko/docs/reference/FEATURE_FLAGS.md +++ b/docs/i18n/ko/docs/reference/FEATURE_FLAGS.md @@ -42,7 +42,7 @@ boolean 플래그는 유효 값이 `"true"`, `"1"` 또는 `"yes"`일 때 ## 플래그 카탈로그 -6개 카테고리에 걸쳐 총 111개의 플래그가 있습니다. **기본값**은 정의에 지정된 기본값으로, DB 재정의와 환경 변수가 +6개 카테고리에 걸쳐 총 110개의 플래그가 있습니다. **기본값**은 정의에 지정된 기본값으로, DB 재정의와 환경 변수가 모두 없을 때 사용되는 값입니다. ### 보안 (10) @@ -107,7 +107,7 @@ boolean 플래그는 유효 값이 `"true"`, `"1"` 또는 `"yes"`일 때 | `RADAR_ENABLED` | 불리언 | `false` | OmniRoute Radar 모듈(카탈로그 피드 화면 및 동기화)을 활성화합니다. 기본적으로 꺼져 있으며, 활성화해도 UI만 사용할 수 있게 됩니다. 데이터 동기화는 별도로 동의해야 합니다. | | `MODEL_ENDPOINT_CAPABILITY_GATE_ENABLED` | 불리언 | `false` | 신뢰할 수 있는 모델 엔드포인트 태그(제공자가 선언하거나 특수 모델 ID에서 추론되거나 운영자가 설정한 태그)를 적용합니다. `/v1/chat/completions`, `/v1/embeddings`, `/v1/rerank`, `/v1/images/generations`, `/v1/videos/generations`, `/v1/music/generations` 및 `/v1/audio/*`에서 모델이 제공하지 않는 엔드포인트로 전송된 요청을 디스패치 전에 HTTP 400으로 거부하고, 태그가 지정된 엔드포인트에 대한 OmniRoute 어댑터가 없는 모델을 `/v1/models`에서 숨기며, 이러한 모델에 대한 대시보드 모델 테스트에는 422로 응답합니다(#16929). 기본적으로 꺼져 있으며, 엔드포인트 태그는 정보 제공 용도로만 유지되고 디스패치는 변경되지 않습니다. | -### 런타임(53) +### 런타임(52) | 키 | 유형 | 기본값 | 재시작 | 설명 | | ---------------------------------------------- | ------- | ------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -154,7 +154,6 @@ boolean 플래그는 유효 값이 `"true"`, `"1"` 또는 `"yes"`일 때 | `KIRO_TOOL_CALL_REPAIR_ENABLED` | boolean | `true` | | 스트리밍 Kiro 응답이 표시 가능한 출력을 생성하기 전에 잘못된 `tool_call` 래퍼(`input.name` 또는 `input.arguments` 누락)를 내보내면, 해당 래퍼를 억제하고 `invalid_kiro_tool_call`로 턴을 종료하는 대신 수정 지침을 추가하여 요청을 한 번 다시 전송합니다. 재시도에는 Kiro 요청이 하나 더 필요하며, 두 번째 시도도 실패하면 `kiro_tool_call_repair_retry_failed`로 턴을 종료합니다. 기본적으로 켜져 있습니다. 첫 번째 잘못된 래퍼에서 턴을 실패 처리하려면 `false`로 설정하세요. | | `CATALOG_MIRROR_REFINEMENTS_ENABLED` | boolean | `false` | | `/v1/models`의 `claude/` 탐색 및 기능 게이트웨이 미러를 개선합니다. 기본적으로 꺼져 있으며, 두 미러 처리 단계 모두 이전과 정확히 동일하게 동작합니다. 켜면 `claude/` 탐색 미러가 나열된 추론 노력 변형(`-low`/`-medium`/`-high`/`-xhigh`)까지 포함하고, 요청 경로에서 구체화할 수 없는 기본 제공 `auto/*` ID와 이미 나열된 별칭은 건너뜁니다. 기능 게이트웨이 미러는 콤보와 OmniRoute에서 합성한 ID(`claude/`, `no-think/`)를 건너뛰고, `owned_by`에서 소유자를 가져오며, 소유자 자체를 게이트웨이로 사용하지 않고, `blockedProviders`에서 비활성화된 게이트웨이와 게이트웨이의 권위 있는 라이브 카탈로그에 포함되지 않은 모델을 건너뜁니다. | | ---------------------------------------------- | ------- | ------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `ANTHROPIC_COMPATIBLE_MID_CONVERSATION_SYSTEM` | boolean | `false` | | 일반 `anthropic-compatible-*` 릴레이(claude → claude 패스스루)에서 대화 중간의 `role: "system"` 턴을 최상위 `system` 매개변수로 끌어올리는 대신 `messages[]` 안에 유지하여, 캐시된 프롬프트 접두사가 여러 턴에 걸쳐 안정적으로 유지되도록 합니다. 클라이언트 자체가 `mid-conversation-system-2026-04-07` `anthropic-beta`를 전송한 경우에만 적용되며, 이 값은 그대로 업스트림에 전달됩니다. 기본적으로 꺼져 있으며, 해당 베타를 지원하지 않는 릴레이는 `role: "system"` 메시지를 400으로 거부하므로 모든 시스템 턴을 이전과 정확히 동일하게 끌어올립니다. | | `MODELS_DEV_EFFORT_TIERS_ENABLED` | boolean | `false` | | 운영자 재정의와 제공자 레지스트리 모두 추론 노력 티어를 선언하지 않은 경우 models.dev가 게시한 추론 노력 티어를 사용하되, 알려진 노력 수준 어휘로 범위를 제한합니다. 또한 명시적으로 빈 티어 목록은 `/v1/models`의 `effort_tiers` 및 콤보 티어 교집합에서 "티어 없음"으로 처리합니다. 기본적으로 꺼져 있으며, `effort_tiers`와 해석된 `supportedThinkingEfforts`는 이전과 정확히 동일하게 유지됩니다. | | `AUTO_REQUEST_RESOLVED_CAPABILITIES` | boolean | `false` | | auto/* 요청이 한 번의 일괄 읽기로 해석된 모델 기능(비전, 추론, 사고)과 관측된 품질 점수를 사용하여 후보 풀을 구성합니다(#16419). 기본적으로 꺼져 있으며, 요청 시점의 스냅샷 가중치는 이전과 정확히 동일하게 유지됩니다. 켜면 이러한 신호가 스냅샷 가중치에 반영되므로 후보 가중치와 선택 결과가 달라질 수 있습니다. | | `AUTO_COMBO_KEYLESS_TRANSPORT_GATE` | boolean | `false` | | 전송에 로컬 브라우저가 필요하거나(`browserBacked`) 로컬에 설치된 CLI가 필요한(`isLocalCli`) 키리스(무인증) 제공자를 `auto/*` 후보 풀에서 제외하여, 응답하지 않는 구성원 때문에 대상별 타임아웃까지 자동 요청이 지연되지 않도록 합니다(#17036). 기본값은 꺼짐이며, 키리스 풀은 변경되지 않습니다. 어느 설정에서든 `/` 직접 호출은 작동합니다. | @@ -251,7 +250,7 @@ boolean 플래그는 유효 값이 `"true"`, `"1"` 또는 `"yes"`일 때 "requiresRestart": false, "warningLevel": "caution", }, - // ... 총 111개 플래그 + // ... 총 110개 플래그 ], "summary": { "total": 56, diff --git a/docs/i18n/lt/docs/reference/FEATURE_FLAGS.md b/docs/i18n/lt/docs/reference/FEATURE_FLAGS.md index f9c4211d818c..e2f2faa5a60d 100644 --- a/docs/i18n/lt/docs/reference/FEATURE_FLAGS.md +++ b/docs/i18n/lt/docs/reference/FEATURE_FLAGS.md @@ -39,7 +39,7 @@ Loginė vėliavėlė laikoma **įjungta**, kai jos efektyvioji reikšmė yra `"t ## Vėliavėlių katalogas -111 vėliavėlių 6 kategorijose. **Numatytoji reikšmė** yra apibrėžime nurodyta numatytoji reikšmė, naudojama, +110 vėliavėlių 6 kategorijose. **Numatytoji reikšmė** yra apibrėžime nurodyta numatytoji reikšmė, naudojama, kai nėra nei DB perrašymo, nei aplinkos kintamojo. ### Saugumas (10) @@ -104,7 +104,7 @@ kai nėra nei DB perrašymo, nei aplinkos kintamojo. | `RADAR_ENABLED` | boolean | `false` | Įjungti „OmniRoute Radar“ modulį (katalogo srauto ekranus ir sinchronizavimą). Pagal numatytąją nuostatą išjungta; įjungus tik atrakinama naudotojo sąsaja — duomenų sinchronizavimą vis tiek reikia įjungti atskirai. | | `MODEL_ENDPOINT_CAPABILITY_GATE_ENABLED` | boolean | `false` | Priverstinai taikyti patikimas modelio galinių taškų žymas (nurodytas tiekėjo, nustatytas pagal specializuoto modelio identifikatorių arba nustatytas operatoriaus): prieš išsiunčiant atmesti užklausą, nusiųstą į modelio neaptarnaujamą galinį tašką, grąžinant HTTP 400 keliuose `/v1/chat/completions`, `/v1/embeddings`, `/v1/rerank`, `/v1/images/generations`, `/v1/videos/generations`, `/v1/music/generations` ir `/v1/audio/*`; atsakyme iš `/v1/models` paslėpti modelius, kurių žymomis pažymėtiems galiniams taškams nėra „OmniRoute“ adapterio; jų atveju į valdymo skydelio modelio testą atsakyti kodu 422 (#16929). Pagal numatytąją nuostatą išjungta: galinių taškų žymos lieka informacinės, o užklausų išsiuntimas nesikeičia. | -### Vykdymo aplinka (53) +### Vykdymo aplinka (52) | Raktas | Tipas | Numatytoji reikšmė | Paleidimas iš naujo | Aprašymas | | ---------------------------------------------- | ------- | ------------------ | ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -151,7 +151,6 @@ kai nėra nei DB perrašymo, nei aplinkos kintamojo. | `KIRO_TOOL_CALL_REPAIR_ENABLED` | boolean | `true` | | Kai srautinio perdavimo Kiro atsakas prieš bet kokią matomą išvestį pateikia netinkamai suformuotą `tool_call` apvalkalą (trūksta `input.name` arba `input.arguments`), jį nuslėpti ir vieną kartą pakartotinai išsiųsti užklausą su taisomuoju nurodymu, užuot užbaigus dialogo žingsnį klaida `invalid_kiro_tool_call`. Pakartotinis bandymas kainuoja vieną papildomą Kiro užklausą; po antrosios nesėkmės dialogo žingsnis užbaigiamas klaida `kiro_tool_call_repair_retry_failed`. Pagal numatytuosius nustatymus įjungta. Nustatykite `false`, kad dialogo žingsnis būtų nutrauktas po pirmojo netinkamai suformuoto apvalkalo. | | `CATALOG_MIRROR_REFINEMENTS_ENABLED` | boolean | `false` | | Patobulinti `claude/` aptikimo ir funkcinių tinklų sietuvų veidrodžius maršrute `/v1/models`. Pagal numatytuosius nustatymus išjungta: abu veidrodinio kūrimo etapai veikia lygiai taip pat kaip anksčiau. Kai įjungta: `claude/` aptikimo veidrodžiai taip pat apima išvardytus samprotavimo pastangų variantus (`-low`/`-medium`/`-high`/`-xhigh`), praleidžia integruotus `auto/*` identifikatorius, kurių užklausos kelias negali realizuoti, ir jau įtrauktus pseudonimus; funkcinių tinklų sietuvų veidrodžiai praleidžia kombinacijas ir OmniRoute sugeneruotus identifikatorius (`claude/`, `no-think/`), savininką ima iš `owned_by`, niekada nenaudoja savininko kaip jo paties tinklų sietuvo, praleidžia `blockedProviders` išjungtus tinklų sietuvus ir modelius, kurių nėra autoritetingame aktyviame tinklų sietuvo kataloge. | | ---------------------------------------------- | ------- | ------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `ANTHROPIC_COMPATIBLE_MID_CONVERSATION_SYSTEM` | boolean | `false` | | Paprastuose `anthropic-compatible-*` perdavimo serveriuose (tiesioginis claude → claude perdavimas) dialogo viduryje esančius `role: "system"` žingsnius palikti `messages[]` viduje, o ne perkelti į aukščiausio lygio `system` parametrą, kad talpykloje saugomas raginimo prefiksas tarp dialogo žingsnių išliktų stabilus. Taikoma tik tada, kai pats klientas išsiuntė `mid-conversation-system-2026-04-07` `anthropic-beta` (persiunčiama pirminiam serveriui nepakeista). Pagal numatytuosius nustatymus išjungta: kiekvienas sistemos žingsnis perkeliamas lygiai taip pat kaip anksčiau, nes beta funkcijos neturintys perdavimo serveriai atmeta `role: "system"` pranešimus su 400 klaida. | | `MODELS_DEV_EFFORT_TIERS_ENABLED` | boolean | `false` | | Naudoti models.dev skelbiamus samprotavimo pastangų lygius (apribotus žinomu pastangų reikšmių žodynu), kai jų nenurodo nei operatoriaus perrašymas, nei teikėjų registras, o aiškiai nurodytą tuščią lygių sąrašą laikyti „lygių nėra“ reikšme `/v1/models` lauke `effort_tiers` ir kombinacijų lygių sankirtose. Pagal numatytuosius nustatymus išjungta: `effort_tiers` ir apskaičiuotos `supportedThinkingEfforts` reikšmės išlieka lygiai tokios pačios kaip anksčiau. | | `AUTO_REQUEST_RESOLVED_CAPABILITIES` | boolean | `false` | | auto/* užklausos sudaro kandidatų telkinį naudodamos apskaičiuotas modelių galimybes (vaizdų apdorojimą, samprotavimą, mąstymą) ir stebėtus kokybės įverčius, gautus vienu paketiniu nuskaitymu (#16419). Pagal numatytuosius nustatymus išjungta: užklausos vykdymo metu sudarytos momentinės kopijos svoriai išlieka lygiai tokie patys kaip anksčiau. Kai įjungta: šie signalai naudojami momentinės kopijos svoriams apskaičiuoti, todėl kandidatų svoriai ir pasirinkimas gali pasikeisti. | | `AUTO_COMBO_KEYLESS_TRANSPORT_GATE` | boolean | `false` | | Neįtraukti autentifikavimo rakto nereikalaujančių (no-auth) teikėjų, kurių transportui reikalinga vietinė naršyklė (`browserBacked`) arba vietoje įdiegta CLI (`isLocalCli`), į `auto/*` kandidatų telkinį, kad užstrigęs narys negalėtų sustabdyti automatinės užklausos iki konkrečiam tikslui skirtojo laiko pabaigos (#17036). Pagal numatytuosius nustatymus išjungta: autentifikavimo rakto nereikalaujančių teikėjų telkinys nesikeičia. Tiesioginiai `/` iškvietimai veikia abiem atvejais. | @@ -248,7 +247,7 @@ Grąžina visas žymas su jų galiojančia reikšme, šaltiniu ir suvestine. "requiresRestart": false, "warningLevel": "caution", }, - // ... visos 111 žymų + // ... visos 110 žymų ], "summary": { "total": 56, diff --git a/docs/i18n/lv/docs/reference/FEATURE_FLAGS.md b/docs/i18n/lv/docs/reference/FEATURE_FLAGS.md index b51fe1517977..f6b1585279da 100644 --- a/docs/i18n/lv/docs/reference/FEATURE_FLAGS.md +++ b/docs/i18n/lv/docs/reference/FEATURE_FLAGS.md @@ -44,7 +44,7 @@ Būla karogs tiek uzskatīts par **iespējotu**, ja tā efektīvā vērtība ir ## Kārodziņu katalogs -111 kārodziņi 6 kategorijās. **Noklusējums** ir definīcijā norādītā noklusējuma vērtība — +110 kārodziņi 6 kategorijās. **Noklusējums** ir definīcijā norādītā noklusējuma vērtība — vērtība, kas tiek izmantota, ja nav ne DB pārrakstīšanas, ne vides mainīgā. ### Drošība (10) @@ -109,7 +109,7 @@ vērtība, kas tiek izmantota, ja nav ne DB pārrakstīšanas, ne vides mainīg | `RADAR_ENABLED` | boolean | `false` | Iespējot OmniRoute Radar moduli (kataloga plūsmas ekrānus un sinhronizāciju). Pēc noklusējuma izslēgts; iespējošana tikai atbloķē lietotāja saskarni — datu sinhronizācijai joprojām nepieciešama atsevišķa piekrišana. | | `MODEL_ENDPOINT_CAPABILITY_GATE_ENABLED` | boolean | `false` | Piemērot uzticamus modeļa galapunktu tagus (nodrošinātāja deklarētus, secinātus no specializēta modeļa identifikatora vai operatora iestatītus): pirms nosūtīšanas ar HTTP 400 noraidīt pieprasījumu, kas nosūtīts galapunktam, kuru modelis neapkalpo, maršrutos `/v1/chat/completions`, `/v1/embeddings`, `/v1/rerank`, `/v1/images/generations`, `/v1/videos/generations`, `/v1/music/generations` un `/v1/audio/*`; maršrutā `/v1/models` paslēpt modeļus, kuru marķētajiem galapunktiem nav OmniRoute adaptera; informācijas paneļa modeļa pārbaudē tiem atbildēt ar 422 (#16929). Pēc noklusējuma izslēgts: galapunktu tagi paliek informatīvi, un nosūtīšana netiek mainīta. | -### Izpildlaika vide (53) +### Izpildlaika vide (52) | Atslēga | Tips | Noklusējums | Restartēšana | Apraksts | | ---------------------------------------------- | ------- | ----------- | ------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -156,7 +156,6 @@ vērtība, kas tiek izmantota, ja nav ne DB pārrakstīšanas, ne vides mainīg | `KIRO_TOOL_CALL_REPAIR_ENABLED` | boolean | `true` | | Ja Kiro straumēšanas atbilde pirms jebkādas redzamas izvades izveido nepareizi formatētu `tool_call` ietvaru (trūkst `input.name` vai `input.arguments`), to apspiest un vienreiz atkārtoti nosūtīt pieprasījumu ar labošanas norādījumu, nevis pabeigt mijiedarbības gājienu ar `invalid_kiro_tool_call`. Atkārtotais mēģinājums izmanto vienu papildu Kiro pieprasījumu; otra kļūme pabeidz mijiedarbības gājienu ar `kiro_tool_call_repair_retry_failed`. Pēc noklusējuma ieslēgts. Iestatiet uz `false`, lai pabeigtu mijiedarbības gājienu ar kļūmi jau pēc pirmā nepareizi formatētā ietvara. | | `CATALOG_MIRROR_REFINEMENTS_ENABLED` | boolean | `false` | | Precizēt `claude/` atklāšanas un funkcionālo vārteju spoguļus maršrutā `/v1/models`. Pēc noklusējuma izslēgts: abas spoguļošanas darbības darbojas tieši tāpat kā iepriekš. Ja ieslēgts: `claude/` atklāšanas spoguļi aptver arī uzskaitītos spriešanas piepūles variantus (`-low`/`-medium`/`-high`/`-xhigh`), izlaiž iebūvētos `auto/*` ID, kurus pieprasījuma ceļš nevar materializēt, un izlaiž jau uzskaitītu aizstājvārdu; funkcionālo vārteju spoguļi izlaiž kombinācijas un OmniRoute sintezētos ID (`claude/`, `no-think/`), iegūst īpašnieku no `owned_by`, nekad neizmanto īpašnieku kā tā paša vārteju, izlaiž vārtejas, kas atspējotas sadaļā `blockedProviders`, un modeļus ārpus vārtejas autoritatīvā aktuālā kataloga. | | ---------------------------------------------- | ------- | ------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `ANTHROPIC_COMPATIBLE_MID_CONVERSATION_SYSTEM` | boolean | `false` | | Vienkāršos `anthropic-compatible-*` starpniekserveros (claude → claude tiešā pārsūtīšana) saglabāt sarunas vidū esošos `role: "system"` gājienus masīvā `messages[]`, nevis pārvietot tos uz augstākā līmeņa parametru `system`, lai kešatmiņā saglabātais uzvednes prefikss starp gājieniem paliktu stabils. Attiecas tikai tad, ja pats klients ir nosūtījis `mid-conversation-system-2026-04-07` `anthropic-beta` (kas tiek pārsūtīts augšup nemainītā veidā). Pēc noklusējuma izslēgts: katrs sistēmas gājiens tiek pārvietots tieši tāpat kā iepriekš, jo starpniekserveri bez šīs beta versijas noraida `role: "system"` ziņojumus ar 400. | | `MODELS_DEV_EFFORT_TIERS_ENABLED` | boolean | `false` | | Izmantot models.dev publicētos spriešanas piepūles līmeņus (ierobežotus līdz zināmajai piepūles vērtību kopai), ja ne operatora pārrakstījums, ne pakalpojumu sniedzēju reģistrs nekādus līmeņus nenorāda, un deklarētu tukšu līmeņu sarakstu uzskatīt par „nav līmeņu” laukā `/v1/models` `effort_tiers` un kombināciju līmeņu šķēlumos. Pēc noklusējuma izslēgts: `effort_tiers` un atrisinātās `supportedThinkingEfforts` vērtības paliek tieši tādas pašas kā iepriekš. | | `AUTO_REQUEST_RESOLVED_CAPABILITIES` | boolean | `false` | | auto/* pieprasījumi izveido kandidātu kopu, vienā lielapjoma lasījumā izmantojot atrisinātās modeļu iespējas (redze, spriešana, domāšana) un novērotos kvalitātes novērtējumus (#16419). Pēc noklusējuma izslēgts: pieprasījuma izpildes laikā iegūtā momentuzņēmuma svari paliek tieši tādi paši kā iepriekš. Ja ieslēgts: šie signāli tiek izmantoti momentuzņēmuma svaros, tāpēc kandidātu svari un atlase var mainīties. | | `AUTO_COMBO_KEYLESS_TRANSPORT_GATE` | boolean | `false` | | Neiekļaut bezatslēgas (bez autentifikācijas) nodrošinātājus, kuru transportam nepieciešama lokāla pārlūkprogramma (`browserBacked`) vai lokāli instalēts CLI (`isLocalCli`), `auto/*` kandidātu kopā, lai iestrēdzis dalībnieks nevarētu aizkavēt automātisku pieprasījumu līdz katram mērķim noteiktā taimauta beigām (#17036). Pēc noklusējuma izslēgts: bezatslēgas nodrošinātāju kopa netiek mainīta. Tiešie `/` izsaukumi darbojas jebkurā gadījumā. | @@ -253,7 +252,7 @@ Atgriež katru karodziņu ar tā faktisko vērtību, avotu un kopsavilkumu. "requiresRestart": false, "warningLevel": "caution", }, - // ... visi 111 karodziņi + // ... visi 110 karodziņi ], "summary": { "total": 56, diff --git a/docs/i18n/ml/docs/reference/FEATURE_FLAGS.md b/docs/i18n/ml/docs/reference/FEATURE_FLAGS.md index 93958ab2a64f..bfa86a8de507 100644 --- a/docs/i18n/ml/docs/reference/FEATURE_FLAGS.md +++ b/docs/i18n/ml/docs/reference/FEATURE_FLAGS.md @@ -32,7 +32,7 @@ ## ഫ്ലാഗ് കാറ്റലോഗ് -6 വിഭാഗങ്ങളിലായി 111 ഫ്ലാഗുകൾ. **ഡിഫോൾട്ട്** എന്നത് നിർവചനത്തിലെ ഡിഫോൾട്ട് മൂല്യമാണ് — DB ഓവർറൈഡോ എൻവയോൺമെന്റ് വേരിയബിളോ ഇല്ലാത്തപ്പോൾ +6 വിഭാഗങ്ങളിലായി 110 ഫ്ലാഗുകൾ. **ഡിഫോൾട്ട്** എന്നത് നിർവചനത്തിലെ ഡിഫോൾട്ട് മൂല്യമാണ് — DB ഓവർറൈഡോ എൻവയോൺമെന്റ് വേരിയബിളോ ഇല്ലാത്തപ്പോൾ ഉപയോഗിക്കുന്ന മൂല്യം. ### സുരക്ഷ (10) @@ -97,7 +97,7 @@ | `RADAR_ENABLED` | boolean | `false` | OmniRoute Radar മൊഡ്യൂൾ (കാറ്റലോഗ് ഫീഡ് സ്ക്രീനുകളും സമന്വയവും) പ്രവർത്തനക്ഷമമാക്കുക. ഡിഫോൾട്ടായി ഓഫ്; പ്രവർത്തനക്ഷമമാക്കുന്നത് UI-ലേക്കുള്ള പ്രവേശനം മാത്രമാണ് അനുവദിക്കുന്നത് — ഡാറ്റാ സമന്വയത്തിന് പ്രത്യേകം സമ്മതം നൽകേണ്ടതുണ്ട്. | | `MODEL_ENDPOINT_CAPABILITY_GATE_ENABLED` | boolean | `false` | വിശ്വസനീയമായ മോഡൽ എൻഡ്പോയിന്റ് ടാഗുകൾ (പ്രൊവൈഡർ പ്രഖ്യാപിച്ചത്, ഒരു സ്പെഷ്യാലിറ്റി മോഡൽ id-യിൽ നിന്ന് അനുമാനിച്ചത്, അല്ലെങ്കിൽ ഓപ്പറേറ്റർ സജ്ജമാക്കിയത്) നിർബന്ധമാക്കുക: `/v1/chat/completions`, `/v1/embeddings`, `/v1/rerank`, `/v1/images/generations`, `/v1/videos/generations`, `/v1/music/generations`, `/v1/audio/*` എന്നിവയിൽ മോഡൽ സേവനം നൽകാത്ത ഒരു എൻഡ്പോയിന്റിലേക്ക് അയച്ച അഭ്യർത്ഥന ഡിസ്പാച്ചിന് മുമ്പ് HTTP 400 ഉപയോഗിച്ച് നിരസിക്കുക; ടാഗ് ചെയ്ത എൻഡ്പോയിന്റുകൾക്ക് OmniRoute അഡാപ്റ്റർ ഇല്ലാത്ത മോഡലുകളെ `/v1/models`-ൽ നിന്ന് മറയ്ക്കുക; അവയ്ക്കായുള്ള ഡാഷ്ബോർഡ് മോഡൽ പരിശോധനയ്ക്ക് 422 ഉപയോഗിച്ച് മറുപടി നൽകുക (#16929). ഡിഫോൾട്ടായി ഓഫ്: എൻഡ്പോയിന്റ് ടാഗുകൾ വിവരദായകമായി തുടരുകയും ഡിസ്പാച്ചിൽ മാറ്റമുണ്ടാകാതിരിക്കുകയും ചെയ്യും. | -### റൺടൈം (53) +### റൺടൈം (52) | കീ | തരം | ഡിഫോൾട്ട് | പുനരാരംഭിക്കൽ | വിവരണം | | ---------------------------------------------- | ------- | --------- | ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -144,7 +144,6 @@ | `KIRO_TOOL_CALL_REPAIR_ENABLED` | boolean | `true` | | ദൃശ്യമാകുന്ന output ഉണ്ടാകുന്നതിന് മുമ്പ് ഒരു streaming Kiro response വികലമായ `tool_call` wrapper (`input.name` അല്ലെങ്കിൽ `input.arguments` ഇല്ലാത്തത്) പുറപ്പെടുവിക്കുമ്പോൾ, turn `invalid_kiro_tool_call` ഉപയോഗിച്ച് അവസാനിപ്പിക്കുന്നതിന് പകരം അത് തടയുകയും തിരുത്തൽ നിർദ്ദേശത്തോടെ request ഒരിക്കൽ വീണ്ടും അയയ്ക്കുകയും ചെയ്യുക. retry-ക്ക് ഒരു അധിക Kiro request ആവശ്യമാണ്; രണ്ടാമത്തെ പരാജയം turn-നെ `kiro_tool_call_repair_retry_failed` ഉപയോഗിച്ച് അവസാനിപ്പിക്കുന്നു. ഡിഫോൾട്ടായി ഓൺ ആണ്. ആദ്യത്തെ വികലമായ wrapper-ൽത്തന്നെ turn പരാജയപ്പെടുത്താൻ `false` ആയി സജ്ജീകരിക്കുക. | | `CATALOG_MIRROR_REFINEMENTS_ENABLED` | boolean | `false` | | `/v1/models`-ലെ `claude/` discovery, functional-gateway mirror-ുകൾ കൂടുതൽ കൃത്യമാക്കുക. ഡിഫോൾട്ടായി ഓഫ് ആണ്: രണ്ട് mirror pass-ുകളും മുമ്പത്തെപ്പോലെതന്നെ പ്രവർത്തിക്കുന്നു. ഓൺ ആക്കുമ്പോൾ: `claude/` discovery mirror-ുകൾ ലിസ്റ്റുചെയ്ത reasoning-effort variant-ുകളും (`-low`/`-medium`/`-high`/`-xhigh`) ഉൾക്കൊള്ളുന്നു, request path-ന് materialize ചെയ്യാനാകാത്ത built-in `auto/*` id-കൾ ഒഴിവാക്കുന്നു, ഇതിനകം ലിസ്റ്റുചെയ്ത alias-ുകളും ഒഴിവാക്കുന്നു; functional-gateway mirror-ുകൾ combo-കളും OmniRoute സൃഷ്ടിച്ച id-കളും (`claude/`, `no-think/`) ഒഴിവാക്കുന്നു, owner-നെ `owned_by`-ൽനിന്ന് എടുക്കുന്നു, owner-നെ ഒരിക്കലും അതിന്റെതന്നെ gateway ആയി ഉപയോഗിക്കുന്നില്ല, `blockedProviders`-ൽ disable ചെയ്ത gateway-കളും ഒരു gateway-യുടെ authoritative live catalog-ന് പുറത്തുള്ള model-ുകളും ഒഴിവാക്കുന്നു. | | ---------------------------------------------- | ------- | ------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `ANTHROPIC_COMPATIBLE_MID_CONVERSATION_SYSTEM` | boolean | `false` | | സാധാരണ `anthropic-compatible-*` relay-കളിൽ (claude → claude passthrough), സംഭാഷണത്തിന്റെ മധ്യത്തിലുള്ള `role: "system"` turn-ുകളെ top-level `system` parameter-ലേക്ക് ഉയർത്തുന്നതിന് പകരം `messages[]`-ൽ നിലനിർത്തുക; ഇതുവഴി cached prompt prefix turn-ുകൾക്കിടയിൽ സ്ഥിരമായി തുടരും. client തന്നെയാണ് `mid-conversation-system-2026-04-07` `anthropic-beta` അയച്ചതെങ്കിൽ മാത്രം ഇത് ബാധകമാണ് (മാറ്റമില്ലാതെ upstream-ലേക്ക് forward ചെയ്യുന്നു). ഡിഫോൾട്ടായി ഓഫ് ആണ്: beta ഇല്ലാത്ത relay-കൾ `role: "system"` message-ുകളെ 400 നൽകി നിരസിക്കുന്നതിനാൽ, ഓരോ system turn-ഉം മുമ്പത്തെപ്പോലെതന്നെ ഉയർത്തുന്നു. | | `MODELS_DEV_EFFORT_TIERS_ENABLED` | boolean | `false` | | operator override-ഓ provider registry-യോ effort tier-ുകൾ പ്രഖ്യാപിക്കാത്തപ്പോൾ, models.dev പ്രസിദ്ധീകരിക്കുന്ന reasoning effort tier-ുകൾ (അറിയപ്പെടുന്ന effort vocabulary-യിലേക്ക് പരിമിതപ്പെടുത്തിയത്) ഉപയോഗിക്കുക; കൂടാതെ, പ്രഖ്യാപിച്ച ശൂന്യമായ tier list-നെ `/v1/models` `effort_tiers`-ലും combo tier intersection-കളിലും "tier-ുകളൊന്നുമില്ല" എന്നായി പരിഗണിക്കുക. ഡിഫോൾട്ടായി ഓഫ് ആണ്: `effort_tiers`-ഉം resolve ചെയ്ത `supportedThinkingEfforts`-ഉം മുമ്പത്തെപ്പോലെതന്നെ തുടരും. | | `AUTO_REQUEST_RESOLVED_CAPABILITIES` | boolean | `false` | | auto/* request-ുകൾ, resolve ചെയ്ത model capability-കളും (vision, reasoning, thinking) നിരീക്ഷിച്ച quality score-ുകളും ഒരൊറ്റ bulk read-ൽ ഉപയോഗിച്ച് candidate pool നിർമ്മിക്കുന്നു (#16419). ഡിഫോൾട്ടായി ഓഫ് ആണ്: request-time snapshot weight-ുകൾ മുമ്പത്തെപ്പോലെതന്നെ തുടരും. ഓൺ ആക്കുമ്പോൾ: ആ signal-ുകൾ snapshot weight-ുകളിലേക്ക് നൽകുന്നതിനാൽ candidate weight-ുകളും selection-ഉം മാറാം. | | `AUTO_COMBO_KEYLESS_TRANSPORT_GATE` | boolean | `false` | | പ്രാദേശിക ബ്രൗസർ (`browserBacked`) അല്ലെങ്കിൽ പ്രാദേശികമായി ഇൻസ്റ്റാൾ ചെയ്ത CLI (`isLocalCli`) ആവശ്യമായ ട്രാൻസ്പോർട്ടുള്ള keyless (no-auth) providers-നെ `auto/*` candidate pool-ൽ നിന്ന് ഒഴിവാക്കുക; അതുവഴി പ്രതികരിക്കാതെ നിൽക്കുന്ന ഒരു അംഗം ഓരോ target-നുമുള്ള timeout അവസാനിക്കുന്നതുവരെ auto request തടഞ്ഞുനിർത്തുന്നത് ഒഴിവാക്കാം (#17036). ഡിഫോൾട്ടായി ഓഫാണ്: keyless pool മാറ്റമില്ലാതെ തുടരും. നേരിട്ടുള്ള `/` calls ഏത് ക്രമീകരണത്തിലും പ്രവർത്തിക്കും. | @@ -243,7 +242,7 @@ "requiresRestart": false, "warningLevel": "caution", }, - // ... എല്ലാ 111 ഫ്ലാഗുകളും + // ... എല്ലാ 110 ഫ്ലാഗുകളും ], "summary": { "total": 56, diff --git a/docs/i18n/mr/docs/reference/FEATURE_FLAGS.md b/docs/i18n/mr/docs/reference/FEATURE_FLAGS.md index 8a7811652302..a1a85514c1b9 100644 --- a/docs/i18n/mr/docs/reference/FEATURE_FLAGS.md +++ b/docs/i18n/mr/docs/reference/FEATURE_FLAGS.md @@ -45,7 +45,7 @@ boolean फ्लॅगचे प्रभावी मूल्य `"true"`, `" ## फ्लॅग सूची -6 श्रेणींमध्ये 111 फ्लॅग. **डीफॉल्ट** म्हणजे व्याख्येत दिलेले डीफॉल्ट मूल्य — DB ओव्हरराइड किंवा पर्यावरण चल यांपैकी कोणतेही उपलब्ध नसताना वापरले जाणारे मूल्य. +6 श्रेणींमध्ये 110 फ्लॅग. **डीफॉल्ट** म्हणजे व्याख्येत दिलेले डीफॉल्ट मूल्य — DB ओव्हरराइड किंवा पर्यावरण चल यांपैकी कोणतेही उपलब्ध नसताना वापरले जाणारे मूल्य. ### सुरक्षा (10) @@ -109,7 +109,7 @@ boolean फ्लॅगचे प्रभावी मूल्य `"true"`, `" | `RADAR_ENABLED` | boolean | `false` | OmniRoute Radar मॉड्यूल (कॅटलॉग फीड स्क्रीन आणि सिंक) सक्षम करा. डीफॉल्टनुसार बंद; सक्षम केल्याने केवळ UI अनलॉक होते — डेटा सिंकसाठी स्वतंत्रपणे निवड करणे आवश्यक असते. | | `MODEL_ENDPOINT_CAPABILITY_GATE_ENABLED` | boolean | `false` | विश्वसनीय मॉडेल एंडपॉइंट टॅगची (प्रदात्याने घोषित केलेले, विशेष मॉडेल id वरून अनुमानित केलेले किंवा ऑपरेटरने सेट केलेले) अंमलबजावणी करा: `/v1/chat/completions`, `/v1/embeddings`, `/v1/rerank`, `/v1/images/generations`, `/v1/videos/generations`, `/v1/music/generations` आणि `/v1/audio/*` वर मॉडेल सेवा देत नसलेल्या एंडपॉइंटकडे पाठवलेली विनंती डिस्पॅचपूर्वी HTTP 400 सह नाकारा; ज्यांच्या टॅग केलेल्या एंडपॉइंटसाठी OmniRoute अडॅप्टर नाही अशी मॉडेल्स `/v1/models` मधून लपवा; त्यांच्यासाठी डॅशबोर्ड मॉडेल चाचणीला 422 ने उत्तर द्या (#16929). डीफॉल्टनुसार बंद: एंडपॉइंट टॅग केवळ माहितीपर राहतात आणि डिस्पॅचमध्ये कोणताही बदल होत नाही. | -### रनटाइम (53) +### रनटाइम (52) | की | प्रकार | डीफॉल्ट | रीस्टार्ट | वर्णन | | ---------------------------------------------- | ------- | ------- | --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -156,7 +156,6 @@ boolean फ्लॅगचे प्रभावी मूल्य `"true"`, `" | `KIRO_TOOL_CALL_REPAIR_ENABLED` | boolean | `true` | | स्ट्रीमिंग Kiro प्रतिसादाने कोणतेही दृश्यमान आउटपुट देण्यापूर्वी सदोष `tool_call` रॅपर (`input.name` किंवा `input.arguments` नसलेला) उत्सर्जित केल्यास, तो दडपून टाका आणि टर्न `invalid_kiro_tool_call` सह समाप्त करण्याऐवजी सुधारात्मक सूचनेसह विनंती एकदा पुन्हा पाठवा. या पुनर्प्रयत्नासाठी एक अतिरिक्त Kiro विनंती खर्ची पडते; दुसरे अपयश टर्न `kiro_tool_call_repair_retry_failed` सह समाप्त करते. डीफॉल्टनुसार सुरू. पहिल्याच सदोष रॅपरवर टर्न अयशस्वी करण्यासाठी `false` वर सेट करा. | | `CATALOG_MIRROR_REFINEMENTS_ENABLED` | boolean | `false` | | `/v1/models` वरील `claude/` डिस्कव्हरी आणि फंक्शनल-गेटवे मिरर अधिक परिष्कृत करा. डीफॉल्टनुसार बंद: दोन्ही मिरर पास पूर्वीप्रमाणेच वागतात. सुरू असल्यास: `claude/` डिस्कव्हरी मिररमध्ये सूचीबद्ध रीझनिंग-एफर्ट प्रकारांचाही (`-low`/`-medium`/`-high`/`-xhigh`) समावेश होतो, विनंती पथ प्रत्यक्ष तयार करू शकत नसलेले अंगभूत `auto/*` आयडी आणि आधीच सूचीबद्ध असलेले उपनाव वगळले जातात; फंक्शनल-गेटवे मिरर कॉम्बो आणि OmniRoute-निर्मित आयडी (`claude/`, `no-think/`) वगळतात, मालक `owned_by` मधून घेतात, मालकालाच त्याचा स्वतःचा गेटवे म्हणून कधीही वापरत नाहीत, `blockedProviders` मध्ये अक्षम केलेले गेटवे आणि गेटवेच्या अधिकृत लाइव्ह कॅटलॉगच्या बाहेरील मॉडेल वगळतात. | | ---------------------------------------------- | ------- | ------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `ANTHROPIC_COMPATIBLE_MID_CONVERSATION_SYSTEM` | boolean | `false` | | साध्या `anthropic-compatible-*` रिलेवर (claude → claude पासथ्रू), संभाषणाच्या मध्यभागी असलेले `role: "system"` टर्न शीर्ष-स्तरीय `system` पॅरामीटरमध्ये वर नेण्याऐवजी `messages[]` मध्येच ठेवा, जेणेकरून कॅश केलेला प्रॉम्प्ट प्रीफिक्स सर्व टर्नमध्ये स्थिर राहील. क्लायंटने स्वतः `mid-conversation-system-2026-04-07` `anthropic-beta` पाठवला असेल तेव्हाच हे लागू होते (जसा आहे तसाच अपस्ट्रीमकडे फॉरवर्ड केला जातो). डीफॉल्टनुसार बंद: प्रत्येक सिस्टम टर्न पूर्वीप्रमाणेच वर नेला जातो, कारण बीटा नसलेले रिले `role: "system"` संदेश 400 सह नाकारतात. | | `MODELS_DEV_EFFORT_TIERS_ENABLED` | boolean | `false` | | ऑपरेटर ओव्हरराइड किंवा प्रोव्हायडर रजिस्ट्री यांपैकी कोणीही कोणतेही स्तर घोषित करत नसताना models.dev प्रकाशित करत असलेले रीझनिंग एफर्ट स्तर (ज्ञात एफर्ट शब्दसंग्रहापुरते मर्यादित) वापरा आणि घोषित केलेली रिकामी स्तर-सूची `/v1/models` `effort_tiers` व कॉम्बो स्तर छेदांमध्ये "कोणतेही स्तर नाहीत" अशी माना. डीफॉल्टनुसार बंद: `effort_tiers` आणि रिझॉल्व्ह केलेले `supportedThinkingEfforts` पूर्वीप्रमाणेच राहतात. | | `AUTO_REQUEST_RESOLVED_CAPABILITIES` | boolean | `false` | | auto/* विनंत्या एका बल्क रीडमध्ये रिझॉल्व्ह केलेल्या मॉडेल क्षमता (व्हिजन, रीझनिंग, थिंकिंग) आणि निरीक्षण केलेले गुणवत्ता गुण वापरून उमेदवार पूल तयार करतात (#16419). डीफॉल्टनुसार बंद: विनंतीच्या वेळेचे स्नॅपशॉट वेट पूर्वीप्रमाणेच राहतात. सुरू असल्यास: हे संकेत स्नॅपशॉट वेटमध्ये समाविष्ट होतात, त्यामुळे उमेदवारांचे वेट आणि निवड बदलू शकतात. | | `AUTO_COMBO_KEYLESS_TRANSPORT_GATE` | boolean | `false` | | ज्या keyless (no-auth) providers च्या transport ला स्थानिक browser (`browserBacked`) किंवा स्थानिकरीत्या स्थापित केलेला CLI (`isLocalCli`) आवश्यक आहे, त्यांना `auto/*` candidate pool मधून वगळा, जेणेकरून एखादा प्रतिसाद न देणारा member प्रत्येक target साठीची timeout मर्यादा संपेपर्यंत auto request थांबवू शकणार नाही (#17036). डीफॉल्टनुसार बंद: keyless pool मध्ये कोणताही बदल होत नाही. थेट `/` calls दोन्ही स्थितींमध्ये कार्य करतात. | @@ -254,7 +253,7 @@ boolean फ्लॅगचे प्रभावी मूल्य `"true"`, `" "requiresRestart": false, "warningLevel": "caution", }, - // ... सर्व 111 फ्लॅग + // ... सर्व 110 फ्लॅग ], "summary": { "total": 56, diff --git a/docs/i18n/ms/docs/reference/FEATURE_FLAGS.md b/docs/i18n/ms/docs/reference/FEATURE_FLAGS.md index 62d450887493..89b9b8c40063 100644 --- a/docs/i18n/ms/docs/reference/FEATURE_FLAGS.md +++ b/docs/i18n/ms/docs/reference/FEATURE_FLAGS.md @@ -44,7 +44,7 @@ Bendera boolean dianggap **didayakan** apabila nilai berkuat kuasanya ialah `"tr ## Katalog Bendera -111 bendera merentas 6 kategori. **Lalai** ialah nilai lalai takrifan — nilai yang +110 bendera merentas 6 kategori. **Lalai** ialah nilai lalai takrifan — nilai yang digunakan apabila tiada penggantian DB mahupun pemboleh ubah persekitaran. ### Keselamatan (10) @@ -109,7 +109,7 @@ digunakan apabila tiada penggantian DB mahupun pemboleh ubah persekitaran. | `RADAR_ENABLED` | boolean | `false` | Dayakan modul OmniRoute Radar (skrin suapan katalog dan penyegerakan). Dilumpuhkan secara lalai; mendayakannya hanya membuka kunci UI — penyegerakan data kekal sebagai pilihan ikut serta yang berasingan. | | `MODEL_ENDPOINT_CAPABILITY_GATE_ENABLED` | boolean | `false` | Kuatkuasakan tag titik akhir model yang dipercayai (diisytiharkan oleh penyedia, disimpulkan daripada id model khusus atau ditetapkan oleh pengendali): tolak permintaan yang dihantar ke titik akhir yang tidak disediakan oleh model dengan HTTP 400 sebelum penghantaran pada `/v1/chat/completions`, `/v1/embeddings`, `/v1/rerank`, `/v1/images/generations`, `/v1/videos/generations`, `/v1/music/generations` dan `/v1/audio/*`; sembunyikan model yang titik akhirnya bertag tetapi tidak mempunyai penyesuai OmniRoute daripada `/v1/models`; balas ujian model papan pemuka dengan 422 untuk model tersebut (#16929). Dilumpuhkan secara lalai: tag titik akhir kekal sebagai maklumat dan penghantaran tidak berubah. | -### Masa Jalan (53) +### Masa Jalan (52) | Kekunci | Jenis | Lalai | Mulakan Semula | Penerangan | | ---------------------------------------------- | ------- | ------- | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | @@ -156,7 +156,6 @@ digunakan apabila tiada penggantian DB mahupun pemboleh ubah persekitaran. | `KIRO_TOOL_CALL_REPAIR_ENABLED` | boolean | `true` | | Apabila respons penstriman Kiro memancarkan pembalut `tool_call` yang tidak sah (tiada `input.name` atau `input.arguments`) sebelum sebarang output yang dapat dilihat, sekatnya dan hantar semula permintaan sekali dengan arahan pembetulan dan bukannya menamatkan giliran dengan `invalid_kiro_tool_call`. Percubaan semula memerlukan satu permintaan Kiro tambahan; kegagalan kali kedua menamatkan giliran dengan `kiro_tool_call_repair_retry_failed`. Dihidupkan secara lalai. Tetapkan kepada `false` untuk menggagalkan giliran pada pembalut tidak sah yang pertama. | | `CATALOG_MIRROR_REFINEMENTS_ENABLED` | boolean | `false` | | Perhalusi cermin penemuan `claude/` dan get laluan berfungsi pada `/v1/models`. Dimatikan secara lalai: kedua-dua proses pencerminan berfungsi tepat seperti sebelumnya. Apabila dihidupkan: cermin penemuan `claude/` turut merangkumi varian usaha penaakulan yang disenaraikan (`-low`/`-medium`/`-high`/`-xhigh`), melangkau ID `auto/*` terbina dalam yang tidak dapat direalisasikan oleh laluan permintaan dan melangkau alias yang sudah disenaraikan; cermin get laluan berfungsi melangkau kombo dan ID yang disintesis oleh OmniRoute (`claude/`, `no-think/`), mengambil pemilik daripada `owned_by`, tidak sekali-kali menggunakan pemilik sebagai get laluannya sendiri, serta melangkau get laluan yang dilumpuhkan dalam `blockedProviders` dan model di luar katalog langsung berautoriti sesuatu get laluan. | | ---------------------------------------------- | ------- | ------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `ANTHROPIC_COMPATIBLE_MID_CONVERSATION_SYSTEM` | boolean | `false` | | Pada geganti `anthropic-compatible-*` biasa (laluan terus claude → claude), kekalkan giliran `role: "system"` di pertengahan perbualan dalam `messages[]` dan bukannya menaikkannya ke parameter `system` peringkat atas, supaya awalan prom cache kekal stabil antara giliran. Terpakai hanya apabila klien itu sendiri menghantar `mid-conversation-system-2026-04-07` `anthropic-beta` (dimajukan ke huluan tanpa perubahan). Dimatikan secara lalai: setiap giliran sistem dinaikkan tepat seperti sebelumnya kerana geganti tanpa beta tersebut menolak mesej `role: "system"` dengan respons 400. | | `MODELS_DEV_EFFORT_TIERS_ENABLED` | boolean | `false` | | Gunakan peringkat usaha penaakulan yang diterbitkan oleh models.dev (dihadkan kepada kosa kata usaha yang diketahui) apabila pengendali mahupun daftar penyedia tidak mengisytiharkan sebarang peringkat, dan anggap senarai peringkat kosong yang diisytiharkan sebagai "tiada peringkat" dalam `effort_tiers` `/v1/models` dan persilangan peringkat kombo. Dimatikan secara lalai: `effort_tiers` dan `supportedThinkingEfforts` yang diselesaikan kekal tepat seperti sebelumnya. | | `AUTO_REQUEST_RESOLVED_CAPABILITIES` | boolean | `false` | | Permintaan auto/* membina kumpulan calon menggunakan keupayaan model yang diselesaikan (penglihatan, penaakulan, pemikiran) dan skor kualiti yang diperhatikan dalam satu bacaan pukal (#16419). Dimatikan secara lalai: pemberat petikan masa permintaan kekal tepat seperti sebelumnya. Apabila dihidupkan: isyarat tersebut dimasukkan ke dalam pemberat petikan, maka pemberat dan pemilihan calon boleh berubah. | | `AUTO_COMBO_KEYLESS_TRANSPORT_GATE` | boolean | `false` | | Kecualikan penyedia tanpa kekunci (tanpa pengesahan) yang pengangkutannya memerlukan pelayar setempat (`browserBacked`) atau CLI yang dipasang secara setempat (`isLocalCli`) daripada kumpulan calon `auto/*`, supaya ahli yang tergantung tidak boleh menyekat permintaan automatik sehingga tamat masa setiap sasaran (#17036). Dimatikan secara lalai: kumpulan tanpa kekunci tidak berubah. Panggilan terus `/` berfungsi dalam kedua-dua keadaan. | @@ -253,7 +252,7 @@ Mengembalikan setiap bendera bersama nilai berkuat kuasa, sumber dan ringkasanny "requiresRestart": false, "warningLevel": "caution", }, - // ... kesemua 111 bendera + // ... kesemua 110 bendera ], "summary": { "total": 56, diff --git a/docs/i18n/mt/docs/reference/FEATURE_FLAGS.md b/docs/i18n/mt/docs/reference/FEATURE_FLAGS.md index 1e96ab26f45a..ee50e7a18955 100644 --- a/docs/i18n/mt/docs/reference/FEATURE_FLAGS.md +++ b/docs/i18n/mt/docs/reference/FEATURE_FLAGS.md @@ -38,7 +38,7 @@ Flag boolean huwa kkunsidrat **attivat** meta l-valur effettiv tiegħu jkun `"tr ## Katalgu tal-Flags -111-il flag f’6 kategoriji. **Il-valur predefinit** huwa l-valur predefinit fid-definizzjoni — il-valur +110-il flag f’6 kategoriji. **Il-valur predefinit** huwa l-valur predefinit fid-definizzjoni — il-valur użat meta la jkun hemm override tad-DB u lanqas varjabbli tal-ambjent. ### Sigurtà (10) @@ -103,7 +103,7 @@ użat meta la jkun hemm override tad-DB u lanqas varjabbli tal-ambjent. | `RADAR_ENABLED` | boolean | `false` | Attiva l-modulu OmniRoute Radar (skrins tal-feed tal-katalgu u sinkronizzazzjoni). Mitfi b’mod predefinit; l-attivazzjoni tiftaħ biss l-UI — is-sinkronizzazzjoni tad-data tibqa’ għażla separata li trid tiġi attivata espliċitament. | | `MODEL_ENDPOINT_CAPABILITY_GATE_ENABLED` | boolean | `false` | Infurza tags fdati tal-endpoints tal-mudell (iddikjarati mill-fornitur, dedotti minn ID ta’ mudell speċjalizzat, jew issettjati mill-operatur): irrifjuta talba mibgħuta lil endpoint li l-mudell ma jservix b’HTTP 400 qabel ma tintbagħat fuq `/v1/chat/completions`, `/v1/embeddings`, `/v1/rerank`, `/v1/images/generations`, `/v1/videos/generations`, `/v1/music/generations` u `/v1/audio/*`; aħbi minn `/v1/models` il-mudelli li l-endpoints immarkati tagħhom ma għandhomx adapter ta’ OmniRoute; wieġeb għat-test tal-mudell tad-dashboard b’422 għalihom (#16929). Mitfi b’mod predefinit: it-tags tal-endpoints jibqgħu informattivi u l-bgħit ma jinbidilx. | -### Ambjent waqt l-eżekuzzjoni (53) +### Ambjent waqt l-eżekuzzjoni (52) | Ċavetta | Tip | Valur predefinit | Bidu mill-ġdid | Deskrizzjoni | | ---------------------------------------------- | ------- | ---------------- | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -150,7 +150,6 @@ użat meta la jkun hemm override tad-DB u lanqas varjabbli tal-ambjent. | `KIRO_TOOL_CALL_REPAIR_ENABLED` | boolean | `true` | | Meta tweġiba Kiro bi streaming toħroġ wrapper `tool_call` iffurmat ħażin (nieqes `input.name` jew `input.arguments`) qabel kwalunkwe output viżibbli, irażżnu u erġa' ibgħat it-talba darba b'istruzzjoni korrettiva minflok ittemm id-dawra b'`invalid_kiro_tool_call`. It-tentattiv mill-ġdid jiswa talba Kiro addizzjonali waħda; it-tieni falliment itemm id-dawra b'`kiro_tool_call_repair_retry_failed`. Attivat b'mod predefinit. Issettja għal `false` biex id-dawra tfalli mal-ewwel wrapper iffurmat ħażin. | | `CATALOG_MIRROR_REFINEMENTS_ENABLED` | boolean | `false` | | Irfina l-mirja tal-iskoperta `claude/` u tal-gateway funzjonali fuq `/v1/models`. Diżattivat b'mod predefinit: iż-żewġ passaġġi tal-immirjar jaħdmu eżattament bħal qabel. Meta attivat: il-mirja tal-iskoperta `claude/` ikopru wkoll il-varjanti elenkati tal-isforz tar-raġunament (`-low`/`-medium`/`-high`/`-xhigh`), jaqbżu l-IDs integrati `auto/*` li l-mogħdija tat-talba ma tistax timmaterjalizza u jaqbżu alias li jkun diġà elenkat; il-mirja tal-gateway funzjonali jaqbżu l-combos u l-IDs iġġenerati minn OmniRoute (`claude/`, `no-think/`), jieħdu s-sid minn `owned_by`, qatt ma jużaw lis-sid bħala l-gateway tiegħu stess, u jaqbżu gateways diżattivati f'`blockedProviders` u mudelli barra mill-katalgu attiv awtorevoli ta' gateway. | | ---------------------------------------------- | ------- | ------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `ANTHROPIC_COMPATIBLE_MID_CONVERSATION_SYSTEM` | boolean | `false` | | Fuq relays sempliċi `anthropic-compatible-*` (claude → trażmissjoni diretta lil claude), żomm id-dawriet ta' `role: "system"` f'nofs konverżazzjoni ġewwa `messages[]` minflok ittellagħhom fil-parametru `system` tal-ogħla livell, sabiex il-prefiss tal-prompt fil-cache jibqa' stabbli tul id-dawriet. Japplika biss meta l-klijent innifsu jkun bagħat il-`mid-conversation-system-2026-04-07` `anthropic-beta` (mgħoddi upstream kif inhu). Diżattivat b'mod predefinit: kull dawra tas-sistema tittella' eżattament bħal qabel, għax relays mingħajr il-beta jirrifjutaw messaġġi `role: "system"` b'400. | | `MODELS_DEV_EFFORT_TIERS_ENABLED` | boolean | `false` | | Uża l-livelli tal-isforz tar-raġunament li jippubblika models.dev (ristretti għall-vokabularju magħruf tal-isforz) meta la konfigurazzjoni prevalenti tal-operatur u lanqas ir-reġistru tal-fornitur ma jiddikjaraw xi livelli, u qis lista ddikjarata iżda vojta ta' livelli bħala "l-ebda livell" f'`effort_tiers` ta' `/v1/models` u fl-intersezzjonijiet tal-livelli tal-combos. Diżattivat b'mod predefinit: `effort_tiers` u `supportedThinkingEfforts` riżolt jibqgħu eżattament bħal qabel. | | `AUTO_REQUEST_RESOLVED_CAPABILITIES` | boolean | `false` | | It-talbiet auto/* jibnu l-ġabra ta' kandidati b'kapaċitajiet riżolti tal-mudelli (viżjoni, raġunament, ħsieb) u punteġġi tal-kwalità osservati f'qari wieħed bl-ingrossa (#16419). Diżattivat b'mod predefinit: il-piżijiet tal-istampa istantanja fil-ħin tat-talba jibqgħu eżattament bħal qabel. Meta attivat: dawk is-sinjali jintużaw fil-piżijiet tal-istampa istantanja, għalhekk il-piżijiet tal-kandidati u l-għażla jistgħu jinbidlu. | | `AUTO_COMBO_KEYLESS_TRANSPORT_GATE` | boolean | `false` | | Żomm lill-fornituri mingħajr ċavetta (mingħajr awtentikazzjoni) li t-trasport tagħhom jeħtieġ brawżer lokali (`browserBacked`) jew CLI installata lokalment (`isLocalCli`) barra mill-grupp ta’ kandidati `auto/*`, sabiex membru li jeħel ma jkunx jista’ jwaqqaf talba awtomatika sakemm jiskadi l-ħin għal kull mira (#17036). Mitfi b’mod awtomatiku: il-grupp mingħajr ċavetta jibqa’ l-istess. Is-sejħiet diretti `/` jaħdmu fiż-żewġ każijiet. | @@ -248,7 +247,7 @@ Jirritorna kull flag bil-valur effettiv tiegħu, is-sors, u taqsira. "requiresRestart": false, "warningLevel": "caution", }, - // ... il-111-il flag kollha + // ... il-110-il flag kollha ], "summary": { "total": 56, diff --git a/docs/i18n/my/docs/reference/FEATURE_FLAGS.md b/docs/i18n/my/docs/reference/FEATURE_FLAGS.md index 66c901e13116..fdd7655ec545 100644 --- a/docs/i18n/my/docs/reference/FEATURE_FLAGS.md +++ b/docs/i18n/my/docs/reference/FEATURE_FLAGS.md @@ -30,7 +30,7 @@ boolean flag တစ်ခု၏ ထိရောက်သောတန်ဖို ## အလံများ စာရင်း -အမျိုးအစား 6 မျိုးတွင် အလံ 111 ခုရှိသည်။ **မူလတန်ဖိုး** ဆိုသည်မှာ သတ်မှတ်ချက်တွင် ပါရှိသော မူလတန်ဖိုးဖြစ်ပြီး DB မှ ထပ်ဆင့်သတ်မှတ်ထားသော တန်ဖိုး သို့မဟုတ် ပတ်ဝန်းကျင် ကိန်းရှင် မရှိသည့်အခါ အသုံးပြုသည့် တန်ဖိုးဖြစ်သည်။ +အမျိုးအစား 6 မျိုးတွင် အလံ 110 ခုရှိသည်။ **မူလတန်ဖိုး** ဆိုသည်မှာ သတ်မှတ်ချက်တွင် ပါရှိသော မူလတန်ဖိုးဖြစ်ပြီး DB မှ ထပ်ဆင့်သတ်မှတ်ထားသော တန်ဖိုး သို့မဟုတ် ပတ်ဝန်းကျင် ကိန်းရှင် မရှိသည့်အခါ အသုံးပြုသည့် တန်ဖိုးဖြစ်သည်။ ### လုံခြုံရေး (10) @@ -94,7 +94,7 @@ boolean flag တစ်ခု၏ ထိရောက်သောတန်ဖို | `RADAR_ENABLED` | boolean | `false` | OmniRoute Radar မော်ဂျူး (ကတ်တလောက် feed မျက်နှာပြင်များနှင့် တစ်ပြေးညီပြုလုပ်ခြင်း) ကို ဖွင့်သည်။ မူလအားဖြင့် ပိတ်ထားသည်။ ဖွင့်ခြင်းသည် UI ကိုသာ အသုံးပြုခွင့်ပေးပြီး ဒေတာတစ်ပြေးညီပြုလုပ်ခြင်းအတွက် သီးခြားခွင့်ပြုချက်ပေးရန် လိုအပ်နေဆဲဖြစ်သည်။ | | `MODEL_ENDPOINT_CAPABILITY_GATE_ENABLED` | boolean | `false` | ယုံကြည်ရသော မော်ဒယ် endpoint tag များ (provider က ကြေညာထားသည့်၊ အထူးပြု မော်ဒယ် ID မှ ကောက်ချက်ချထားသည့် သို့မဟုတ် အော်ပရေတာက သတ်မှတ်ထားသည့် tag များ) ကို မဖြစ်မနေလိုက်နာစေသည်။ မော်ဒယ်က ဝန်ဆောင်မှုမပေးသော endpoint သို့ ပေးပို့သည့် တောင်းဆိုမှုကို `/v1/chat/completions`, `/v1/embeddings`, `/v1/rerank`, `/v1/images/generations`, `/v1/videos/generations`, `/v1/music/generations` နှင့် `/v1/audio/*` တို့တွင် dispatch မလုပ်မီ HTTP 400 ဖြင့် ငြင်းပယ်သည်။ Tag တပ်ထားသော endpoint များအတွက် OmniRoute adapter မရှိသည့် မော်ဒယ်များကို `/v1/models` မှ ဖျောက်ထားပြီး ၎င်းတို့အတွက် ဒက်ရှ်ဘုတ် မော်ဒယ်စမ်းသပ်မှုကို 422 ဖြင့် တုံ့ပြန်သည် (#16929)။ မူလအားဖြင့် ပိတ်ထားသည်။ Endpoint tag များသည် အချက်အလက်ပေးရန်အတွက်သာ ဆက်လက်တည်ရှိပြီး dispatch လုပ်ဆောင်ပုံမှာ မပြောင်းလဲပါ။ | -### လုပ်ဆောင်ချိန် (53) +### လုပ်ဆောင်ချိန် (52) | Key | Type | Default | Restart | Description | | ---------------------------------------------- | ------- | ------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | @@ -141,7 +141,6 @@ boolean flag တစ်ခု၏ ထိရောက်သောတန်ဖို | `KIRO_TOOL_CALL_REPAIR_ENABLED` | boolean | `true` | | Streaming Kiro response တစ်ခုသည် မြင်ရသော output မထုတ်ပေးမီ ပုံစံမမှန်သည့် `tool_call` wrapper (`input.name` သို့မဟုတ် `input.arguments` မပါရှိခြင်း) ကို ထုတ်လွှတ်ပါက ၎င်းကို ဖျောက်ထားပြီး turn ကို `invalid_kiro_tool_call` ဖြင့် အဆုံးသတ်မည့်အစား ပြင်ဆင်ရန် ညွှန်ကြားချက်တစ်ခုနှင့် request ကို တစ်ကြိမ် ပြန်ပို့ပါ။ Retry ကြောင့် Kiro request တစ်ခု ထပ်မံကုန်ကျမည်ဖြစ်ပြီး ဒုတိယအကြိမ် မအောင်မြင်ပါက turn ကို `kiro_tool_call_repair_retry_failed` ဖြင့် အဆုံးသတ်သည်။ ပုံသေအားဖြင့် ဖွင့်ထားသည်။ ပထမဆုံး ပုံစံမမှန်သည့် wrapper တွင်ပင် turn ကို မအောင်မြင်စေရန် `false` ဟု သတ်မှတ်ပါ။ | | `CATALOG_MIRROR_REFINEMENTS_ENABLED` | boolean | `false` | | `/v1/models` ရှိ `claude/` discovery mirror များနှင့် functional-gateway mirror များကို ပိုမိုတိကျအောင် ပြင်ဆင်ပါ။ ပုံသေအားဖြင့် ပိတ်ထားသည်- mirror pass နှစ်ခုစလုံးသည် ယခင်အတိုင်း အတိအကျ လုပ်ဆောင်သည်။ ဖွင့်ထားပါက `claude/` discovery mirror များတွင် စာရင်းပြုထားသော reasoning-effort variant များ (`-low`/`-medium`/`-high`/`-xhigh`) ကိုပါ ထည့်သွင်းပေးပြီး request path က ဖန်တီးမပေးနိုင်သည့် built-in `auto/*` id များနှင့် စာရင်းတွင် ရှိပြီးသား alias များကို ကျော်သွားသည်။ Functional-gateway mirror များသည် combo များနှင့် OmniRoute က ဖန်တီးထားသော id များ (`claude/`, `no-think/`) ကို ကျော်သွားပြီး owner ကို `owned_by` မှ ရယူကာ owner ကို ၎င်း၏ကိုယ်ပိုင် gateway အဖြစ် မည်သည့်အခါမျှ အသုံးမပြုဘဲ `blockedProviders` တွင် ပိတ်ထားသည့် gateway များနှင့် gateway တစ်ခု၏ authoritative live catalog ပြင်ပရှိ model များကို ကျော်သွားသည်။ | | ---------------------------------------------- | ------- | ------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `ANTHROPIC_COMPATIBLE_MID_CONVERSATION_SYSTEM` | boolean | `false` | | ပုံမှန် `anthropic-compatible-*` relay များ (claude → claude passthrough) တွင် စကားဝိုင်းအလယ်ရှိ `role: "system"` turn များကို top-level `system` parameter သို့ ရွှေ့တင်မည့်အစား `messages[]` အတွင်း ဆက်လက်ထားရှိပါ။ ထို့ကြောင့် cache လုပ်ထားသော prompt prefix သည် turn များတစ်လျှောက် တည်ငြိမ်နေမည်ဖြစ်သည်။ Client ကိုယ်တိုင် `mid-conversation-system-2026-04-07` `anthropic-beta` ကို ပို့ထားသည့်အခါတွင်သာ သက်ရောက်သည် (upstream သို့ မူလအတိုင်း forward လုပ်သည်)။ ပုံသေအားဖြင့် ပိတ်ထားသည်- beta မပါသော relay များသည် `role: "system"` message များကို 400 ဖြင့် ငြင်းပယ်သောကြောင့် system turn တိုင်းကို ယခင်အတိုင်း အတိအကျ ရွှေ့တင်သည်။ | | `MODELS_DEV_EFFORT_TIERS_ENABLED` | boolean | `false` | | Operator override နှင့် provider registry နှစ်ခုစလုံးက effort tier များ မကြေညာထားသည့်အခါ models.dev က ထုတ်ပြန်သော reasoning effort tier များကို အသုံးပြုပါ (သတ်မှတ်ထားသော effort ဝေါဟာရများအထိသာ ကန့်သတ်ထားသည်)။ ထို့အပြင် tier list အလွတ်တစ်ခုကို `/v1/models` `effort_tiers` နှင့် combo tier intersection များတွင် "tier မရှိ" ဟု သတ်မှတ်ပါ။ ပုံသေအားဖြင့် ပိတ်ထားသည်- `effort_tiers` နှင့် ဖြေရှင်းသတ်မှတ်ပြီးသော `supportedThinkingEfforts` တို့သည် ယခင်အတိုင်း အတိအကျ ဆက်ရှိနေသည်။ | | `AUTO_REQUEST_RESOLVED_CAPABILITIES` | boolean | `false` | | auto/* request များသည် ဖြေရှင်းသတ်မှတ်ထားသော model capability များ (vision၊ reasoning၊ thinking) နှင့် လေ့လာမှတ်တမ်းတင်ထားသော quality score များကို bulk read တစ်ကြိမ်တည်းဖြင့် ရယူကာ candidate pool ကို တည်ဆောက်သည် (#16419)။ ပုံသေအားဖြင့် ပိတ်ထားသည်- request လုပ်ချိန်ရှိ snapshot weight များသည် ယခင်အတိုင်း အတိအကျ ဆက်ရှိနေသည်။ ဖွင့်ထားပါက ထို signal များကို snapshot weight များတွက်ချက်ရာတွင် ထည့်သွင်းအသုံးပြုသဖြင့် candidate weight များနှင့် ရွေးချယ်မှု ပြောင်းလဲနိုင်သည်။ | | `AUTO_COMBO_KEYLESS_TRANSPORT_GATE` | boolean | `false` | | သယ်ယူပို့ဆောင်ရေးအတွက် စက်တွင်းဘရောက်ဇာ (`browserBacked`) သို့မဟုတ် စက်တွင်း၌ ထည့်သွင်းထားသော CLI (`isLocalCli`) လိုအပ်သည့် keyless (စစ်မှန်ကြောင်းအတည်ပြုမှုမလိုသော) provider များကို `auto/*` candidate pool ပြင်ပတွင် ထားရှိပါသည်။ ထို့ကြောင့် တုံ့ပြန်မှုရပ်တန့်နေသော member တစ်ခုက target တစ်ခုချင်းစီအတွက် သတ်မှတ်ထားသော အချိန်ကုန်ဆုံးသည်အထိ auto request တစ်ခုကို ရပ်ဆိုင်းမထားနိုင်ပါ (#17036)။ ပုံသေအားဖြင့် ပိတ်ထားသည်- keyless pool ကို ပြောင်းလဲခြင်းမရှိပါ။ တိုက်ရိုက် `/` call များသည် မည်သည့်အခြေအနေတွင်မဆို အလုပ်လုပ်ပါသည်။ | @@ -238,7 +237,7 @@ Flag တစ်ခုစီ၏ အမှန်တကယ်အသုံးပြ "requiresRestart": false, "warningLevel": "caution", }, - // ... flag 111 ခုလုံး + // ... flag 110 ခုလုံး ], "summary": { "total": 56, diff --git a/docs/i18n/ne/docs/reference/FEATURE_FLAGS.md b/docs/i18n/ne/docs/reference/FEATURE_FLAGS.md index 66065b219eb1..6ce63f80ba8a 100644 --- a/docs/i18n/ne/docs/reference/FEATURE_FLAGS.md +++ b/docs/i18n/ne/docs/reference/FEATURE_FLAGS.md @@ -44,7 +44,7 @@ ## फ्ल्याग सूची -६ वटा वर्गमा 111 फ्ल्यागहरू छन्। **पूर्वनिर्धारित** भनेको परिभाषामा तोकिएको पूर्वनिर्धारित मान हो — +६ वटा वर्गमा 110 फ्ल्यागहरू छन्। **पूर्वनिर्धारित** भनेको परिभाषामा तोकिएको पूर्वनिर्धारित मान हो — जुन DB ओभरराइड वा वातावरणीय चरमध्ये कुनै पनि उपलब्ध नभएको अवस्थामा प्रयोग गरिन्छ। ### सुरक्षा (10) @@ -109,7 +109,7 @@ | `RADAR_ENABLED` | boolean | `false` | OmniRoute Radar मोड्युल (क्याटलग फिड स्क्रिनहरू र समक्रमण) सक्षम गर्नुहोस्। पूर्वनिर्धारित रूपमा बन्द; यसलाई सक्षम गर्दा UI मात्र अनलक हुन्छ — डेटा समक्रमणका लागि छुट्टै सहमति अझै आवश्यक हुन्छ। | | `MODEL_ENDPOINT_CAPABILITY_GATE_ENABLED` | boolean | `false` | विश्वसनीय मोडेल endpoint ट्यागहरू (प्रदायकद्वारा घोषित, विशेष मोडेल id बाट अनुमानित वा सञ्चालकद्वारा सेट गरिएको) लागू गर्नुहोस्: मोडेलले सेवा नदिने endpoint मा पठाइएको अनुरोधलाई `/v1/chat/completions`, `/v1/embeddings`, `/v1/rerank`, `/v1/images/generations`, `/v1/videos/generations`, `/v1/music/generations` र `/v1/audio/*` मा डिस्प्याच गर्नुअघि HTTP 400 सहित अस्वीकार गर्नुहोस्; ट्याग गरिएका endpoint हरूका लागि OmniRoute एडाप्टर नभएका मोडेलहरूलाई `/v1/models` बाट लुकाउनुहोस्; तिनका लागि ड्यासबोर्ड मोडेल परीक्षणमा 422 प्रतिक्रिया दिनुहोस् (#16929)। पूर्वनिर्धारित रूपमा बन्द: endpoint ट्यागहरू सूचनामूलक नै रहन्छन् र डिस्प्याच अपरिवर्तित रहन्छ। | -### रनटाइम (53) +### रनटाइम (52) | कुञ्जी | प्रकार | पूर्वनिर्धारित | पुनः सुरु | विवरण | | ---------------------------------------------- | ------- | -------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | @@ -156,7 +156,6 @@ | `KIRO_TOOL_CALL_REPAIR_ENABLED` | boolean | `true` | | कुनै स्ट्रिमिङ Kiro प्रतिक्रियाले कुनै पनि दृश्यात्मक आउटपुटअघि विकृत `tool_call` र्यापर (`input.name` वा `input.arguments` नभएको) उत्सर्जन गर्दा, टर्नलाई `invalid_kiro_tool_call` सहित अन्त्य गर्नुको सट्टा त्यसलाई रोक्नुहोस् र सुधारात्मक निर्देशनसहित अनुरोध एकपटक पुनः पठाउनुहोस्। पुनःप्रयासमा एउटा अतिरिक्त Kiro अनुरोध लाग्छ; दोस्रो असफलताले टर्नलाई `kiro_tool_call_repair_retry_failed` सहित अन्त्य गर्छ। पूर्वनिर्धारित रूपमा सक्रिय। पहिलो विकृत र्यापरमै टर्न असफल बनाउन `false` मा सेट गर्नुहोस्। | | `CATALOG_MIRROR_REFINEMENTS_ENABLED` | boolean | `false` | | `/v1/models` मा `claude/` डिस्कभरी र फङ्सनल-गेटवे मिररहरू परिष्कृत गर्नुहोस्। पूर्वनिर्धारित रूपमा बन्द: दुवै मिरर पासहरू पहिलेझैँ ठ्याक्कै व्यवहार गर्छन्। सक्रिय हुँदा: `claude/` डिस्कभरी मिररहरूले सूचीबद्ध रिजनिङ-एफर्ट भेरियन्टहरू (`-low`/`-medium`/`-high`/`-xhigh`) पनि समेट्छन्, अनुरोध पथले मूर्त रूप दिन नसक्ने अन्तर्निर्मित `auto/*` आईडीहरू छोड्छन् र पहिले नै सूचीबद्ध एलियस पनि छोड्छन्; फङ्सनल-गेटवे मिररहरूले कम्बोहरू र OmniRoute-द्वारा संश्लेषित आईडीहरू (`claude/`, `no-think/`) छोड्छन्, स्वामित्वकर्ता `owned_by` बाट लिन्छन्, स्वामित्वकर्तालाई कहिल्यै आफ्नै गेटवेका रूपमा प्रयोग गर्दैनन्, `blockedProviders` मा निष्क्रिय गरिएका गेटवेहरू र कुनै गेटवेको आधिकारिक लाइभ क्याटलगबाहिरका मोडेलहरू छोड्छन्। | | ---------------------------------------------- | ------- | ------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `ANTHROPIC_COMPATIBLE_MID_CONVERSATION_SYSTEM` | boolean | `false` | | साधारण `anthropic-compatible-*` रिलेहरूमा (claude → claude पासथ्रु), वार्ताको बीचका `role: "system"` टर्नहरूलाई शीर्ष-स्तरको `system` प्यारामिटरमा उचाल्नुको सट्टा `messages[]` भित्रै राख्नुहोस्, जसले गर्दा क्यास गरिएको प्रम्प्ट प्रिफिक्स टर्नहरूबीच स्थिर रहन्छ। क्लाइन्ट स्वयंले `mid-conversation-system-2026-04-07` `anthropic-beta` पठाएको अवस्थामा मात्र लागू हुन्छ (अपस्ट्रिममा जस्ताको तस्तै फर्वार्ड गरिन्छ)। पूर्वनिर्धारित रूपमा बन्द: प्रत्येक सिस्टम टर्न पहिलेझैँ ठ्याक्कै उचालिन्छ, किनभने बिटा नभएका रिलेहरूले `role: "system"` सन्देशहरूलाई 400 सहित अस्वीकार गर्छन्। | | `MODELS_DEV_EFFORT_TIERS_ENABLED` | boolean | `false` | | अपरेटर ओभरराइड वा प्रोभाइडर रजिस्ट्रीमध्ये कसैले पनि कुनै टियर घोषणा नगरेको अवस्थामा models.dev ले प्रकाशित गर्ने रिजनिङ एफर्ट टियरहरू (ज्ञात एफर्ट शब्दावलीमा सीमित) प्रयोग गर्नुहोस्, र `/v1/models` को `effort_tiers` तथा कम्बो टियर इन्टरसेक्सनहरूमा घोषित खाली टियर सूचीलाई "कुनै टियर छैन" का रूपमा लिनुहोस्। पूर्वनिर्धारित रूपमा बन्द: `effort_tiers` र रिजोल्भ गरिएको `supportedThinkingEfforts` पहिलेझैँ ठ्याक्कै रहन्छन्। | | `AUTO_REQUEST_RESOLVED_CAPABILITIES` | boolean | `false` | | auto/* अनुरोधहरूले एकल बल्क रिडमा रिजोल्भ गरिएका मोडेल क्षमताहरू (भिजन, रिजनिङ, थिङ्किङ) र अवलोकन गरिएका गुणस्तर स्कोरहरू प्रयोग गरी उम्मेदवार पूल निर्माण गर्छन् (#16419)। पूर्वनिर्धारित रूपमा बन्द: अनुरोध-समयका स्न्यापसट वेटहरू पहिलेझैँ ठ्याक्कै रहन्छन्। सक्रिय हुँदा: ती संकेतहरू स्न्यापसट वेटहरूमा समावेश हुन्छन्, त्यसैले उम्मेदवार वेटहरू र चयन परिवर्तन हुन सक्छन्। | | `AUTO_COMBO_KEYLESS_TRANSPORT_GATE` | बुलियन | `false` | | स्थानीय ब्राउजर (`browserBacked`) वा स्थानीय रूपमा स्थापना गरिएको CLI (`isLocalCli`) आवश्यक पर्ने ट्रान्सपोर्ट भएका पासवर्डरहित (प्रमाणीकरण-रहित) प्रदायकहरूलाई `auto/*` उम्मेदवार समूहबाट बाहिर राख्नुहोस्, ताकि अड्किएको सदस्यले प्रति-लक्ष्य समयसीमा नसकिँदासम्म स्वतः अनुरोधलाई रोक्न नसकोस् (#17036)। पूर्वनिर्धारित रूपमा बन्द: पासवर्डरहित समूह अपरिवर्तित रहन्छ। प्रत्यक्ष `/` कलहरू दुवै अवस्थामा काम गर्छन्। | @@ -253,7 +252,7 @@ "requiresRestart": false, "warningLevel": "caution", }, - // ... सबै 111 फ्ल्यागहरू + // ... सबै 110 फ्ल्यागहरू ], "summary": { "total": 56, diff --git a/docs/i18n/nl/docs/reference/FEATURE_FLAGS.md b/docs/i18n/nl/docs/reference/FEATURE_FLAGS.md index a915cc920314..0e32b193c316 100644 --- a/docs/i18n/nl/docs/reference/FEATURE_FLAGS.md +++ b/docs/i18n/nl/docs/reference/FEATURE_FLAGS.md @@ -43,7 +43,7 @@ Een boolean-vlag wordt als **ingeschakeld** beschouwd wanneer de effectieve waar ## Vlaggencatalogus -111 vlaggen verdeeld over 6 categorieën. **Standaardwaarde** is de standaardwaarde uit de definitie — de waarde +110 vlaggen verdeeld over 6 categorieën. **Standaardwaarde** is de standaardwaarde uit de definitie — de waarde die wordt gebruikt wanneer er geen database-overschrijving en geen omgevingsvariabele aanwezig is. ### Beveiliging (10) @@ -108,7 +108,7 @@ die wordt gebruikt wanneer er geen database-overschrijving en geen omgevingsvari | `RADAR_ENABLED` | boolean | `false` | Schakel de OmniRoute Radar-module in (schermen voor catalogusfeeds en synchronisatie). Standaard uitgeschakeld; inschakelen ontgrendelt alleen de UI — gegevenssynchronisatie blijft een afzonderlijke opt-in. | | `MODEL_ENDPOINT_CAPABILITY_GATE_ENABLED` | boolean | `false` | Dwing vertrouwde modeleindpunttags af (opgegeven door de provider, afgeleid van een gespecialiseerd model-id of ingesteld door de operator): wijs een aanvraag die naar een eindpunt wordt verzonden dat het model niet bedient vóór verzending af met HTTP 400 op `/v1/chat/completions`, `/v1/embeddings`, `/v1/rerank`, `/v1/images/generations`, `/v1/videos/generations`, `/v1/music/generations` en `/v1/audio/*`; verberg modellen waarvan de getagde eindpunten geen OmniRoute-adapter hebben in `/v1/models`; beantwoord de dashboardmodeltest voor deze modellen met 422 (#16929). Standaard uitgeschakeld: eindpunttags blijven informatief en de verzending blijft ongewijzigd. | -### Runtime (53) +### Runtime (52) | Sleutel | Type | Standaard | Herstart | Beschrijving | | ---------------------------------------------- | ------- | --------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -155,7 +155,6 @@ die wordt gebruikt wanneer er geen database-overschrijving en geen omgevingsvari | `KIRO_TOOL_CALL_REPAIR_ENABLED` | boolean | `true` | | Wanneer een streamend Kiro-antwoord vóór zichtbare uitvoer een ongeldige `tool_call`-wrapper produceert (waarbij `input.name` of `input.arguments` ontbreekt), onderdruk deze dan en verzend het verzoek eenmaal opnieuw met een corrigerende instructie, in plaats van de beurt te beëindigen met `invalid_kiro_tool_call`. De nieuwe poging kost één extra Kiro-verzoek; bij een tweede fout eindigt de beurt met `kiro_tool_call_repair_retry_failed`. Standaard ingeschakeld. Stel in op `false` om de beurt bij de eerste ongeldige wrapper te beëindigen. | | `CATALOG_MIRROR_REFINEMENTS_ENABLED` | boolean | `false` | | Verfijn de discovery- en functional-gateway-mirrors voor `claude/` op `/v1/models`. Standaard uitgeschakeld: beide mirror-passes gedragen zich precies zoals voorheen. Indien ingeschakeld: discovery-mirrors voor `claude/` omvatten ook vermelde varianten voor redeneerinspanning (`-low`/`-medium`/`-high`/`-xhigh`), slaan ingebouwde `auto/*`-id's over die het verzoekpad niet kan materialiseren en slaan een reeds vermelde alias over; functional-gateway-mirrors slaan combo's en door OmniRoute gegenereerde id's (`claude/`, `no-think/`) over, nemen de eigenaar uit `owned_by`, gebruiken de eigenaar nooit als diens eigen gateway, slaan gateways over die in `blockedProviders` zijn uitgeschakeld en modellen die niet in de gezaghebbende livecatalogus van een gateway staan. | | ---------------------------------------------- | ------- | ------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `ANTHROPIC_COMPATIBLE_MID_CONVERSATION_SYSTEM` | boolean | `false` | | Houd bij gewone `anthropic-compatible-*`-relays (claude → claude-passthrough) `role: "system"`-beurten midden in een gesprek binnen `messages[]` in plaats van ze naar de `system`-parameter op het hoogste niveau te verplaatsen, zodat het gecachte promptvoorvoegsel tussen beurten stabiel blijft. Alleen van toepassing wanneer de client zelf de `mid-conversation-system-2026-04-07`-`anthropic-beta` heeft verzonden (ongewijzigd upstream doorgestuurd). Standaard uitgeschakeld: elke systeembeurt wordt precies zoals voorheen verplaatst, omdat relays zonder de bèta berichten met `role: "system"` afwijzen met 400. | | `MODELS_DEV_EFFORT_TIERS_ENABLED` | boolean | `false` | | Gebruik de niveaus voor redeneerinspanning die models.dev publiceert (beperkt tot de bekende inspanningswaarden) wanneer noch een override van een beheerder, noch het providerregister niveaus definieert, en behandel een gedeclareerde lege niveaulijst als 'geen niveaus' in `/v1/models` `effort_tiers` en doorsneden van combo-niveaus. Standaard uitgeschakeld: `effort_tiers` en de herleide `supportedThinkingEfforts` blijven precies zoals voorheen. | | `AUTO_REQUEST_RESOLVED_CAPABILITIES` | boolean | `false` | | auto/*-verzoeken bouwen de kandidatenpool op met herleide modelmogelijkheden (beeld, redeneren, gedachtegang) en waargenomen kwaliteitsscores in één bulkleesbewerking (#16419). Standaard uitgeschakeld: de snapshotgewichten tijdens het verzoek blijven precies zoals voorheen. Indien ingeschakeld: deze signalen worden meegenomen in de snapshotgewichten, waardoor de gewichten en selectie van kandidaten kunnen veranderen. | | `AUTO_COMBO_KEYLESS_TRANSPORT_GATE` | boolean | `false` | | Sluit sleutelloze providers (zonder authenticatie) waarvan het transport een lokale browser (`browserBacked`) of een lokaal geïnstalleerde CLI (`isLocalCli`) vereist, uit van de kandidatenpool `auto/*`, zodat een vastgelopen lid een automatisch verzoek niet kan blokkeren tot de time-out per doel is bereikt (#17036). Standaard uitgeschakeld: de sleutelloze pool blijft ongewijzigd. Rechtstreekse aanroepen van `/` werken in beide gevallen. | @@ -252,7 +251,7 @@ Retourneert elke flag met de effectieve waarde, bron en een samenvatting. "requiresRestart": false, "warningLevel": "caution", }, - // ... alle 111 flags + // ... alle 110 flags ], "summary": { "total": 56, diff --git a/docs/i18n/no/docs/reference/FEATURE_FLAGS.md b/docs/i18n/no/docs/reference/FEATURE_FLAGS.md index f189c0d2ff79..1073d42516d3 100644 --- a/docs/i18n/no/docs/reference/FEATURE_FLAGS.md +++ b/docs/i18n/no/docs/reference/FEATURE_FLAGS.md @@ -44,7 +44,7 @@ Et boolsk flagg anses som **aktivert** når dets effektive verdi er `"true"`, ## Flaggkatalog -111 flagg fordelt på 6 kategorier. **Standardverdi** er standardverdien i definisjonen — verdien +110 flagg fordelt på 6 kategorier. **Standardverdi** er standardverdien i definisjonen — verdien som brukes når verken en databaseoverstyring eller en miljøvariabel finnes. ### Sikkerhet (10) @@ -109,7 +109,7 @@ som brukes når verken en databaseoverstyring eller en miljøvariabel finnes. | `RADAR_ENABLED` | boolsk | `false` | Aktiver OmniRoute Radar-modulen (skjermbilder for katalogstrømmer og synkronisering). Av som standard; aktivering låser bare opp brukergrensesnittet — datasynkronisering krever fortsatt separat samtykke. | | `MODEL_ENDPOINT_CAPABILITY_GATE_ENABLED` | boolsk | `false` | Håndhev klarerte endepunktstagger for modeller (oppgitt av leverandøren, utledet fra en spesialisert modell-ID eller angitt av operatøren): Avvis en forespørsel som sendes til et endepunkt modellen ikke betjener, med HTTP 400 før videresending på `/v1/chat/completions`, `/v1/embeddings`, `/v1/rerank`, `/v1/images/generations`, `/v1/videos/generations`, `/v1/music/generations` og `/v1/audio/*`; skjul modeller med taggede endepunkter som ikke har noen OmniRoute-adapter, fra `/v1/models`; besvar modelltesten i kontrollpanelet med 422 for disse (#16929). Av som standard: Endepunktstaggene forblir kun informative, og videresendingen er uendret. | -### Kjøretid (53) +### Kjøretid (52) | Nøkkel | Type | Standardverdi | Omstart | Beskrivelse | | ---------------------------------------------- | ------- | ------------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -156,7 +156,6 @@ som brukes når verken en databaseoverstyring eller en miljøvariabel finnes. | `KIRO_TOOL_CALL_REPAIR_ENABLED` | boolean | `true` | | Når et strømmet Kiro-svar sender en ugyldig utformet `tool_call`-innpakning (mangler `input.name` eller `input.arguments`) før noe synlig resultat, undertrykkes den, og forespørselen sendes på nytt én gang med en korrigerende instruksjon i stedet for å avslutte turen med `invalid_kiro_tool_call`. Det nye forsøket koster én ekstra Kiro-forespørsel; en ny feil avslutter turen med `kiro_tool_call_repair_retry_failed`. Aktivert som standard. Sett til `false` for å avslutte turen ved den første ugyldige innpakningen. | | `CATALOG_MIRROR_REFINEMENTS_ENABLED` | boolean | `false` | | Finjuster speilene for oppdagelse av `claude/` og funksjonelle gatewayer på `/v1/models`. Deaktivert som standard: Begge speilingspassene fungerer nøyaktig som før. Når aktivert: Oppdagelsesspeil for `claude/` dekker også oppførte varianter av resonneringsinnsats (`-low`/`-medium`/`-high`/`-xhigh`), hopper over innebygde `auto/*`-ID-er som forespørselsbanen ikke kan materialisere, og hopper over et alias som allerede er oppført. Speil for funksjonelle gatewayer hopper over kombinasjoner og ID-er syntetisert av OmniRoute (`claude/`, `no-think/`), henter eieren fra `owned_by`, bruker aldri eieren som sin egen gateway, og hopper over gatewayer som er deaktivert i `blockedProviders`, samt modeller utenfor en gateways autoritative livekatalog. | | ---------------------------------------------- | ------- | ------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `ANTHROPIC_COMPATIBLE_MID_CONVERSATION_SYSTEM` | boolean | `false` | | På vanlige `anthropic-compatible-*`-reléer (claude → claude-direktevideresending) beholdes `role: "system"`-turer midt i samtalen i `messages[]` i stedet for å løftes til `system`-parameteren på toppnivå, slik at det bufrede ledetekstprefikset forblir stabilt på tvers av turer. Gjelder bare når klienten selv sendte `mid-conversation-system-2026-04-07`-verdien for `anthropic-beta` (videresendt oppstrøms som den er). Deaktivert som standard: Hver systemtur løftes nøyaktig som før, fordi reléer uten betaen avviser meldinger med `role: "system"` med 400. | | `MODELS_DEV_EFFORT_TIERS_ENABLED` | boolean | `false` | | Bruk nivåene for resonneringsinnsats som models.dev publiserer (begrenset til det kjente vokabularet for innsats), når verken en operatøroverstyring eller leverandørregisteret deklarerer noen, og behandle en deklarert tom nivåliste som «ingen nivåer» i `/v1/models` `effort_tiers` og snittet mellom kombinasjonsnivåer. Deaktivert som standard: `effort_tiers` og den utledede `supportedThinkingEfforts` forblir nøyaktig som før. | | `AUTO_REQUEST_RESOLVED_CAPABILITIES` | boolean | `false` | | auto/*-forespørsler bygger kandidatpoolen med utledede modellfunksjoner (syn, resonnering, tenkning) og observerte kvalitetspoeng i én samlet lesing (#16419). Deaktivert som standard: Vektene i øyeblikksbildet på forespørselstidspunktet forblir nøyaktig som før. Når aktivert: Disse signalene inngår i vektene i øyeblikksbildet, slik at kandidatvekter og utvalg kan endres. | | `AUTO_COMBO_KEYLESS_TRANSPORT_GATE` | boolean | `false` | | Hold nøkkelløse leverandører (uten autentisering) der transporten krever en lokal nettleser (`browserBacked`) eller en lokalt installert CLI (`isLocalCli`), utenfor kandidatgruppen `auto/*`, slik at et medlem som henger, ikke kan blokkere en automatisk forespørsel frem til tidsavbruddet per mål (#17036). Av som standard: Den nøkkelløse gruppen er uendret. Direkte kall til `/` fungerer uansett. | @@ -253,7 +252,7 @@ Returnerer hvert flagg med gjeldende verdi, kilde og et sammendrag. "requiresRestart": false, "warningLevel": "caution", }, - // ... alle 111 flagg + // ... alle 110 flagg ], "summary": { "total": 56, diff --git a/docs/i18n/or/docs/reference/FEATURE_FLAGS.md b/docs/i18n/or/docs/reference/FEATURE_FLAGS.md index ea1f4c52214d..e587cb10239a 100644 --- a/docs/i18n/or/docs/reference/FEATURE_FLAGS.md +++ b/docs/i18n/or/docs/reference/FEATURE_FLAGS.md @@ -33,7 +33,7 @@ ## ଫ୍ଲାଗ୍ କ୍ୟାଟାଲଗ୍ -6ଟି ବର୍ଗରେ 111ଟି ଫ୍ଲାଗ୍। **ଡିଫଲ୍ଟ** ହେଉଛି ସଂଜ୍ଞାର ଡିଫଲ୍ଟ — ଯେତେବେଳେ କୌଣସି DB ଓଭରରାଇଡ୍ କିମ୍ବା ପରିବେଶ ଭେରିଏବଲ୍ ଉପଲବ୍ଧ ନଥାଏ, ସେତେବେଳେ ବ୍ୟବହୃତ ହେଉଥିବା ମୂଲ୍ୟ। +6ଟି ବର୍ଗରେ 110ଟି ଫ୍ଲାଗ୍। **ଡିଫଲ୍ଟ** ହେଉଛି ସଂଜ୍ଞାର ଡିଫଲ୍ଟ — ଯେତେବେଳେ କୌଣସି DB ଓଭରରାଇଡ୍ କିମ୍ବା ପରିବେଶ ଭେରିଏବଲ୍ ଉପଲବ୍ଧ ନଥାଏ, ସେତେବେଳେ ବ୍ୟବହୃତ ହେଉଥିବା ମୂଲ୍ୟ। ### ସୁରକ୍ଷା (10) @@ -97,7 +97,7 @@ | `RADAR_ENABLED` | boolean | `false` | OmniRoute Radar ମଡ୍ୟୁଲ୍ (କ୍ୟାଟାଲଗ୍ ଫିଡ୍ ସ୍କ୍ରିନ୍ ଏବଂ ସିଙ୍କ୍) ସକ୍ଷମ କରନ୍ତୁ। ଡିଫଲ୍ଟ ଭାବେ ବନ୍ଦ; ଏହାକୁ ସକ୍ଷମ କଲେ କେବଳ UI ଅନ୍ଲକ୍ ହୁଏ — ଡାଟା ସିଙ୍କ୍ ଏକ ପୃଥକ ଇଚ୍ଛାଧୀନ ବିକଳ୍ପ ଭାବେ ରହେ। | | `MODEL_ENDPOINT_CAPABILITY_GATE_ENABLED` | boolean | `false` | ବିଶ୍ୱସ୍ତ ମଡେଲ୍ ଏଣ୍ଡପଏଣ୍ଟ ଟ୍ୟାଗ୍ଗୁଡ଼ିକୁ (ପ୍ରଦାନକାରୀ-ଘୋଷିତ, କୌଣସି ବିଶେଷ ମଡେଲ୍ idରୁ ଅନୁମାନିତ, କିମ୍ବା ଅପରେଟର୍ଙ୍କ ଦ୍ୱାରା ସେଟ୍ କରାଯାଇଥିବା) ପ୍ରବର୍ତ୍ତନ କରନ୍ତୁ: `/v1/chat/completions`, `/v1/embeddings`, `/v1/rerank`, `/v1/images/generations`, `/v1/videos/generations`, `/v1/music/generations` ଏବଂ `/v1/audio/*`ରେ ମଡେଲ୍ ସେବା ପ୍ରଦାନ କରୁନଥିବା ଏକ ଏଣ୍ଡପଏଣ୍ଟକୁ ପଠାଯାଇଥିବା ଅନୁରୋଧକୁ ଡିସ୍ପ୍ୟାଚ୍ ପୂର୍ବରୁ HTTP 400 ସହିତ ପ୍ରତ୍ୟାଖ୍ୟାନ କରନ୍ତୁ; ଯେଉଁ ମଡେଲ୍ଗୁଡ଼ିକର ଟ୍ୟାଗ୍ ହୋଇଥିବା ଏଣ୍ଡପଏଣ୍ଟ ପାଇଁ କୌଣସି OmniRoute ଆଡାପ୍ଟର୍ ନାହିଁ, ସେଗୁଡ଼ିକୁ `/v1/models`ରୁ ଲୁଚାନ୍ତୁ; ସେଗୁଡ଼ିକ ପାଇଁ ଡ୍ୟାଶବୋର୍ଡ ମଡେଲ୍ ପରୀକ୍ଷାର ଉତ୍ତର 422 ସହିତ ଦିଅନ୍ତୁ (#16929)। ଡିଫଲ୍ଟ ଭାବେ ବନ୍ଦ: ଏଣ୍ଡପଏଣ୍ଟ ଟ୍ୟାଗ୍ଗୁଡ଼ିକ କେବଳ ସୂଚନାମୂଳକ ରହେ ଏବଂ ଡିସ୍ପ୍ୟାଚ୍ ଅପରିବର୍ତ୍ତିତ ରହେ। | -### ରନ୍ଟାଇମ୍ (53) +### ରନ୍ଟାଇମ୍ (52) | କୀ | ପ୍ରକାର | ଡିଫଲ୍ଟ | ପୁନଃଆରମ୍ଭ | ବିବରଣୀ | | ---------------------------------------------- | ------- | ------- | --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -144,7 +144,6 @@ | `KIRO_TOOL_CALL_REPAIR_ENABLED` | boolean | `true` | | କୌଣସି ଦୃଶ୍ୟମାନ ଆଉଟପୁଟ୍ ପୂର୍ବରୁ ଏକ ଷ୍ଟ୍ରିମିଂ Kiro ପ୍ରତିକ୍ରିୟା ବିକୃତ `tool_call` ରାପର୍ (`input.name` କିମ୍ବା `input.arguments` ଅନୁପସ୍ଥିତ) ନିର୍ଗତ କଲେ, `invalid_kiro_tool_call` ସହିତ ଟର୍ନ୍ ଶେଷ କରିବା ପରିବର୍ତ୍ତେ ଏହାକୁ ଦମନ କରନ୍ତୁ ଏବଂ ଏକ ସଂଶୋଧନାତ୍ମକ ନିର୍ଦ୍ଦେଶ ସହିତ ଅନୁରୋଧଟିକୁ ଥରେ ପୁନଃ ପଠାନ୍ତୁ। ପୁନଃଚେଷ୍ଟା ପାଇଁ ଗୋଟିଏ ଅତିରିକ୍ତ Kiro ଅନୁରୋଧ ଲାଗେ; ଦ୍ୱିତୀୟ ବିଫଳତା `kiro_tool_call_repair_retry_failed` ସହିତ ଟର୍ନ୍କୁ ଶେଷ କରେ। ଡିଫଲ୍ଟ ଭାବେ ଚାଲୁ। ପ୍ରଥମ ବିକୃତ ରାପର୍ରେ ହିଁ ଟର୍ନ୍କୁ ବିଫଳ କରିବା ପାଇଁ `false` ସେଟ୍ କରନ୍ତୁ। | | `CATALOG_MIRROR_REFINEMENTS_ENABLED` | boolean | `false` | | `/v1/models`ରେ `claude/` ଆବିଷ୍କାର ଏବଂ କାର୍ଯ୍ୟକ୍ଷମ-ଗେଟୱେ ମିରର୍ଗୁଡ଼ିକୁ ପରିଷ୍କୃତ କରନ୍ତୁ। ଡିଫଲ୍ଟ ଭାବେ ବନ୍ଦ: ଉଭୟ ମିରର୍ ପାସ୍ ପୂର୍ବପରି ଠିକ୍ ସମାନ ଭାବେ କାମ କରେ। ଚାଲୁ ଥିଲେ: `claude/` ଆବିଷ୍କାର ମିରର୍ଗୁଡ଼ିକ ତାଲିକାଭୁକ୍ତ ରିଜନିଂ-ଏଫର୍ଟ ଭାରିଆଣ୍ଟଗୁଡ଼ିକୁ (`-low`/`-medium`/`-high`/`-xhigh`) ମଧ୍ୟ ଅନ୍ତର୍ଭୁକ୍ତ କରେ, ଅନୁରୋଧ ପଥ ସାକାର କରିପାରୁ ନଥିବା ବିଲ୍ଟ-ଇନ୍ `auto/*` idଗୁଡ଼ିକୁ ଏବଂ ପୂର୍ବରୁ ତାଲିକାଭୁକ୍ତ ଏକ ଉପନାମକୁ ଏଡ଼ାଏ; କାର୍ଯ୍ୟକ୍ଷମ-ଗେଟୱେ ମିରର୍ଗୁଡ଼ିକ କମ୍ବୋ ଏବଂ OmniRoute-ସିନ୍ଥେସାଇଜ୍ଡ୍ idଗୁଡ଼ିକୁ (`claude/`, `no-think/`) ଏଡ଼ାଏ, `owned_by`ରୁ ମାଲିକ ନିଏ, ମାଲିକକୁ କେବେ ମଧ୍ୟ ତାହାର ନିଜସ୍ୱ ଗେଟୱେ ଭାବେ ବ୍ୟବହାର କରେ ନାହିଁ, `blockedProviders`ରେ ଅକ୍ଷମ କରାଯାଇଥିବା ଗେଟୱେଗୁଡ଼ିକୁ ଏବଂ ଏକ ଗେଟୱେର ପ୍ରାମାଣିକ ଲାଇଭ୍ କ୍ୟାଟାଲଗ୍ ବାହାରେ ଥିବା ମଡେଲ୍ଗୁଡ଼ିକୁ ଏଡ଼ାଏ। | | ---------------------------------------------- | ------- | ------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `ANTHROPIC_COMPATIBLE_MID_CONVERSATION_SYSTEM` | boolean | `false` | | ସାଧାରଣ `anthropic-compatible-*` ରିଲେଗୁଡ଼ିକରେ (claude → claude ପାସ୍ଥ୍ରୁ), ମଧ୍ୟ-ବାର୍ତ୍ତାଳାପ `role: "system"` ଟର୍ନ୍ଗୁଡ଼ିକୁ ଶୀର୍ଷ-ସ୍ତରୀୟ `system` ପାରାମିଟର୍କୁ ଉଠାଇବା ପରିବର୍ତ୍ତେ `messages[]` ମଧ୍ୟରେ ରଖନ୍ତୁ, ଯାହାଦ୍ୱାରା କ୍ୟାଶ୍ ହୋଇଥିବା ପ୍ରମ୍ପ୍ଟ ପ୍ରିଫିକ୍ସ ଟର୍ନ୍ଗୁଡ଼ିକ ମଧ୍ୟରେ ସ୍ଥିର ରହେ। କେବଳ କ୍ଲାଏଣ୍ଟ ନିଜେ `mid-conversation-system-2026-04-07` `anthropic-beta` ପଠାଇଥିଲେ ଏହା ପ୍ରଯୁଜ୍ୟ (ଯଥା-ଅବସ୍ଥାରେ ଅପ୍ଷ୍ଟ୍ରିମ୍କୁ ଫରୱାର୍ଡ କରାଯାଏ)। ଡିଫଲ୍ଟ ଭାବେ ବନ୍ଦ: ପ୍ରତ୍ୟେକ ସିଷ୍ଟମ୍ ଟର୍ନ୍ ପୂର୍ବପରି ଠିକ୍ ସେହିଭଳି ଉପରକୁ ଉଠାଯାଏ, କାରଣ ବିଟା ବିନା ରିଲେଗୁଡ଼ିକ `role: "system"` ବାର୍ତ୍ତାଗୁଡ଼ିକୁ 400 ସହିତ ପ୍ରତ୍ୟାଖ୍ୟାନ କରନ୍ତି। | | `MODELS_DEV_EFFORT_TIERS_ENABLED` | boolean | `false` | | ଯେତେବେଳେ କୌଣସି ଅପରେଟର୍ ଓଭର୍ରାଇଡ୍ କିମ୍ବା ପ୍ରଦାନକାରୀ ରେଜିଷ୍ଟ୍ରି କୌଣସି ସ୍ତର ଘୋଷଣା କରେ ନାହିଁ, ସେତେବେଳେ models.dev ପ୍ରକାଶ କରୁଥିବା ରିଜନିଂ ଏଫର୍ଟ ସ୍ତରଗୁଡ଼ିକୁ (ଜଣାଶୁଣା ଏଫର୍ଟ ଶବ୍ଦାବଳୀ ପର୍ଯ୍ୟନ୍ତ ସୀମିତ) ବ୍ୟବହାର କରନ୍ତୁ ଏବଂ `/v1/models` `effort_tiers` ଓ କମ୍ବୋ ସ୍ତର ଇଣ୍ଟର୍ସେକ୍ସନ୍ଗୁଡ଼ିକରେ ଘୋଷିତ ଖାଲି ସ୍ତର ତାଲିକାକୁ "କୌଣସି ସ୍ତର ନାହିଁ" ଭାବେ ଧରନ୍ତୁ। ଡିଫଲ୍ଟ ଭାବେ ବନ୍ଦ: `effort_tiers` ଏବଂ ସମାଧାନ ହୋଇଥିବା `supportedThinkingEfforts` ପୂର୍ବପରି ଠିକ୍ ସମାନ ରହେ। | | `AUTO_REQUEST_RESOLVED_CAPABILITIES` | boolean | `false` | | auto/* ଅନୁରୋଧଗୁଡ଼ିକ ଗୋଟିଏ ବଲ୍କ ରିଡ୍ରେ ସମାଧାନ ହୋଇଥିବା ମଡେଲ୍ କ୍ଷମତାଗୁଡ଼ିକ (ଭିଜନ୍, ରିଜନିଂ, ଥିଙ୍କିଂ) ଏବଂ ପର୍ଯ୍ୟବେକ୍ଷିତ ଗୁଣବତ୍ତା ସ୍କୋର୍ ସହିତ ପ୍ରାର୍ଥୀ ପୁଲ୍ ତିଆରି କରେ (#16419)। ଡିଫଲ୍ଟ ଭାବେ ବନ୍ଦ: ଅନୁରୋଧ-ସମୟର ସ୍ନାପ୍ସଟ୍ ଓଜନଗୁଡ଼ିକ ପୂର୍ବପରି ଠିକ୍ ସମାନ ରହେ। ଚାଲୁ ଥିଲେ: ସେହି ସଙ୍କେତଗୁଡ଼ିକ ସ୍ନାପ୍ସଟ୍ ଓଜନରେ ଅବଦାନ ଦିଏ, ତେଣୁ ପ୍ରାର୍ଥୀ ଓଜନ ଏବଂ ଚୟନ ପରିବର୍ତ୍ତିତ ହୋଇପାରେ। | | `AUTO_COMBO_KEYLESS_TRANSPORT_GATE` | boolean | `false` | | ଯେଉଁ କୀ-ବିହୀନ (ପ୍ରମାଣୀକରଣ-ବିହୀନ) ପ୍ରଦାତାଙ୍କ ପରିବହନ ପାଇଁ ଏକ ସ୍ଥାନୀୟ ବ୍ରାଉଜର୍ (`browserBacked`) କିମ୍ବା ସ୍ଥାନୀୟ ଭାବେ ଇନ୍ଷ୍ଟଲ୍ ହୋଇଥିବା CLI (`isLocalCli`) ଆବଶ୍ୟକ, ସେମାନଙ୍କୁ `auto/*` ପ୍ରାର୍ଥୀ ପୁଲ୍ରୁ ବାହାରେ ରଖନ୍ତୁ, ଯାହାଦ୍ୱାରା ଅଟକିଥିବା ଜଣେ ସଦସ୍ୟ ପ୍ରତି-ଲକ୍ଷ୍ୟ ସମୟସୀମା ପର୍ଯ୍ୟନ୍ତ ଏକ ସ୍ୱୟଂଚାଳିତ ଅନୁରୋଧକୁ ଅଟକାଇ ରଖିପାରିବେ ନାହିଁ (#17036)। ଡିଫଲ୍ଟ ଭାବେ ବନ୍ଦ: କୀ-ବିହୀନ ପୁଲ୍ ଅପରିବର୍ତ୍ତିତ ରହେ। ପ୍ରତ୍ୟକ୍ଷ `/` କଲ୍ ଉଭୟ କ୍ଷେତ୍ରରେ କାମ କରେ। | @@ -241,7 +240,7 @@ "requiresRestart": false, "warningLevel": "caution", }, - // ... ସମସ୍ତ 111ଟି ଫ୍ଲାଗ୍ + // ... ସମସ୍ତ 110ଟି ଫ୍ଲାଗ୍ ], "summary": { "total": 56, diff --git a/docs/i18n/pa/docs/reference/FEATURE_FLAGS.md b/docs/i18n/pa/docs/reference/FEATURE_FLAGS.md index ee2f0a4d5d54..de322da30755 100644 --- a/docs/i18n/pa/docs/reference/FEATURE_FLAGS.md +++ b/docs/i18n/pa/docs/reference/FEATURE_FLAGS.md @@ -47,7 +47,7 @@ ## ਫਲੈਗ ਕੈਟਾਲਾਗ -6 ਸ਼੍ਰੇਣੀਆਂ ਵਿੱਚ 111 ਫਲੈਗ। **ਡਿਫਾਲਟ** ਤੋਂ ਭਾਵ ਪਰਿਭਾਸ਼ਾ ਦਾ ਡਿਫਾਲਟ ਹੈ — ਉਹ ਮੁੱਲ +6 ਸ਼੍ਰੇਣੀਆਂ ਵਿੱਚ 110 ਫਲੈਗ। **ਡਿਫਾਲਟ** ਤੋਂ ਭਾਵ ਪਰਿਭਾਸ਼ਾ ਦਾ ਡਿਫਾਲਟ ਹੈ — ਉਹ ਮੁੱਲ ਜੋ ਉਦੋਂ ਵਰਤਿਆ ਜਾਂਦਾ ਹੈ ਜਦੋਂ ਨਾ ਤਾਂ ਕੋਈ DB ਓਵਰਰਾਈਡ ਅਤੇ ਨਾ ਹੀ ਕੋਈ ਵਾਤਾਵਰਣ ਵੇਰੀਏਬਲ ਮੌਜੂਦ ਹੋਵੇ। ### ਸੁਰੱਖਿਆ (10) @@ -112,7 +112,7 @@ | `RADAR_ENABLED` | boolean | `false` | OmniRoute Radar ਮੋਡੀਊਲ (ਕੈਟਾਲਾਗ ਫੀਡ ਸਕ੍ਰੀਨਾਂ ਅਤੇ ਸਿੰਕ) ਨੂੰ ਸਮਰੱਥ ਕਰੋ। ਡਿਫੌਲਟ ਤੌਰ 'ਤੇ ਬੰਦ; ਇਸਨੂੰ ਸਮਰੱਥ ਕਰਨ ਨਾਲ ਸਿਰਫ਼ UI ਅਨਲੌਕ ਹੁੰਦਾ ਹੈ — ਡਾਟਾ ਸਿੰਕ ਲਈ ਵੱਖਰੀ ਸਹਿਮਤੀ ਦੀ ਲੋੜ ਰਹਿੰਦੀ ਹੈ। | | `MODEL_ENDPOINT_CAPABILITY_GATE_ENABLED` | boolean | `false` | ਭਰੋਸੇਯੋਗ ਮਾਡਲ ਐਂਡਪੌਇੰਟ ਟੈਗਾਂ (ਪ੍ਰੋਵਾਈਡਰ ਵੱਲੋਂ ਘੋਸ਼ਿਤ, ਕਿਸੇ ਵਿਸ਼ੇਸ਼ ਮਾਡਲ id ਤੋਂ ਅਨੁਮਾਨਿਤ, ਜਾਂ ਓਪਰੇਟਰ ਵੱਲੋਂ ਸੈੱਟ ਕੀਤੇ) ਨੂੰ ਲਾਗੂ ਕਰੋ: `/v1/chat/completions`, `/v1/embeddings`, `/v1/rerank`, `/v1/images/generations`, `/v1/videos/generations`, `/v1/music/generations` ਅਤੇ `/v1/audio/*` ਉੱਤੇ ਡਿਸਪੈਚ ਤੋਂ ਪਹਿਲਾਂ ਉਸ ਐਂਡਪੌਇੰਟ ਨੂੰ ਭੇਜੀ ਬੇਨਤੀ ਨੂੰ HTTP 400 ਨਾਲ ਰੱਦ ਕਰੋ ਜਿਸਨੂੰ ਮਾਡਲ ਸਰਵ ਨਹੀਂ ਕਰਦਾ; `/v1/models` ਤੋਂ ਉਹ ਮਾਡਲ ਲੁਕਾਓ ਜਿਨ੍ਹਾਂ ਦੇ ਟੈਗ ਕੀਤੇ ਐਂਡਪੌਇੰਟਾਂ ਲਈ ਕੋਈ OmniRoute ਅਡੈਪਟਰ ਨਹੀਂ ਹੈ; ਉਨ੍ਹਾਂ ਲਈ ਡੈਸ਼ਬੋਰਡ ਮਾਡਲ ਟੈਸਟ ਦਾ ਜਵਾਬ 422 ਨਾਲ ਦਿਓ (#16929)। ਡਿਫੌਲਟ ਤੌਰ 'ਤੇ ਬੰਦ: ਐਂਡਪੌਇੰਟ ਟੈਗ ਸਿਰਫ਼ ਜਾਣਕਾਰੀ ਲਈ ਰਹਿੰਦੇ ਹਨ ਅਤੇ ਡਿਸਪੈਚ ਵਿੱਚ ਕੋਈ ਬਦਲਾਅ ਨਹੀਂ ਹੁੰਦਾ। | -### ਰਨਟਾਈਮ (53) +### ਰਨਟਾਈਮ (52) | ਕੁੰਜੀ | ਕਿਸਮ | ਮੂਲ ਮੁੱਲ | ਮੁੜ-ਚਾਲੂ | ਵੇਰਵਾ | | ---------------------------------------------- | ------- | -------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -159,7 +159,6 @@ | `KIRO_TOOL_CALL_REPAIR_ENABLED` | boolean | `true` | | ਜਦੋਂ ਕੋਈ ਸਟ੍ਰੀਮਿੰਗ Kiro ਜਵਾਬ ਕਿਸੇ ਵੀ ਦਿਖਾਈ ਦੇਣ ਵਾਲੇ ਆਉਟਪੁੱਟ ਤੋਂ ਪਹਿਲਾਂ ਇੱਕ ਗਲਤ-ਰੂਪਿਤ `tool_call` ਰੈਪਰ (`input.name` ਜਾਂ `input.arguments` ਗੁੰਮ ਹੋਵੇ) ਭੇਜੇ, ਤਾਂ ਇਸ ਨੂੰ ਦਬਾਓ ਅਤੇ ਟਰਨ ਨੂੰ `invalid_kiro_tool_call` ਨਾਲ ਖਤਮ ਕਰਨ ਦੀ ਬਜਾਏ ਸੁਧਾਰਾਤਮਕ ਹਦਾਇਤ ਸਮੇਤ ਬੇਨਤੀ ਇੱਕ ਵਾਰ ਮੁੜ ਭੇਜੋ। ਮੁੜ-ਕੋਸ਼ਿਸ਼ ਲਈ ਇੱਕ ਵਾਧੂ Kiro ਬੇਨਤੀ ਦੀ ਲਾਗਤ ਪੈਂਦੀ ਹੈ; ਦੂਜੀ ਅਸਫਲਤਾ ਟਰਨ ਨੂੰ `kiro_tool_call_repair_retry_failed` ਨਾਲ ਖਤਮ ਕਰਦੀ ਹੈ। ਮੂਲ ਰੂਪ ਵਿੱਚ ਚਾਲੂ। ਪਹਿਲੇ ਗਲਤ-ਰੂਪਿਤ ਰੈਪਰ 'ਤੇ ਹੀ ਟਰਨ ਨੂੰ ਅਸਫਲ ਕਰਨ ਲਈ `false` ਸੈੱਟ ਕਰੋ। | | `CATALOG_MIRROR_REFINEMENTS_ENABLED` | boolean | `false` | | `/v1/models` ਉੱਤੇ `claude/` ਖੋਜ ਅਤੇ ਫੰਕਸ਼ਨਲ-ਗੇਟਵੇ ਮਿਰਰਾਂ ਨੂੰ ਸੁਧਾਰੋ। ਮੂਲ ਰੂਪ ਵਿੱਚ ਬੰਦ: ਦੋਵੇਂ ਮਿਰਰ ਪਾਸ ਬਿਲਕੁਲ ਪਹਿਲਾਂ ਵਾਂਗ ਕੰਮ ਕਰਦੇ ਹਨ। ਚਾਲੂ ਹੋਣ 'ਤੇ: `claude/` ਖੋਜ ਮਿਰਰ ਸੂਚੀਬੱਧ ਰੀਜ਼ਨਿੰਗ-ਐਫ਼ਰਟ ਵੇਰੀਐਂਟਾਂ (`-low`/`-medium`/`-high`/`-xhigh`) ਨੂੰ ਵੀ ਕਵਰ ਕਰਦੇ ਹਨ, ਉਹ ਬਿਲਟ-ਇਨ `auto/*` ids ਛੱਡਦੇ ਹਨ ਜਿਨ੍ਹਾਂ ਨੂੰ ਬੇਨਤੀ ਪਾਥ ਸਾਕਾਰ ਨਹੀਂ ਕਰ ਸਕਦਾ ਅਤੇ ਪਹਿਲਾਂ ਹੀ ਸੂਚੀਬੱਧ ਉਪਨਾਮ ਨੂੰ ਛੱਡਦੇ ਹਨ; ਫੰਕਸ਼ਨਲ-ਗੇਟਵੇ ਮਿਰਰ ਕਾਂਬੋ ਅਤੇ OmniRoute ਦੁਆਰਾ ਬਣਾਏ ids (`claude/`, `no-think/`) ਨੂੰ ਛੱਡਦੇ ਹਨ, ਮਾਲਕ ਨੂੰ `owned_by` ਤੋਂ ਲੈਂਦੇ ਹਨ, ਮਾਲਕ ਨੂੰ ਕਦੇ ਵੀ ਉਸਦੇ ਆਪਣੇ ਗੇਟਵੇ ਵਜੋਂ ਨਹੀਂ ਵਰਤਦੇ, `blockedProviders` ਵਿੱਚ ਅਯੋਗ ਕੀਤੇ ਗੇਟਵੇ ਅਤੇ ਕਿਸੇ ਗੇਟਵੇ ਦੇ ਅਧਿਕਾਰਤ ਲਾਈਵ ਕੈਟਾਲਾਗ ਤੋਂ ਬਾਹਰਲੇ ਮਾਡਲ ਛੱਡਦੇ ਹਨ। | | ---------------------------------------------- | ------- | ------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `ANTHROPIC_COMPATIBLE_MID_CONVERSATION_SYSTEM` | boolean | `false` | | ਸਧਾਰਨ `anthropic-compatible-*` ਰੀਲੇਆਂ (claude → claude ਪਾਸਥਰੂ) ਉੱਤੇ, ਗੱਲਬਾਤ ਦੇ ਵਿਚਕਾਰਲੇ `role: "system"` ਟਰਨਾਂ ਨੂੰ ਸਿਖਰ-ਪੱਧਰੀ `system` ਪੈਰਾਮੀਟਰ ਵਿੱਚ ਚੁੱਕਣ ਦੀ ਬਜਾਏ `messages[]` ਦੇ ਅੰਦਰ ਰੱਖੋ, ਤਾਂ ਜੋ ਕੈਸ਼ ਕੀਤਾ ਪ੍ਰੌਮਪਟ ਪ੍ਰੀਫਿਕਸ ਟਰਨਾਂ ਦਰਮਿਆਨ ਸਥਿਰ ਰਹੇ। ਇਹ ਸਿਰਫ਼ ਉਦੋਂ ਲਾਗੂ ਹੁੰਦਾ ਹੈ ਜਦੋਂ ਕਲਾਇੰਟ ਨੇ ਖੁਦ `mid-conversation-system-2026-04-07` `anthropic-beta` ਭੇਜਿਆ ਹੋਵੇ (ਜਿਸ ਨੂੰ ਅੱਪਸਟ੍ਰੀਮ ਵੱਲ ਜਿਵੇਂ ਦਾ ਤਿਵੇਂ ਫਾਰਵਰਡ ਕੀਤਾ ਜਾਂਦਾ ਹੈ)। ਮੂਲ ਰੂਪ ਵਿੱਚ ਬੰਦ: ਹਰੇਕ ਸਿਸਟਮ ਟਰਨ ਨੂੰ ਬਿਲਕੁਲ ਪਹਿਲਾਂ ਵਾਂਗ ਚੁੱਕਿਆ ਜਾਂਦਾ ਹੈ, ਕਿਉਂਕਿ ਬੀਟਾ ਤੋਂ ਬਿਨਾਂ ਰੀਲੇ `role: "system"` ਸੁਨੇਹਿਆਂ ਨੂੰ 400 ਨਾਲ ਰੱਦ ਕਰਦੇ ਹਨ। | | `MODELS_DEV_EFFORT_TIERS_ENABLED` | boolean | `false` | | ਜਦੋਂ ਨਾ ਤਾਂ ਕੋਈ ਓਪਰੇਟਰ ਓਵਰਰਾਈਡ ਅਤੇ ਨਾ ਹੀ ਪ੍ਰੋਵਾਈਡਰ ਰਜਿਸਟਰੀ ਕੋਈ ਟੀਅਰ ਘੋਸ਼ਿਤ ਕਰੇ, ਤਾਂ models.dev ਵੱਲੋਂ ਪ੍ਰਕਾਸ਼ਿਤ ਰੀਜ਼ਨਿੰਗ ਐਫ਼ਰਟ ਟੀਅਰਾਂ (ਜਾਣੀ-ਪਛਾਣੀ ਐਫ਼ਰਟ ਸ਼ਬਦਾਵਲੀ ਤੱਕ ਸੀਮਤ) ਦੀ ਵਰਤੋਂ ਕਰੋ ਅਤੇ `/v1/models` ਦੇ `effort_tiers` ਅਤੇ ਕਾਂਬੋ ਟੀਅਰ ਇੰਟਰਸੈਕਸ਼ਨਾਂ ਵਿੱਚ ਘੋਸ਼ਿਤ ਖਾਲੀ ਟੀਅਰ ਸੂਚੀ ਨੂੰ "ਕੋਈ ਟੀਅਰ ਨਹੀਂ" ਮੰਨੋ। ਮੂਲ ਰੂਪ ਵਿੱਚ ਬੰਦ: `effort_tiers` ਅਤੇ ਰਿਜ਼ਾਲਵ ਕੀਤਾ `supportedThinkingEfforts` ਬਿਲਕੁਲ ਪਹਿਲਾਂ ਵਾਂਗ ਰਹਿੰਦੇ ਹਨ। | | `AUTO_REQUEST_RESOLVED_CAPABILITIES` | boolean | `false` | | auto/* ਬੇਨਤੀਆਂ ਇੱਕੋ ਬਲਕ ਰੀਡ ਵਿੱਚ ਰਿਜ਼ਾਲਵ ਕੀਤੀਆਂ ਮਾਡਲ ਸਮਰੱਥਾਵਾਂ (ਵਿਜ਼ਨ, ਰੀਜ਼ਨਿੰਗ, ਥਿੰਕਿੰਗ) ਅਤੇ ਦੇਖੇ ਗਏ ਗੁਣਵੱਤਾ ਸਕੋਰਾਂ ਨਾਲ ਉਮੀਦਵਾਰ ਪੂਲ ਬਣਾਉਂਦੀਆਂ ਹਨ (#16419)। ਮੂਲ ਰੂਪ ਵਿੱਚ ਬੰਦ: ਬੇਨਤੀ-ਸਮੇਂ ਦੇ ਸਨੈਪਸ਼ਾਟ ਵੇਟ ਬਿਲਕੁਲ ਪਹਿਲਾਂ ਵਾਂਗ ਰਹਿੰਦੇ ਹਨ। ਚਾਲੂ ਹੋਣ 'ਤੇ: ਉਹ ਸੰਕੇਤ ਸਨੈਪਸ਼ਾਟ ਵੇਟਾਂ ਵਿੱਚ ਸ਼ਾਮਲ ਹੁੰਦੇ ਹਨ, ਇਸ ਲਈ ਉਮੀਦਵਾਰ ਵੇਟ ਅਤੇ ਚੋਣ ਬਦਲ ਸਕਦੇ ਹਨ। | | `AUTO_COMBO_KEYLESS_TRANSPORT_GATE` | boolean | `false` | | ਉਹ keyless (no-auth) ਪ੍ਰਦਾਤਾਵਾਂ, ਜਿਨ੍ਹਾਂ ਦੇ transport ਨੂੰ ਸਥਾਨਕ browser (`browserBacked`) ਜਾਂ ਸਥਾਨਕ ਤੌਰ 'ਤੇ ਇੰਸਟਾਲ ਕੀਤੇ CLI (`isLocalCli`) ਦੀ ਲੋੜ ਹੁੰਦੀ ਹੈ, ਨੂੰ `auto/*` ਉਮੀਦਵਾਰ ਪੂਲ ਤੋਂ ਬਾਹਰ ਰੱਖੋ, ਤਾਂ ਜੋ ਕੋਈ ਅਟਕਿਆ ਹੋਇਆ ਮੈਂਬਰ ਪ੍ਰਤੀ-target timeout ਤੱਕ auto ਬੇਨਤੀ ਨੂੰ ਨਾ ਰੋਕ ਸਕੇ (#17036)। ਮੂਲ ਰੂਪ ਵਿੱਚ ਬੰਦ: keyless ਪੂਲ ਵਿੱਚ ਕੋਈ ਤਬਦੀਲੀ ਨਹੀਂ ਹੁੰਦੀ। ਸਿੱਧੀਆਂ `/` ਕਾਲਾਂ ਦੋਵੇਂ ਹਾਲਤਾਂ ਵਿੱਚ ਕੰਮ ਕਰਦੀਆਂ ਹਨ। | @@ -256,7 +255,7 @@ "requiresRestart": false, "warningLevel": "caution", }, - // ... ਸਾਰੇ 111 ਫਲੈਗ + // ... ਸਾਰੇ 110 ਫਲੈਗ ], "summary": { "total": 56, diff --git a/docs/i18n/phi/docs/reference/FEATURE_FLAGS.md b/docs/i18n/phi/docs/reference/FEATURE_FLAGS.md index 65c095c06d9e..9e6da75cb0d0 100644 --- a/docs/i18n/phi/docs/reference/FEATURE_FLAGS.md +++ b/docs/i18n/phi/docs/reference/FEATURE_FLAGS.md @@ -44,7 +44,7 @@ Itinuturing na **enabled** ang isang boolean flag kapag ang effective value nito ## Katalogo ng mga Flag -111 flag sa 6 na kategorya. Ang **Default** ay ang default ng depinisyon — ang value na +110 flag sa 6 na kategorya. Ang **Default** ay ang default ng depinisyon — ang value na ginagamit kapag walang DB override o environment variable. ### Seguridad (10) @@ -109,7 +109,7 @@ ginagamit kapag walang DB override o environment variable. | `RADAR_ENABLED` | boolean | `false` | Paganahin ang module na OmniRoute Radar (mga screen ng catalog feed at pag-sync). Naka-off bilang default; ina-unlock lamang ng pagpapagana nito ang UI — nananatiling hiwalay na opt-in ang pag-sync ng data. | | `MODEL_ENDPOINT_CAPABILITY_GATE_ENABLED` | boolean | `false` | Ipatupad ang mga pinagkakatiwalaang tag ng endpoint ng modelo (idineklarang provider, natukoy mula sa isang specialty model id, o itinakda ng operator): tanggihan gamit ang HTTP 400, bago ipadala, ang kahilingang ipinadala sa isang endpoint na hindi sinusuportahan ng modelo sa `/v1/chat/completions`, `/v1/embeddings`, `/v1/rerank`, `/v1/images/generations`, `/v1/videos/generations`, `/v1/music/generations` at `/v1/audio/*`; itago mula sa `/v1/models` ang mga modelong walang OmniRoute adapter para sa kanilang mga naka-tag na endpoint; tumugon gamit ang 422 sa pagsusuri ng modelo sa dashboard para sa mga ito (#16929). Naka-off bilang default: nananatiling pang-impormasyon ang mga endpoint tag at hindi nagbabago ang pagpapadala. | -### Runtime (53) +### Runtime (52) | Key | Uri | Default | Restart | Paglalarawan | | ---------------------------------------------- | ------- | ------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -156,7 +156,6 @@ ginagamit kapag walang DB override o environment variable. | `KIRO_TOOL_CALL_REPAIR_ENABLED` | boolean | `true` | | Kapag naglabas ang isang streaming na tugon ng Kiro ng malformed na `tool_call` wrapper (walang `input.name` o `input.arguments`) bago ang anumang nakikitang output, pigilan ito at muling ipadala nang isang beses ang request na may tagubiling pangwasto sa halip na tapusin ang turn gamit ang `invalid_kiro_tool_call`. Nagkakahalaga ang retry ng isang karagdagang Kiro request; tatapusin ng pangalawang pagkabigo ang turn gamit ang `kiro_tool_call_repair_retry_failed`. Naka-on bilang default. Itakda sa `false` upang mabigo ang turn sa unang malformed na wrapper. | | `CATALOG_MIRROR_REFINEMENTS_ENABLED` | boolean | `false` | | Pinuhin ang mga mirror ng discovery at functional gateway ng `claude/` sa `/v1/models`. Naka-off bilang default: gumagana ang parehong mirror pass nang eksakto gaya ng dati. Kapag naka-on: sinasaklaw din ng mga discovery mirror ng `claude/` ang mga nakalistang variant ng reasoning effort (`-low`/`-medium`/`-high`/`-xhigh`), nilalaktawan ang mga built-in na `auto/*` id na hindi maisasakatuparan ng request path, at nilalaktawan ang alias na nakalista na; nilalaktawan ng mga functional-gateway mirror ang mga combo at id na binuo ng OmniRoute (`claude/`, `no-think/`), kinukuha ang may-ari mula sa `owned_by`, hindi kailanman ginagamit ang may-ari bilang sarili nitong gateway, at nilalaktawan ang mga gateway na naka-disable sa `blockedProviders` at mga model na wala sa authoritative live catalog ng isang gateway. | | ---------------------------------------------- | ------- | ------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `ANTHROPIC_COMPATIBLE_MID_CONVERSATION_SYSTEM` | boolean | `false` | | Sa mga karaniwang `anthropic-compatible-*` relay (`claude → claude passthrough`), panatilihin sa loob ng `messages[]` ang mga turn na may `role: "system"` sa gitna ng pag-uusap sa halip na iangat ang mga ito sa top-level na `system` parameter, upang manatiling stable ang naka-cache na prompt prefix sa bawat turn. Nalalapat lamang kapag ang client mismo ang nagpadala ng `mid-conversation-system-2026-04-07` `anthropic-beta` (ipinapasa upstream nang walang pagbabago). Naka-off bilang default: inaangat ang bawat system turn gaya mismo ng dati, dahil tinatanggihan ng mga relay na walang beta ang mga mensaheng may `role: "system"` gamit ang 400. | | `MODELS_DEV_EFFORT_TIERS_ENABLED` | boolean | `false` | | Gamitin ang mga reasoning effort tier na inilalathala ng models.dev (nilimitahan sa kilalang bokabularyo ng effort) kapag walang idinedeklara ang operator override o provider registry, at ituring ang idineklarang walang-lamang tier list bilang "walang mga tier" sa `/v1/models` `effort_tiers` at mga intersection ng combo tier. Naka-off bilang default: nananatiling eksaktong gaya ng dati ang `effort_tiers` at ang nalutas na `supportedThinkingEfforts`. | | `AUTO_REQUEST_RESOLVED_CAPABILITIES` | boolean | `false` | | Binubuo ng mga request na auto/* ang candidate pool gamit ang nalutas na mga kakayahan ng model (vision, reasoning, thinking) at mga naobserbahang quality score sa isang bulk read (#16419). Naka-off bilang default: nananatiling eksaktong gaya ng dati ang mga snapshot weight sa oras ng request. Kapag naka-on: ginagamit ang mga signal na iyon sa mga snapshot weight, kaya maaaring magbago ang mga candidate weight at pagpili. | | `AUTO_COMBO_KEYLESS_TRANSPORT_GATE` | boolean | `false` | | Huwag isama sa pool ng mga kandidato ng `auto/*` ang mga keyless (walang auth) na provider na nangangailangan ang transport ng lokal na browser (`browserBacked`) o lokal na naka-install na CLI (`isLocalCli`), upang hindi maantala ng isang hindi tumutugon na miyembro ang isang auto request hanggang sa timeout ng bawat target (#17036). Naka-off bilang default: hindi nagbabago ang keyless pool. Gumagana ang mga direktang tawag sa `/` sa alinmang setting. | @@ -253,7 +252,7 @@ Ibinabalik ang bawat flag kasama ang aktuwal na value, source, at buod nito. "requiresRestart": false, "warningLevel": "caution", }, - // ... lahat ng 111 flag + // ... lahat ng 110 flag ], "summary": { "total": 56, diff --git a/docs/i18n/pl/docs/reference/FEATURE_FLAGS.md b/docs/i18n/pl/docs/reference/FEATURE_FLAGS.md index 4f01e11fffb2..ac290a3e46e0 100644 --- a/docs/i18n/pl/docs/reference/FEATURE_FLAGS.md +++ b/docs/i18n/pl/docs/reference/FEATURE_FLAGS.md @@ -44,7 +44,7 @@ Flaga logiczna jest uznawana za **włączoną**, gdy jej efektywna wartość to ## Katalog flag -111 flag w 6 kategoriach. **Wartość domyślna** jest wartością domyślną definicji — używaną, +110 flag w 6 kategoriach. **Wartość domyślna** jest wartością domyślną definicji — używaną, gdy nie określono ani nadpisania w bazie danych, ani zmiennej środowiskowej. ### Bezpieczeństwo (10) @@ -109,7 +109,7 @@ gdy nie określono ani nadpisania w bazie danych, ani zmiennej środowiskowej. | `RADAR_ENABLED` | boolean | `false` | Włącza moduł OmniRoute Radar (ekrany kanału katalogu i synchronizację). Domyślnie wyłączone; włączenie jedynie odblokowuje interfejs użytkownika — synchronizacja danych nadal wymaga osobnej zgody. | | `MODEL_ENDPOINT_CAPABILITY_GATE_ENABLED` | boolean | `false` | Wymusza stosowanie zaufanych tagów punktów końcowych modelu (zadeklarowanych przez dostawcę, wywnioskowanych z identyfikatora wyspecjalizowanego modelu lub ustawionych przez operatora): przed wysłaniem odrzuca z kodem HTTP 400 żądanie skierowane do punktu końcowego, którego model nie obsługuje, w przypadku `/v1/chat/completions`, `/v1/embeddings`, `/v1/rerank`, `/v1/images/generations`, `/v1/videos/generations`, `/v1/music/generations` oraz `/v1/audio/*`; ukrywa w `/v1/models` modele, których oznaczone tagami punkty końcowe nie mają adaptera OmniRoute; zwraca kod 422 w odpowiedzi na test modelu w panelu (#16929). Domyślnie wyłączone: tagi punktów końcowych pozostają informacyjne, a sposób wysyłania żądań nie ulega zmianie. | -### Środowisko uruchomieniowe (53) +### Środowisko uruchomieniowe (52) | Klucz | Typ | Domyślnie | Restart | Opis | | ---------------------------------------------- | ------- | --------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -156,7 +156,6 @@ gdy nie określono ani nadpisania w bazie danych, ani zmiennej środowiskowej. | `KIRO_TOOL_CALL_REPAIR_ENABLED` | boolean | `true` | | Gdy strumieniowa odpowiedź Kiro emituje nieprawidłową otoczkę `tool_call` (bez `input.name` lub `input.arguments`) przed jakimkolwiek widocznym wynikiem, pomiń ją i ponownie wyślij żądanie jeden raz z instrukcją korygującą, zamiast kończyć turę błędem `invalid_kiro_tool_call`. Ponowienie wiąże się z kosztem jednego dodatkowego żądania Kiro; drugi błąd kończy turę błędem `kiro_tool_call_repair_retry_failed`. Domyślnie włączone. Ustaw na `false`, aby zakończyć turę niepowodzeniem po pierwszej nieprawidłowej otoczce. | | `CATALOG_MIRROR_REFINEMENTS_ENABLED` | boolean | `false` | | Udoskonal mechanizmy wykrywania `claude/` i kopie lustrzane bram funkcjonalnych w `/v1/models`. Domyślnie wyłączone: oba przebiegi tworzenia kopii lustrzanych działają dokładnie tak jak wcześniej. Po włączeniu: kopie lustrzane wykrywania `claude/` obejmują również wymienione warianty nakładu rozumowania (`-low`/`-medium`/`-high`/`-xhigh`), pomijają wbudowane identyfikatory `auto/*`, których ścieżka żądania nie może utworzyć, oraz pomijają już wymieniony alias; kopie lustrzane bram funkcjonalnych pomijają kombinacje i identyfikatory syntetyzowane przez OmniRoute (`claude/`, `no-think/`), pobierają właściciela z `owned_by`, nigdy nie używają właściciela jako jego własnej bramy, pomijają bramy wyłączone w `blockedProviders` oraz modele spoza autorytatywnego, aktywnego katalogu bramy. | | ---------------------------------------------- | ------- | ------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `ANTHROPIC_COMPATIBLE_MID_CONVERSATION_SYSTEM` | boolean | `false` | | W zwykłych przekaźnikach `anthropic-compatible-*` (claude → claude bez przekształceń) zachowuj występujące w trakcie konwersacji tury `role: "system"` wewnątrz `messages[]`, zamiast przenosić je do parametru najwyższego poziomu `system`, dzięki czemu buforowany prefiks promptu pozostaje stabilny pomiędzy turami. Ma zastosowanie tylko wtedy, gdy sam klient wysłał `mid-conversation-system-2026-04-07` w `anthropic-beta` (przekazywane bez zmian do usługi nadrzędnej). Domyślnie wyłączone: każda tura systemowa jest przenoszona dokładnie tak jak wcześniej, ponieważ przekaźniki bez tej wersji beta odrzucają komunikaty `role: "system"` z błędem 400. | | `MODELS_DEV_EFFORT_TIERS_ENABLED` | boolean | `false` | | Używaj poziomów nakładu rozumowania publikowanych przez models.dev (ograniczonych do znanego słownika nakładu), gdy nie deklaruje ich ani nadpisanie operatora, ani rejestr dostawców, oraz traktuj zadeklarowaną pustą listę poziomów jako „brak poziomów” w `/v1/models` `effort_tiers` i przecięciach poziomów kombinacji. Domyślnie wyłączone: `effort_tiers` i rozwiązane `supportedThinkingEfforts` pozostają dokładnie takie jak wcześniej. | | `AUTO_REQUEST_RESOLVED_CAPABILITIES` | boolean | `false` | | Żądania auto/* tworzą pulę kandydatów na podstawie rozwiązanych możliwości modeli (przetwarzanie obrazu, rozumowanie, proces myślenia) oraz zaobserwowanych ocen jakości w jednym odczycie zbiorczym (#16419). Domyślnie wyłączone: wagi migawki używane podczas obsługi żądania pozostają dokładnie takie jak wcześniej. Po włączeniu: sygnały te wpływają na wagi migawki, dlatego wagi kandydatów i wybór mogą ulec zmianie. | | `AUTO_COMBO_KEYLESS_TRANSPORT_GATE` | boolean | `false` | | Wyklucz z puli kandydatów `auto/*` dostawców bez klucza (bez uwierzytelniania), których transport wymaga lokalnej przeglądarki (`browserBacked`) lub lokalnie zainstalowanego narzędzia CLI (`isLocalCli`), aby zawieszony element nie mógł blokować automatycznego żądania aż do upłynięcia limitu czasu dla danego celu (#17036). Domyślnie wyłączone: pula bez klucza pozostaje bez zmian. Bezpośrednie wywołania `/` działają w obu przypadkach. | @@ -253,7 +252,7 @@ Zwraca wszystkie flagi wraz z ich obowiązującą wartością, źródłem i pods "requiresRestart": false, "warningLevel": "caution", }, - // ... all 111 flags + // ... all 110 flags ], "summary": { "total": 56, diff --git a/docs/i18n/pt-BR/docs/reference/FEATURE_FLAGS.md b/docs/i18n/pt-BR/docs/reference/FEATURE_FLAGS.md index 7649b065f314..8ecc8281a22a 100644 --- a/docs/i18n/pt-BR/docs/reference/FEATURE_FLAGS.md +++ b/docs/i18n/pt-BR/docs/reference/FEATURE_FLAGS.md @@ -44,7 +44,7 @@ Uma flag booleana é considerada **habilitada** quando seu valor efetivo é `"tr ## Catálogo de flags -111 flags em 6 categorias. **Padrão** é o padrão da definição — o valor +110 flags em 6 categorias. **Padrão** é o padrão da definição — o valor usado quando não há uma substituição no banco de dados nem uma variável de ambiente presente. ### Segurança (10) @@ -109,7 +109,7 @@ usado quando não há uma substituição no banco de dados nem uma variável de | `RADAR_ENABLED` | boolean | `false` | Ativa o módulo OmniRoute Radar (telas de feeds do catálogo e sincronização). Desativado por padrão; a ativação apenas desbloqueia a interface — a sincronização de dados continua sendo uma opção separada. | | `MODEL_ENDPOINT_CAPABILITY_GATE_ENABLED` | boolean | `false` | Impõe tags confiáveis de endpoints de modelo (declaradas pelo provedor, inferidas de um ID de modelo especializado ou definidas pelo operador): rejeita, com HTTP 400 antes do despacho, uma solicitação enviada a um endpoint que o modelo não atende em `/v1/chat/completions`, `/v1/embeddings`, `/v1/rerank`, `/v1/images/generations`, `/v1/videos/generations`, `/v1/music/generations` e `/v1/audio/*`; oculta de `/v1/models` os modelos cujos endpoints marcados não possuem um adaptador do OmniRoute; responde ao teste de modelo do painel com 422 para esses modelos (#16929). Desativado por padrão: as tags de endpoint permanecem informativas e o despacho não é alterado. | -### Runtime (53) +### Runtime (52) | Chave | Tipo | Padrão | Reinicialização | Descrição | | ---------------------------------------------- | ------- | ------- | --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -156,7 +156,6 @@ usado quando não há uma substituição no banco de dados nem uma variável de | `KIRO_TOOL_CALL_REPAIR_ENABLED` | boolean | `true` | | Quando uma resposta de streaming do Kiro emite um wrapper `tool_call` malformado (sem `input.name` ou `input.arguments`) antes de qualquer saída visível, ele é suprimido e a solicitação é reenviada uma vez com uma instrução corretiva, em vez de encerrar o turno com `invalid_kiro_tool_call`. A nova tentativa custa uma solicitação adicional ao Kiro; uma segunda falha encerra o turno com `kiro_tool_call_repair_retry_failed`. Ativado por padrão. Defina como `false` para fazer o turno falhar no primeiro wrapper malformado. | | `CATALOG_MIRROR_REFINEMENTS_ENABLED` | boolean | `false` | | Refina os espelhos de descoberta `claude/` e de gateways funcionais em `/v1/models`. Desativado por padrão: ambas as passagens de espelhamento se comportam exatamente como antes. Quando ativado: os espelhos de descoberta `claude/` também abrangem as variantes listadas de esforço de raciocínio (`-low`/`-medium`/`-high`/`-xhigh`), ignoram IDs integrados `auto/*` que o caminho da solicitação não consegue materializar e ignoram aliases já listados; os espelhos de gateways funcionais ignoram combos e IDs sintetizados pelo OmniRoute (`claude/`, `no-think/`), obtêm o proprietário de `owned_by`, nunca usam o proprietário como seu próprio gateway, ignoram gateways desativados em `blockedProviders` e modelos fora do catálogo ativo autoritativo de um gateway. | | ---------------------------------------------- | ------- | ------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `ANTHROPIC_COMPATIBLE_MID_CONVERSATION_SYSTEM` | boolean | `false` | | Em relays simples `anthropic-compatible-*` (passagem direta de claude → claude), mantém turnos `role: "system"` no meio da conversa dentro de `messages[]`, em vez de movê-los para o parâmetro `system` de nível superior, para que o prefixo do prompt armazenado em cache permaneça estável entre os turnos. Aplica-se somente quando o próprio cliente enviou o `anthropic-beta` `mid-conversation-system-2026-04-07` (encaminhado ao upstream sem alterações). Desativado por padrão: cada turno do sistema é movido exatamente como antes, pois relays sem o beta rejeitam mensagens `role: "system"` com o erro 400. | | `MODELS_DEV_EFFORT_TIERS_ENABLED` | boolean | `false` | | Usa os níveis de esforço de raciocínio publicados pelo models.dev (restritos ao vocabulário de esforço conhecido) quando nem uma substituição do operador nem o registro do provedor declara algum, e trata uma lista de níveis declarada como vazia como "sem níveis" em `effort_tiers` de `/v1/models` e nas interseções de níveis dos combos. Desativado por padrão: `effort_tiers` e `supportedThinkingEfforts` resolvidos permanecem exatamente como antes. | | `AUTO_REQUEST_RESOLVED_CAPABILITIES` | boolean | `false` | | As solicitações auto/* criam o pool de candidatos com recursos de modelo resolvidos (visão, raciocínio, pensamento) e pontuações de qualidade observadas em uma única leitura em massa (#16419). Desativado por padrão: os pesos do snapshot no momento da solicitação permanecem exatamente como antes. Quando ativado: esses sinais alimentam os pesos do snapshot, portanto os pesos e a seleção dos candidatos podem mudar. | | `AUTO_COMBO_KEYLESS_TRANSPORT_GATE` | boolean | `false` | | Mantém fora do conjunto de candidatos `auto/*` os provedores sem chave (sem autenticação) cujo transporte exige um navegador local (`browserBacked`) ou uma CLI instalada localmente (`isLocalCli`), para que um membro travado não possa bloquear uma solicitação automática até o tempo limite por destino (#17036). Desativado por padrão: o conjunto sem chave permanece inalterado. Chamadas diretas a `/` funcionam de qualquer forma. | @@ -253,7 +252,7 @@ Retorna todas as flags com seus valores efetivos, origens e um resumo. "requiresRestart": false, "warningLevel": "caution", }, - // ... todas as 111 flags + // ... todas as 110 flags ], "summary": { "total": 56, diff --git a/docs/i18n/pt/docs/reference/FEATURE_FLAGS.md b/docs/i18n/pt/docs/reference/FEATURE_FLAGS.md index 086a5902c37f..0bb0967b67e8 100644 --- a/docs/i18n/pt/docs/reference/FEATURE_FLAGS.md +++ b/docs/i18n/pt/docs/reference/FEATURE_FLAGS.md @@ -35,7 +35,7 @@ Um sinalizador booleano é considerado **ativado** quando o seu valor efetivo é ## Catálogo de flags -111 flags distribuídas por 6 categorias. **Predefinição** é o valor predefinido na definição — o valor +110 flags distribuídas por 6 categorias. **Predefinição** é o valor predefinido na definição — o valor utilizado quando não existe uma substituição na BD nem uma variável de ambiente. ### Segurança (10) @@ -100,7 +100,7 @@ utilizado quando não existe uma substituição na BD nem uma variável de ambie | `RADAR_ENABLED` | boolean | `false` | Ativa o módulo Radar do OmniRoute (ecrãs do feed do catálogo e sincronização). Desativado por predefinição; a ativação apenas desbloqueia a interface — a sincronização de dados continua a exigir uma adesão separada. | | `MODEL_ENDPOINT_CAPABILITY_GATE_ENABLED` | boolean | `false` | Impõe etiquetas fidedignas de endpoints de modelos (declaradas pelo fornecedor, inferidas a partir do ID de um modelo especializado ou definidas pelo operador): rejeita, com HTTP 400 e antes do envio, um pedido enviado para um endpoint que o modelo não suporta em `/v1/chat/completions`, `/v1/embeddings`, `/v1/rerank`, `/v1/images/generations`, `/v1/videos/generations`, `/v1/music/generations` e `/v1/audio/*`; oculta em `/v1/models` os modelos cujos endpoints etiquetados não têm um adaptador OmniRoute; responde com 422 no teste de modelos do dashboard para esses modelos (#16929). Desativado por predefinição: as etiquetas dos endpoints mantêm-se meramente informativas e o envio permanece inalterado. | -### Runtime (53) +### Runtime (52) | Chave | Tipo | Predefinição | Reinício | Descrição | | ---------------------------------------------- | ------- | ------------ | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -147,7 +147,6 @@ utilizado quando não existe uma substituição na BD nem uma variável de ambie | `KIRO_TOOL_CALL_REPAIR_ENABLED` | boolean | `true` | | Quando uma resposta de transmissão do Kiro emitir um invólucro `tool_call` malformado (sem `input.name` ou `input.arguments`) antes de qualquer saída visível, suprimi-lo e reenviar o pedido uma vez com uma instrução corretiva, em vez de terminar o turno com `invalid_kiro_tool_call`. A nova tentativa implica o custo de um pedido Kiro adicional; uma segunda falha termina o turno com `kiro_tool_call_repair_retry_failed`. Ativado por predefinição. Defina como `false` para fazer o turno falhar no primeiro invólucro malformado. | | `CATALOG_MIRROR_REFINEMENTS_ENABLED` | boolean | `false` | | Refinar os espelhos de descoberta `claude/` e do gateway funcional em `/v1/models`. Desativado por predefinição: ambas as passagens de espelhamento comportam-se exatamente como antes. Quando ativado: os espelhos de descoberta `claude/` também abrangem as variantes listadas de esforço de raciocínio (`-low`/`-medium`/`-high`/`-xhigh`), ignoram os IDs `auto/*` incorporados que o caminho do pedido não consegue materializar e ignoram um alias já listado; os espelhos do gateway funcional ignoram combinações e IDs sintetizados pelo OmniRoute (`claude/`, `no-think/`), obtêm o proprietário de `owned_by`, nunca utilizam o proprietário como o seu próprio gateway, ignoram gateways desativados em `blockedProviders` e modelos fora do catálogo ativo autoritativo de um gateway. | | ---------------------------------------------- | ------- | ------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `ANTHROPIC_COMPATIBLE_MID_CONVERSATION_SYSTEM` | boolean | `false` | | Em retransmissores `anthropic-compatible-*` simples (passagem direta de claude → claude), manter os turnos `role: "system"` a meio da conversa dentro de `messages[]`, em vez de os mover para o parâmetro `system` de nível superior, para que o prefixo da instrução em cache permaneça estável entre turnos. Aplica-se apenas quando o próprio cliente tiver enviado a versão beta `mid-conversation-system-2026-04-07` de `anthropic-beta` (reencaminhada para montante sem alterações). Desativado por predefinição: todos os turnos do sistema são movidos exatamente como antes, porque os retransmissores sem a versão beta rejeitam mensagens `role: "system"` com o erro 400. | | `MODELS_DEV_EFFORT_TIERS_ENABLED` | boolean | `false` | | Utilizar os níveis de esforço de raciocínio publicados pelo models.dev (restringidos ao vocabulário de esforço conhecido) quando nem uma substituição do operador nem o registo do fornecedor declarar algum, e tratar uma lista de níveis declarada como vazia como "sem níveis" em `effort_tiers` de `/v1/models` e nas interseções de níveis das combinações. Desativado por predefinição: `effort_tiers` e `supportedThinkingEfforts` resolvidos permanecem exatamente como antes. | | `AUTO_REQUEST_RESOLVED_CAPABILITIES` | boolean | `false` | | Os pedidos auto/* criam o conjunto de candidatos com as capacidades resolvidas dos modelos (visão, raciocínio, reflexão) e as pontuações de qualidade observadas numa única leitura em massa (#16419). Desativado por predefinição: os pesos do instantâneo no momento do pedido permanecem exatamente como antes. Quando ativado: esses sinais alimentam os pesos do instantâneo, pelo que os pesos e a seleção dos candidatos podem mudar. | | `AUTO_COMBO_KEYLESS_TRANSPORT_GATE` | boolean | `false` | | Exclui do conjunto de candidatos `auto/*` os fornecedores sem chave (sem autenticação) cujo transporte necessita de um navegador local (`browserBacked`) ou de uma CLI instalada localmente (`isLocalCli`), para que um membro bloqueado não possa atrasar um pedido automático até ao limite de tempo por destino (#17036). Desativado por predefinição: o conjunto sem chave permanece inalterado. As chamadas diretas `/` funcionam em qualquer dos casos. | @@ -244,7 +243,7 @@ Devolve todos os sinalizadores com o respetivo valor efetivo, origem e um resumo "requiresRestart": false, "warningLevel": "caution", }, - // ... todos os 111 sinalizadores + // ... todos os 110 sinalizadores ], "summary": { "total": 56, diff --git a/docs/i18n/ro/docs/reference/FEATURE_FLAGS.md b/docs/i18n/ro/docs/reference/FEATURE_FLAGS.md index ff0cd91f14c5..81aa5ab79901 100644 --- a/docs/i18n/ro/docs/reference/FEATURE_FLAGS.md +++ b/docs/i18n/ro/docs/reference/FEATURE_FLAGS.md @@ -44,7 +44,7 @@ Un flag boolean este considerat **activat** atunci când valoarea sa efectivă e ## Catalogul flag-urilor -111 flag-uri în 6 categorii. Valoarea **implicită** este valoarea implicită din definiție — valoarea +110 flag-uri în 6 categorii. Valoarea **implicită** este valoarea implicită din definiție — valoarea utilizată atunci când nu există nici o suprascriere în baza de date, nici o variabilă de mediu. ### Securitate (10) @@ -109,7 +109,7 @@ utilizată atunci când nu există nici o suprascriere în baza de date, nici o | `RADAR_ENABLED` | boolean | `false` | Activează modulul OmniRoute Radar (ecranele fluxului catalogului și sincronizarea). Dezactivat implicit; activarea deblochează numai interfața cu utilizatorul — sincronizarea datelor necesită în continuare consimțământ separat. | | `MODEL_ENDPOINT_CAPABILITY_GATE_ENABLED` | boolean | `false` | Impune etichetele de încredere pentru endpointurile modelelor (declarate de furnizor, deduse din identificatorul unui model specializat sau configurate de operator): respinge cu HTTP 400, înainte de expediere, o solicitare trimisă către un endpoint pe care modelul nu îl deservește, pentru `/v1/chat/completions`, `/v1/embeddings`, `/v1/rerank`, `/v1/images/generations`, `/v1/videos/generations`, `/v1/music/generations` și `/v1/audio/*`; ascunde din `/v1/models` modelele ale căror endpointuri etichetate nu au un adaptor OmniRoute; returnează 422 pentru acestea la testul modelului din panoul de control (#16929). Dezactivată implicit: etichetele endpointurilor rămân informative, iar expedierea nu se modifică. | -### Execuție (53) +### Execuție (52) | Cheie | Tip | Implicit | Repornire | Descriere | | ---------------------------------------------- | ------- | -------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -156,7 +156,6 @@ utilizată atunci când nu există nici o suprascriere în baza de date, nici o | `KIRO_TOOL_CALL_REPAIR_ENABLED` | boolean | `true` | | Când un răspuns Kiro transmis în flux emite un înveliș `tool_call` incorect format (lipsește `input.name` sau `input.arguments`) înaintea oricărui rezultat vizibil, îl suprimă și retrimite solicitarea o singură dată cu o instrucțiune de corectare, în loc să încheie interacțiunea cu `invalid_kiro_tool_call`. Reîncercarea implică o solicitare Kiro suplimentară; un al doilea eșec încheie interacțiunea cu `kiro_tool_call_repair_retry_failed`. Activat în mod implicit. Setați la `false` pentru ca interacțiunea să eșueze la primul înveliș incorect format. | | `CATALOG_MIRROR_REFINEMENTS_ENABLED` | boolean | `false` | | Rafinează oglinzile de descoperire `claude/` și ale gateway-urilor funcționale din `/v1/models`. Dezactivat în mod implicit: ambele treceri de oglindire se comportă exact ca înainte. Când este activat: oglinzile de descoperire `claude/` includ și variantele listate pentru nivelul de efort de raționament (`-low`/`-medium`/`-high`/`-xhigh`), omit ID-urile `auto/*` încorporate pe care calea solicitării nu le poate materializa și omit un alias deja listat; oglinzile gateway-urilor funcționale omit combinațiile și ID-urile sintetizate de OmniRoute (`claude/`, `no-think/`), preiau proprietarul din `owned_by`, nu folosesc niciodată proprietarul drept propriul gateway, omit gateway-urile dezactivate în `blockedProviders` și modelele care nu se află în catalogul live oficial al unui gateway. | | ---------------------------------------------- | ------- | ------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `ANTHROPIC_COMPATIBLE_MID_CONVERSATION_SYSTEM` | boolean | `false` | | Pe releele simple `anthropic-compatible-*` (transmitere directă claude → claude), păstrează interacțiunile `role: "system"` din mijlocul conversației în `messages[]`, în loc să le mute în parametrul de nivel superior `system`, astfel încât prefixul promptului memorat în cache să rămână stabil între interacțiuni. Se aplică numai când clientul însuși a trimis versiunea beta Anthropic `mid-conversation-system-2026-04-07` `anthropic-beta` (redirecționată în amonte ca atare). Dezactivat în mod implicit: fiecare interacțiune de sistem este mutată exact ca înainte, deoarece releele fără versiunea beta resping mesajele `role: "system"` cu 400. | | `MODELS_DEV_EFFORT_TIERS_ENABLED` | boolean | `false` | | Folosește nivelurile de efort de raționament publicate de models.dev (restrânse la vocabularul cunoscut pentru efort) atunci când nici o suprascriere din partea operatorului, nici registrul furnizorului nu declară vreunul și tratează o listă declarată goală de niveluri drept „fără niveluri” în `effort_tiers` din `/v1/models` și în intersecțiile nivelurilor combinațiilor. Dezactivat în mod implicit: `effort_tiers` și valorile rezolvate `supportedThinkingEfforts` rămân exact ca înainte. | | `AUTO_REQUEST_RESOLVED_CAPABILITIES` | boolean | `false` | | Solicitările auto/* construiesc grupul de candidați folosind capabilitățile rezolvate ale modelelor (vedere, raționament, gândire) și scorurile de calitate observate într-o singură citire în bloc (#16419). Dezactivat în mod implicit: ponderile instantaneului din momentul solicitării rămân exact ca înainte. Când este activat: aceste semnale sunt incluse în ponderile instantaneului, astfel încât ponderile candidaților și selecția se pot schimba. | | `AUTO_COMBO_KEYLESS_TRANSPORT_GATE` | boolean | `false` | | Exclude furnizorii fără cheie (fără autentificare) al căror transport necesită un browser local (`browserBacked`) sau un CLI instalat local (`isLocalCli`) din grupul de candidați `auto/*`, astfel încât un membru blocat să nu poată întârzia o solicitare automată până la expirarea timpului-limită per destinație (#17036). Dezactivat implicit: grupul fără cheie rămâne neschimbat. Apelurile directe `/` funcționează în ambele cazuri. | @@ -253,7 +252,7 @@ Returnează fiecare indicator împreună cu valoarea efectivă, sursa și un rez "requiresRestart": false, "warningLevel": "caution", }, - // ... toți cei 111 indicatori + // ... toți cei 110 indicatori ], "summary": { "total": 56, diff --git a/docs/i18n/ru/docs/reference/FEATURE_FLAGS.md b/docs/i18n/ru/docs/reference/FEATURE_FLAGS.md index a3e60bdcec56..fb5059ce5d65 100644 --- a/docs/i18n/ru/docs/reference/FEATURE_FLAGS.md +++ b/docs/i18n/ru/docs/reference/FEATURE_FLAGS.md @@ -44,7 +44,7 @@ ## Каталог флагов -111 флагов в 6 категориях. **По умолчанию** — это значение по умолчанию из определения, которое +110 флагов в 6 категориях. **По умолчанию** — это значение по умолчанию из определения, которое используется, если отсутствуют как переопределение в БД, так и переменная окружения. ### Безопасность (10) @@ -109,7 +109,7 @@ | `RADAR_ENABLED` | boolean | `false` | Включить модуль OmniRoute Radar (экраны ленты каталога и синхронизацию). По умолчанию отключено; включение лишь разблокирует пользовательский интерфейс — синхронизация данных остаётся отдельной функцией, требующей явного включения. | | `MODEL_ENDPOINT_CAPABILITY_GATE_ENABLED` | boolean | `false` | Применять доверенные теги конечных точек модели (объявленные провайдером, определённые по идентификатору специализированной модели или заданные оператором): до отправки отклонять с HTTP 400 запросы, направленные к конечной точке, которую модель не обслуживает, для `/v1/chat/completions`, `/v1/embeddings`, `/v1/rerank`, `/v1/images/generations`, `/v1/videos/generations`, `/v1/music/generations` и `/v1/audio/*`; скрывать из `/v1/models` модели, для конечных точек с тегами которых отсутствует адаптер OmniRoute; возвращать для них 422 при проверке модели из панели управления (#16929). По умолчанию отключено: теги конечных точек остаются информационными, а отправка запросов не изменяется. | -### Среда выполнения (53) +### Среда выполнения (52) | Ключ | Тип | По умолчанию | Перезапуск | Описание | | ---------------------------------------------- | ------- | ------------ | ---------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -156,7 +156,6 @@ | `KIRO_TOOL_CALL_REPAIR_ENABLED` | boolean | `true` | | Если потоковый ответ Kiro выдаёт некорректную оболочку `tool_call` (отсутствует `input.name` или `input.arguments`) до появления какого-либо видимого вывода, подавить её и один раз повторно отправить запрос с корректирующей инструкцией вместо завершения хода с ошибкой `invalid_kiro_tool_call`. Повторная попытка требует одного дополнительного запроса Kiro; вторая неудача завершает ход с ошибкой `kiro_tool_call_repair_retry_failed`. По умолчанию включено. Установите значение `false`, чтобы завершать ход с ошибкой при первой некорректной оболочке. | | `CATALOG_MIRROR_REFINEMENTS_ENABLED` | boolean | `false` | | Уточнить зеркала обнаружения `claude/` и функционального шлюза на `/v1/models`. По умолчанию выключено: оба прохода зеркалирования работают точно так же, как раньше. Если включено: зеркала обнаружения `claude/` также охватывают перечисленные варианты уровня интенсивности рассуждений (`-low`/`-medium`/`-high`/`-xhigh`), пропускают встроенные идентификаторы `auto/*`, которые невозможно материализовать через путь запроса, и уже перечисленные псевдонимы; зеркала функционального шлюза пропускают комбо и идентификаторы, синтезированные OmniRoute (`claude/`, `no-think/`), получают владельца из `owned_by`, никогда не используют владельца в качестве его собственного шлюза, а также пропускают шлюзы, отключённые в `blockedProviders`, и модели, отсутствующие в официальном актуальном каталоге шлюза. | | ---------------------------------------------- | ------- | ------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `ANTHROPIC_COMPATIBLE_MID_CONVERSATION_SYSTEM` | boolean | `false` | | На обычных ретрансляторах `anthropic-compatible-*` (сквозная передача claude → claude) сохранять встречающиеся в середине диалога сообщения `role: "system"` внутри `messages[]` вместо их переноса в параметр верхнего уровня `system`, чтобы кэшированный префикс подсказки оставался стабильным между ходами. Применяется только тогда, когда сам клиент отправил `mid-conversation-system-2026-04-07` `anthropic-beta` (передаётся вышестоящему сервису без изменений). По умолчанию выключено: каждое системное сообщение переносится точно так же, как раньше, поскольку ретрансляторы без этой бета-функции отклоняют сообщения `role: "system"` с кодом 400. | | `MODELS_DEV_EFFORT_TIERS_ENABLED` | boolean | `false` | | Использовать публикуемые models.dev уровни интенсивности рассуждений (ограниченные известным набором значений), когда они не объявлены ни переопределением оператора, ни реестром поставщика, и трактовать объявленный пустой список уровней как «уровни отсутствуют» в `/v1/models` `effort_tiers` и пересечениях уровней комбо. По умолчанию выключено: `effort_tiers` и разрешённые `supportedThinkingEfforts` остаются точно такими же, как раньше. | | `AUTO_REQUEST_RESOLVED_CAPABILITIES` | boolean | `false` | | Запросы auto/* формируют пул кандидатов с разрешёнными возможностями моделей (зрение, рассуждения, мышление) и наблюдаемыми оценками качества за одно массовое чтение (#16419). По умолчанию выключено: веса снимка на момент запроса остаются точно такими же, как раньше. Если включено: эти сигналы передаются в веса снимка, поэтому веса кандидатов и выбор могут измениться. | | `AUTO_COMBO_KEYLESS_TRANSPORT_GATE` | boolean | `false` | | Исключать провайдеров без ключей (без аутентификации), транспорт которых требует локального браузера (`browserBacked`) или локально установленного CLI (`isLocalCli`), из пула кандидатов `auto/*`, чтобы зависший участник не мог задержать автоматический запрос до истечения тайм-аута для цели (#17036). По умолчанию отключено: пул без ключей остается неизменным. Прямые вызовы `/` работают в любом случае. | @@ -253,7 +252,7 @@ "requiresRestart": false, "warningLevel": "caution", }, - // ... все 111 флагов + // ... все 110 флагов ], "summary": { "total": 56, diff --git a/docs/i18n/si/docs/reference/FEATURE_FLAGS.md b/docs/i18n/si/docs/reference/FEATURE_FLAGS.md index 2277b13c9586..91a8e2b2f320 100644 --- a/docs/i18n/si/docs/reference/FEATURE_FLAGS.md +++ b/docs/i18n/si/docs/reference/FEATURE_FLAGS.md @@ -45,7 +45,7 @@ boolean ෆ්ලෑගයක බලපැවැත්වෙන අගය `"true ## ෆ්ලැග් නාමාවලිය -කාණ්ඩ 6ක් යටතේ ෆ්ලැග් 111ක් ඇත. **පෙරනිමිය** යනු අර්ථ දැක්වීමේ පෙරනිමි අගයයි — DB අතික්රමණයක් හෝ පරිසර විචල්යයක් හෝ නොමැති විට භාවිත කරන අගයයි. +කාණ්ඩ 6ක් යටතේ ෆ්ලැග් 110ක් ඇත. **පෙරනිමිය** යනු අර්ථ දැක්වීමේ පෙරනිමි අගයයි — DB අතික්රමණයක් හෝ පරිසර විචල්යයක් හෝ නොමැති විට භාවිත කරන අගයයි. ### ආරක්ෂාව (10) @@ -109,7 +109,7 @@ boolean ෆ්ලෑගයක බලපැවැත්වෙන අගය `"true | `RADAR_ENABLED` | boolean | `false` | OmniRoute Radar මොඩියුලය (නාමාවලි සංග්රහ තිර සහ සමමුහුර්තකරණය) සක්රීය කරන්න. පෙරනිමියෙන් අක්රියයි; සක්රීය කිරීමෙන් අගුළු හැරෙන්නේ UI පමණි — දත්ත සමමුහුර්තකරණය වෙනම තෝරා සක්රීය කළ යුතුය. | | `MODEL_ENDPOINT_CAPABILITY_GATE_ENABLED` | boolean | `false` | විශ්වාසදායක මාදිලි අන්තලක්ෂ්ය ටැග (සපයන්නා විසින් ප්රකාශිත, විශේෂිත මාදිලි id එකකින් අනුමාන කළ, හෝ ක්රියාකරු විසින් සැකසූ) බලාත්මක කරන්න: මාදිලිය සේවය නොකරන අන්තලක්ෂ්යයකට යැවූ ඉල්ලීමක් `/v1/chat/completions`, `/v1/embeddings`, `/v1/rerank`, `/v1/images/generations`, `/v1/videos/generations`, `/v1/music/generations` සහ `/v1/audio/*` මත යොමු කිරීමට පෙර HTTP 400 සමඟ ප්රතික්ෂේප කරන්න; ටැග් කළ අන්තලක්ෂ්ය සඳහා OmniRoute ඇඩැප්ටරයක් නොමැති මාදිලි `/v1/models` වෙතින් සඟවන්න; ඒවා සඳහා උපකරණ පුවරුවේ මාදිලි පරීක්ෂණයට 422 සමඟ පිළිතුරු දෙන්න (#16929). පෙරනිමියෙන් අක්රියයි: අන්තලක්ෂ්ය ටැග තොරතුරුමය ලෙස පවතින අතර යොමු කිරීම වෙනස් නොවේ. | -### ධාවනකාලය (53) +### ධාවනකාලය (52) | යතුර | වර්ගය | පෙරනිමිය | නැවත ආරම්භය | විස්තරය | | ---------------------------------------------- | ------- | -------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | @@ -156,7 +156,6 @@ boolean ෆ්ලෑගයක බලපැවැත්වෙන අගය `"true | `KIRO_TOOL_CALL_REPAIR_ENABLED` | boolean | `true` | | streaming Kiro ප්රතිචාරයක් දෘශ්ය ප්රතිදානයක් නිකුත් කිරීමට පෙර විකෘති `tool_call` wrapper එකක් (`input.name` හෝ `input.arguments` නොමැති) නිකුත් කළ විට, turn එක `invalid_kiro_tool_call` සමඟ අවසන් කිරීම වෙනුවට එය යටපත් කර නිවැරදි කිරීමේ උපදෙසක් සමඟ ඉල්ලීම එක් වරක් නැවත යවන්න. නැවත උත්සාහයට එක් අමතර Kiro ඉල්ලීමක පිරිවැයක් දැරීමට සිදු වේ; දෙවන අසාර්ථක වීමකදී turn එක `kiro_tool_call_repair_retry_failed` සමඟ අවසන් වේ. පෙරනිමියෙන් සක්රියයි. පළමු විකෘති wrapper එකේදීම turn එක අසාර්ථක කිරීමට `false` ලෙස සකසන්න. | | `CATALOG_MIRROR_REFINEMENTS_ENABLED` | boolean | `false` | | `/v1/models` හි `claude/` discovery සහ functional-gateway mirrors වැඩිදියුණු කරන්න. පෙරනිමියෙන් අක්රියයි: mirror passes දෙකම පෙර පරිදිම ක්රියා කරයි. සක්රිය කළ විට: `claude/` discovery mirrors ලැයිස්තුගත reasoning-effort variants (`-low`/`-medium`/`-high`/`-xhigh`) ද ආවරණය කරයි, request path එකට materialize කළ නොහැකි built-in `auto/*` ids මඟහරින අතර දැනටමත් ලැයිස්තුගත alias එකක්ද මඟහරියි; functional-gateway mirrors combos සහ OmniRoute විසින් synthesized කළ ids (`claude/`, `no-think/`) මඟහරියි, owner එක `owned_by` වෙතින් ලබා ගනී, කිසිවිටෙක owner එක එහිම gateway ලෙස භාවිත නොකරයි, `blockedProviders` තුළ අක්රිය කර ඇති gateways සහ gateway එකක authoritative live catalog එකෙන් පිටත ඇති models මඟහරියි. | | ---------------------------------------------- | ------- | ------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `ANTHROPIC_COMPATIBLE_MID_CONVERSATION_SYSTEM` | boolean | `false` | | සාමාන්ය `anthropic-compatible-*` relays මත (claude → claude passthrough), සංවාදය අතරතුර ඇති `role: "system"` turns ඉහළ මට්ටමේ `system` parameter එකට ඔසවා තැබීම වෙනුවට `messages[]` තුළ තබා ගන්න; එවිට cached prompt prefix එක turns අතර ස්ථාවරව පවතී. මෙය අදාළ වන්නේ client විසින්ම `mid-conversation-system-2026-04-07` `anthropic-beta` යවා ඇති විට පමණි (වෙනසක් නොකර upstream වෙත යවනු ලැබේ). පෙරනිමියෙන් අක්රියයි: beta එක නොමැති relays විසින් `role: "system"` messages 400 සමඟ ප්රතික්ෂේප කරන බැවින්, සෑම system turn එකක්ම පෙර පරිදිම ඔසවා තබනු ලැබේ. | | `MODELS_DEV_EFFORT_TIERS_ENABLED` | boolean | `false` | | operator override එකක් හෝ provider registry එකක් tiers ප්රකාශ නොකරන විට, models.dev පළ කරන reasoning effort tiers (දන්නා effort වචන මාලාවට සීමා කර) භාවිත කර, ප්රකාශිත හිස් tier ලැයිස්තුවක් `/v1/models` හි `effort_tiers` සහ combo tier intersections සඳහා "tiers නැත" ලෙස සලකන්න. පෙරනිමියෙන් අක්රියයි: `effort_tiers` සහ resolve කළ `supportedThinkingEfforts` පෙර පරිදිම පවතී. | | `AUTO_REQUEST_RESOLVED_CAPABILITIES` | boolean | `false` | | auto/* ඉල්ලීම්, එක් bulk read එකකින් resolve කළ model capabilities (vision, reasoning, thinking) සහ නිරීක්ෂණය කළ quality scores භාවිතයෙන් අපේක්ෂක සංචිතය ගොඩනඟයි (#16419). පෙරනිමියෙන් අක්රියයි: request-time snapshot weights පෙර පරිදිම පවතී. සක්රිය කළ විට: එම signals snapshot weights වෙත ලබා දෙන බැවින්, අපේක්ෂක weights සහ තේරීම වෙනස් විය හැක. | | `AUTO_COMBO_KEYLESS_TRANSPORT_GATE` | boolean | `false` | | දේශීය බ්රවුසරයක් (`browserBacked`) හෝ දේශීයව ස්ථාපනය කළ CLI එකක් (`isLocalCli`) අවශ්ය වන ප්රවාහනයක් සහිත keyless (no-auth) සපයන්නන් `auto/*` අපේක්ෂක සංචිතයෙන් බැහැරව තබන්න, එවිට ප්රතිචාර නොදක්වන සාමාජිකයෙකුට එක් එක් ඉලක්කයේ කාලසීමාව අවසන් වන තෙක් ස්වයංක්රීය ඉල්ලීමක් අවහිර කළ නොහැක (#17036). පෙරනිමියෙන් අක්රියයි: keyless සංචිතය වෙනස් නොවේ. සෘජු `/` ඇමතුම් ඕනෑම ආකාරයකින් ක්රියා කරයි. | @@ -253,7 +252,7 @@ boolean ෆ්ලෑගයක බලපැවැත්වෙන අගය `"true "requiresRestart": false, "warningLevel": "caution", }, - // ... සියලු ධජ 111 + // ... සියලු ධජ 110 ], "summary": { "total": 56, diff --git a/docs/i18n/sk/docs/reference/FEATURE_FLAGS.md b/docs/i18n/sk/docs/reference/FEATURE_FLAGS.md index a79ea439aba3..c46536f800b8 100644 --- a/docs/i18n/sk/docs/reference/FEATURE_FLAGS.md +++ b/docs/i18n/sk/docs/reference/FEATURE_FLAGS.md @@ -44,7 +44,7 @@ Booleovský príznak sa považuje za **povolený**, keď je jeho efektívna hodn ## Katalóg príznakov -111 príznakov v 6 kategóriách. **Predvolená hodnota** je predvolená hodnota definície — hodnota +110 príznakov v 6 kategóriách. **Predvolená hodnota** je predvolená hodnota definície — hodnota použitá v prípade, keď nie je prítomné prepísanie v DB ani premenná prostredia. ### Bezpečnosť (10) @@ -109,7 +109,7 @@ použitá v prípade, keď nie je prítomné prepísanie v DB ani premenná pros | `RADAR_ENABLED` | boolean | `false` | Povolí modul OmniRoute Radar (obrazovky katalógového kanála a synchronizáciu). Predvolene je vypnuté; povolením sa iba sprístupní používateľské rozhranie — synchronizácia údajov zostáva samostatnou voliteľnou možnosťou. | | `MODEL_ENDPOINT_CAPABILITY_GATE_ENABLED` | boolean | `false` | Vynúti dôveryhodné značky koncových bodov modelu (deklarované poskytovateľom, odvodené z identifikátora špecializovaného modelu alebo nastavené operátorom): pred odoslaním odmietne s HTTP 400 požiadavku odoslanú na koncový bod, ktorý model neobsluhuje, na `/v1/chat/completions`, `/v1/embeddings`, `/v1/rerank`, `/v1/images/generations`, `/v1/videos/generations`, `/v1/music/generations` a `/v1/audio/*`; skryje z `/v1/models` modely, ktorých označené koncové body nemajú adaptér OmniRoute; pri teste modelu v ovládacom paneli pre ne odpovie stavom 422 (#16929). Predvolene je vypnuté: značky koncových bodov zostávajú iba informatívne a odosielanie sa nemení. | -### Runtime (53) +### Runtime (52) | Kľúč | Typ | Predvolené | Reštartovanie | Popis | | ---------------------------------------------- | ------- | ---------- | ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -156,7 +156,6 @@ použitá v prípade, keď nie je prítomné prepísanie v DB ani premenná pros | `KIRO_TOOL_CALL_REPAIR_ENABLED` | boolean | `true` | | Keď streamovaná odpoveď Kiro odošle poškodený obal `tool_call` (chýba `input.name` alebo `input.arguments`) pred akýmkoľvek viditeľným výstupom, potlačiť ho a namiesto ukončenia ťahu s chybou `invalid_kiro_tool_call` raz znova odoslať požiadavku s opravnou inštrukciou. Opakovaný pokus stojí jednu ďalšiu požiadavku Kiro; druhé zlyhanie ukončí ťah s chybou `kiro_tool_call_repair_retry_failed`. Predvolene zapnuté. Nastavením na `false` sa ťah ukončí už pri prvom poškodenom obale. | | `CATALOG_MIRROR_REFINEMENTS_ENABLED` | boolean | `false` | | Spresniť zrkadlá zisťovania `claude/` a funkčnej brány na `/v1/models`. Predvolene vypnuté: oba priechody zrkadlenia fungujú presne ako predtým. Pri zapnutí: zrkadlá zisťovania `claude/` zahŕňajú aj uvedené varianty intenzity uvažovania (`-low`/`-medium`/`-high`/`-xhigh`), vynechávajú vstavané identifikátory `auto/*`, ktoré cesta požiadavky nedokáže vytvoriť, a vynechávajú už uvedený alias; zrkadlá funkčnej brány vynechávajú kombinácie a identifikátory syntetizované službou OmniRoute (`claude/`, `no-think/`), preberajú vlastníka z `owned_by`, nikdy nepoužívajú vlastníka ako jeho vlastnú bránu, vynechávajú brány zakázané v `blockedProviders` a modely mimo autoritatívneho živého katalógu brány. | | ---------------------------------------------- | ------- | ------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `ANTHROPIC_COMPATIBLE_MID_CONVERSATION_SYSTEM` | boolean | `false` | | Na jednoduchých prenosových službách `anthropic-compatible-*` (priame odovzdanie claude → claude) ponechať ťahy `role: "system"` uprostred konverzácie v poli `messages[]` namiesto ich presunutia do parametra `system` najvyššej úrovne, aby zostala predpona výzvy vo vyrovnávacej pamäti medzi ťahmi stabilná. Platí iba vtedy, keď samotný klient odoslal `mid-conversation-system-2026-04-07` v hlavičke `anthropic-beta` (bez zmeny preposlanej nadradenej službe). Predvolene vypnuté: každý systémový ťah sa presunie presne ako predtým, pretože prenosové služby bez tejto beta verzie odmietajú správy `role: "system"` so stavom 400. | | `MODELS_DEV_EFFORT_TIERS_ENABLED` | boolean | `false` | | Použiť úrovne intenzity uvažovania publikované službou models.dev (obmedzené na známu množinu hodnôt intenzity), keď žiadne úrovne nedeklaruje ani prepísanie operátorom, ani register poskytovateľov, a považovať deklarovaný prázdny zoznam úrovní za „žiadne úrovne“ v poli `/v1/models` `effort_tiers` a v prienikoch úrovní kombinácií. Predvolene vypnuté: `effort_tiers` a vyriešené `supportedThinkingEfforts` zostávajú presne ako predtým. | | `AUTO_REQUEST_RESOLVED_CAPABILITIES` | boolean | `false` | | Požiadavky auto/* zostavujú fond kandidátov s vyriešenými schopnosťami modelov (obrazové vstupy, uvažovanie, premýšľanie) a pozorovanými skóre kvality v rámci jedného hromadného čítania (#16419). Predvolene vypnuté: váhy snímky v čase požiadavky zostávajú presne ako predtým. Pri zapnutí: tieto signály vstupujú do váh snímky, takže váhy kandidátov a výber sa môžu zmeniť. | | `AUTO_COMBO_KEYLESS_TRANSPORT_GATE` | boolean | `false` | | Vyradí bezkľúčových poskytovateľov (bez autentifikácie), ktorých transport vyžaduje lokálny prehliadač (`browserBacked`) alebo lokálne nainštalované CLI (`isLocalCli`), z množiny kandidátov `auto/*`, aby zaseknutý člen nemohol pozastaviť automatickú požiadavku až do vypršania časového limitu pre daný cieľ (#17036). Predvolene vypnuté: bezkľúčová množina zostáva nezmenená. Priame volania `/` fungujú v oboch prípadoch. | @@ -253,7 +252,7 @@ Vráti každý príznak spolu s jeho výslednou hodnotou, zdrojom a súhrnom. "requiresRestart": false, "warningLevel": "caution", }, - // ... všetkých 111 príznakov + // ... všetkých 110 príznakov ], "summary": { "total": 56, diff --git a/docs/i18n/sl/docs/reference/FEATURE_FLAGS.md b/docs/i18n/sl/docs/reference/FEATURE_FLAGS.md index 4eea13d9f851..7cb54281e7cf 100644 --- a/docs/i18n/sl/docs/reference/FEATURE_FLAGS.md +++ b/docs/i18n/sl/docs/reference/FEATURE_FLAGS.md @@ -44,7 +44,7 @@ Logična zastavica se šteje za **omogočeno**, kadar je njena dejanska vrednost ## Katalog zastavic -111 zastavic v 6 kategorijah. **Privzeta vrednost** je privzeta vrednost definicije — vrednost, +110 zastavic v 6 kategorijah. **Privzeta vrednost** je privzeta vrednost definicije — vrednost, ki se uporabi, kadar ni prisotna niti preglasitev v zbirki podatkov niti okoljska spremenljivka. ### Varnost (10) @@ -109,7 +109,7 @@ ki se uporabi, kadar ni prisotna niti preglasitev v zbirki podatkov niti okoljsk | `RADAR_ENABLED` | boolean | `false` | Omogoči modul OmniRoute Radar (zasloni s kataloškimi viri in sinhronizacija). Privzeto je izklopljeno; omogočanje zgolj odklene uporabniški vmesnik — sinhronizacija podatkov ostane ločena možnost, ki jo je treba izrecno omogočiti. | | `MODEL_ENDPOINT_CAPABILITY_GATE_ENABLED` | boolean | `false` | Uveljavi zaupanja vredne oznake končnih točk modela (ki jih navede ponudnik, so izpeljane iz ID-ja specializiranega modela ali jih nastavi upravljavec): zahtevo, poslano končni točki, ki je model ne podpira, pred posredovanjem zavrne z odgovorom HTTP 400 na `/v1/chat/completions`, `/v1/embeddings`, `/v1/rerank`, `/v1/images/generations`, `/v1/videos/generations`, `/v1/music/generations` in `/v1/audio/*`; v `/v1/models` skrije modele, katerih označene končne točke nimajo vmesnika OmniRoute; pri preskusu modela na nadzorni plošči zanje odgovori s 422 (#16929). Privzeto je izklopljeno: oznake končnih točk ostanejo zgolj informativne, posredovanje pa se ne spremeni. | -### Izvajalno okolje (53) +### Izvajalno okolje (52) | Ključ | Vrsta | Privzeto | Ponovni zagon | Opis | | ---------------------------------------------- | ------- | -------- | ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -156,7 +156,6 @@ ki se uporabi, kadar ni prisotna niti preglasitev v zbirki podatkov niti okoljsk | `KIRO_TOOL_CALL_REPAIR_ENABLED` | boolean | `true` | | Ko pretočni odgovor Kiro pred kakršnim koli vidnim izhodom odda nepravilno oblikovan ovoj `tool_call` (manjka `input.name` ali `input.arguments`), ga zavrzi in zahtevo enkrat znova pošlji s popravnim navodilom, namesto da korak končaš z `invalid_kiro_tool_call`. Ponovni poskus zahteva eno dodatno zahtevo Kiro; druga napaka konča korak z `kiro_tool_call_repair_retry_failed`. Privzeto je vklopljeno. Nastavi na `false`, da se korak konča z napako že pri prvem nepravilno oblikovanem ovoju. | | `CATALOG_MIRROR_REFINEMENTS_ENABLED` | boolean | `false` | | Izboljšaj zrcala odkrivanja `claude/` in funkcionalnih prehodov na `/v1/models`. Privzeto je izklopljeno: oba postopka zrcaljenja delujeta natanko tako kot prej. Ko je vklopljeno: zrcala odkrivanja `claude/` vključujejo tudi navedene različice intenzivnosti sklepanja (`-low`/`-medium`/`-high`/`-xhigh`), preskočijo vgrajene ID-je `auto/*`, ki jih pot zahteve ne more ustvariti, in že navedene vzdevke; zrcala funkcionalnih prehodov preskočijo kombinacije ter ID-je, ki jih je ustvaril OmniRoute (`claude/`, `no-think/`), lastnika pridobijo iz `owned_by`, lastnika nikoli ne uporabijo kot njegov lasten prehod ter preskočijo prehode, onemogočene v `blockedProviders`, in modele zunaj avtoritativnega trenutnega kataloga prehoda. | | ---------------------------------------------- | ------- | ------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `ANTHROPIC_COMPATIBLE_MID_CONVERSATION_SYSTEM` | boolean | `false` | | Pri običajnih posrednikih `anthropic-compatible-*` (neposredno posredovanje claude → claude) ohrani sporočila sredi pogovora z `role: "system"` znotraj `messages[]`, namesto da jih premakneš v parameter najvišje ravni `system`, tako da predpomnjena predpona poziva ostane stabilna med koraki. Velja samo, ko je odjemalec sam poslal `mid-conversation-system-2026-04-07` `anthropic-beta` (nespremenjeno posredovano nadrejenemu ponudniku). Privzeto je izklopljeno: vsako sistemsko sporočilo se premakne natanko tako kot prej, saj posredniki brez te različice beta sporočila z `role: "system"` zavrnejo z odgovorom 400. | | `MODELS_DEV_EFFORT_TIERS_ENABLED` | boolean | `false` | | Uporabi stopnje intenzivnosti sklepanja, ki jih objavlja models.dev (omejene na znani nabor vrednosti intenzivnosti), kadar jih ne določa niti preglasitev upravljavca niti register ponudnika, in obravnavaj navedeni prazen seznam stopenj kot »brez stopenj« v `/v1/models` `effort_tiers` ter presekih stopenj kombinacij. Privzeto je izklopljeno: `effort_tiers` in razrešene vrednosti `supportedThinkingEfforts` ostanejo natanko takšne kot prej. | | `AUTO_REQUEST_RESOLVED_CAPABILITIES` | boolean | `false` | | Zahteve auto/* sestavijo nabor kandidatov z razrešenimi zmogljivostmi modela (vid, sklepanje, razmišljanje) in opaženimi ocenami kakovosti v enem množičnem branju (#16419). Privzeto je izklopljeno: uteži posnetka ob času zahteve ostanejo natanko takšne kot prej. Ko je vklopljeno: ti signali vplivajo na uteži posnetka, zato se lahko uteži kandidatov in izbira spremenijo. | | `AUTO_COMBO_KEYLESS_TRANSPORT_GATE` | boolean | `false` | | Ponudnike brez ključa (brez preverjanja pristnosti), katerih transport potrebuje lokalni brskalnik (`browserBacked`) ali lokalno nameščen CLI (`isLocalCli`), izloči iz nabora kandidatov `auto/*`, da neodziven član ne more zadržati samodejne zahteve do izteka časovne omejitve posameznega cilja (#17036). Privzeto izklopljeno: nabor brez ključa ostane nespremenjen. Neposredni klici `/` delujejo v obeh primerih. | @@ -253,7 +252,7 @@ Vrne vse zastavice z njihovo dejansko vrednostjo, virom in povzetkom. "requiresRestart": false, "warningLevel": "caution", }, - // ... vseh 111 zastavic + // ... vseh 110 zastavic ], "summary": { "total": 56, diff --git a/docs/i18n/sr/docs/reference/FEATURE_FLAGS.md b/docs/i18n/sr/docs/reference/FEATURE_FLAGS.md index 95803ebc69b6..08d5399d9167 100644 --- a/docs/i18n/sr/docs/reference/FEATURE_FLAGS.md +++ b/docs/i18n/sr/docs/reference/FEATURE_FLAGS.md @@ -44,7 +44,7 @@ ## Каталог заставица -111 заставица у 6 категорија. **Подразумевана вредност** је вредност из дефиниције — вредност +110 заставица у 6 категорија. **Подразумевана вредност** је вредност из дефиниције — вредност која се користи када не постоје ни замена у бази података ни променљива окружења. ### Безбедност (10) @@ -109,7 +109,7 @@ | `RADAR_ENABLED` | boolean | `false` | Омогућава модул OmniRoute Radar (екране каталошког извора и синхронизацију). Подразумевано је искључено; омогућавање само откључава кориснички интерфејс — синхронизација података остаје засебна опција која се мора изричито укључити. | | `MODEL_ENDPOINT_CAPABILITY_GATE_ENABLED` | boolean | `false` | Спроводи поуздане ознаке крајњих тачака модела (које је декларисао добављач, које су изведене из ID-а специјализованог модела или које је поставио оператер): одбија захтев послат крајњој тачки коју модел не опслужује, уз HTTP 400 пре слања, на `/v1/chat/completions`, `/v1/embeddings`, `/v1/rerank`, `/v1/images/generations`, `/v1/videos/generations`, `/v1/music/generations` и `/v1/audio/*`; сакрива из `/v1/models` моделе чије означене крајње тачке немају OmniRoute адаптер; за њих на тест модела на контролној табли одговара статусом 422 (#16929). Подразумевано је искључено: ознаке крајњих тачака остају информативне и слање се не мења. | -### Извршно окружење (53) +### Извршно окружење (52) | Кључ | Тип | Подразумевано | Поновно покретање | Опис | | ---------------------------------------------- | ------- | ------------- | ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -156,7 +156,6 @@ | `KIRO_TOOL_CALL_REPAIR_ENABLED` | boolean | `true` | | Када Kiro одговор који се стримује емитује неисправан омотач `tool_call` (недостаје `input.name` или `input.arguments`) пре било каквог видљивог излаза, потисни га и једном поново пошаљи захтев са исправљајућим упутством уместо да завршиш потез грешком `invalid_kiro_tool_call`. Поновни покушај захтева један додатни Kiro захтев; други неуспех завршава потез грешком `kiro_tool_call_repair_retry_failed`. Подразумевано је укључено. Поставите на `false` да би се потез прекинуо при првом неисправном омотачу. | | `CATALOG_MIRROR_REFINEMENTS_ENABLED` | boolean | `false` | | Побољшај огледала откривања `claude/` и функционалног мрежног пролаза на `/v1/models`. Подразумевано је искључено: оба пролаза кроз огледала понашају се потпуно исто као раније. Када је укључено: огледала откривања `claude/` обухватају и наведене варијанте интензитета резоновања (`-low`/`-medium`/`-high`/`-xhigh`), прескачу уграђене `auto/*` идентификаторе које путања захтева не може да материјализује и прескачу већ наведени алијас; огледала функционалног мрежног пролаза прескачу комбинације и идентификаторе које је синтетизовао OmniRoute (`claude/`, `no-think/`), преузимају власника из `owned_by`, никада не користе власника као његов сопствени мрежни пролаз, прескачу мрежне пролазе онемогућене у `blockedProviders` и моделе који нису у меродавном активном каталогу мрежног пролаза. | | ---------------------------------------------- | ------- | ------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `ANTHROPIC_COMPATIBLE_MID_CONVERSATION_SYSTEM` | boolean | `false` | | На обичним `anthropic-compatible-*` релејима (директно прослеђивање claude → claude), задржи потезе `role: "system"` усред разговора унутар `messages[]` уместо да их премешташ у параметар највишег нивоа `system`, како би кеширани префикс упита остао стабилан током више потеза. Примењује се само када је сам клијент послао `mid-conversation-system-2026-04-07` `anthropic-beta` (која се непромењена прослеђује узводно). Подразумевано је искључено: сваки системски потез премешта се као и раније, јер релеји без те бета-функције одбијају поруке `role: "system"` одговором 400. | | `MODELS_DEV_EFFORT_TIERS_ENABLED` | boolean | `false` | | Користи нивое интензитета резоновања које објављује models.dev (ограничене на познати скуп вредности интензитета) када их не декларишу ни операторско преиначење ни регистар добављача, а декларисану празну листу нивоа третирај као „без нивоа“ у `/v1/models` `effort_tiers` и пресецима нивоа комбинација. Подразумевано је искључено: `effort_tiers` и разрешени `supportedThinkingEfforts` остају потпуно исти као раније. | | `AUTO_REQUEST_RESOLVED_CAPABILITIES` | boolean | `false` | | Захтеви auto/* граде скуп кандидата користећи разрешене могућности модела (вид, резоновање, размишљање) и забележене оцене квалитета у једном групном читању (#16419). Подразумевано је искључено: тежине снимка стања у време захтева остају потпуно исте као раније. Када је укључено: ти сигнали утичу на тежине снимка стања, па се тежине кандидата и избор могу променити. | | `AUTO_COMBO_KEYLESS_TRANSPORT_GATE` | boolean | `false` | | Изостављајте провајдере без кључа (без аутентификације) чији транспорт захтева локални прегледач (`browserBacked`) или локално инсталиран CLI (`isLocalCli`) из скупа кандидата `auto/*`, како заглављени члан не би могао да заустави аутоматски захтев до истека временског ограничења по циљу (#17036). Подразумевано је искључено: скуп без кључа остаје непромењен. Директни позиви `/` функционишу у оба случаја. | @@ -253,7 +252,7 @@ "requiresRestart": false, "warningLevel": "caution", }, - // ... свих 111 заставица + // ... свих 110 заставица ], "summary": { "total": 56, diff --git a/docs/i18n/sv/docs/reference/FEATURE_FLAGS.md b/docs/i18n/sv/docs/reference/FEATURE_FLAGS.md index 01c96ccea5aa..3587f1a9b885 100644 --- a/docs/i18n/sv/docs/reference/FEATURE_FLAGS.md +++ b/docs/i18n/sv/docs/reference/FEATURE_FLAGS.md @@ -44,7 +44,7 @@ En boolesk flagga anses vara **aktiverad** när dess effektiva värde är `"true ## Flaggkatalog -111 flaggor i 6 kategorier. **Standardvärde** är definitionens standardvärde — det värde +110 flaggor i 6 kategorier. **Standardvärde** är definitionens standardvärde — det värde som används när varken en åsidosättning i databasen eller en miljövariabel finns. ### Säkerhet (10) @@ -109,7 +109,7 @@ som används när varken en åsidosättning i databasen eller en miljövariabel | `RADAR_ENABLED` | boolean | `false` | Aktivera OmniRoute Radar-modulen (skärmar för katalogflöden och synkronisering). Av som standard; aktivering låser endast upp användargränssnittet – datasynkronisering kräver fortfarande ett separat aktivt val. | | `MODEL_ENDPOINT_CAPABILITY_GATE_ENABLED` | boolean | `false` | Tillämpa betrodda taggar för modellers slutpunkter (leverantörsdeklarerade, härledda från en specialiserad modells id eller angivna av operatören): avvisa en förfrågan som skickas till en slutpunkt som modellen inte betjänar med HTTP 400 före dirigering på `/v1/chat/completions`, `/v1/embeddings`, `/v1/rerank`, `/v1/images/generations`, `/v1/videos/generations`, `/v1/music/generations` och `/v1/audio/*`; dölj modeller vars taggade slutpunkter saknar en OmniRoute-adapter från `/v1/models`; besvara modelltestet på instrumentpanelen med 422 för dem (#16929). Av som standard: slutpunktstaggar förblir informativa och dirigeringen är oförändrad. | -### Körning (53) +### Körning (52) | Nyckel | Typ | Standardvärde | Omstart | Beskrivning | | ---------------------------------------------- | ------- | ------------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -156,7 +156,6 @@ som används när varken en åsidosättning i databasen eller en miljövariabel | `KIRO_TOOL_CALL_REPAIR_ENABLED` | boolean | `true` | | När ett strömmande Kiro-svar avger ett felaktigt `tool_call`-omslag (där `input.name` eller `input.arguments` saknas) före synlig utdata, undertrycks det och begäran skickas på nytt en gång med en korrigerande instruktion, i stället för att turen avslutas med `invalid_kiro_tool_call`. Återförsöket kostar en extra Kiro-begäran; ett andra misslyckande avslutar turen med `kiro_tool_call_repair_retry_failed`. På som standard. Ange `false` för att låta turen misslyckas vid det första felaktiga omslaget. | | `CATALOG_MIRROR_REFINEMENTS_ENABLED` | boolean | `false` | | Förfina identifierings- och funktionella gateway-speglingar för `claude/` på `/v1/models`. Av som standard: båda speglingsstegen fungerar exakt som tidigare. På: identifieringsspeglingar för `claude/` omfattar även listade varianter för resonemangsansträngning (`-low`/`-medium`/`-high`/`-xhigh`), hoppar över inbyggda `auto/*`-id:n som sökvägen för begäran inte kan materialisera och hoppar över alias som redan finns i listan; funktionella gateway-speglingar hoppar över kombinationer och id:n som syntetiserats av OmniRoute (`claude/`, `no-think/`), hämtar ägaren från `owned_by`, använder aldrig ägaren som sin egen gateway och hoppar över gatewayar som är inaktiverade i `blockedProviders` samt modeller utanför en gateways auktoritativa livekatalog. | | ---------------------------------------------- | ------- | ------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `ANTHROPIC_COMPATIBLE_MID_CONVERSATION_SYSTEM` | boolean | `false` | | På vanliga `anthropic-compatible-*`-reläer (claude → claude-vidarebefordran), behåll `role: "system"`-turer mitt i konversationen inuti `messages[]` i stället för att flytta upp dem till toppnivåparametern `system`, så att den cachade promptens prefix förblir stabilt mellan turerna. Gäller endast när klienten själv skickade `mid-conversation-system-2026-04-07`-betan `anthropic-beta` (vidarebefordras uppströms i oförändrat skick). Av som standard: varje systemtur flyttas upp precis som tidigare, eftersom reläer utan betan avvisar meddelanden med `role: "system"` med 400. | | `MODELS_DEV_EFFORT_TIERS_ENABLED` | boolean | `false` | | Använd de nivåer för resonemangsansträngning som models.dev publicerar (begränsade till den kända terminologin för ansträngning) när varken en operatörsåsidosättning eller leverantörsregistret deklarerar några, och behandla en deklarerad tom nivålista som ”inga nivåer” i `/v1/models` `effort_tiers` och skärningsmängder för kombinationsnivåer. Av som standard: `effort_tiers` och matchade `supportedThinkingEfforts` förblir exakt som tidigare. | | `AUTO_REQUEST_RESOLVED_CAPABILITIES` | boolean | `false` | | auto/*-begäranden bygger kandidatpoolen med matchade modellfunktioner (bild, resonemang, tänkande) och observerade kvalitetspoäng i en enda massläsning (#16419). Av som standard: vikterna i ögonblicksbilden vid begärandetillfället förblir exakt som tidigare. På: dessa signaler matas in i ögonblicksbildens vikter, så kandidatvikter och val kan ändras. | | `AUTO_COMBO_KEYLESS_TRANSPORT_GATE` | boolean | `false` | | Uteslut nyckellösa leverantörer (utan autentisering) vars transport kräver en lokal webbläsare (`browserBacked`) eller ett lokalt installerat CLI (`isLocalCli`) från kandidatpoolen `auto/*`, så att en medlem som har hängt sig inte kan blockera en automatisk begäran fram till tidsgränsen per mål (#17036). Av som standard: den nyckellösa poolen är oförändrad. Direkta anrop till `/` fungerar oavsett inställning. | @@ -253,7 +252,7 @@ Returnerar varje flagga med dess effektiva värde, källa och en sammanfattning. "requiresRestart": false, "warningLevel": "caution", }, - // ... alla 111 flaggor + // ... alla 110 flaggor ], "summary": { "total": 56, diff --git a/docs/i18n/sw/docs/reference/FEATURE_FLAGS.md b/docs/i18n/sw/docs/reference/FEATURE_FLAGS.md index b6651dabf0d2..ac927f3dce91 100644 --- a/docs/i18n/sw/docs/reference/FEATURE_FLAGS.md +++ b/docs/i18n/sw/docs/reference/FEATURE_FLAGS.md @@ -44,7 +44,7 @@ Bendera ya boolean huchukuliwa kuwa **imewashwa** wakati thamani yake inayotumik ## Katalogi ya Bendera -Bendera 111 katika kategoria 6. **Chaguo-msingi** ni chaguo-msingi la ufafanuzi — thamani +Bendera 110 katika kategoria 6. **Chaguo-msingi** ni chaguo-msingi la ufafanuzi — thamani inayotumika wakati hakuna ubatilishaji wa DB wala kigeu cha mazingira. ### Usalama (10) @@ -109,7 +109,7 @@ inayotumika wakati hakuna ubatilishaji wa DB wala kigeu cha mazingira. | `RADAR_ENABLED` | boolean | `false` | Washa moduli ya OmniRoute Radar (skrini za mipasho ya katalogi na usawazishaji). Imezimwa kwa chaguo-msingi; kuiwasha hufungua kiolesura cha mtumiaji pekee — usawazishaji wa data bado unahitaji kuwezeshwa kando. | | `MODEL_ENDPOINT_CAPABILITY_GATE_ENABLED` | boolean | `false` | Tekeleza lebo zinazoaminika za ncha za modeli (zilizotangazwa na mtoa huduma, zilizobainishwa kutoka kwa kitambulisho cha modeli maalum, au zilizowekwa na mwendeshaji): kataa kwa HTTP 400 kabla ya kutuma ombi lililotumwa kwenye ncha ambayo haitumiki na modeli katika `/v1/chat/completions`, `/v1/embeddings`, `/v1/rerank`, `/v1/images/generations`, `/v1/videos/generations`, `/v1/music/generations` na `/v1/audio/*`; ficha kutoka `/v1/models` modeli ambazo ncha zake zenye lebo hazina adapta ya OmniRoute; jibu jaribio la modeli la dashibodi kwa 422 kwa modeli hizo (#16929). Imezimwa kwa chaguo-msingi: lebo za ncha hubaki za taarifa na utumaji haubadiliki. | -### Wakati wa utekelezaji (53) +### Wakati wa utekelezaji (52) | Ufunguo | Aina | Chaguomsingi | Kuanzisha upya | Maelezo | | ---------------------------------------------- | ------- | ------------ | -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -156,7 +156,6 @@ inayotumika wakati hakuna ubatilishaji wa DB wala kigeu cha mazingira. | `KIRO_TOOL_CALL_REPAIR_ENABLED` | boolean | `true` | | Wakati jibu la Kiro linalotiririshwa linatoa kifungashio cha `tool_call` chenye hitilafu (kinakosa `input.name` au `input.arguments`) kabla ya towe lolote linaloonekana, kifiche na utume ombi tena mara moja pamoja na maagizo ya kurekebisha badala ya kumaliza zamu kwa `invalid_kiro_tool_call`. Jaribio hilo la pili hugharimu ombi moja la ziada la Kiro; hitilafu ya pili humaliza zamu kwa `kiro_tool_call_repair_retry_failed`. Imewashwa kwa chaguo-msingi. Weka kuwa `false` ili zamu ishindwe kwenye kifungashio cha kwanza chenye hitilafu. | | `CATALOG_MIRROR_REFINEMENTS_ENABLED` | boolean | `false` | | Boresha vioo vya ugunduzi vya `claude/` na vya lango la kiutendaji kwenye `/v1/models`. Imezimwa kwa chaguo-msingi: hatua zote mbili za uakisi hufanya kazi sawasawa na hapo awali. Ikiwashwa: vioo vya ugunduzi vya `claude/` pia hujumuisha vibadala vilivyoorodheshwa vya kiwango cha juhudi za u reasoning (`-low`/`-medium`/`-high`/`-xhigh`), huruka vitambulisho vilivyojengewa ndani vya `auto/*` ambavyo njia ya ombi haiwezi kuunda na huruka lakabu ambayo tayari imeorodheshwa; vioo vya lango la kiutendaji huruka combo na vitambulisho vilivyoundwa na OmniRoute (`claude/`, `no-think/`), huchukua mmiliki kutoka `owned_by`, havitumii kamwe mmiliki kama lango lake lenyewe, na huruka malango yaliyozimwa katika `blockedProviders` pamoja na modeli zilizo nje ya katalogi hai yenye mamlaka ya lango. | | ---------------------------------------------- | ------- | ------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `ANTHROPIC_COMPATIBLE_MID_CONVERSATION_SYSTEM` | boolean | `false` | | Kwenye relay za kawaida za `anthropic-compatible-*` (claude → upitishaji wa moja kwa moja wa claude), weka zamu za katikati ya mazungumzo za `role: "system"` ndani ya `messages[]` badala ya kuzipeleka kwenye kigezo cha kiwango cha juu cha `system`, ili kiambishi awali cha kidokezo kilichohifadhiwa kwenye akiba kiendelee kuwa thabiti katika zamu zote. Hutumika tu wakati kiteja chenyewe kilituma `mid-conversation-system-2026-04-07` `anthropic-beta` (ambayo hupelekwa juu kama ilivyo). Imezimwa kwa chaguo-msingi: kila zamu ya mfumo hupelekwa juu sawasawa na hapo awali, kwa sababu relay zisizo na beta hukataa ujumbe wa `role: "system"` kwa 400. | | `MODELS_DEV_EFFORT_TIERS_ENABLED` | boolean | `false` | | Tumia viwango vya juhudi za u reasoning vinavyochapishwa na models.dev (vilivyopunguzwa hadi msamiati unaojulikana wa juhudi) wakati hakuna ubatilishaji wa opereta wala sajili ya mtoa huduma inayotangaza viwango vyovyote, na chukulia orodha tupu ya viwango iliyotangazwa kuwa "hakuna viwango" katika `effort_tiers` ya `/v1/models` na makutano ya viwango vya combo. Imezimwa kwa chaguo-msingi: `effort_tiers` na `supportedThinkingEfforts` iliyotatuliwa hubaki sawasawa na hapo awali. | | `AUTO_REQUEST_RESOLVED_CAPABILITIES` | boolean | `false` | | Maombi ya auto/* huunda kundi la wagombea kwa kutumia uwezo uliotatuliwa wa modeli (uoni, u reasoning, fikra) na alama za ubora zilizobainishwa katika usomaji mmoja wa mkupuo (#16419). Imezimwa kwa chaguo-msingi: uzani wa taswira ya wakati wa ombi hubaki sawasawa na hapo awali. Ikiwashwa: ishara hizo huingizwa kwenye uzani wa taswira, kwa hivyo uzani na uteuzi wa wagombea unaweza kubadilika. | | `AUTO_COMBO_KEYLESS_TRANSPORT_GATE` | boolean | `false` | | Ondoa watoa huduma wasiotumia ufunguo (bila uthibitishaji) ambao usafirishaji wao unahitaji kivinjari cha ndani (`browserBacked`) au CLI iliyosakinishwa ndani ya mfumo (`isLocalCli`) kwenye kundi la wagombea la `auto/*`, ili mshiriki aliyekwama asiweze kuchelewesha ombi la kiotomatiki hadi muda wa kusubiri kwa kila lengo uishe (#17036). Imezimwa kwa chaguo-msingi: kundi lisilotumia ufunguo halibadilishwi. Miito ya moja kwa moja ya `/` hufanya kazi kwa hali yoyote. | @@ -253,7 +252,7 @@ Hurejesha kila kiashiria pamoja na thamani yake inayotumika, chanzo, na muhtasar "requiresRestart": false, "warningLevel": "caution", }, - // ... viashiria vyote 111 + // ... viashiria vyote 110 ], "summary": { "total": 56, diff --git a/docs/i18n/ta/docs/reference/FEATURE_FLAGS.md b/docs/i18n/ta/docs/reference/FEATURE_FLAGS.md index fe97fb01a103..a5432657987b 100644 --- a/docs/i18n/ta/docs/reference/FEATURE_FLAGS.md +++ b/docs/i18n/ta/docs/reference/FEATURE_FLAGS.md @@ -46,7 +46,7 @@ ## கொடிப் பட்டியல் -6 வகைகளில் 111 கொடிகள் உள்ளன. **இயல்புநிலை** என்பது வரையறையின் இயல்புநிலை — DB மேலெழுதலும் சூழல் மாறியும் இல்லாதபோது +6 வகைகளில் 110 கொடிகள் உள்ளன. **இயல்புநிலை** என்பது வரையறையின் இயல்புநிலை — DB மேலெழுதலும் சூழல் மாறியும் இல்லாதபோது பயன்படுத்தப்படும் மதிப்பு. ### பாதுகாப்பு (10) @@ -111,7 +111,7 @@ | `RADAR_ENABLED` | boolean | `false` | OmniRoute Radar தொகுதியை (பட்டியல் ஊட்டத் திரைகள் மற்றும் ஒத்திசைவு) இயக்குகிறது. இயல்பாக முடக்கப்பட்டுள்ளது; இதை இயக்குவது UI-ஐ மட்டுமே அணுகக்கூடியதாக்கும் — தரவு ஒத்திசைவிற்கு இன்னும் தனியாக ஒப்புதல் அளிக்க வேண்டும். | | `MODEL_ENDPOINT_CAPABILITY_GATE_ENABLED` | boolean | `false` | நம்பகமான மாதிரி முனைப்புள்ளிக் குறிச்சொற்களை (வழங்குநரால் அறிவிக்கப்பட்டவை, சிறப்பு மாதிரி id-இலிருந்து ஊகிக்கப்பட்டவை அல்லது இயக்குநரால் அமைக்கப்பட்டவை) அமல்படுத்துகிறது: `/v1/chat/completions`, `/v1/embeddings`, `/v1/rerank`, `/v1/images/generations`, `/v1/videos/generations`, `/v1/music/generations` மற்றும் `/v1/audio/*` ஆகியவற்றில் மாதிரி சேவையளிக்காத முனைப்புள்ளிக்கு அனுப்பப்படும் கோரிக்கையை அனுப்புவதற்கு முன் HTTP 400 உடன் நிராகரிக்கும்; குறிச்சொல்லிடப்பட்ட முனைப்புள்ளிகளுக்கு OmniRoute adapter இல்லாத மாதிரிகளை `/v1/models`-இலிருந்து மறைக்கும்; அவற்றுக்கான கட்டுப்பாட்டுப் பலகை மாதிரிச் சோதனைக்கு 422 எனப் பதிலளிக்கும் (#16929). இயல்பாக முடக்கப்பட்டுள்ளது: முனைப்புள்ளிக் குறிச்சொற்கள் தகவலுக்காக மட்டுமே இருக்கும், மேலும் அனுப்புதல் மாறாது. | -### இயக்கநேரம் (53) +### இயக்கநேரம் (52) | விசை | வகை | இயல்புநிலை | மறுதொடக்கம் | விளக்கம் | | ---------------------------------------------- | ------- | ---------- | ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -158,7 +158,6 @@ | `KIRO_TOOL_CALL_REPAIR_ENABLED` | boolean | `true` | | காணக்கூடிய வெளியீடு எதற்கும் முன், ஒரு streaming Kiro பதில் தவறாக வடிவமைக்கப்பட்ட `tool_call` wrapper-ஐ (`input.name` அல்லது `input.arguments` இல்லாமல்) வெளியிட்டால், திருப்பத்தை `invalid_kiro_tool_call` உடன் முடிப்பதற்குப் பதிலாக அதை அடக்கி, திருத்த வழிமுறையுடன் கோரிக்கையை ஒருமுறை மீண்டும் அனுப்பவும். இந்த மறுமுயற்சிக்கு ஒரு கூடுதல் Kiro கோரிக்கை செலவாகும்; இரண்டாவது தோல்வி திருப்பத்தை `kiro_tool_call_repair_retry_failed` உடன் முடிக்கும். இயல்புநிலையில் இயக்கப்பட்டுள்ளது. முதல் தவறாக வடிவமைக்கப்பட்ட wrapper-லேயே திருப்பத்தைத் தோல்வியடையச் செய்ய `false` என அமைக்கவும். | | `CATALOG_MIRROR_REFINEMENTS_ENABLED` | boolean | `false` | | `/v1/models`-இல் உள்ள `claude/` கண்டறிதல் மற்றும் functional-gateway mirror-களை மேம்படுத்தவும். இயல்புநிலையில் முடக்கப்பட்டுள்ளது: இரண்டு mirror செயல்பாடுகளும் முன்பிருந்ததைப் போலவே செயல்படும். இயக்கப்பட்டால்: `claude/` கண்டறிதல் mirror-கள் பட்டியலிடப்பட்ட reasoning-effort மாறுபாடுகளையும் (`-low`/`-medium`/`-high`/`-xhigh`) உள்ளடக்கும்; கோரிக்கைப் பாதையால் உருவாக்க முடியாத உள்ளமைந்த `auto/*` id-களையும் ஏற்கனவே பட்டியலிடப்பட்ட alias-ஐயும் தவிர்க்கும்; functional-gateway mirror-கள் காம்போக்களையும் OmniRoute உருவாக்கிய id-களையும் (`claude/`, `no-think/`) தவிர்த்து, `owned_by`-இலிருந்து உரிமையாளரை எடுத்துக்கொண்டு, உரிமையாளரையே அதன் சொந்த gateway-ஆக ஒருபோதும் பயன்படுத்தாமல், `blockedProviders`-இல் முடக்கப்பட்ட gateway-களையும் ஒரு gateway-இன் அதிகாரப்பூர்வ live catalog-க்கு வெளியே உள்ள model-களையும் தவிர்க்கும். | | ---------------------------------------------- | ------- | ------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `ANTHROPIC_COMPATIBLE_MID_CONVERSATION_SYSTEM` | boolean | `false` | | சாதாரண `anthropic-compatible-*` relay-களில் (claude → claude passthrough), உரையாடலின் நடுவிலுள்ள `role: "system"` திருப்பங்களை மேல்-நிலை `system` அளவுருவுக்கு உயர்த்துவதற்குப் பதிலாக `messages[]`-க்குள் வைத்திருக்கவும்; இதனால் cache செய்யப்பட்ட prompt prefix திருப்பங்கள் முழுவதும் நிலையாக இருக்கும். client தானாகவே `mid-conversation-system-2026-04-07` `anthropic-beta`-வை அனுப்பியிருக்கும்போது மட்டுமே இது பொருந்தும் (மேல்நிலைக்கு மாற்றமின்றி அனுப்பப்படும்). இயல்புநிலையில் முடக்கப்பட்டுள்ளது: beta இல்லாத relay-கள் `role: "system"` செய்திகளை 400 உடன் நிராகரிப்பதால், ஒவ்வொரு system திருப்பமும் முன்பிருந்ததைப் போலவே உயர்த்தப்படும். | | `MODELS_DEV_EFFORT_TIERS_ENABLED` | boolean | `false` | | operator override அல்லது provider registry எதுவும் reasoning effort tier-களை அறிவிக்காதபோது, models.dev வெளியிடும் tier-களைப் பயன்படுத்தவும் (அறியப்பட்ட effort சொற்களஞ்சியத்திற்கு வரையறுக்கப்பட்டவை); மேலும், அறிவிக்கப்பட்ட காலியான tier பட்டியலை `/v1/models` `effort_tiers` மற்றும் காம்போ tier intersection-களில் "tier-கள் இல்லை" எனக் கருதவும். இயல்புநிலையில் முடக்கப்பட்டுள்ளது: `effort_tiers` மற்றும் தீர்மானிக்கப்பட்ட `supportedThinkingEfforts` முன்பிருந்ததைப் போலவே இருக்கும். | | `AUTO_REQUEST_RESOLVED_CAPABILITIES` | boolean | `false` | | auto/* கோரிக்கைகள், தீர்மானிக்கப்பட்ட model திறன்கள் (vision, reasoning, thinking) மற்றும் கவனிக்கப்பட்ட quality score-களைக் கொண்டு ஒரே bulk read-இல் சாத்தியமான இலக்குத் தொகுப்பை உருவாக்கும் (#16419). இயல்புநிலையில் முடக்கப்பட்டுள்ளது: கோரிக்கை நேர snapshot weight-கள் முன்பிருந்ததைப் போலவே இருக்கும். இயக்கப்பட்டால்: அந்த signal-கள் snapshot weight-களுக்குப் பயன்படுத்தப்படும்; எனவே சாத்தியமான இலக்குகளின் weight-களும் தேர்வும் மாறக்கூடும். | | `AUTO_COMBO_KEYLESS_TRANSPORT_GATE` | boolean | `false` | | உள்ளூர் உலாவி (`browserBacked`) அல்லது உள்ளூரில் நிறுவப்பட்ட CLI (`isLocalCli`) தேவைப்படும் போக்குவரத்தைக் கொண்ட keyless (அங்கீகாரம் இல்லாத) வழங்குநர்களை `auto/*` தேர்வு候வுகளின் தொகுப்பிலிருந்து விலக்கி வைக்கிறது; இதனால் பதிலளிக்காமல் சிக்கிக்கொண்ட ஓர் உறுப்பினர், ஒவ்வொரு இலக்கிற்குமான காலக்கெடு முடியும் வரை auto கோரிக்கையைத் தாமதப்படுத்த முடியாது (#17036). இயல்புநிலையில் முடக்கப்பட்டுள்ளது: keyless தொகுப்பு மாற்றமின்றி இருக்கும். நேரடி `/` அழைப்புகள் இரு நிலைகளிலும் செயல்படும். | @@ -256,7 +255,7 @@ "requiresRestart": false, "warningLevel": "caution", }, - // ... அனைத்து 111 கொடிகளும் + // ... அனைத்து 110 கொடிகளும் ], "summary": { "total": 56, diff --git a/docs/i18n/te/docs/reference/FEATURE_FLAGS.md b/docs/i18n/te/docs/reference/FEATURE_FLAGS.md index dade5e60c2e7..286e399bba68 100644 --- a/docs/i18n/te/docs/reference/FEATURE_FLAGS.md +++ b/docs/i18n/te/docs/reference/FEATURE_FLAGS.md @@ -45,7 +45,7 @@ boolean ఫ్లాగ్ ప్రభావవంతమైన విలువ ## ఫ్లాగ్ కేటలాగ్ -6 వర్గాలలో 111 ఫ్లాగ్లు ఉన్నాయి. **డిఫాల్ట్** అనేది నిర్వచనంలోని డిఫాల్ట్ — DB ఓవర్రైడ్ లేదా పర్యావరణ వేరియబుల్ రెండింటిలో ఏదీ లేనప్పుడు ఉపయోగించే విలువ. +6 వర్గాలలో 110 ఫ్లాగ్లు ఉన్నాయి. **డిఫాల్ట్** అనేది నిర్వచనంలోని డిఫాల్ట్ — DB ఓవర్రైడ్ లేదా పర్యావరణ వేరియబుల్ రెండింటిలో ఏదీ లేనప్పుడు ఉపయోగించే విలువ. ### భద్రత (10) @@ -109,7 +109,7 @@ boolean ఫ్లాగ్ ప్రభావవంతమైన విలువ | `RADAR_ENABLED` | boolean | `false` | OmniRoute Radar మాడ్యూల్ను (కేటలాగ్ ఫీడ్ స్క్రీన్లు మరియు సింక్) ప్రారంభించండి. డిఫాల్ట్గా ఆఫ్లో ఉంటుంది; దీన్ని ప్రారంభించడం UIని మాత్రమే అన్లాక్ చేస్తుంది — డేటా సింక్కు ఇప్పటికీ విడిగా సమ్మతి అవసరం. | | `MODEL_ENDPOINT_CAPABILITY_GATE_ENABLED` | boolean | `false` | విశ్వసనీయ మోడల్ ఎండ్పాయింట్ ట్యాగ్లను (ప్రొవైడర్ ప్రకటించినవి, ప్రత్యేక మోడల్ id నుండి ఊహించినవి లేదా ఆపరేటర్ సెట్ చేసినవి) అమలు చేయండి: `/v1/chat/completions`, `/v1/embeddings`, `/v1/rerank`, `/v1/images/generations`, `/v1/videos/generations`, `/v1/music/generations` మరియు `/v1/audio/*`పై మోడల్ అందించని ఎండ్పాయింట్కు పంపిన అభ్యర్థనను పంపిణీకి ముందే HTTP 400తో తిరస్కరించండి; ట్యాగ్ చేసిన ఎండ్పాయింట్లకు OmniRoute అడాప్టర్ లేని మోడళ్లను `/v1/models` నుండి దాచండి; వాటి కోసం డాష్బోర్డ్ మోడల్ పరీక్షకు 422తో ప్రతిస్పందించండి (#16929). డిఫాల్ట్గా ఆఫ్లో ఉంటుంది: ఎండ్పాయింట్ ట్యాగ్లు సమాచారాత్మకంగానే ఉంటాయి మరియు పంపిణీలో మార్పు ఉండదు. | -### రన్టైమ్ (53) +### రన్టైమ్ (52) | కీ | రకం | డిఫాల్ట్ | పునఃప్రారంభం | వివరణ | | ---------------------------------------------- | ------- | -------- | ------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -156,7 +156,6 @@ boolean ఫ్లాగ్ ప్రభావవంతమైన విలువ | `KIRO_TOOL_CALL_REPAIR_ENABLED` | boolean | `true` | | ఏదైనా కనిపించే అవుట్పుట్కు ముందు స్ట్రీమింగ్ Kiro ప్రతిస్పందన తప్పుగా రూపొందించిన `tool_call` ర్యాపర్ను (`input.name` లేదా `input.arguments` లేకుండా) విడుదల చేసినప్పుడు, దాన్ని అణచివేసి, టర్న్ను `invalid_kiro_tool_call`తో ముగించడానికి బదులుగా సరిదిద్దే సూచనతో అభ్యర్థనను ఒకసారి మళ్లీ పంపండి. ఈ రీట్రైకి ఒక అదనపు Kiro అభ్యర్థన ఖర్చవుతుంది; రెండోసారి విఫలమైతే టర్న్ `kiro_tool_call_repair_retry_failed`తో ముగుస్తుంది. డిఫాల్ట్గా ఆన్లో ఉంటుంది. తప్పుగా రూపొందించిన మొదటి ర్యాపర్కే టర్న్ విఫలమయ్యేలా చేయడానికి `false`గా సెట్ చేయండి. | | `CATALOG_MIRROR_REFINEMENTS_ENABLED` | boolean | `false` | | `/v1/models`లోని `claude/` డిస్కవరీ మరియు ఫంక్షనల్-గేట్వే మిర్రర్లను మెరుగుపరచండి. డిఫాల్ట్గా ఆఫ్లో ఉంటుంది: రెండు మిర్రర్ పాస్లు మునుపటిలాగే ఖచ్చితంగా ప్రవర్తిస్తాయి. ఆన్లో ఉన్నప్పుడు: `claude/` డిస్కవరీ మిర్రర్లు జాబితా చేసిన రీజనింగ్-ఎఫర్ట్ వేరియంట్లను (`-low`/`-medium`/`-high`/`-xhigh`) కూడా కవర్ చేస్తాయి, అభ్యర్థన పాత్ రూపొందించలేని బిల్ట్-ఇన్ `auto/*` idsను దాటవేస్తాయి మరియు ఇప్పటికే జాబితాలో ఉన్న అలియాస్ను దాటవేస్తాయి; ఫంక్షనల్-గేట్వే మిర్రర్లు కాంబోలు మరియు OmniRoute రూపొందించిన idsను (`claude/`, `no-think/`) దాటవేస్తాయి, యజమానిని `owned_by` నుండి తీసుకుంటాయి, యజమానినే దాని స్వంత గేట్వేగా ఎప్పుడూ ఉపయోగించవు, `blockedProviders`లో నిలిపివేసిన గేట్వేలను మరియు గేట్వే యొక్క అధికారిక లైవ్ కేటలాగ్ వెలుపల ఉన్న మోడల్లను దాటవేస్తాయి. | | ---------------------------------------------- | ------- | ------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `ANTHROPIC_COMPATIBLE_MID_CONVERSATION_SYSTEM` | boolean | `false` | | సాధారణ `anthropic-compatible-*` రిలేలలో (claude → claude పాస్త్రూ), సంభాషణ మధ్యలోని `role: "system"` టర్న్లను అగ్ర-స్థాయి `system` పారామీటర్లోకి ఎత్తడానికి బదులుగా `messages[]`లోనే ఉంచండి, తద్వారా క్యాష్ చేసిన ప్రాంప్ట్ ప్రిఫిక్స్ టర్న్ల అంతటా స్థిరంగా ఉంటుంది. క్లయింట్ స్వయంగా `mid-conversation-system-2026-04-07` `anthropic-beta`ను పంపినప్పుడు మాత్రమే ఇది వర్తిస్తుంది (అప్స్ట్రీమ్కు యథాతథంగా ఫార్వర్డ్ చేయబడుతుంది). డిఫాల్ట్గా ఆఫ్లో ఉంటుంది: బీటా లేని రిలేలు `role: "system"` సందేశాలను 400తో తిరస్కరిస్తాయి కాబట్టి, ప్రతి సిస్టమ్ టర్న్ మునుపటిలాగే అగ్ర-స్థాయికి ఎత్తబడుతుంది. | | `MODELS_DEV_EFFORT_TIERS_ENABLED` | boolean | `false` | | ఆపరేటర్ ఓవర్రైడ్ లేదా ప్రొవైడర్ రిజిస్ట్రీ ఏ టియర్లనూ ప్రకటించనప్పుడు, models.dev ప్రచురించే రీజనింగ్ ఎఫర్ట్ టియర్లను (తెలిసిన ఎఫర్ట్ పదజాలానికి పరిమితం చేసి) ఉపయోగించండి; అలాగే ప్రకటించిన ఖాళీ టియర్ జాబితాను `/v1/models` `effort_tiers` మరియు కాంబో టియర్ ఇంటర్సెక్షన్లలో "టియర్లు లేవు"గా పరిగణించండి. డిఫాల్ట్గా ఆఫ్లో ఉంటుంది: `effort_tiers` మరియు పరిష్కరించిన `supportedThinkingEfforts` మునుపటిలాగే ఖచ్చితంగా ఉంటాయి. | | `AUTO_REQUEST_RESOLVED_CAPABILITIES` | boolean | `false` | | auto/* అభ్యర్థనలు పరిష్కరించిన మోడల్ సామర్థ్యాలు (విజన్, రీజనింగ్, థింకింగ్) మరియు గమనించిన నాణ్యత స్కోర్లతో ఒకే బల్క్ రీడ్లో అభ్యర్థి పూల్ను రూపొందిస్తాయి (#16419). డిఫాల్ట్గా ఆఫ్లో ఉంటుంది: అభ్యర్థన-సమయ స్నాప్షాట్ వెయిట్లు మునుపటిలాగే ఖచ్చితంగా ఉంటాయి. ఆన్లో ఉన్నప్పుడు: ఆ సంకేతాలు స్నాప్షాట్ వెయిట్లకు ఇన్పుట్గా ఉపయోగించబడతాయి, కాబట్టి అభ్యర్థి వెయిట్లు మరియు ఎంపిక మారవచ్చు. | | `AUTO_COMBO_KEYLESS_TRANSPORT_GATE` | boolean | `false` | | స్థానిక బ్రౌజర్ (`browserBacked`) లేదా స్థానికంగా ఇన్స్టాల్ చేసిన CLI (`isLocalCli`) అవసరమైన ట్రాన్స్పోర్ట్ను కలిగి ఉన్న కీలెస్ (నో-ఆత్) ప్రొవైడర్లను `auto/*` అభ్యర్థుల పూల్కు దూరంగా ఉంచుతుంది, తద్వారా నిలిచిపోయిన సభ్యుడు ప్రతి-టార్గెట్ టైమ్అవుట్ వరకు ఆటో అభ్యర్థనను నిలిపివేయలేడు (#17036). డిఫాల్ట్గా ఆఫ్లో ఉంటుంది: కీలెస్ పూల్ మారదు. ప్రత్యక్ష `/` కాల్లు ఏ విధంగానైనా పనిచేస్తాయి. | @@ -254,7 +253,7 @@ boolean ఫ్లాగ్ ప్రభావవంతమైన విలువ "requiresRestart": false, "warningLevel": "caution", }, - // ... మొత్తం 111 ఫ్లాగ్లు + // ... మొత్తం 110 ఫ్లాగ్లు ], "summary": { "total": 56, diff --git a/docs/i18n/th/docs/reference/FEATURE_FLAGS.md b/docs/i18n/th/docs/reference/FEATURE_FLAGS.md index 645d47e494b6..ae0a181f2df1 100644 --- a/docs/i18n/th/docs/reference/FEATURE_FLAGS.md +++ b/docs/i18n/th/docs/reference/FEATURE_FLAGS.md @@ -44,7 +44,7 @@ Feature flag คือตัวสลับที่มีชื่อ (ชน ## แค็ตตาล็อกแฟล็ก -แฟล็ก 111 รายการใน 6 หมวดหมู่ **ค่าเริ่มต้น** คือค่าเริ่มต้นที่กำหนดไว้ ซึ่งจะถูกใช้ +แฟล็ก 110 รายการใน 6 หมวดหมู่ **ค่าเริ่มต้น** คือค่าเริ่มต้นที่กำหนดไว้ ซึ่งจะถูกใช้ เมื่อไม่มีทั้งค่าที่เขียนทับใน DB และตัวแปรสภาพแวดล้อม ### ความปลอดภัย (10) @@ -109,7 +109,7 @@ Feature flag คือตัวสลับที่มีชื่อ (ชน | `RADAR_ENABLED` | boolean | `false` | เปิดใช้งานโมดูล OmniRoute Radar (หน้าจอฟีดแค็ตตาล็อกและการซิงค์) ปิดเป็นค่าเริ่มต้น การเปิดใช้งานจะปลดล็อกเฉพาะ UI เท่านั้น ส่วนการซิงค์ข้อมูลยังคงต้องเลือกเปิดใช้งานแยกต่างหาก | | `MODEL_ENDPOINT_CAPABILITY_GATE_ENABLED` | boolean | `false` | บังคับใช้แท็ก endpoint ของโมเดลที่เชื่อถือได้ (ประกาศโดยผู้ให้บริการ อนุมานจากรหัสโมเดลเฉพาะทาง หรือกำหนดโดยผู้ดูแลระบบ): ปฏิเสธคำขอที่ส่งไปยัง endpoint ซึ่งโมเดลไม่ได้ให้บริการด้วย HTTP 400 ก่อนส่งต่อบน `/v1/chat/completions`, `/v1/embeddings`, `/v1/rerank`, `/v1/images/generations`, `/v1/videos/generations`, `/v1/music/generations` และ `/v1/audio/*`; ซ่อนโมเดลที่ endpoint ตามแท็กไม่มีอะแดปเตอร์ OmniRoute จาก `/v1/models`; ตอบกลับการทดสอบโมเดลในแดชบอร์ดด้วย 422 สำหรับโมเดลเหล่านั้น (#16929) ปิดเป็นค่าเริ่มต้น โดยแท็ก endpoint จะยังคงใช้เป็นข้อมูลเท่านั้นและการส่งต่อจะไม่เปลี่ยนแปลง | -### รันไทม์ (53) +### รันไทม์ (52) | คีย์ | ชนิดข้อมูล | ค่าเริ่มต้น | รีสตาร์ต | คำอธิบาย | | ---------------------------------------------- | ---------- | ----------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -156,7 +156,6 @@ Feature flag คือตัวสลับที่มีชื่อ (ชน | `KIRO_TOOL_CALL_REPAIR_ENABLED` | boolean | `true` | | เมื่อการตอบกลับแบบสตรีมของ Kiro ส่ง wrapper `tool_call` ที่มีรูปแบบไม่ถูกต้อง (ไม่มี `input.name` หรือ `input.arguments`) ก่อนมีเอาต์พุตที่มองเห็นได้ ให้ระงับ wrapper นั้นและส่งคำขออีกครั้งหนึ่งพร้อมคำสั่งแก้ไข แทนที่จะจบรอบด้วย `invalid_kiro_tool_call` การลองใหม่มีค่าใช้จ่ายเท่ากับคำขอ Kiro เพิ่มอีกหนึ่งครั้ง หากล้มเหลวเป็นครั้งที่สอง รอบจะจบด้วย `kiro_tool_call_repair_retry_failed` เปิดไว้โดยค่าเริ่มต้น ตั้งค่าเป็น `false` เพื่อให้รอบล้มเหลวทันทีเมื่อพบ wrapper ที่มีรูปแบบไม่ถูกต้องครั้งแรก | | `CATALOG_MIRROR_REFINEMENTS_ENABLED` | boolean | `false` | | ปรับปรุงมิเรอร์สำหรับการค้นพบ `claude/` และ functional gateway บน `/v1/models` ปิดไว้โดยค่าเริ่มต้น: กระบวนการมิเรอร์ทั้งสองทำงานเหมือนเดิมทุกประการ เมื่อเปิด: มิเรอร์การค้นพบ `claude/` จะครอบคลุมตัวแปรระดับความพยายามในการใช้เหตุผลที่แสดงไว้ด้วย (`-low`/`-medium`/`-high`/`-xhigh`), ข้าม id `auto/*` ในตัวที่เส้นทางคำขอไม่สามารถสร้างให้เป็นรูปธรรมได้ และข้าม alias ที่แสดงอยู่แล้ว ส่วนมิเรอร์ functional gateway จะข้ามคอมโบและ id ที่ OmniRoute สังเคราะห์ขึ้น (`claude/`, `no-think/`), ใช้เจ้าของจาก `owned_by`, ไม่ใช้เจ้าของเป็น gateway ของตัวเอง, ข้าม gateway ที่ถูกปิดใช้งานใน `blockedProviders` และข้ามโมเดลที่อยู่นอกแค็ตตาล็อกที่ใช้งานอยู่ซึ่งเป็นแหล่งข้อมูลหลักของ gateway | | ---------------------------------------------- | ------- | ------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `ANTHROPIC_COMPATIBLE_MID_CONVERSATION_SYSTEM` | boolean | `false` | | บน relay `anthropic-compatible-*` แบบปกติ (ส่งผ่าน claude → claude) ให้เก็บเทิร์น `role: "system"` ที่อยู่กลางบทสนทนาไว้ภายใน `messages[]` แทนการย้ายขึ้นไปไว้ในพารามิเตอร์ `system` ระดับบน เพื่อให้คำนำหน้าพรอมป์ที่แคชไว้คงที่ในแต่ละเทิร์น ใช้เฉพาะเมื่อไคลเอนต์ส่ง `anthropic-beta` `mid-conversation-system-2026-04-07` มาด้วยตนเอง (ส่งต่อไปยัง upstream ตามเดิมโดยไม่แก้ไข) ปิดไว้โดยค่าเริ่มต้น: เทิร์นระบบทุกเทิร์นจะถูกย้ายขึ้นไปเหมือนเดิมทุกประการ เนื่องจาก relay ที่ไม่มี beta จะปฏิเสธข้อความ `role: "system"` ด้วย 400 | | `MODELS_DEV_EFFORT_TIERS_ENABLED` | boolean | `false` | | ใช้ระดับความพยายามในการใช้เหตุผลที่ models.dev เผยแพร่ (จำกัดให้เหลือเฉพาะคำศัพท์ระดับความพยายามที่รู้จัก) เมื่อทั้งการกำหนดทับโดยผู้ดูแลระบบและรีจิสทรีของผู้ให้บริการไม่ได้ประกาศระดับใดไว้ และให้ถือว่ารายการระดับที่ประกาศเป็นค่าว่างหมายถึง "ไม่มีระดับ" ใน `effort_tiers` ของ `/v1/models` และการหาส่วนร่วมของระดับในคอมโบ ปิดไว้โดยค่าเริ่มต้น: `effort_tiers` และ `supportedThinkingEfforts` ที่ผ่านการแก้ค่าแล้วจะยังคงเหมือนเดิมทุกประการ | | `AUTO_REQUEST_RESOLVED_CAPABILITIES` | boolean | `false` | | คำขอ auto/* จะสร้างพูลตัวเลือกโดยใช้ความสามารถของโมเดลที่ผ่านการแก้ค่าแล้ว (การมองเห็น การใช้เหตุผล การคิดวิเคราะห์) และคะแนนคุณภาพที่สังเกตได้จากการอ่านแบบกลุ่มครั้งเดียว (#16419) ปิดไว้โดยค่าเริ่มต้น: น้ำหนัก snapshot ณ เวลาที่ส่งคำขอจะยังคงเหมือนเดิมทุกประการ เมื่อเปิด: สัญญาณเหล่านั้นจะถูกนำไปใช้กับน้ำหนัก snapshot ดังนั้นน้ำหนักของตัวเลือกและผลการเลือกอาจเปลี่ยนแปลงได้ | | `AUTO_COMBO_KEYLESS_TRANSPORT_GATE` | boolean | `false` | | กันผู้ให้บริการแบบไม่ใช้คีย์ (ไม่มีการยืนยันตัวตน) ซึ่งการรับส่งข้อมูลต้องใช้เบราว์เซอร์ภายในเครื่อง (`browserBacked`) หรือ CLI ที่ติดตั้งภายในเครื่อง (`isLocalCli`) ออกจากกลุ่มตัวเลือก `auto/*` เพื่อไม่ให้สมาชิกที่ค้างอยู่ทำให้คำขออัตโนมัติติดค้างจนถึงระยะหมดเวลาของแต่ละเป้าหมาย (#17036) ปิดไว้โดยค่าเริ่มต้น: กลุ่มแบบไม่ใช้คีย์จะไม่มีการเปลี่ยนแปลง การเรียก `/` โดยตรงใช้งานได้ไม่ว่าจะเปิดหรือปิดก็ตาม | @@ -253,7 +252,7 @@ Feature flag คือตัวสลับที่มีชื่อ (ชน "requiresRestart": false, "warningLevel": "caution", }, - // ... แฟล็กทั้งหมด 111 รายการ + // ... แฟล็กทั้งหมด 110 รายการ ], "summary": { "total": 56, diff --git a/docs/i18n/tr/docs/reference/FEATURE_FLAGS.md b/docs/i18n/tr/docs/reference/FEATURE_FLAGS.md index b85c2e520e27..550882821bbc 100644 --- a/docs/i18n/tr/docs/reference/FEATURE_FLAGS.md +++ b/docs/i18n/tr/docs/reference/FEATURE_FLAGS.md @@ -44,7 +44,7 @@ Bir boolean bayrağı, etkin değeri `"true"`, `"1"` veya `"yes"` olduğunda ## Bayrak Kataloğu -6 kategoride 111 bayrak. **Varsayılan**, tanımda belirtilen varsayılan değerdir — bir DB geçersiz kılma değeri veya ortam değişkeni bulunmadığında +6 kategoride 110 bayrak. **Varsayılan**, tanımda belirtilen varsayılan değerdir — bir DB geçersiz kılma değeri veya ortam değişkeni bulunmadığında kullanılan değerdir. ### Güvenlik (10) @@ -109,7 +109,7 @@ kullanılan değerdir. | `RADAR_ENABLED` | boolean | `false` | OmniRoute Radar modülünü (katalog akışı ekranları ve senkronizasyon) etkinleştirir. Varsayılan olarak kapalıdır; etkinleştirmek yalnızca kullanıcı arayüzünün kilidini açar — veri senkronizasyonu ayrıca onay verilmesini gerektirir. | | `MODEL_ENDPOINT_CAPABILITY_GATE_ENABLED` | boolean | `false` | Güvenilir model uç noktası etiketlerini (sağlayıcı tarafından bildirilmiş, özel amaçlı bir model kimliğinden çıkarılmış veya operatör tarafından ayarlanmış) uygular: `/v1/chat/completions`, `/v1/embeddings`, `/v1/rerank`, `/v1/images/generations`, `/v1/videos/generations`, `/v1/music/generations` ve `/v1/audio/*` üzerinde, modelin hizmet vermediği bir uç noktaya gönderilen isteği dağıtımdan önce HTTP 400 ile reddeder; etiketlenmiş uç noktaları için OmniRoute bağdaştırıcısı bulunmayan modelleri `/v1/models` üzerinden gizler; bunlar için pano model testine 422 ile yanıt verir (#16929). Varsayılan olarak kapalıdır: uç noktası etiketleri yalnızca bilgilendirme amaçlı kalır ve dağıtım değişmez. | -### Çalışma Zamanı (53) +### Çalışma Zamanı (52) | Anahtar | Tür | Varsayılan | Yeniden Başlatma | Açıklama | | ---------------------------------------------- | ------- | ---------- | ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -156,7 +156,6 @@ kullanılan değerdir. | `KIRO_TOOL_CALL_REPAIR_ENABLED` | boolean | `true` | | Akış hâlindeki bir Kiro yanıtı, görünür herhangi bir çıktıdan önce hatalı biçimlendirilmiş bir `tool_call` sarmalayıcısı (`input.name` veya `input.arguments` eksik) yayınladığında, turu `invalid_kiro_tool_call` ile sonlandırmak yerine bunu engelleyin ve düzeltici bir talimatla isteği bir kez yeniden gönderin. Yeniden deneme, fazladan bir Kiro isteğine mal olur; ikinci bir hata turu `kiro_tool_call_repair_retry_failed` ile sonlandırır. Varsayılan olarak açıktır. İlk hatalı sarmalayıcıda turu başarısız kılmak için `false` olarak ayarlayın. | | `CATALOG_MIRROR_REFINEMENTS_ENABLED` | boolean | `false` | | `/v1/models` üzerindeki `claude/` keşif ve işlevsel ağ geçidi yansımalarını iyileştirin. Varsayılan olarak kapalıdır: her iki yansıtma geçişi de tam olarak önceki gibi davranır. Açıkken: `claude/` keşif yansımaları ayrıca listelenen akıl yürütme eforu varyantlarını (`-low`/`-medium`/`-high`/`-xhigh`) kapsar, istek yolunun oluşturamadığı yerleşik `auto/*` kimliklerini ve zaten listelenmiş takma adları atlar; işlevsel ağ geçidi yansımaları kombinasyonları ve OmniRoute tarafından sentezlenen kimlikleri (`claude/`, `no-think/`) atlar, sahibi `owned_by` alanından alır, sahibin kendisini hiçbir zaman kendi ağ geçidi olarak kullanmaz, `blockedProviders` içinde devre dışı bırakılan ağ geçitlerini ve bir ağ geçidinin yetkili canlı kataloğu dışındaki modelleri atlar. | | ---------------------------------------------- | ------- | ------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `ANTHROPIC_COMPATIBLE_MID_CONVERSATION_SYSTEM` | boolean | `false` | | Düz `anthropic-compatible-*` aktarıcılarında (claude → claude doğrudan geçişi), tur ortasındaki `role: "system"` dönüşlerini üst düzey `system` parametresine taşımak yerine `messages[]` içinde tutun; böylece önbelleğe alınmış istem öneki turlar arasında kararlı kalır. Yalnızca istemcinin kendisi `mid-conversation-system-2026-04-07` `anthropic-beta` değerini göndermişse geçerlidir (yukarı akışa olduğu gibi iletilir). Varsayılan olarak kapalıdır: beta özelliği bulunmayan aktarıcılar `role: "system"` mesajlarını 400 ile reddettiğinden, her sistem dönüşü tam olarak önceki gibi üst düzeye taşınır. | | `MODELS_DEV_EFFORT_TIERS_ENABLED` | boolean | `false` | | Ne bir operatör geçersiz kılması ne de sağlayıcı kayıt defteri herhangi bir katman bildirdiğinde, models.dev tarafından yayımlanan akıl yürütme eforu katmanlarını (bilinen efor sözlüğüyle sınırlandırılmış olarak) kullanın ve bildirilmiş boş bir katman listesini `/v1/models` `effort_tiers` ile kombinasyon katmanı kesişimlerinde "katman yok" olarak değerlendirin. Varsayılan olarak kapalıdır: `effort_tiers` ve çözümlenmiş `supportedThinkingEfforts` tam olarak önceki gibi kalır. | | `AUTO_REQUEST_RESOLVED_CAPABILITIES` | boolean | `false` | | auto/* istekleri, aday havuzunu çözümlenmiş model yetenekleri (görüntü, akıl yürütme, düşünme) ve gözlemlenen kalite puanlarıyla tek bir toplu okumada oluşturur (#16419). Varsayılan olarak kapalıdır: istek anındaki anlık görüntü ağırlıkları tam olarak önceki gibi kalır. Açıkken: bu sinyaller anlık görüntü ağırlıklarını besler; dolayısıyla aday ağırlıkları ve seçim değişebilir. | | `AUTO_COMBO_KEYLESS_TRANSPORT_GATE` | boolean | `false` | | Aktarımı yerel bir tarayıcı (`browserBacked`) veya yerel olarak kurulmuş bir CLI (`isLocalCli`) gerektiren anahtarsız (kimlik doğrulamasız) sağlayıcıları `auto/*` aday havuzunun dışında tutar; böylece yanıt vermeyen bir üye, hedef başına zaman aşımı süresine kadar otomatik bir isteği duraklatamaz (#17036). Varsayılan olarak kapalıdır: anahtarsız havuz değişmez. Doğrudan `/` çağrıları her iki durumda da çalışır. | @@ -254,7 +253,7 @@ Her bayrağı geçerli değeri, kaynağı ve bir özetle birlikte döndürür. "requiresRestart": false, "warningLevel": "caution", }, - // ... 111 bayrağın tümü + // ... 110 bayrağın tümü ], "summary": { "total": 56, diff --git a/docs/i18n/uk-UA/docs/reference/FEATURE_FLAGS.md b/docs/i18n/uk-UA/docs/reference/FEATURE_FLAGS.md index 77a0eeadfe76..cc57598af631 100644 --- a/docs/i18n/uk-UA/docs/reference/FEATURE_FLAGS.md +++ b/docs/i18n/uk-UA/docs/reference/FEATURE_FLAGS.md @@ -44,7 +44,7 @@ ## Каталог прапорців -111 прапорців у 6 категоріях. **Значення за замовчуванням** — це значення, визначене за замовчуванням, яке +110 прапорців у 6 категоріях. **Значення за замовчуванням** — це значення, визначене за замовчуванням, яке використовується, коли немає ані перевизначення в БД, ані змінної середовища. ### Безпека (10) @@ -109,7 +109,7 @@ | `RADAR_ENABLED` | boolean | `false` | Вмикає модуль OmniRoute Radar (екрани стрічки каталогу та синхронізацію). Типово вимкнено; увімкнення лише розблоковує інтерфейс користувача — синхронізація даних залишається окремою функцією, яку потрібно явно ввімкнути. | | `MODEL_ENDPOINT_CAPABILITY_GATE_ENABLED` | boolean | `false` | Забезпечує дотримання надійних тегів кінцевих точок моделі (оголошених постачальником, визначених зі спеціалізованого ідентифікатора моделі або заданих оператором): до надсилання відхиляє з кодом HTTP 400 запит, надісланий до кінцевої точки, яку модель не обслуговує, для `/v1/chat/completions`, `/v1/embeddings`, `/v1/rerank`, `/v1/images/generations`, `/v1/videos/generations`, `/v1/music/generations` і `/v1/audio/*`; приховує з `/v1/models` моделі, для позначених тегами кінцевих точок яких немає адаптера OmniRoute; повертає код 422 під час перевірки таких моделей на інформаційній панелі (#16929). Типово вимкнено: теги кінцевих точок залишаються інформаційними, а надсилання не змінюється. | -### Середовище виконання (53) +### Середовище виконання (52) | Ключ | Тип | За замовчуванням | Перезапуск | Опис | | ---------------------------------------------- | ------- | ---------------- | ---------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -156,7 +156,6 @@ | `KIRO_TOOL_CALL_REPAIR_ENABLED` | boolean | `true` | | Коли потокова відповідь Kiro видає некоректну оболонку `tool_call` (відсутній `input.name` або `input.arguments`) до будь-якого видимого виведення, приховати її та один раз повторно надіслати запит із виправною інструкцією замість завершення ходу з `invalid_kiro_tool_call`. Повторна спроба потребує одного додаткового запиту Kiro; друга помилка завершує хід із `kiro_tool_call_repair_retry_failed`. Типово ввімкнено. Установіть `false`, щоб завершувати хід із помилкою після першої некоректної оболонки. | | `CATALOG_MIRROR_REFINEMENTS_ENABLED` | boolean | `false` | | Уточнити дзеркала виявлення `claude/` і функціональних шлюзів у `/v1/models`. Типово вимкнено: обидва проходи дзеркалювання працюють точно так само, як раніше. Якщо ввімкнено: дзеркала виявлення `claude/` також охоплюють перелічені варіанти інтенсивності міркувань (`-low`/`-medium`/`-high`/`-xhigh`), пропускають вбудовані ідентифікатори `auto/*`, які шлях запиту не може матеріалізувати, і пропускають уже перелічені псевдоніми; дзеркала функціональних шлюзів пропускають комбінації та синтезовані OmniRoute ідентифікатори (`claude/`, `no-think/`), отримують власника з `owned_by`, ніколи не використовують власника як його власний шлюз, пропускають шлюзи, вимкнені в `blockedProviders`, і моделі поза авторитетним актуальним каталогом шлюзу. | | ---------------------------------------------- | ------- | ------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `ANTHROPIC_COMPATIBLE_MID_CONVERSATION_SYSTEM` | boolean | `false` | | У звичайних ретрансляторах `anthropic-compatible-*` (наскрізне передавання claude → claude) зберігати ходи `role: "system"` посеред розмови всередині `messages[]` замість підняття їх до параметра верхнього рівня `system`, щоб кешований префікс запиту залишався стабільним між ходами. Застосовується лише тоді, коли сам клієнт надіслав `mid-conversation-system-2026-04-07` `anthropic-beta` (пересилається без змін). Типово вимкнено: кожен системний хід піднімається так само, як раніше, оскільки ретранслятори без цієї бета-функції відхиляють повідомлення `role: "system"` з кодом 400. | | `MODELS_DEV_EFFORT_TIERS_ENABLED` | boolean | `false` | | Використовувати опубліковані models.dev рівні інтенсивності міркувань (звужені до відомого словника інтенсивності), коли ані перевизначення оператора, ані реєстр постачальника не оголошують жодного рівня, і трактувати оголошений порожній список рівнів як "відсутність рівнів" у `/v1/models` `effort_tiers` та перетинах рівнів комбінацій. Типово вимкнено: `effort_tiers` і визначені `supportedThinkingEfforts` залишаються точно такими самими, як раніше. | | `AUTO_REQUEST_RESOLVED_CAPABILITIES` | boolean | `false` | | Запити auto/* формують пул кандидатів із визначеними можливостями моделей (зір, міркування, мислення) і спостережуваними оцінками якості за допомогою одного пакетного читання (#16419). Типово вимкнено: ваги знімка під час запиту залишаються точно такими самими, як раніше. Якщо ввімкнено: ці сигнали впливають на ваги знімка, тому ваги кандидатів і вибір можуть змінитися. | | `AUTO_COMBO_KEYLESS_TRANSPORT_GATE` | boolean | `false` | | Виключати з пулу кандидатів `auto/*` провайдерів без ключів (без автентифікації), транспорт яких потребує локального браузера (`browserBacked`) або локально встановленого CLI (`isLocalCli`), щоб завислий учасник не міг затримати автоматичний запит до завершення тайм-ауту для цільового об’єкта (#17036). За замовчуванням вимкнено: пул без ключів залишається без змін. Прямі виклики `/` працюють в обох випадках. | @@ -253,7 +252,7 @@ "requiresRestart": false, "warningLevel": "caution", }, - // ... усі 111 прапорців + // ... усі 110 прапорців ], "summary": { "total": 56, diff --git a/docs/i18n/ur/docs/reference/FEATURE_FLAGS.md b/docs/i18n/ur/docs/reference/FEATURE_FLAGS.md index 48c789fef2fe..2d6ac601bdbe 100644 --- a/docs/i18n/ur/docs/reference/FEATURE_FLAGS.md +++ b/docs/i18n/ur/docs/reference/FEATURE_FLAGS.md @@ -45,7 +45,7 @@ ## فلیگ کیٹلاگ -6 زمروں میں 111 فلیگز۔ **ڈیفالٹ** سے مراد تعریف میں مقرر کردہ ڈیفالٹ قدر ہے — یعنی وہ قدر +6 زمروں میں 110 فلیگز۔ **ڈیفالٹ** سے مراد تعریف میں مقرر کردہ ڈیفالٹ قدر ہے — یعنی وہ قدر جو اس وقت استعمال ہوتی ہے جب نہ کوئی DB اوور رائیڈ موجود ہو اور نہ کوئی ماحولیاتی متغیر۔ ### سیکیورٹی (10) @@ -110,7 +110,7 @@ | `RADAR_ENABLED` | boolean | `false` | OmniRoute Radar ماڈیول (کیٹلاگ فیڈ اسکرینز اور سنک) فعال کریں۔ طے شدہ طور پر غیر فعال؛ اسے فعال کرنے سے صرف UI دستیاب ہوتا ہے — ڈیٹا سنک کے لیے الگ سے رضامندی اب بھی درکار ہے۔ | | `MODEL_ENDPOINT_CAPABILITY_GATE_ENABLED` | boolean | `false` | قابلِ اعتماد ماڈل اینڈ پوائنٹ ٹیگز (فراہم کنندہ کے اعلان کردہ، کسی خصوصی ماڈل id سے اخذ کردہ، یا آپریٹر کے مقرر کردہ) نافذ کریں: `/v1/chat/completions`، `/v1/embeddings`، `/v1/rerank`، `/v1/images/generations`، `/v1/videos/generations`، `/v1/music/generations` اور `/v1/audio/*` پر ایسے اینڈ پوائنٹ کو بھیجی گئی درخواست کو، جسے ماڈل سپورٹ نہیں کرتا، ڈسپیچ سے پہلے HTTP 400 کے ساتھ مسترد کریں؛ `/v1/models` سے ان ماڈلز کو چھپائیں جن کے ٹیگ شدہ اینڈ پوائنٹس کے لیے کوئی OmniRoute اڈاپٹر موجود نہیں؛ اور ان کے لیے ڈیش بورڈ ماڈل ٹیسٹ کا جواب 422 کے ساتھ دیں (#16929)۔ طے شدہ طور پر غیر فعال: اینڈ پوائنٹ ٹیگز صرف معلوماتی رہتے ہیں اور ڈسپیچ میں کوئی تبدیلی نہیں ہوتی۔ | -### رن ٹائم (53) +### رن ٹائم (52) | کلید | قسم | طے شدہ | دوبارہ آغاز | تفصیل | | ---------------------------------------------- | ------- | ------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -157,7 +157,6 @@ | `KIRO_TOOL_CALL_REPAIR_ENABLED` | boolean | `true` | | جب کسی streaming Kiro جواب میں کسی بھی قابلِ دید output سے پہلے ناقص `tool_call` wrapper خارج ہو (جس میں `input.name` یا `input.arguments` موجود نہ ہو)، تو اسے دبا دیں اور turn کو `invalid_kiro_tool_call` کے ساتھ ختم کرنے کے بجائے اصلاحی ہدایت کے ساتھ درخواست ایک بار دوبارہ بھیجیں۔ retry پر ایک اضافی Kiro درخواست کی لاگت آتی ہے؛ دوسری ناکامی turn کو `kiro_tool_call_repair_retry_failed` کے ساتھ ختم کرتی ہے۔ بطور ڈیفالٹ آن۔ پہلے ہی ناقص wrapper پر turn ناکام کرنے کے لیے اسے `false` پر سیٹ کریں۔ | | `CATALOG_MIRROR_REFINEMENTS_ENABLED` | boolean | `false` | | `/v1/models` پر `claude/` discovery اور functional-gateway mirrors کو بہتر بنائیں۔ بطور ڈیفالٹ بند: دونوں mirror passes بالکل پہلے کی طرح کام کرتے ہیں۔ آن ہونے پر: `claude/` discovery mirrors فہرست شدہ reasoning-effort variants (`-low`/`-medium`/`-high`/`-xhigh`) کا بھی احاطہ کرتے ہیں، ان built-in `auto/*` ids کو چھوڑ دیتے ہیں جنہیں request path عملی شکل نہیں دے سکتا، اور پہلے سے فہرست شدہ alias کو بھی چھوڑ دیتے ہیں؛ functional-gateway mirrors کومبوز اور OmniRoute کے بنائے ہوئے ids (`claude/`، `no-think/`) کو چھوڑتے ہیں، owner کو `owned_by` سے لیتے ہیں، owner کو کبھی اس کا اپنا gateway نہیں بناتے، `blockedProviders` میں غیر فعال gateways اور کسی gateway کے authoritative live catalog سے باہر کے models کو چھوڑ دیتے ہیں۔ | | ---------------------------------------------- | ------- | ------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `ANTHROPIC_COMPATIBLE_MID_CONVERSATION_SYSTEM` | boolean | `false` | | سادہ `anthropic-compatible-*` relays (claude → claude passthrough) پر، گفتگو کے درمیان آنے والے `role: "system"` turns کو top-level `system` parameter میں منتقل کرنے کے بجائے `messages[]` کے اندر رکھیں، تاکہ cached prompt prefix تمام turns میں مستحکم رہے۔ یہ صرف اس وقت لاگو ہوتا ہے جب client نے خود `mid-conversation-system-2026-04-07` `anthropic-beta` بھیجا ہو (جسے upstream کی طرف جوں کا توں forward کیا جاتا ہے)۔ بطور ڈیفالٹ بند: ہر system turn کو پہلے کی طرح منتقل کیا جاتا ہے، کیونکہ beta کے بغیر relays، `role: "system"` messages کو 400 کے ساتھ مسترد کر دیتے ہیں۔ | | `MODELS_DEV_EFFORT_TIERS_ENABLED` | boolean | `false` | | جب نہ کوئی operator override اور نہ ہی provider registry کسی tier کا اعلان کرے، تو models.dev کے شائع کردہ reasoning effort tiers استعمال کریں (جنہیں معروف effort vocabulary تک محدود کیا گیا ہو)، اور `/v1/models` کے `effort_tiers` اور combo tier intersections میں اعلان کردہ خالی tier list کو "کوئی tiers نہیں" سمجھیں۔ بطور ڈیفالٹ بند: `effort_tiers` اور resolved `supportedThinkingEfforts` بالکل پہلے جیسے رہتے ہیں۔ | | `AUTO_REQUEST_RESOLVED_CAPABILITIES` | boolean | `false` | | auto/* درخواستیں ایک bulk read میں resolved model capabilities (vision، reasoning، thinking) اور مشاہدہ شدہ quality scores کے ساتھ candidate pool بناتی ہیں (#16419)۔ بطور ڈیفالٹ بند: request-time snapshot weights بالکل پہلے جیسے رہتے ہیں۔ آن ہونے پر: یہ signals snapshot weights میں شامل ہوتے ہیں، اس لیے candidate weights اور selection تبدیل ہو سکتے ہیں۔ | | `AUTO_COMBO_KEYLESS_TRANSPORT_GATE` | boolean | `false` | | ایسے keyless (no-auth) فراہم کنندگان کو `auto/*` کے امیدواروں کے مجموعے سے خارج رکھیں جن کے transport کو مقامی browser (`browserBacked`) یا مقامی طور پر نصب شدہ CLI (`isLocalCli`) درکار ہو، تاکہ کوئی معطل رکن کسی auto درخواست کو فی ہدف timeout تک نہ روک سکے (#17036)۔ بطور ڈیفالٹ بند: keyless مجموعہ تبدیل نہیں ہوتا۔ براہِ راست `/` کالز دونوں صورتوں میں کام کرتی ہیں۔ | @@ -254,7 +253,7 @@ "requiresRestart": false, "warningLevel": "caution", }, - // ... تمام 111 فلیگز + // ... تمام 110 فلیگز ], "summary": { "total": 56, diff --git a/docs/i18n/uz/docs/reference/FEATURE_FLAGS.md b/docs/i18n/uz/docs/reference/FEATURE_FLAGS.md index dfe19701a241..a03fddbf0ec7 100644 --- a/docs/i18n/uz/docs/reference/FEATURE_FLAGS.md +++ b/docs/i18n/uz/docs/reference/FEATURE_FLAGS.md @@ -48,7 +48,7 @@ boʻlsa, u **yoqilgan** hisoblanadi (`isFeatureFlagEnabled()`ga qarang). ## Bayroqlar katalogi -6 ta toifa bo‘yicha 111 ta bayroq. **Standart qiymat** — DB orqali qayta belgilanmagan va muhit o‘zgaruvchisi mavjud bo‘lmagan hollarda ishlatiladigan ta’rifdagi standart qiymat. +6 ta toifa bo‘yicha 110 ta bayroq. **Standart qiymat** — DB orqali qayta belgilanmagan va muhit o‘zgaruvchisi mavjud bo‘lmagan hollarda ishlatiladigan ta’rifdagi standart qiymat. ### Xavfsizlik (10) @@ -112,7 +112,7 @@ boʻlsa, u **yoqilgan** hisoblanadi (`isFeatureFlagEnabled()`ga qarang). | `RADAR_ENABLED` | boolean | `false` | OmniRoute Radar modulini (katalog tasmasi ekranlari va sinxronlash) yoqing. Standart boʻyicha oʻchiq; uni yoqish faqat foydalanuvchi interfeysini ochadi — maʼlumotlarni sinxronlash alohida tarzda ixtiyoriy yoqilishi kerak. | | `MODEL_ENDPOINT_CAPABILITY_GATE_ENABLED` | boolean | `false` | Ishonchli model oxirgi nuqtasi teglarini (provayder eʼlon qilgan, maxsus model identifikatoridan aniqlangan yoki operator oʻrnatgan) majburiy qoʻllang: `/v1/chat/completions`, `/v1/embeddings`, `/v1/rerank`, `/v1/images/generations`, `/v1/videos/generations`, `/v1/music/generations` va `/v1/audio/*` yoʻllarida model xizmat koʻrsatmaydigan oxirgi nuqtaga yuborilgan soʻrovni joʻnatishdan oldin HTTP 400 bilan rad eting; teglangan oxirgi nuqtalari uchun OmniRoute adapteri mavjud boʻlmagan modellarni `/v1/models` dan yashiring; ular uchun boshqaruv panelidagi model sinoviga 422 bilan javob bering (#16929). Standart boʻyicha oʻchiq: oxirgi nuqta teglari faqat axborot xususiyatini saqlab qoladi va joʻnatish oʻzgarmaydi. | -### Ishlash muhiti (53) +### Ishlash muhiti (52) | Kalit | Tur | Standart qiymat | Qayta ishga tushirish | Tavsif | | ---------------------------------------------- | ------- | --------------- | --------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -159,7 +159,6 @@ boʻlsa, u **yoqilgan** hisoblanadi (`isFeatureFlagEnabled()`ga qarang). | `KIRO_TOOL_CALL_REPAIR_ENABLED` | boolean | `true` | | Oqimli Kiro javobi koʻrinadigan natijadan oldin notoʻgʻri tuzilgan `tool_call` oʻramini (`input.name` yoki `input.arguments` mavjud emas) chiqarsa, turni `invalid_kiro_tool_call` bilan yakunlash oʻrniga uni bostiring va soʻrovni tuzatish koʻrsatmasi bilan bir marta qayta yuboring. Qayta urinish bitta qoʻshimcha Kiro soʻrovi xarajatini keltirib chiqaradi; ikkinchi xato turni `kiro_tool_call_repair_retry_failed` bilan yakunlaydi. Standart holatda yoqilgan. Birinchi notoʻgʻri tuzilgan oʻramdayoq turni xato bilan yakunlash uchun `false` qiymatini oʻrnating. | | `CATALOG_MIRROR_REFINEMENTS_ENABLED` | boolean | `false` | | `/v1/models` dagi `claude/` aniqlash va funksional shlyuz koʻzgularini takomillashtiring. Standart holatda oʻchiq: har ikkala koʻzgulash bosqichi avvalgidek ishlaydi. Yoqilganda: `claude/` aniqlash koʻzgulari roʻyxatdagi fikrlash saʼy-harakati variantlarini (`-low`/`-medium`/`-high`/`-xhigh`) ham qamrab oladi, soʻrov yoʻli hosil qila olmaydigan ichki `auto/*` identifikatorlarini va roʻyxatda allaqachon mavjud boʻlgan taxallusni oʻtkazib yuboradi; funksional shlyuz koʻzgulari kombinatsiyalar va OmniRoute yaratgan identifikatorlarni (`claude/`, `no-think/`) oʻtkazib yuboradi, egani `owned_by` dan oladi, hech qachon egani uning oʻz shlyuzi sifatida ishlatmaydi, `blockedProviders` da oʻchirilgan shlyuzlarni hamda shlyuzning vakolatli jonli katalogidan tashqaridagi modellarni oʻtkazib yuboradi. | | ---------------------------------------------- | ------- | ------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `ANTHROPIC_COMPATIBLE_MID_CONVERSATION_SYSTEM` | boolean | `false` | | Oddiy `anthropic-compatible-*` relaylarida (claude → claude oʻzgarishsiz uzatish) suhbat oʻrtasidagi `role: "system"` turnlarini yuqori darajadagi `system` parametriga koʻtarish oʻrniga `messages[]` ichida saqlang, shunda keshlangan prompt prefiksi turnlar davomida barqaror qoladi. Faqat mijozning oʻzi `mid-conversation-system-2026-04-07` `anthropic-beta` ni yuborganida qoʻllanadi (yuqori oqimga oʻzgarishsiz uzatiladi). Standart holatda oʻchiq: har bir tizim turni avvalgidek yuqori darajaga koʻtariladi, chunki beta-versiyasiz relaylar `role: "system"` xabarlarini 400 bilan rad etadi. | | `MODELS_DEV_EFFORT_TIERS_ENABLED` | boolean | `false` | | Operator qayta belgilashi ham, provayder reyestri ham hech qanday darajani eʼlon qilmaganida models.dev chop etadigan fikrlash saʼy-harakati darajalaridan (maʼlum saʼy-harakat lugʻati bilan cheklangan holda) foydalaning va eʼlon qilingan boʻsh darajalar roʻyxatini `/v1/models` dagi `effort_tiers` hamda kombinatsiya darajalari kesishmalarida "darajalar yoʻq" deb hisoblang. Standart holatda oʻchiq: `effort_tiers` va aniqlangan `supportedThinkingEfforts` aynan avvalgidek qoladi. | | `AUTO_REQUEST_RESOLVED_CAPABILITIES` | boolean | `false` | | auto/* soʻrovlari nomzodlar toʻplamini aniqlangan model imkoniyatlari (koʻrish, mulohaza yuritish, fikrlash) va kuzatilgan sifat ballari asosida bitta ommaviy oʻqish orqali yaratadi (#16419). Standart holatda oʻchiq: soʻrov vaqtidagi oniy holat vaznlari aynan avvalgidek qoladi. Yoqilganda: bu signallar oniy holat vaznlariga taʼsir qiladi, shu sababli nomzodlar vaznlari va tanlov oʻzgarishi mumkin. | | `AUTO_COMBO_KEYLESS_TRANSPORT_GATE` | boolean | `false` | | Transporti mahalliy brauzerni (`browserBacked`) yoki mahalliy o‘rnatilgan CLI’ni (`isLocalCli`) talab qiladigan kalitsiz (autentifikatsiyasiz) provayderlarni `auto/*` nomzodlar to‘plamidan chiqarib turadi, shunda javob bermay qolgan a’zo avtomatik so‘rovni har bir nishon uchun belgilangan vaqt tugaguncha to‘xtatib qo‘ya olmaydi (#17036). Standart holatda o‘chiq: kalitsiz provayderlar to‘plami o‘zgarmaydi. Bevosita `/` chaqiruvlari har ikki holatda ham ishlaydi. | @@ -257,7 +256,7 @@ Har bir bayroqni uning samarali qiymati, manbasi va qisqacha ma’lumoti bilan q "requiresRestart": false, "warningLevel": "caution", }, - // ... jami 111 ta bayroq + // ... jami 110 ta bayroq ], "summary": { "total": 56, diff --git a/docs/i18n/vi/docs/reference/FEATURE_FLAGS.md b/docs/i18n/vi/docs/reference/FEATURE_FLAGS.md index cae2c2caf321..3a026c508f71 100644 --- a/docs/i18n/vi/docs/reference/FEATURE_FLAGS.md +++ b/docs/i18n/vi/docs/reference/FEATURE_FLAGS.md @@ -39,7 +39,7 @@ Một cờ boolean được coi là **đã bật** khi giá trị hiệu quả c ## Danh mục cờ -111 cờ thuộc 6 danh mục. **Mặc định** là giá trị mặc định trong định nghĩa — giá trị +110 cờ thuộc 6 danh mục. **Mặc định** là giá trị mặc định trong định nghĩa — giá trị được sử dụng khi không có giá trị ghi đè trong DB cũng như biến môi trường. ### Bảo mật (10) @@ -104,7 +104,7 @@ Một cờ boolean được coi là **đã bật** khi giá trị hiệu quả c | `RADAR_ENABLED` | boolean | `false` | Bật mô-đun OmniRoute Radar (các màn hình nguồn cấp danh mục và đồng bộ hóa). Mặc định là tắt; việc bật tùy chọn này chỉ mở khóa giao diện người dùng — đồng bộ hóa dữ liệu vẫn là một tùy chọn đăng ký riêng. | | `MODEL_ENDPOINT_CAPABILITY_GATE_ENABLED` | boolean | `false` | Thực thi các thẻ điểm cuối model đáng tin cậy (do nhà cung cấp khai báo, được suy luận từ id của model chuyên biệt hoặc do người vận hành đặt): từ chối bằng HTTP 400 trước khi điều phối một yêu cầu được gửi đến điểm cuối mà model không hỗ trợ trên `/v1/chat/completions`, `/v1/embeddings`, `/v1/rerank`, `/v1/images/generations`, `/v1/videos/generations`, `/v1/music/generations` và `/v1/audio/*`; ẩn khỏi `/v1/models` các model có điểm cuối được gắn thẻ nhưng không có bộ điều hợp OmniRoute; trả về 422 cho chúng trong bài kiểm tra model trên bảng điều khiển (#16929). Mặc định là tắt: các thẻ điểm cuối vẫn chỉ mang tính thông tin và quá trình điều phối không thay đổi. | -### Runtime (53) +### Runtime (52) | Khóa | Kiểu | Mặc định | Khởi động lại | Mô tả | | ---------------------------------------------- | ------- | -------- | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | @@ -151,7 +151,6 @@ Một cờ boolean được coi là **đã bật** khi giá trị hiệu quả c | `KIRO_TOOL_CALL_REPAIR_ENABLED` | boolean | `true` | | Khi phản hồi Kiro dạng luồng phát ra một trình bao `tool_call` không hợp lệ (thiếu `input.name` hoặc `input.arguments`) trước bất kỳ đầu ra hiển thị nào, hãy chặn nó và gửi lại yêu cầu một lần kèm hướng dẫn sửa lỗi thay vì kết thúc lượt với `invalid_kiro_tool_call`. Lần thử lại tốn thêm một yêu cầu Kiro; nếu thất bại lần thứ hai, lượt sẽ kết thúc với `kiro_tool_call_repair_retry_failed`. Mặc định bật. Đặt thành `false` để lượt thất bại ngay tại trình bao không hợp lệ đầu tiên. | | `CATALOG_MIRROR_REFINEMENTS_ENABLED` | boolean | `false` | | Tinh chỉnh các bản phản chiếu khám phá `claude/` và cổng chức năng trên `/v1/models`. Mặc định tắt: cả hai lượt phản chiếu hoạt động chính xác như trước đây. Khi bật: các bản phản chiếu khám phá `claude/` cũng bao gồm những biến thể mức độ suy luận được liệt kê (`-low`/`-medium`/`-high`/`-xhigh`), bỏ qua các ID `auto/*` tích hợp sẵn mà đường dẫn yêu cầu không thể hiện thực hóa và bỏ qua bí danh đã được liệt kê; các bản phản chiếu cổng chức năng bỏ qua combo và các ID do OmniRoute tổng hợp (`claude/`, `no-think/`), lấy chủ sở hữu từ `owned_by`, không bao giờ dùng chủ sở hữu làm cổng của chính nó, bỏ qua các cổng bị vô hiệu hóa trong `blockedProviders` và các mô hình nằm ngoài danh mục hoạt động có thẩm quyền của cổng. | | ---------------------------------------------- | ------- | ------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `ANTHROPIC_COMPATIBLE_MID_CONVERSATION_SYSTEM` | boolean | `false` | | Trên các bộ chuyển tiếp `anthropic-compatible-*` thuần túy (chuyển tiếp nguyên trạng claude → claude), giữ các lượt `role: "system"` giữa cuộc hội thoại bên trong `messages[]` thay vì đưa chúng lên tham số `system` cấp cao nhất, để tiền tố lời nhắc đã lưu trong bộ nhớ đệm duy trì ổn định qua các lượt. Chỉ áp dụng khi chính máy khách đã gửi `anthropic-beta` `mid-conversation-system-2026-04-07` (được chuyển tiếp nguyên trạng lên thượng nguồn). Mặc định tắt: mọi lượt hệ thống đều được đưa lên như trước đây, vì các bộ chuyển tiếp không có beta sẽ từ chối thông báo `role: "system"` với lỗi 400. | | `MODELS_DEV_EFFORT_TIERS_ENABLED` | boolean | `false` | | Sử dụng các bậc mức độ suy luận do models.dev công bố (được giới hạn trong tập giá trị mức độ đã biết) khi cả phần ghi đè của bên vận hành lẫn sổ đăng ký nhà cung cấp đều không khai báo bậc nào, đồng thời coi danh sách bậc được khai báo nhưng trống là "không có bậc" trong `effort_tiers` của `/v1/models` và trong các giao của bậc combo. Mặc định tắt: `effort_tiers` và `supportedThinkingEfforts` đã phân giải vẫn giữ nguyên như trước đây. | | `AUTO_REQUEST_RESOLVED_CAPABILITIES` | boolean | `false` | | Các yêu cầu auto/* xây dựng nhóm ứng viên bằng các khả năng mô hình đã phân giải (thị giác, suy luận, tư duy) và điểm chất lượng quan sát được trong một lần đọc hàng loạt (#16419). Mặc định tắt: trọng số ảnh chụp nhanh tại thời điểm yêu cầu vẫn giữ nguyên như trước đây. Khi bật: các tín hiệu đó được đưa vào trọng số ảnh chụp nhanh, vì vậy trọng số ứng viên và kết quả lựa chọn có thể thay đổi. | | `AUTO_COMBO_KEYLESS_TRANSPORT_GATE` | boolean | `false` | | Loại các nhà cung cấp không khóa (không xác thực) có phương thức truyền tải cần trình duyệt cục bộ (`browserBacked`) hoặc CLI được cài đặt cục bộ (`isLocalCli`) khỏi nhóm ứng viên `auto/*`, để một thành viên bị treo không thể làm đình trệ yêu cầu tự động cho đến khi hết thời gian chờ của từng đích (#17036). Tắt theo mặc định: nhóm không khóa không thay đổi. Các lệnh gọi trực tiếp `/` vẫn hoạt động trong cả hai trường hợp. | @@ -248,7 +247,7 @@ Trả về mọi cờ cùng với giá trị hiệu lực, nguồn và thông ti "requiresRestart": false, "warningLevel": "caution", }, - // ... toàn bộ 111 cờ + // ... toàn bộ 110 cờ ], "summary": { "total": 56, diff --git a/docs/i18n/yo/docs/reference/FEATURE_FLAGS.md b/docs/i18n/yo/docs/reference/FEATURE_FLAGS.md index 6f45f6a32ce1..00676b17124c 100644 --- a/docs/i18n/yo/docs/reference/FEATURE_FLAGS.md +++ b/docs/i18n/yo/docs/reference/FEATURE_FLAGS.md @@ -43,7 +43,7 @@ A ka àmì boolean sí **tí a ti mú ṣiṣẹ́** nígbà tí iye tó ń ṣi ## Àkójọ Àwọn Flag -Àwọn flag 111 ní àwọn ẹ̀ka 6. **Àiyipada** ni iye àiyipada inú ìtumọ̀ — iye tí a +Àwọn flag 110 ní àwọn ẹ̀ka 6. **Àiyipada** ni iye àiyipada inú ìtumọ̀ — iye tí a máa ń lò nígbà tí kò sí ìkọ̀wé-àtúnṣe DB tàbí oníyí àyíká. ### Ààbò (10) @@ -108,7 +108,7 @@ máa ń lò nígbà tí kò sí ìkọ̀wé-àtúnṣe DB tàbí oníyí àyík | `RADAR_ENABLED` | boolean | `false` | Tan module Radar ti OmniRoute (àwọn ojú-ìwé àfihàn katalogi àti ìmúdọ́gba). Ó wà ní pípa ní ìpìlẹ̀; títàn án kàn máa ń ṣí UI sílẹ̀ — ìmúdọ́gba dátà ṣì jẹ́ àṣàyàn ọ̀tọ̀ tí a gbọ́dọ̀ yàn láti darapọ̀ mọ́. | | `MODEL_ENDPOINT_CAPABILITY_GATE_ENABLED` | boolean | `false` | Fipá mú àwọn tag endpoint àwòṣe tí a fọkàn tán (èyí tí olùpèsè kéde, tí a ṣàròjinlẹ̀ rẹ̀ láti inú ID àwòṣe amọ̀ràn, tàbí tí olùdarí ṣètò): kọ ìbéèrè tí a fi ránṣẹ́ sí endpoint tí àwòṣe náà kò ṣe àtìlẹ́yìn fún pẹ̀lú HTTP 400 kí a tó rán an lọ lórí `/v1/chat/completions`, `/v1/embeddings`, `/v1/rerank`, `/v1/images/generations`, `/v1/videos/generations`, `/v1/music/generations` àti `/v1/audio/*`; fi àwọn àwòṣe tí àwọn endpoint tí a fi tag sí wọn kò ní adapter OmniRoute pamọ́ kúrò ní `/v1/models`; dáhùn sí ìdánwò àwòṣe dashboard pẹ̀lú 422 fún wọn (#16929). Ó wà ní pípa ní ìpìlẹ̀: àwọn tag endpoint yóò wà fún ìfitonilétí nìkan, ìfiránṣẹ́ sì kò ní yí padà. | -### Runtime (53) +### Runtime (52) | Kọ́kọ́rọ́ | Irú | Àiyipada | Ìtunbẹ̀rẹ̀ | Àpèjúwe | | ---------------------------------------------- | ------- | -------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -155,7 +155,6 @@ máa ń lò nígbà tí kò sí ìkọ̀wé-àtúnṣe DB tàbí oníyí àyík | `KIRO_TOOL_CALL_REPAIR_ENABLED` | boolean | `true` | | Nígbà tí ìdáhùn Kiro tó ń ṣàn bá ṣe àgbéjáde àpò `tool_call` tí ìṣètò rẹ̀ kò pé (tí `input.name` tàbí `input.arguments` kò sí) ṣáájú àbájáde èyíkéyìí tó hàn, pa á mọ́ kí o sì tún fi ìbéèrè náà ránṣẹ́ lẹ́ẹ̀kan pẹ̀lú ìtọ́ni àtúnṣe dípò kí o parí ìyípo náà pẹ̀lú `invalid_kiro_tool_call`. Àtúndánwò náà ná ìbéèrè Kiro àfikún kan; ìkùnà kejì máa parí ìyípo náà pẹ̀lú `kiro_tool_call_repair_retry_failed`. Ó wà ní títàn ní àtẹ̀yìnwá. Ṣètò rẹ̀ sí `false` láti mú kí ìyípo náà kùnà ní àpò àkọ́kọ́ tí ìṣètò rẹ̀ kò pé. | | `CATALOG_MIRROR_REFINEMENTS_ENABLED` | boolean | `false` | | Ṣe àtúnṣe àwọn àwòrán-díjìtálì ìṣàwárí `claude/` àti functional-gateway lórí `/v1/models`. Ó wà ní pípa ní àtẹ̀yìnwá: àwọn ìlànà àwòrán-díjìtálì méjèèjì ń ṣiṣẹ́ gẹ́gẹ́ bí ti tẹ́lẹ̀ pátápátá. Ní títàn: àwọn àwòrán-díjìtálì ìṣàwárí `claude/` tún máa bo àwọn variant ìpele ìsapá ìrònú tí a ṣe àkójọ wọn (`-low`/`-medium`/`-high`/`-xhigh`), fo àwọn id `auto/*` tí a kọ sínú ẹ̀rọ tí ipa-ọ̀nà ìbéèrè kò lè dá sílẹ̀, kí o sì fo alias kan tí a ti ṣe àkójọ rẹ̀ tẹ́lẹ̀; àwọn àwòrán-díjìtálì functional-gateway máa fo àwọn combo àti àwọn id tí OmniRoute dá (`claude/`, `no-think/`), gba olówó láti `owned_by`, má ṣe lo olówó náà gẹ́gẹ́ bí gateway tirẹ̀ láé, fo àwọn gateway tí a ti pa nínú `blockedProviders` àti àwọn model tí kò sí nínú katalogi alààyè tí gateway kan ní àṣẹ lórí rẹ̀. | | ---------------------------------------------- | ------- | ------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `ANTHROPIC_COMPATIBLE_MID_CONVERSATION_SYSTEM` | boolean | `false` | | Lórí àwọn relay `anthropic-compatible-*` lasán (claude → ìfiránṣẹ́ claude tààrà), pa àwọn ìyípo `role: "system"` àárín ìjíròrò mọ́ sínú `messages[]` dípò gbígbé wọn sókè sínú paramita `system` ìpele-òkè, kí ìṣáájú prompt tó wà ní cache lè dúró ṣinṣin láàárín àwọn ìyípo. Ó kan nígbà tí client fúnra rẹ̀ bá fi `mid-conversation-system-2026-04-07` `anthropic-beta` ránṣẹ́ (a ó fi ránṣẹ́ sí upstream bí ó ṣe rí). Ó wà ní pípa ní àtẹ̀yìnwá: gbogbo ìyípo system ni a máa ń gbé sókè gẹ́gẹ́ bí ti tẹ́lẹ̀ pátápátá, nítorí pé àwọn relay tí kò ní beta náà máa kọ àwọn ìfiránṣẹ́ `role: "system"` sílẹ̀ pẹ̀lú 400. | | `MODELS_DEV_EFFORT_TIERS_ENABLED` | boolean | `false` | | Lo àwọn ìpele ìsapá ìrònú tí models.dev ń tẹ̀ jáde (tí a dín sí àwọn ọ̀rọ̀ ìsapá tí a mọ̀) nígbà tí àtúnṣe operator tàbí registry provider kò bá kéde èyíkéyìí, kí o sì ka àkójọ ìpele ṣófo tí a kéde sí "kò sí àwọn ìpele" nínú `effort_tiers` `/v1/models` àti àwọn ìkóríta ìpele combo. Ó wà ní pípa ní àtẹ̀yìnwá: `effort_tiers` àti `supportedThinkingEfforts` tí a yanjú máa dúró gẹ́gẹ́ bí ti tẹ́lẹ̀ pátápátá. | | `AUTO_REQUEST_RESOLVED_CAPABILITIES` | boolean | `false` | | Àwọn ìbéèrè auto/* máa kọ àkójọpọ̀ olùdíje pẹ̀lú àwọn agbára model tí a yanjú (ìran, ìfèrònú, ìrònú) àti àwọn àmì ìdíwọ̀n dídára tí a ṣàkíyèsí nínú kíkà olópò kan (#16419). Ó wà ní pípa ní àtẹ̀yìnwá: àwọn ìwọ̀n snapshot ní àkókò ìbéèrè máa dúró gẹ́gẹ́ bí ti tẹ́lẹ̀ pátápátá. Ní títàn: àwọn àmì wọ̀nyẹn máa wọ inú àwọn ìwọ̀n snapshot, nítorí náà àwọn ìwọ̀n olùdíje àti àṣàyàn lè yí padà. | | `AUTO_COMBO_KEYLESS_TRANSPORT_GATE` | boolean | `false` | | Yọ àwọn olupèsè aláìní kọ́kọ́rọ́ (kò sí ìfàṣẹsí), tí ìrìnàjò wọn nílò aṣàwákiri abẹ́lẹ̀ (`browserBacked`) tàbí CLI tí a fi sí ẹ̀rọ abẹ́lẹ̀ (`isLocalCli`), kúrò nínú àkójọpọ̀ olùdíje `auto/*`, kí ọmọ ẹgbẹ́ tó há má bàa dá ìbéèrè aládàáṣiṣẹ́ dúró títí àkókò ìparí fún ibi-afẹ́ kọ̀ọ̀kan (#17036). Ó wà ní pípa ní àiyipada: àkójọpọ̀ aláìní kọ́kọ́rọ́ kò yí padà. Àwọn ìpè `/` tààrà ń ṣiṣẹ́ láìka ìṣètò yìí sí. | @@ -252,7 +251,7 @@ Gbogbo ìṣe ń gba ipa-ọ̀nà kan ṣoṣo kọjá: "requiresRestart": false, "warningLevel": "caution", }, - // ... gbogbo àwọn àmì 111 + // ... gbogbo àwọn àmì 110 ], "summary": { "total": 56, diff --git a/docs/i18n/zh-CN/docs/reference/FEATURE_FLAGS.md b/docs/i18n/zh-CN/docs/reference/FEATURE_FLAGS.md index d7e9520e216d..77f56da46e4f 100644 --- a/docs/i18n/zh-CN/docs/reference/FEATURE_FLAGS.md +++ b/docs/i18n/zh-CN/docs/reference/FEATURE_FLAGS.md @@ -39,7 +39,7 @@ ## 标志目录 -共 111 个标志,分为 6 个类别。**默认值**是定义中的默认值,即既不存在数据库覆盖值,也不存在环境变量时所使用的值。 +共 110 个标志,分为 6 个类别。**默认值**是定义中的默认值,即既不存在数据库覆盖值,也不存在环境变量时所使用的值。 ### 安全性 (10) @@ -103,7 +103,7 @@ | `RADAR_ENABLED` | 布尔值 | `false` | 启用 OmniRoute Radar 模块(目录信息流页面和同步)。默认关闭;启用后仅解锁 UI,数据同步仍需单独选择启用。 | | `MODEL_ENDPOINT_CAPABILITY_GATE_ENABLED` | 布尔值 | `false` | 强制执行可信的模型端点标签(由提供者声明、根据专用模型 ID 推断或由操作员设置):对于发送到模型不支持的端点的请求,在分发前以 HTTP 400 拒绝,适用于 `/v1/chat/completions`、`/v1/embeddings`、`/v1/rerank`、`/v1/images/generations`、`/v1/videos/generations`、`/v1/music/generations` 和 `/v1/audio/*`;在 `/v1/models` 中隐藏其标签所指端点没有 OmniRoute 适配器的模型;仪表板模型测试针对这些模型返回 422(#16929)。默认关闭:端点标签仅供参考,分发行为保持不变。 | -### 运行时(53) +### 运行时(52) | 键 | 类型 | 默认值 | 重启 | 描述 | | ---------------------------------------------- | ------- | ------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -150,7 +150,6 @@ | `KIRO_TOOL_CALL_REPAIR_ENABLED` | boolean | `true` | | 当流式 Kiro 响应在产生任何可见输出之前发出格式错误的 `tool_call` 包装器(缺少 `input.name` 或 `input.arguments`)时,将其抑制,并附加纠正指令重新发送一次请求,而不是以 `invalid_kiro_tool_call` 结束当前轮次。重试会额外消耗一次 Kiro 请求;若第二次仍失败,则以 `kiro_tool_call_repair_retry_failed` 结束当前轮次。默认开启。设置为 `false` 可在首次出现格式错误的包装器时直接使当前轮次失败。 | | `CATALOG_MIRROR_REFINEMENTS_ENABLED` | boolean | `false` | | 优化 `/v1/models` 上的 `claude/` 发现镜像和功能网关镜像。默认关闭:两次镜像处理的行为都与之前完全相同。开启后:`claude/` 发现镜像也会涵盖已列出的推理强度变体(`-low`/`-medium`/`-high`/`-xhigh`),跳过请求路径无法实例化的内置 `auto/*` ID,并跳过已列出的别名;功能网关镜像会跳过组合和 OmniRoute 合成的 ID(`claude/`、`no-think/`),从 `owned_by` 获取所有者,绝不将所有者自身用作网关,跳过在 `blockedProviders` 中被禁用的网关,以及不在网关权威实时目录中的模型。 | | ---------------------------------------------- | ------- | ------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `ANTHROPIC_COMPATIBLE_MID_CONVERSATION_SYSTEM` | boolean | `false` | | 在普通的 `anthropic-compatible-*` 中继上(claude → claude 直通),将对话中途的 `role: "system"` 轮次保留在 `messages[]` 中,而不是提升到顶层 `system` 参数,以便缓存的提示词前缀在各轮之间保持稳定。仅当客户端自身发送了 `mid-conversation-system-2026-04-07` `anthropic-beta` 时适用(按原样转发到上游)。默认关闭:每个系统轮次都会像以前一样被提升,因为不支持该 beta 的中继会以 400 拒绝 `role: "system"` 消息。 | | `MODELS_DEV_EFFORT_TIERS_ENABLED` | boolean | `false` | | 当运营方覆盖配置和提供者注册表均未声明任何推理强度层级时,使用 models.dev 发布的推理强度层级(限定为已知的强度词汇表),并在 `/v1/models` 的 `effort_tiers` 和组合层级交集中,将显式声明的空层级列表视为“无层级”。默认关闭:`effort_tiers` 和解析后的 `supportedThinkingEfforts` 与之前完全相同。 | | `AUTO_REQUEST_RESOLVED_CAPABILITIES` | boolean | `false` | | auto/* 请求通过一次批量读取,使用解析后的模型能力(视觉、推理、思考)和观测到的质量分数来构建候选池 (#16419)。默认关闭:请求时的快照权重与之前完全相同。开启后:这些信号将用于快照权重,因此候选项权重和选择结果可能发生变化。 | | `AUTO_COMBO_KEYLESS_TRANSPORT_GATE` | boolean | `false` | | 将传输方式需要本地浏览器(`browserBacked`)或本地安装的 CLI(`isLocalCli`)的无密钥(无身份验证)提供者排除在 `auto/*` 候选池之外,以免某个无响应的成员导致自动请求一直停滞到每目标超时为止(#17036)。默认关闭:无密钥池保持不变。无论是否启用,直接调用 `/` 都可正常工作。 | @@ -246,7 +245,7 @@ "requiresRestart": false, "warningLevel": "caution", }, - // ... 全部 111 个标志 + // ... 全部 110 个标志 ], "summary": { "total": 56, diff --git a/docs/i18n/zh-TW/docs/reference/FEATURE_FLAGS.md b/docs/i18n/zh-TW/docs/reference/FEATURE_FLAGS.md index bb18fcfa5679..a37f32ca601d 100644 --- a/docs/i18n/zh-TW/docs/reference/FEATURE_FLAGS.md +++ b/docs/i18n/zh-TW/docs/reference/FEATURE_FLAGS.md @@ -43,7 +43,7 @@ ## 旗標目錄 -共 111 個旗標,分為 6 個類別。**預設值**是定義中的預設值——當資料庫覆寫值和環境變數皆不存在時所使用的值。 +共 110 個旗標,分為 6 個類別。**預設值**是定義中的預設值——當資料庫覆寫值和環境變數皆不存在時所使用的值。 ### 安全性 (10) @@ -107,7 +107,7 @@ | `RADAR_ENABLED` | boolean | `false` | 啟用 OmniRoute Radar 模組(目錄動態消息畫面與同步)。預設關閉;啟用只會解鎖使用者介面,資料同步仍需另外選擇加入。 | | `MODEL_ENDPOINT_CAPABILITY_GATE_ENABLED` | boolean | `false` | 強制執行受信任的模型端點標籤(由提供者宣告、根據專用模型 ID 推斷,或由操作人員設定):若請求傳送至模型未提供服務的端點,則在分派至 `/v1/chat/completions`、`/v1/embeddings`、`/v1/rerank`、`/v1/images/generations`、`/v1/videos/generations`、`/v1/music/generations` 和 `/v1/audio/*` 前,以 HTTP 400 拒絕;在 `/v1/models` 中隱藏其已標記端點沒有 OmniRoute 配接器的模型;對這些模型的儀表板模型測試回應 422(#16929)。預設關閉:端點標籤僅供參考,分派行為不變。 | -### 執行階段 (53) +### 執行階段 (52) | 鍵 | 類型 | 預設值 | 重新啟動 | 說明 | | ---------------------------------------------- | ------- | ------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -154,7 +154,6 @@ | `KIRO_TOOL_CALL_REPAIR_ENABLED` | boolean | `true` | | 當串流 Kiro 回應在任何可見輸出前發出格式錯誤的 `tool_call` 包裝器(缺少 `input.name` 或 `input.arguments`)時,隱藏該內容並附加修正指示重新傳送請求一次,而不是以 `invalid_kiro_tool_call` 結束該輪。重試會額外耗用一次 Kiro 請求;若第二次仍失敗,則會以 `kiro_tool_call_repair_retry_failed` 結束該輪。預設開啟。設為 `false` 可在首次出現格式錯誤的包裝器時讓該輪失敗。 | | `CATALOG_MIRROR_REFINEMENTS_ENABLED` | boolean | `false` | | 改進 `/v1/models` 上的 `claude/` 探索鏡像與功能閘道鏡像。預設關閉:兩個鏡像處理流程的行為均與先前完全相同。開啟後:`claude/` 探索鏡像也會涵蓋列出的推理強度變體(`-low`/`-medium`/`-high`/`-xhigh`)、略過請求路徑無法具現化的內建 `auto/*` ID,並略過已列出的別名;功能閘道鏡像會略過組合與 OmniRoute 合成的 ID(`claude/`、`no-think/`)、從 `owned_by` 取得擁有者、絕不將擁有者用作其自身的閘道,並略過在 `blockedProviders` 中停用的閘道,以及不在閘道權威即時目錄中的模型。 | | ---------------------------------------------- | ------- | ------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `ANTHROPIC_COMPATIBLE_MID_CONVERSATION_SYSTEM` | boolean | `false` | | 在一般的 `anthropic-compatible-*` 中繼服務(claude → claude 直通傳遞)上,將對話中途的 `role: "system"` 輪次保留在 `messages[]` 內,而不是將其提升至頂層 `system` 參數,讓快取的提示詞前綴在各輪之間保持穩定。僅在用戶端本身傳送了 `mid-conversation-system-2026-04-07` `anthropic-beta` 時適用(按原樣轉送至上游)。預設關閉:每個系統輪次都會完全比照先前方式提升,因為不支援該 beta 的中繼服務會以 400 拒絕 `role: "system"` 訊息。 | | `MODELS_DEV_EFFORT_TIERS_ENABLED` | boolean | `false` | | 當操作員覆寫與提供者登錄檔均未宣告任何推理強度層級時,使用 models.dev 發布的推理強度層級(限縮為已知的強度詞彙),並在 `/v1/models` 的 `effort_tiers` 與組合層級交集中,將明確宣告的空層級清單視為「無層級」。預設關閉:`effort_tiers` 與解析後的 `supportedThinkingEfforts` 會維持與先前完全相同。 | | `AUTO_REQUEST_RESOLVED_CAPABILITIES` | boolean | `false` | | auto/* 請求會透過一次批次讀取,使用解析後的模型能力(視覺、推理、思考)與觀測到的品質分數建立候選集區(#16419)。預設關閉:請求時的快照權重會維持與先前完全相同。開啟後:這些訊號會納入快照權重,因此候選項目的權重與選擇結果可能改變。 | | `AUTO_COMBO_KEYLESS_TRANSPORT_GATE` | boolean | `false` | | 將傳輸方式需要本機瀏覽器(`browserBacked`)或本機安裝之 CLI(`isLocalCli`)的無金鑰(無驗證)提供者排除在 `auto/*` 候選集之外,避免無回應的成員導致自動請求停滯,直到達到各目標的逾時時間(#17036)。預設關閉:無金鑰提供者集維持不變。無論是否啟用,直接呼叫 `/` 均可運作。 | @@ -249,7 +248,7 @@ "requiresRestart": false, "warningLevel": "caution", }, - // ... 全部 111 個旗標 + // ... 全部 110 個旗標 ], "summary": { "total": 56, diff --git a/docs/reference/FEATURE_FLAGS.md b/docs/reference/FEATURE_FLAGS.md index ff69ddeb1210..4c3d0b749f63 100644 --- a/docs/reference/FEATURE_FLAGS.md +++ b/docs/reference/FEATURE_FLAGS.md @@ -46,7 +46,7 @@ A boolean flag is considered **enabled** when its effective value is `"true"`, ## Flag Catalog -111 flags across 6 categories. **Default** is the definition default — the value +110 flags across 6 categories. **Default** is the definition default — the value used when neither a DB override nor an environment variable is present. ### Security (10) @@ -111,7 +111,7 @@ used when neither a DB override nor an environment variable is present. | `RADAR_ENABLED` | boolean | `false` | Enable the OmniRoute Radar module (catalog feed screens and sync). Off by default; enabling only unlocks the UI — data sync remains a separate opt-in. | | `MODEL_ENDPOINT_CAPABILITY_GATE_ENABLED` | boolean | `false` | Enforce trusted model endpoint tags (provider-declared, inferred from a specialty model id, or operator-set): reject a request sent to an endpoint the model does not serve with HTTP 400 before dispatch on `/v1/chat/completions`, `/v1/embeddings`, `/v1/rerank`, `/v1/images/generations`, `/v1/videos/generations`, `/v1/music/generations` and `/v1/audio/*`; hide models whose tagged endpoints have no OmniRoute adapter from `/v1/models`; answer the dashboard model test with 422 for them (#16929). Off by default: endpoint tags stay informational and dispatch is unchanged. | -### Runtime (53) +### Runtime (52) | Key | Type | Default | Restart | Description | | ---------------------------------------------- | ------- | ------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -158,7 +158,6 @@ used when neither a DB override nor an environment variable is present. | `KIRO_TOOL_CALL_REPAIR_ENABLED` | boolean | `true` | | When a streaming Kiro response emits a malformed `tool_call` wrapper (missing `input.name` or `input.arguments`) before any visible output, suppress it and re-send the request once with a corrective instruction instead of ending the turn with `invalid_kiro_tool_call`. The retry costs one extra Kiro request; a second failure ends the turn with `kiro_tool_call_repair_retry_failed`. On by default. Set to `false` to fail the turn on the first malformed wrapper. | | `CATALOG_MIRROR_REFINEMENTS_ENABLED` | boolean | `false` | | Refine the `claude/` discovery and functional-gateway mirrors on `/v1/models`. Off by default: both mirror passes behave exactly as before. On: `claude/` discovery mirrors also cover listed reasoning-effort variants (`-low`/`-medium`/`-high`/`-xhigh`), skip built-in `auto/*` ids the request path cannot materialize and skip an alias already listed; functional-gateway mirrors skip combos and OmniRoute-synthesized ids (`claude/`, `no-think/`), take the owner from `owned_by`, never use the owner as its own gateway, skip gateways disabled in `blockedProviders` and models outside a gateway's authoritative live catalog. | | ---------------------------------------------- | ------- | ------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `ANTHROPIC_COMPATIBLE_MID_CONVERSATION_SYSTEM` | boolean | `false` | | On plain `anthropic-compatible-*` relays (claude → claude passthrough), keep mid-conversation `role: "system"` turns inside `messages[]` instead of hoisting them into the top-level `system` parameter, so the cached prompt prefix stays stable across turns. Applies only when the client itself sent the `mid-conversation-system-2026-04-07` `anthropic-beta` (forwarded upstream as-is). Off by default: every system turn is hoisted exactly as before, because relays without the beta reject `role: "system"` messages with 400. | | `MODELS_DEV_EFFORT_TIERS_ENABLED` | boolean | `false` | | Use the reasoning effort tiers models.dev publishes (narrowed to the known effort vocabulary) when neither an operator override nor the provider registry declares any, and treat a declared empty tier list as "no tiers" in `/v1/models` `effort_tiers` and combo tier intersections. Off by default: `effort_tiers` and resolved `supportedThinkingEfforts` stay exactly as before. | | `AUTO_REQUEST_RESOLVED_CAPABILITIES` | boolean | `false` | | auto/* requests build the candidate pool with resolved model capabilities (vision, reasoning, thinking) and observed quality scores in one bulk read (#16419). Off by default: request-time snapshot weights stay exactly as before. On: those signals feed the snapshot weights, so candidate weights and selection can change. | | `AUTO_COMBO_KEYLESS_TRANSPORT_GATE` | boolean | `false` | | Hold keyless (no-auth) providers whose transport needs a local browser (`browserBacked`) or a locally installed CLI (`isLocalCli`) out of the `auto/*` candidate pool, so a hung member cannot stall an auto request until the per-target timeout (#17036). Off by default: the keyless pool is unchanged. Direct `/` calls work either way. | @@ -255,7 +254,7 @@ Returns every flag with its effective value, source, and a summary. "requiresRestart": false, "warningLevel": "caution", }, - // ... all 111 flags + // ... all 110 flags ], "summary": { "total": 56, diff --git a/open-sse/config/anthropicHeaders.ts b/open-sse/config/anthropicHeaders.ts index 81486fe5e995..d48bc9f2c50b 100644 --- a/open-sse/config/anthropicHeaders.ts +++ b/open-sse/config/anthropicHeaders.ts @@ -394,12 +394,12 @@ export function mergeClientAnthropicBeta( * third-party upstream did not advertise. A merge that survives to nothing leaves * the header unset instead of emitting an empty one. * - * Same seeded path: with ANTHROPIC_COMPATIBLE_MID_CONVERSATION_SYSTEM on and the beta - * negotiated by the client, chatCore keeps mid-conversation system turns inside messages[] - * for these relays (open-sse/services/claudeMidConversationSystem.ts), so the client's own - * `mid-conversation-system-2026-04-07` token is forwarded too — never invented, and only - * while such a turn is still in the body (with the flag off every turn is hoisted first, - * so the header stays exactly as before). + * Same seeded path: when a connection opts into keepMidConversationSystem, chatCore keeps + * mid-conversation system turns inside messages[] for these relays + * (open-sse/services/claudeMidConversationSystem.ts), so the client's own + * `mid-conversation-system-2026-04-07` token is forwarded too when the client sent it — + * never invented, and only while such a turn is still in the body (with the opt-in off every + * turn is hoisted first, so the header stays exactly as before). */ export function applyClientAnthropicBeta( headers: Record, diff --git a/open-sse/handlers/chatCore.ts b/open-sse/handlers/chatCore.ts index eda7dd00cfe1..6d93a6a69d27 100644 --- a/open-sse/handlers/chatCore.ts +++ b/open-sse/handlers/chatCore.ts @@ -2071,13 +2071,8 @@ async function handleChatCoreInner({ ...options, provider, sourceFormat, - // #16236: in-place mid-conversation system turns are opt-in AND need the client's beta. - clientAnthropicBeta: getHeaderValueCaseInsensitive( - clientRawRequest?.headers ?? null, - "anthropic-beta" - ), - isMidConversationSystemEnabled: () => - isFeatureFlagEnabled("ANTHROPIC_COMPATIBLE_MID_CONVERSATION_SYSTEM"), + // #16236: in-place mid-conversation system turns are a per-connection opt-in. + providerSpecificData: credentials?.providerSpecificData, }, log ); diff --git a/open-sse/handlers/chatCore/claudeSystemRole.ts b/open-sse/handlers/chatCore/claudeSystemRole.ts index 84cf45e2ada9..f3f3a8f41f73 100644 --- a/open-sse/handlers/chatCore/claudeSystemRole.ts +++ b/open-sse/handlers/chatCore/claudeSystemRole.ts @@ -231,16 +231,14 @@ export function hoistLeadingTextSystemMessages(payload: Record) export type SystemRoleRoute = { provider?: string | null; sourceFormat?: string | null; - /** The client's own `anthropic-beta` header (the beta must be negotiated by the client). */ - clientAnthropicBeta?: string | null; - /** Injected ANTHROPIC_COMPATIBLE_MID_CONVERSATION_SYSTEM reader; absent/throwing = off. */ - isMidConversationSystemEnabled?: (() => boolean) | null; + /** The connection's providerSpecificData (carries the keepMidConversationSystem opt-in). */ + providerSpecificData?: unknown; }; /** * Picks how system/developer turns leave messages[] before a native Claude upstream call. * Upstreams that accept mid-conversation system turns (resolveClaudeMidConversationSystemPolicy: - * opt-in flag + client-negotiated beta) only get the leading run hoisted + * per-connection keepMidConversationSystem opt-in) only get the leading run hoisted * (and directive-only messages moved off messages[0]), so later turns keep their position * and the cache prefix stays stable. Accepting `system` turns says nothing about OpenAI * `developer` turns, so a body carrying one keeps the full hoist. @@ -253,8 +251,7 @@ export function liftSystemRoleMessages( provider: route?.provider, sourceFormat: route?.sourceFormat, targetFormat: FORMATS.CLAUDE, - clientAnthropicBeta: route?.clientAnthropicBeta, - isPassthroughEnabled: route?.isMidConversationSystemEnabled, + providerSpecificData: route?.providerSpecificData, }); const messages = Array.isArray(payload.messages) ? (payload.messages as Array<{ role?: unknown }>) diff --git a/open-sse/services/claudeMidConversationSystem.ts b/open-sse/services/claudeMidConversationSystem.ts index 6ba3f02ca830..f4471ff5dbb6 100644 --- a/open-sse/services/claudeMidConversationSystem.ts +++ b/open-sse/services/claudeMidConversationSystem.ts @@ -9,65 +9,45 @@ * the cache each turn. * * Keeping those turns in place is only valid on an upstream that accepts `role: "system"` - * inside `messages[]`, which Anthropic gates behind the `mid-conversation-system-2026-04-07` - * beta. Many Anthropic-shaped relays (GLM, Kimi, MiniMax endpoints) do not support it and - * answer such a body with 400, so the in-place shape is used only when BOTH hold: - * 1. the operator turned on `ANTHROPIC_COMPATIBLE_MID_CONVERSATION_SYSTEM` (default off), - * 2. the client itself negotiated the beta in its `anthropic-beta` header — the same signal - * native `claude` relies on (shouldUseMidConversationSystem in executors/claudeIdentity.ts - * adds the beta it then needs). OmniRoute never invents the beta; the client's token is - * forwarded by applyClientAnthropicBeta (config/anthropicHeaders.ts) while such a turn is - * still in the body. - * Anything else — flag off, beta absent, flag read failing — keeps the legacy full hoist. + * inside `messages[]`. Many Anthropic-shaped relays (GLM, Kimi, MiniMax endpoints) answer such + * a body with 400, and whether a relay accepts it is a property of that relay, not of the + * client: a client does not always send the `mid-conversation-system-2026-04-07` beta even + * when its relay accepts the shape. So the in-place shape is a per-connection opt-in + * (`providerSpecificData.keepMidConversationSystem`, default off). The client's own beta is + * still forwarded when present (applyClientAnthropicBeta, config/anthropicHeaders.ts) and is + * never invented. Anything else keeps the legacy full hoist. */ -import { MID_CONVERSATION_SYSTEM_BETA } from "../config/anthropicHeaders.ts"; import { isClaudeCodeCompatibleProvider } from "./claudeCodeCompatible.ts"; import { FORMATS } from "../translator/formats.ts"; -export const MID_CONVERSATION_SYSTEM_FLAG = "ANTHROPIC_COMPATIBLE_MID_CONVERSATION_SYSTEM"; - -/** Whether a client `anthropic-beta` comma-list carries the mid-conversation-system token. */ -export function clientNegotiatedMidConversationSystem(clientBeta: unknown): boolean { - if (typeof clientBeta !== "string") return false; - return clientBeta - .split(",") - .some((token) => token.trim().toLowerCase() === MID_CONVERSATION_SYSTEM_BETA); -} - -/** Fail closed: a throwing flag reader means "off" (legacy hoist). */ -function readFlag(isEnabled: (() => boolean) | null | undefined): boolean { - if (typeof isEnabled !== "function") return false; - try { - return isEnabled() === true; - } catch { - return false; - } +/** Per-connection opt-in on providerSpecificData (strict boolean). */ +export function keepsMidConversationSystem(providerSpecificData: unknown): boolean { + return ( + !!providerSpecificData && + typeof providerSpecificData === "object" && + (providerSpecificData as Record).keepMidConversationSystem === true + ); } /** * True when this request should keep its mid-conversation system turns in `messages[]`. * Only plain `anthropic-compatible-*` passthrough is covered: native `claude` keeps its own * model/shape policy (shouldUseMidConversationSystem) and the CC bridge builds its own body. - * The flag reader is injected (chatCore owns the feature-flag import) and only invoked once the - * cheap scope checks passed. */ export function resolveClaudeMidConversationSystemPolicy({ provider, sourceFormat, targetFormat, - clientAnthropicBeta, - isPassthroughEnabled, + providerSpecificData, }: { provider: string | null | undefined; sourceFormat: string | null | undefined; targetFormat: string | null | undefined; - clientAnthropicBeta?: string | null; - isPassthroughEnabled?: (() => boolean) | null; + providerSpecificData?: unknown; }): boolean { if (sourceFormat !== FORMATS.CLAUDE || targetFormat !== FORMATS.CLAUDE) return false; if (typeof provider !== "string" || !provider.startsWith("anthropic-compatible-")) return false; if (isClaudeCodeCompatibleProvider(provider)) return false; - if (!clientNegotiatedMidConversationSystem(clientAnthropicBeta)) return false; - return readFlag(isPassthroughEnabled); + return keepsMidConversationSystem(providerSpecificData); } diff --git a/open-sse/services/systemPrompt.ts b/open-sse/services/systemPrompt.ts index a22c4f4a7905..605720ab436b 100644 --- a/open-sse/services/systemPrompt.ts +++ b/open-sse/services/systemPrompt.ts @@ -232,7 +232,7 @@ export function injectSystemPromptPostTranslation( // early-return and silently got zero injection. // An anthropic-compatible relay can still carry system-role turns inside // messages[] when the opt-in mid-conversation-system passthrough kept them in - // place (#16236 — ANTHROPIC_COMPATIBLE_MID_CONVERSATION_SYSTEM + client beta; + // place (#16236 — per-connection keepMidConversationSystem opt-in; // with it off every turn is hoisted before this point, so nothing changes); // those are conversation turns, not the prompt carrier — writing the suffix // onto the LAST one would move it every turn and break the cached prefix. diff --git a/src/app/(dashboard)/dashboard/providers/[id]/components/modals/EditConnectionModal.tsx b/src/app/(dashboard)/dashboard/providers/[id]/components/modals/EditConnectionModal.tsx index b8419bfc63e4..16c98737ea69 100644 --- a/src/app/(dashboard)/dashboard/providers/[id]/components/modals/EditConnectionModal.tsx +++ b/src/app/(dashboard)/dashboard/providers/[id]/components/modals/EditConnectionModal.tsx @@ -177,6 +177,7 @@ export default function EditConnectionModal({ allowPaidCredits: connectionProviderSpecificData?.allowPaidCredits === true, passthroughModels: connectionProviderSpecificData?.passthroughModels === true, disableCooling: connectionProviderSpecificData?.disableCooling === true, + keepMidConversationSystem: connectionProviderSpecificData?.keepMidConversationSystem === true, importFreeModelsOnly: connectionProviderSpecificData?.importFreeModelsOnly === true, tunnelId: stringField(connectionProviderSpecificData?.tunnelId), runtimeKey: "", @@ -253,6 +254,7 @@ export default function EditConnectionModal({ const apiKeyOptional = providerAllowsOptionalApiKey(provider) || Boolean(isNoAuthWebSessionCredential); const isCcCompatible = isClaudeCodeCompatibleProvider(provider); + const isPlainAnthropicCompatible = isAnthropicCompatibleProvider(provider) && !isCcCompatible; const isCompatible = isOpenAICompatibleProvider(provider) || isAnthropicCompatibleProvider(provider); const vertexCopy = isVertex ? getVertexCredentialCopy(t) : null; @@ -405,6 +407,8 @@ export default function EditConnectionModal({ allowPaidCredits: connection.providerSpecificData?.allowPaidCredits === true, passthroughModels: connection?.providerSpecificData?.passthroughModels === true, disableCooling: connection?.providerSpecificData?.disableCooling === true, + keepMidConversationSystem: + connection?.providerSpecificData?.keepMidConversationSystem === true, importFreeModelsOnly: connection?.providerSpecificData?.importFreeModelsOnly === true, tunnelId: stringField(connection.providerSpecificData?.tunnelId), runtimeKey: "", @@ -726,6 +730,10 @@ export default function EditConnectionModal({ } if (updates.providerSpecificData) { updates.providerSpecificData.disableCooling = formData.disableCooling ? true : undefined; + if (isPlainAnthropicCompatible) { + updates.providerSpecificData.keepMidConversationSystem = + formData.keepMidConversationSystem ? true : undefined; + } updates.providerSpecificData.peakHourProtection = normalizePeakHourProtectionForSave( formData.peakHourProtection ); @@ -788,6 +796,22 @@ export default function EditConnectionModal({ )} /> ) : null; + const keepMidConversationSystemToggle = isPlainAnthropicCompatible ? ( + setFormData({ ...formData, keepMidConversationSystem: checked })} + label={providerText( + t, + "keepMidConversationSystemLabel", + "Keep mid-conversation system messages in place" + )} + description={providerText( + t, + "keepMidConversationSystemDescription", + 'Leave system messages sent mid-conversation inside the message list instead of moving them into the top-level system prompt, so the prompt cache keeps hitting across turns. Enable only if this upstream accepts role "system" inside messages (otherwise it answers 400).' + )} + /> + ) : null; const openaiResponsesStoreToggle = isResponsesConnection ? ( setFormData({ ...formData, disableCooling: checked })} diff --git a/src/i18n/messages/am.json b/src/i18n/messages/am.json index 3f15f115bdfc..3172bb3dce6c 100644 --- a/src/i18n/messages/am.json +++ b/src/i18n/messages/am.json @@ -1028,7 +1028,6 @@ "featureFlagOpencodeV2ClientFingerprintEnabledDescription": "በOpenCode Zen/Go ጥያቄዎች ላይ የOpenCode v2 CLI ደንበኛ አሻራን ይላኩ፦ opencode/latest/<2.x>/cli User-Agent እና x-opencode-client cli፤ እንዲሁም በነፃ ዕቅድ ጥያቄዎች ላይ የv2 ክፍለ-ጊዜ ራስጌዎችን፣ የv2 CLI read/shell መሣሪያ መርሃግብሮችን እና tool_choice \"none\" እንዳይኖር ያድርጉ። በነባሪነት ጠፍቷል፦ እያንዳንዱ የOpenCode ጥያቄ አሁን ያለውን ማንነት፣ ራስጌዎች እና የጥያቄ አካል ሳይለውጥ ይጠብቃል።", "featureFlagComboEmptyPoolNoEligibleTargetsEnabledDescription": "የauto combo የዕጩዎች ስብስብ የመሣሪያ ጥሪ ቅድመ-ማጣሪያው ከመስራቱ በፊት ባዶ ከሆነ (ሁሉም ዒላማዎች በcooldowns፣ lockouts ወይም exhaustion ምክንያት ሲዘለሉ)፣ ከቀድሞው 400 \"ምንም ዒላማ የመሣሪያ ጥሪን አይደግፍም\" capability_mismatch ይልቅ 503 no_eligible_targets ይመልሳል። በነባሪነት ጠፍቷል፦ ባዶው ስብስብ ቀድሞውን 400 ይዞ ይቆያል።", "featureFlagModelEndpointCapabilityGateEnabledDescription": "የታመኑ የሞዴል መጨረሻ ነጥብ መለያዎችን (በአቅራቢው ካታሎግ የተገለጹ፣ ከልዩ ሞዴል መታወቂያ የተገመቱ፣ ወይም በኦፕሬተሩ የተዘጋጁ) በግዴታ ተግብር፦ ሞዴሉ ወደማያገለግለው መጨረሻ ነጥብ የተላከን ጥያቄ ወደ /v1/chat/completions፣ /v1/embeddings፣ /v1/rerank፣ /v1/images/generations፣ /v1/videos/generations፣ /v1/music/generations እና /v1/audio/* ከማሰራጨት በፊት በHTTP 400 ውድቅ አድርግ፤ መለያ የተሰጣቸው መጨረሻ ነጥቦች የOmniRoute አስማሚ የሌላቸውን ሞዴሎች ከ/v1/models ደብቅ፤ እና ለእነዚህ ሞዴሎች የዳሽቦርድ ሞዴል ሙከራን በ422 መልስ። በነባሪ ጠፍቷል፦ የመጨረሻ ነጥብ መለያዎች ለመረጃ ብቻ ሆነው ይቆያሉ፣ እና ጥያቄዎች ልክ እንደበፊቱ ይሰራጫሉ።", - "featureFlagAnthropicCompatibleMidConversationSystemDescription": "በመደበኛ anthropic-ተኳሃኝ ሪሌዎች ላይ፣ የተሸጎጠው የፕሮምፕት ቅድመ ቅጥያ በየተራው የተረጋጋ ሆኖ እንዲቆይ፣ በውይይት መካከል ያሉ የስርዓት ተራዎችን ወደ ከፍተኛ ደረጃው የስርዓት ፕሮምፕት ከማዛወር ይልቅ በመልዕክት ዝርዝሩ ውስጥ ያቆዩ። ይህ የሚሠራው ደንበኛው ራሱ mid-conversation-system-2026-04-07 ቤታን ሲልክ ብቻ ነው። በነባሪነት ጠፍቷል፦ ያ ቤታ የሌላቸው ሪሌዎች በመልዕክት ዝርዝሩ ውስጥ ያሉ የስርዓት ተራዎችን ስለማይቀበሉ፣ እያንዳንዱ የስርዓት ተራ ልክ እንደበፊቱ ወደ የስርዓት ፕሮምፕቱ ይዛወራል።", "featureFlagModelsDevEffortTiersEnabledDescription": "ኦፕሬተር መሻርም ሆነ የአቅራቢው መዝገብ ምንም ደረጃዎችን ባላወጀበት ጊዜ models.dev የሚያትማቸውን የማመዛዘን ጥረት ደረጃዎች ይጠቀሙ፤ የታወቁ የጥረት ደረጃዎችን ብቻ ያስቀሩ፣ እና ባዶ የታወጀ የደረጃዎች ዝርዝርን \"ምንም ደረጃዎች የሉም\" ብለው ይቁጠሩ። በነባሪ ጠፍቷል፦ የሞዴል ካታሎጉ እና በመጨረሻ የተወሰኑት የጥረት ደረጃዎች ልክ እንደበፊቱ ይቆያሉ።", "featureFlagAutoRequestResolvedCapabilitiesDescription": "auto/* ጥያቄዎች የእጩ ስብስቡን በተፈቱ የሞዴል ችሎታዎች (ራዕይ፣ ምክንያታዊነት፣ አስተሳሰብ) እና በተስተዋሉ የጥራት ውጤቶች በአንድ የጅምላ ንባብ እንዲገነቡ ያስችላል። በነባሪ ጠፍቷል፦ በጥያቄ ጊዜ የእጩዎች ክብደቶች ልክ እንደበፊቱ ይቆያሉ። ሲበራ፦ እነዚህ ምልክቶች የቅጽበታዊ ክብደቶችን ይመግባሉ፣ ስለዚህ የእጩዎች ክብደቶች እና ምርጫ ሊቀየሩ ይችላሉ።", "featureFlagCredentialHealthRetestAutoDisabledDescription": "የምስክርነት መረጃ ጤንነት ቅኝቱ፣ ጌትዌዩ በራሱ ያጠፋቸውን ግንኙነቶች (እያንዳንዳቸውን ቢበዛ በየ6 ሰዓቱ አንድ ጊዜ፣ በእያንዳንዱ ቅኝት 5) እንደገና እንዲፈትሽ እና ዳግም ፍተሻቸውን ሲያልፉ መልሶ እንዲያበራቸው ፍቀድ። በነባሪነት ጠፍቷል፦ እንዲህ ያሉ ግንኙነቶች ኦፕሬተር እርምጃ እስኪወስድ ድረስ ጠፍተው ይቆያሉ። በኦፕሬተር የተሰናከሉ እና በቋሚነት የታገዱ ግንኙነቶች ፈጽሞ ዳግም አይፈተሹም።", diff --git a/src/i18n/messages/ar.json b/src/i18n/messages/ar.json index a4834638a9fb..9c3decfac875 100644 --- a/src/i18n/messages/ar.json +++ b/src/i18n/messages/ar.json @@ -1028,7 +1028,6 @@ "featureFlagOpencodeV2ClientFingerprintEnabledDescription": "أرسل بصمة عميل واجهة سطر الأوامر OpenCode v2 في طلبات OpenCode Zen/Go: وهي User-Agent بالقيمة opencode/latest/<2.x>/cli وx-opencode-client cli، وفي طلبات الفئة المجانية أرسل ترويسات جلسة v2 ومخططات أداتي القراءة/الصدفة لواجهة سطر الأوامر v2، ومن دون tool_choice \"none\". يكون هذا الخيار معطّلًا افتراضيًا: يحتفظ كل طلب OpenCode بالهوية والترويسات والنص الحالي دون تغيير.", "featureFlagComboEmptyPoolNoEligibleTargetsEnabledDescription": "عندما تكون مجموعة المرشحين لمجموعة تلقائية فارغة بالفعل قبل تشغيل عامل التصفية المسبق لاستدعاء الأدوات (إذ تُتخطّى جميع الوجهات بسبب فترات التهدئة أو الحظر أو الاستنفاد)، أرجِع 503 no_eligible_targets بدلًا من الخطأ القديم 400 \"لا توجد وجهة تدعم استدعاء الأدوات\" capability_mismatch. يكون هذا الخيار معطّلًا افتراضيًا: تحتفظ المجموعة الفارغة بالخطأ القديم 400.", "featureFlagModelEndpointCapabilityGateEnabledDescription": "فرض علامات نقاط نهاية النماذج الموثوقة (المُعلنة في كتالوج المزوّد، أو المستنتجة من معرّف نموذج متخصص، أو التي يعيّنها المشغّل): رفض الطلب المُرسل إلى نقطة نهاية لا يخدمها النموذج برمز HTTP 400 قبل توجيهه على /v1/chat/completions و/v1/embeddings و/v1/rerank و/v1/images/generations و/v1/videos/generations و/v1/music/generations و/v1/audio/*؛ وإخفاء النماذج التي لا تحتوي نقاط نهايتها الموسومة على محوّل OmniRoute من /v1/models؛ والرد على اختبار النموذج في لوحة المعلومات برمز 422 لهذه النماذج. هذه الميزة معطّلة افتراضيًا: تظل علامات نقاط النهاية معلوماتية، وتُوجّه الطلبات تمامًا كما كان من قبل.", - "featureFlagAnthropicCompatibleMidConversationSystemDescription": "في المرحّلات العادية المتوافقة مع Anthropic، أبقِ رسائل النظام التي تأتي في منتصف المحادثة داخل قائمة الرسائل بدلًا من نقلها إلى موجّه النظام ذي المستوى الأعلى، كي تظل بادئة الموجّه المخزنة مؤقتًا ثابتة عبر الأدوار. لا ينطبق ذلك إلا عندما يكون العميل نفسه قد أرسل الإصدار التجريبي mid-conversation-system-2026-04-07. يكون هذا الخيار معطّلًا افتراضيًا: تُنقل كل رسالة نظام إلى موجّه النظام كما كان يحدث تمامًا من قبل، لأن المرحّلات التي لا تدعم ذلك الإصدار التجريبي ترفض رسائل النظام الموجودة في قائمة الرسائل.", "featureFlagModelsDevEffortTiersEnabledDescription": "استخدم مستويات جهد الاستدلال التي ينشرها models.dev عندما لا يحدّد تجاوزٌ من المشغّل ولا سجلّ المزوّد أيّ مستويات، مع الاحتفاظ بمستويات الجهد المعروفة فقط، واعتبار قائمة المستويات المُعلنة الفارغة على أنها «لا توجد مستويات». معطّل افتراضيًا: يظلّ كتالوج النماذج ومستويات الجهد المحسومة كما كانا تمامًا.", "featureFlagAutoRequestResolvedCapabilitiesDescription": "يسمح لطلبات auto/* ببناء مجموعة المرشحين باستخدام قدرات النماذج المحسومة (الرؤية، الاستدلال، التفكير) ودرجات الجودة المرصودة، مقروءة دفعة واحدة. معطّل افتراضيًا: تبقى أوزان المرشحين وقت الطلب كما كانت تمامًا. عند التفعيل: تغذي هذه الإشارات أوزان اللقطة، لذا قد تتغير أوزان المرشحين والاختيار.", "featureFlagCredentialHealthRetestAutoDisabledDescription": "اسمح لفحص سلامة بيانات الاعتماد بإعادة اختبار الاتصالات التي أوقفتها البوابة تلقائيًا (بحد أقصى مرة واحدة كل 6 ساعات لكل اتصال، و5 اتصالات لكل فحص)، وإعادة تشغيل أي اتصال يجتاز الاختبار. يكون هذا الخيار معطّلًا افتراضيًا: تظل هذه الاتصالات متوقفة حتى يتدخل أحد المشغّلين. لا تُعاد أبدًا اختبارات الاتصالات التي عطّلها المشغّل أو المحظورة نهائيًا.", diff --git a/src/i18n/messages/az.json b/src/i18n/messages/az.json index cb68fbb8d4e9..97c42fde4066 100644 --- a/src/i18n/messages/az.json +++ b/src/i18n/messages/az.json @@ -1028,7 +1028,6 @@ "featureFlagOpencodeV2ClientFingerprintEnabledDescription": "OpenCode Zen/Go sorğularında OpenCode v2 CLI klientinin identifikatorunu göndərin: opencode/latest/<2.x>/cli User-Agent və x-opencode-client cli, pulsuz səviyyəli sorğularda isə v2 sessiya başlıqlarını, v2 CLI read/shell alət sxemlərini göndərin və tool_choice \"none\" istifadə etməyin. Defolt olaraq deaktivdir: hər OpenCode sorğusu cari identikliyi, başlıqları və gövdəni dəyişməz saxlayır.", "featureFlagComboEmptyPoolNoEligibleTargetsEnabledDescription": "Avtomatik kombinasiyanın namizəd hovuzu alət çağırışı üzrə ilkin filtr işə düşməzdən əvvəl artıq boş olduqda (bütün hədəflər gözləmə müddətləri, bloklamalar və ya tükənmə səbəbindən ötürüldükdə), köhnə 400 \"Heç bir hədəf alət çağırışını dəstəkləmir\" capability_mismatch əvəzinə 503 no_eligible_targets cavabını qaytarın. Defolt olaraq deaktivdir: boş hovuz köhnə 400 cavabını saxlayır.", "featureFlagModelEndpointCapabilityGateEnabledDescription": "Etibarlı model son nöqtəsi teqlərini (provayder kataloqu tərəfindən bəyan edilən, ixtisaslaşmış model identifikatorundan müəyyən edilən və ya operator tərəfindən təyin olunan) məcburi tətbiq edin: /v1/chat/completions, /v1/embeddings, /v1/rerank, /v1/images/generations, /v1/videos/generations, /v1/music/generations və /v1/audio/* ünvanlarında modelin xidmət göstərmədiyi son nöqtəyə göndərilən sorğunu yönləndirmədən əvvəl HTTP 400 ilə rədd edin; teqlənmiş son nöqtələri üçün OmniRoute adapteri olmayan modelləri /v1/models siyahısından gizlədin; və belə modellər üçün idarəetmə panelindəki model testinə 422 ilə cavab verin. Defolt olaraq söndürülüb: son nöqtə teqləri məlumat xarakterli qalır və sorğular əvvəlki qaydada yönləndirilir.", - "featureFlagAnthropicCompatibleMidConversationSystemDescription": "Adi anthropic-uyğun relelərdə, söhbətin ortasındakı sistem mesajlarını üst səviyyəli sistem sorğusuna köçürmək əvəzinə mesaj siyahısında saxlayın ki, keşlənmiş sorğu prefiksi mesajlar arasında sabit qalsın. Yalnız müştərinin özü mid-conversation-system-2026-04-07 beta versiyasını göndərdikdə tətbiq olunur. Defolt olaraq deaktivdir: həmin beta versiyasını dəstəkləməyən relelər mesaj siyahısındakı sistem mesajlarını rədd etdiyi üçün hər bir sistem mesajı əvvəlki kimi sistem sorğusuna köçürülür.", "featureFlagModelsDevEffortTiersEnabledDescription": "Nə operatorun üstəgəl təyini, nə də provayder reyestri hər hansı səviyyə elan etdikdə, yalnız məlum səy səviyyələrini saxlamaqla models.dev tərəfindən dərc olunan düşünmə səyi səviyyələrindən istifadə edin və boş elan edilmiş səviyyə siyahısını «səviyyə yoxdur» kimi qəbul edin. Defolt olaraq deaktivdir: model kataloqu və müəyyən edilmiş səy səviyyələri əvvəlki kimi qalır.", "featureFlagAutoRequestResolvedCapabilitiesDescription": "auto/* sorğularına namizəd hovuzunu həll olunmuş model imkanları (görmə, mühakimə, düşünmə) və müşahidə olunan keyfiyyət balları ilə bir toplu oxumada qurmağa imkan verir. Defolt olaraq söndürülüb: sorğu zamanı namizəd çəkiləri əvvəlki kimi qalır. Aktiv olduqda: bu siqnallar snapshot çəkilərinə daxil olur, buna görə namizəd çəkiləri və seçim dəyişə bilər.", "featureFlagCredentialHealthRetestAutoDisabledDescription": "Etimadnamə vəziyyətinin yoxlanılması şlüzün öz-özünə söndürdüyü bağlantıları yenidən sınaqdan keçirsin (hər biri ən çox 6 saatda bir dəfə, hər yoxlamada 5 bağlantı) və yenidən sınaq uğurla keçdikdə bağlantını yenidən aktivləşdirsin. Defolt olaraq söndürülüb: belə bağlantılar operator müdaxilə edənədək söndürülmüş qalır. Operator tərəfindən söndürülmüş və daimi bloklanmış bağlantılar heç vaxt yenidən sınaqdan keçirilmir.", diff --git a/src/i18n/messages/bg.json b/src/i18n/messages/bg.json index fd8f5b94b763..557ab1657edd 100644 --- a/src/i18n/messages/bg.json +++ b/src/i18n/messages/bg.json @@ -1028,7 +1028,6 @@ "featureFlagOpencodeV2ClientFingerprintEnabledDescription": "Изпращане на отпечатъка на CLI клиента OpenCode v2 при заявки към OpenCode Zen/Go: User-Agent opencode/latest/<2.x>/cli и x-opencode-client cli, а при заявки в безплатния план — заглавките на сесията от v2, схемите на CLI инструментите read/shell от v2 и без tool_choice \"none\". Изключено по подразбиране: всяка заявка на OpenCode запазва текущата идентичност, заглавки и тяло непроменени.", "featureFlagComboEmptyPoolNoEligibleTargetsEnabledDescription": "Когато наборът от кандидати на автоматична комбинация вече е празен, преди да се изпълни предварителното филтриране за извикване на инструменти (всички цели са пропуснати поради периоди на изчакване, блокирания или изчерпване), отговорете с 503 no_eligible_targets вместо с наследения 400 „No target supports tool calling“ capability_mismatch. Изключено по подразбиране: при празен набор се запазва наследеният 400.", "featureFlagModelEndpointCapabilityGateEnabledDescription": "Налагане на доверени тагове за крайни точки на модели (декларирани от каталога на доставчика, изведени от идентификатор на специализиран модел или зададени от оператора): отхвърляне с HTTP 400 преди изпращане на заявка до крайна точка, която моделът не обслужва, за /v1/chat/completions, /v1/embeddings, /v1/rerank, /v1/images/generations, /v1/videos/generations, /v1/music/generations и /v1/audio/*; скриване от /v1/models на модели, чиито маркирани крайни точки нямат адаптер на OmniRoute; и връщане на отговор 422 при теста на модел в таблото за управление за такива модели. Изключено по подразбиране: таговете за крайни точки остават само информативни и заявките се изпращат точно както преди.", - "featureFlagAnthropicCompatibleMidConversationSystemDescription": "При обикновени anthropic-съвместими релейни услуги запазвайте системните реплики по средата на разговора в списъка със съобщения, вместо да ги премествате в системната подкана от най-горно ниво, така че кешираният префикс на подканата да остава непроменен между репликите. Прилага се само когато самият клиент е изпратил бета-флага mid-conversation-system-2026-04-07. По подразбиране е изключено: всяка системна реплика се премества в системната подкана точно както преди, защото релейните услуги без този бета-флаг отхвърлят системни реплики в списъка със съобщения.", "featureFlagModelsDevEffortTiersEnabledDescription": "Използвайте нивата на усилие при разсъждение, публикувани от models.dev, когато такива не са зададени нито чрез операторско заместване, нито в регистъра на доставчика, като запазвате само познатите нива на усилие и третирате празния списък с декларирани нива като „без нива“. Изключено по подразбиране: каталогът с модели и определените нива на усилие остават точно както преди.", "featureFlagAutoRequestResolvedCapabilitiesDescription": "Позволява на заявките auto/* да изграждат пула от кандидати с разрешени възможности на моделите (зрение, разсъждение, мислене) и наблюдавани оценки за качество, прочетени наведнъж. Изключено по подразбиране: теглата на кандидатите при заявка остават точно както преди. Включено: тези сигнали захранват теглата на моментната снимка, така че теглата на кандидатите и изборът могат да се променят.", "featureFlagCredentialHealthRetestAutoDisabledDescription": "Позволете на проверката за състоянието на идентификационните данни да тества повторно връзките, които шлюзът е изключил автоматично (най-много веднъж на всеки 6 часа за всяка връзка, по 5 на проверка), и да включи отново дадена връзка, когато повторният ѝ тест е успешен. По подразбиране е изключено: такива връзки остават изключени, докато оператор не предприеме действие. Връзките, деактивирани от оператор, и окончателно забранените връзки никога не се тестват повторно.", diff --git a/src/i18n/messages/bn.json b/src/i18n/messages/bn.json index f25e1950fdfc..f079f9196bbc 100644 --- a/src/i18n/messages/bn.json +++ b/src/i18n/messages/bn.json @@ -1028,7 +1028,6 @@ "featureFlagOpencodeV2ClientFingerprintEnabledDescription": "OpenCode Zen/Go অনুরোধে OpenCode v2 CLI ক্লায়েন্ট ফিঙ্গারপ্রিন্ট পাঠান: opencode/latest/<2.x>/cli User-Agent এবং x-opencode-client cli; আর free-tier অনুরোধে v2 session headers, v2 CLI read/shell tool schemas পাঠান এবং tool_choice \"none\" পাঠাবেন না। ডিফল্টভাবে বন্ধ: প্রতিটি OpenCode অনুরোধের বর্তমান পরিচয়, headers এবং body অপরিবর্তিত থাকে।", "featureFlagComboEmptyPoolNoEligibleTargetsEnabledDescription": "টুল-কলিং প্রি-ফিল্টার চালানোর আগেই যদি কোনো auto combo-এর candidate pool খালি থাকে (cooldown, lockout বা exhaustion-এর কারণে প্রতিটি target বাদ পড়ে), তাহলে পুরোনো 400 \"No target supports tool calling\" capability_mismatch-এর পরিবর্তে 503 no_eligible_targets রিটার্ন করুন। ডিফল্টভাবে বন্ধ: pool খালি থাকলে পুরোনো 400-ই বজায় থাকবে।", "featureFlagModelEndpointCapabilityGateEnabledDescription": "বিশ্বস্ত মডেল এন্ডপয়েন্ট ট্যাগ প্রয়োগ করুন (প্রোভাইডার ক্যাটালগে ঘোষিত, বিশেষায়িত মডেল id থেকে অনুমান করা, অথবা অপারেটর দ্বারা সেট করা): /v1/chat/completions, /v1/embeddings, /v1/rerank, /v1/images/generations, /v1/videos/generations, /v1/music/generations এবং /v1/audio/*-এ মডেলটি যে এন্ডপয়েন্টে সেবা দেয় না, সেখানে পাঠানো অনুরোধ ডিসপ্যাচের আগে HTTP 400 দিয়ে প্রত্যাখ্যান করুন; যেসব ট্যাগযুক্ত এন্ডপয়েন্টের জন্য কোনো OmniRoute অ্যাডাপ্টার নেই, সেই মডেলগুলো /v1/models থেকে লুকিয়ে রাখুন; এবং এমন মডেলের ক্ষেত্রে ড্যাশবোর্ড মডেল পরীক্ষায় 422 দিয়ে উত্তর দিন। ডিফল্টভাবে বন্ধ: এন্ডপয়েন্ট ট্যাগগুলো কেবল তথ্যমূলক থাকে এবং অনুরোধগুলো আগের মতোই ডিসপ্যাচ করা হয়।", - "featureFlagAnthropicCompatibleMidConversationSystemDescription": "সাধারণ anthropic-compatible রিলেতে, কথোপকথনের মাঝখানের system turn-গুলোকে top-level system prompt-এ সরিয়ে না নিয়ে message list-এর ভেতরেই রাখুন, যাতে cached prompt prefix প্রতিটি turn-এ স্থিতিশীল থাকে। এটি কেবল তখনই প্রযোজ্য, যখন client নিজেই mid-conversation-system-2026-04-07 beta পাঠিয়েছে। ডিফল্টভাবে বন্ধ: আগের মতোই প্রতিটি system turn-কে system prompt-এ সরিয়ে নেওয়া হয়, কারণ ওই beta ছাড়া রিলেগুলো message list-এ থাকা system turn প্রত্যাখ্যান করে।", "featureFlagModelsDevEffortTiersEnabledDescription": "অপারেটর ওভাররাইড বা প্রদানকারী রেজিস্ট্রি—কোনোটিই কোনো রিজনিং এফোর্ট টিয়ার ঘোষণা না করলে models.dev-এর প্রকাশিত টিয়ারগুলো ব্যবহার করুন, শুধু পরিচিত এফোর্ট লেভেলগুলো রাখুন এবং ঘোষিত টিয়ারের তালিকা খালি হলে সেটিকে \"কোনো টিয়ার নেই\" হিসেবে বিবেচনা করুন। ডিফল্টভাবে বন্ধ: মডেল ক্যাটালগ ও নির্ধারিত এফোর্ট টিয়ারগুলো ঠিক আগের মতোই থাকবে।", "featureFlagAutoRequestResolvedCapabilitiesDescription": "auto/* অনুরোধগুলিকে সমাধানকৃত মডেল সক্ষমতা (ভিশন, রিজনিং, থিংকিং) এবং পর্যবেক্ষিত গুণমান স্কোর দিয়ে এক বাল্ক পাঠে প্রার্থী পুল তৈরি করতে দেয়। ডিফল্টভাবে বন্ধ: অনুরোধের সময়ের প্রার্থী ওজন হুবহু আগের মতোই থাকে। চালু থাকলে: এই সংকেতগুলি স্ন্যাপশট ওজনে যুক্ত হয়, তাই প্রার্থী ওজন ও নির্বাচন বদলাতে পারে।", "featureFlagCredentialHealthRetestAutoDisabledDescription": "ক্রেডেনশিয়াল হেলথ সুইপকে গেটওয়ে নিজে থেকে বন্ধ করে দেওয়া সংযোগগুলো পুনরায় পরীক্ষা করতে দিন (প্রতিটি সর্বোচ্চ ৬ ঘণ্টায় একবার, প্রতি সুইপে ৫টি) এবং পুনঃপরীক্ষায় উত্তীর্ণ হলে সংযোগটি আবার চালু করতে দিন। ডিফল্টভাবে বন্ধ: অপারেটর পদক্ষেপ না নেওয়া পর্যন্ত এ ধরনের সংযোগ বন্ধই থাকে। অপারেটর কর্তৃক নিষ্ক্রিয় এবং স্থায়ীভাবে নিষিদ্ধ সংযোগগুলো কখনোই পুনরায় পরীক্ষা করা হয় না।", diff --git a/src/i18n/messages/bs.json b/src/i18n/messages/bs.json index 4fe8861d5d66..81da59983bba 100644 --- a/src/i18n/messages/bs.json +++ b/src/i18n/messages/bs.json @@ -1028,7 +1028,6 @@ "featureFlagOpencodeV2ClientFingerprintEnabledDescription": "Šalji otisak CLI klijenta OpenCode v2 u OpenCode Zen/Go zahtjevima: opencode/latest/<2.x>/cli User-Agent i x-opencode-client cli, a u zahtjevima besplatnog nivoa zaglavlja v2 sesije, v2 CLI sheme alata read/shell i bez tool_choice \"none\". Zadano je isključeno: svaki OpenCode zahtjev zadržava trenutni identitet, zaglavlja i tijelo nepromijenjenim.", "featureFlagComboEmptyPoolNoEligibleTargetsEnabledDescription": "Kada je skup kandidata automatske kombinacije već prazan prije pokretanja predfiltera za pozivanje alata (svaki cilj je preskočen zbog perioda čekanja, zaključavanja ili iscrpljenosti), vrati 503 no_eligible_targets umjesto ranijeg odgovora 400 „Nijedan cilj ne podržava pozivanje alata” capability_mismatch. Podrazumijevano je isključeno: prazan skup zadržava raniji odgovor 400.", "featureFlagModelEndpointCapabilityGateEnabledDescription": "Nametnite pouzdane oznake krajnjih tačaka modela (koje je deklarirao katalog pružatelja, koje su izvedene iz ID-a specijaliziranog modela ili koje je postavio operater): odbijte zahtjev poslan krajnjoj tački koju model ne opslužuje s HTTP 400 prije prosljeđivanja na /v1/chat/completions, /v1/embeddings, /v1/rerank, /v1/images/generations, /v1/videos/generations, /v1/music/generations i /v1/audio/*; sakrijte iz /v1/models modele čije označene krajnje tačke nemaju OmniRoute adapter; te odgovorite statusom 422 na test modela na kontrolnoj ploči za takve modele. Podrazumijevano isključeno: oznake krajnjih tačaka ostaju informativne, a zahtjevi se prosljeđuju isto kao i ranije.", - "featureFlagAnthropicCompatibleMidConversationSystemDescription": "Na običnim relejima kompatibilnim s anthropic, zadržite sistemske poruke usred razgovora unutar liste poruka umjesto da ih premještate u sistemski upit najvišeg nivoa, kako bi keširani prefiks upita ostao stabilan kroz više poruka. Primjenjuje se samo kada je sam klijent poslao betu mid-conversation-system-2026-04-07. Zadano je isključeno: svaka sistemska poruka premješta se u sistemski upit tačno kao i ranije, jer releji bez te bete odbijaju sistemske poruke u listi poruka.", "featureFlagModelsDevEffortTiersEnabledDescription": "Koristi nivoe intenziteta zaključivanja koje objavljuje models.dev kada ih ne navodi ni nadjačavanje operatora ni registar pružatelja usluga, zadržavajući samo poznate nivoe intenziteta, a praznu listu deklarisanih nivoa tretiraj kao „bez nivoa“. Podrazumijevano je isključeno: katalog modela i razriješeni nivoi intenziteta ostaju potpuno isti kao prije.", "featureFlagAutoRequestResolvedCapabilitiesDescription": "Omogućava zahtjevima auto/* da izgrade skup kandidata s razriješenim mogućnostima modela (vid, rezonovanje, razmišljanje) i uočenim ocjenama kvaliteta, učitanim u jednom skupnom čitanju. Isključeno po zadanim postavkama: težine kandidata u vrijeme zahtjeva ostaju potpuno iste kao prije. Uključeno: ti signali ulaze u težine snimka, pa se težine kandidata i izbor mogu promijeniti.", "featureFlagCredentialHealthRetestAutoDisabledDescription": "Dozvolite provjeri ispravnosti vjerodajnica da ponovo testira veze koje je pristupnik sam isključio (svaku najviše jednom svakih 6 sati, 5 po provjeri) i ponovo uključi vezu kada uspješno prođe test. Zadano isključeno: takve veze ostaju isključene dok operater ne poduzme radnju. Veze koje je operater isključio i trajno zabranjene veze nikada se ne testiraju ponovo.", diff --git a/src/i18n/messages/cs.json b/src/i18n/messages/cs.json index 0f0b4d28ae55..28bc40a7512a 100644 --- a/src/i18n/messages/cs.json +++ b/src/i18n/messages/cs.json @@ -1028,7 +1028,6 @@ "featureFlagOpencodeV2ClientFingerprintEnabledDescription": "Odesílat otisk klienta OpenCode v2 CLI v požadavcích OpenCode Zen/Go: User-Agent opencode/latest/<2.x>/cli a x-opencode-client cli; u požadavků bezplatné úrovně také hlavičky relace v2, schémata nástrojů read/shell pro v2 a žádné tool_choice \"none\". Ve výchozím nastavení vypnuto: každý požadavek OpenCode zachovává současnou identitu, hlavičky a tělo beze změny.", "featureFlagComboEmptyPoolNoEligibleTargetsEnabledDescription": "Pokud je fond kandidátů automatické kombinace prázdný již před spuštěním předběžného filtru pro volání nástrojů (všechny cíle byly přeskočeny kvůli čekacím lhůtám, uzamčením nebo vyčerpání), vraťte chybu 503 no_eligible_targets namísto původní chyby 400 „Žádný cíl nepodporuje volání nástrojů“ capability_mismatch. Ve výchozím nastavení je tato možnost vypnutá: při prázdném fondu se nadále vrací původní chyba 400.", "featureFlagModelEndpointCapabilityGateEnabledDescription": "Vynucovat důvěryhodné značky koncových bodů modelu (deklarované katalogem poskytovatele, odvozené z ID specializovaného modelu nebo nastavené operátorem): požadavek odeslaný na koncový bod, který model neobsluhuje, odmítnout před předáním s kódem HTTP 400 na /v1/chat/completions, /v1/embeddings, /v1/rerank, /v1/images/generations, /v1/videos/generations, /v1/music/generations a /v1/audio/*; skrýt z /v1/models modely, jejichž označené koncové body nemají adaptér OmniRoute; a při testu modelu na řídicím panelu vrátit pro takové modely kód 422. Ve výchozím nastavení vypnuto: značky koncových bodů zůstávají pouze informativní a požadavky se předávají přesně jako dříve.", - "featureFlagAnthropicCompatibleMidConversationSystemDescription": "U běžných relé kompatibilních s Anthropic ponechte systémové zprávy uprostřed konverzace v seznamu zpráv namísto jejich přesouvání do systémové výzvy nejvyšší úrovně, aby předpona výzvy uložená v mezipaměti zůstala mezi jednotlivými tahy stabilní. Platí pouze v případě, že samotný klient odeslal betaverzi mid-conversation-system-2026-04-07. Ve výchozím nastavení je tato možnost vypnutá: každý systémový tah se přesune do systémové výzvy přesně jako dříve, protože relé bez této betaverze systémové tahy v seznamu zpráv odmítají.", "featureFlagModelsDevEffortTiersEnabledDescription": "Použít úrovně intenzity uvažování publikované na models.dev, pokud nejsou žádné deklarovány přepsáním operátora ani registrem poskytovatelů, přičemž se zachovají pouze známé úrovně intenzity a prázdný seznam deklarovaných úrovní se považuje za „žádné úrovně“. Ve výchozím nastavení vypnuto: katalog modelů a výsledné úrovně intenzity zůstanou beze změny.", "featureFlagAutoRequestResolvedCapabilitiesDescription": "Umožní požadavkům auto/* sestavit fond kandidátů s vyřešenými schopnostmi modelů (vidění, uvažování, přemýšlení) a pozorovaným skóre kvality, načtenými jedním hromadným čtením. Ve výchozím stavu vypnuto: váhy kandidátů v době požadavku zůstávají přesně jako dříve. Zapnuto: tyto signály vstupují do vah snímku, takže se váhy kandidátů i výběr mohou změnit.", "featureFlagCredentialHealthRetestAutoDisabledDescription": "Umožnit kontrole stavu přihlašovacích údajů znovu otestovat připojení, která brána sama vypnula (každé nejvýše jednou za 6 hodin, 5 během jedné kontroly), a po úspěšném opakovaném testu je znovu zapnout. Ve výchozím nastavení vypnuto: taková připojení zůstanou vypnutá, dokud nezasáhne operátor. Připojení zakázaná operátorem a trvale zablokovaná připojení se nikdy znovu netestují.", diff --git a/src/i18n/messages/da.json b/src/i18n/messages/da.json index 994f56dba32e..8dba7f785736 100644 --- a/src/i18n/messages/da.json +++ b/src/i18n/messages/da.json @@ -1028,7 +1028,6 @@ "featureFlagOpencodeV2ClientFingerprintEnabledDescription": "Send OpenCode v2 CLI-klientens fingeraftryk på OpenCode Zen/Go-anmodninger: opencode/latest/<2.x>/cli User-Agent og x-opencode-client cli, og på gratisniveauanmodninger v2-sessionsheaderne, v2 CLI-read/shell-værktøjsskemaerne og ingen tool_choice \"none\". Deaktiveret som standard: Alle OpenCode-anmodninger beholder den nuværende identitet, headers og brødtekst uændret.", "featureFlagComboEmptyPoolNoEligibleTargetsEnabledDescription": "Når en automatisk kombinations kandidatpulje allerede er tom, før forfilteret for værktøjskald kører (alle mål er sprunget over på grund af nedkølingsperioder, spærringer eller udtømning), skal der svares med 503 no_eligible_targets i stedet for den tidligere 400-fejl \"Intet mål understøtter værktøjskald\" capability_mismatch. Deaktiveret som standard: Den tomme pulje beholder den tidligere 400-fejl.", "featureFlagModelEndpointCapabilityGateEnabledDescription": "Håndhæv betroede tags for modelendepunkter (angivet af udbyderkataloget, udledt fra et specialiseret model-id eller indstillet af operatøren): Afvis en anmodning, der sendes til et endepunkt, som modellen ikke understøtter, med HTTP 400 før videresendelse på /v1/chat/completions, /v1/embeddings, /v1/rerank, /v1/images/generations, /v1/videos/generations, /v1/music/generations og /v1/audio/*; skjul modeller, hvis taggede endepunkter ikke har en OmniRoute-adapter, fra /v1/models; og besvar dashboardets modeltest med 422 for sådanne modeller. Deaktiveret som standard: Endepunktstags forbliver informative, og anmodninger videresendes præcis som før.", - "featureFlagAnthropicCompatibleMidConversationSystemDescription": "På almindelige Anthropic-kompatible relæer skal systembeskeder midt i samtalen forblive i beskedlisten i stedet for at blive flyttet til systemprompten på øverste niveau, så det cachede promptpræfiks forbliver stabilt på tværs af beskeder. Gælder kun, når klienten selv har sendt betaen mid-conversation-system-2026-04-07. Deaktiveret som standard: Alle systembeskeder flyttes til systemprompten nøjagtigt som før, fordi relæer uden denne beta afviser systembeskeder i beskedlisten.", "featureFlagModelsDevEffortTiersEnabledDescription": "Brug de niveauer for ræsonneringsindsats, som models.dev udgiver, når hverken en operatørtilsidesættelse eller udbyderregistret angiver nogen, behold kun kendte indsatsniveauer, og behandl en tom liste over angivne niveauer som \"ingen niveauer\". Deaktiveret som standard: Modelkataloget og de fastlagte indsatsniveauer forbliver præcis som før.", "featureFlagAutoRequestResolvedCapabilitiesDescription": "Lader auto/*-anmodninger opbygge kandidatpuljen med afklarede modelfunktioner (vision, ræsonnement, tænkning) og observerede kvalitetsscorer, læst i én samlet læsning. Slået fra som standard: kandidatvægte på anmodningstidspunktet forbliver præcis som før. Slået til: disse signaler indgår i snapshot-vægtene, så kandidatvægte og valg kan ændre sig.", "featureFlagCredentialHealthRetestAutoDisabledDescription": "Lad sundhedstjekket af legitimationsoplysninger genteste forbindelser, som gatewayen selv har deaktiveret (højst én gang hver 6. time pr. forbindelse, 5 pr. gennemgang), og aktivere dem igen, når gentesten lykkes. Deaktiveret som standard: Sådanne forbindelser forbliver deaktiveret, indtil en operatør griber ind. Forbindelser, der er deaktiveret af en operatør eller permanent udelukket, gentestes aldrig.", diff --git a/src/i18n/messages/de.json b/src/i18n/messages/de.json index c6564124e79c..6cd8bbf9ada8 100644 --- a/src/i18n/messages/de.json +++ b/src/i18n/messages/de.json @@ -1028,7 +1028,6 @@ "featureFlagOpencodeV2ClientFingerprintEnabledDescription": "Den Fingerabdruck des OpenCode-v2-CLI-Clients bei OpenCode-Zen/Go-Anfragen senden: den User-Agent opencode/latest/<2.x>/cli und x-opencode-client cli sowie bei Free-Tier-Anfragen die v2-Sitzungsheader, die v2-CLI-Read/Shell-Tool-Schemas und kein tool_choice \"none\". Standardmäßig deaktiviert: Jede OpenCode-Anfrage behält die aktuelle Identität, die Header und den Body unverändert bei.", "featureFlagComboEmptyPoolNoEligibleTargetsEnabledDescription": "Wenn der Kandidatenpool einer Auto-Kombination bereits leer ist, bevor der Tool-Calling-Vorfilter ausgeführt wird (alle Ziele wurden aufgrund von Abklingzeiten, Sperren oder Erschöpfung übersprungen), antworte mit 503 no_eligible_targets statt mit dem bisherigen 400-Fehler „No target supports tool calling“ capability_mismatch. Standardmäßig deaktiviert: Bei leerem Pool wird weiterhin der bisherige 400-Fehler zurückgegeben.", "featureFlagModelEndpointCapabilityGateEnabledDescription": "Vertrauenswürdige Modell-Endpunkt-Tags erzwingen (vom Anbieterkatalog deklariert, aus einer spezialisierten Modell-ID abgeleitet oder vom Betreiber festgelegt): Eine an einen Endpunkt gesendete Anfrage, den das Modell nicht unterstützt, wird vor der Weiterleitung an /v1/chat/completions, /v1/embeddings, /v1/rerank, /v1/images/generations, /v1/videos/generations, /v1/music/generations und /v1/audio/* mit HTTP 400 abgelehnt; Modelle, für deren markierte Endpunkte kein OmniRoute-Adapter vorhanden ist, werden aus /v1/models ausgeblendet; und der Modelltest im Dashboard antwortet für solche Modelle mit 422. Standardmäßig deaktiviert: Endpunkt-Tags bleiben rein informativ und Anfragen werden genau wie zuvor weitergeleitet.", - "featureFlagAnthropicCompatibleMidConversationSystemDescription": "Bei einfachen anthropic-kompatiblen Relays bleiben System-Turns innerhalb einer laufenden Konversation in der Nachrichtenliste, anstatt in den System-Prompt auf oberster Ebene verschoben zu werden, sodass das zwischengespeicherte Prompt-Präfix über mehrere Turns hinweg stabil bleibt. Dies gilt nur, wenn der Client selbst die Beta mid-conversation-system-2026-04-07 gesendet hat. Standardmäßig deaktiviert: Jeder System-Turn wird genau wie zuvor in den System-Prompt verschoben, da Relays ohne diese Beta System-Turns in der Nachrichtenliste ablehnen.", "featureFlagModelsDevEffortTiersEnabledDescription": "Verwende die von models.dev veröffentlichten Stufen für den Reasoning-Aufwand, wenn weder ein Betreiber-Override noch das Anbieterregister welche deklariert. Behalte dabei nur bekannte Aufwandsstufen bei und behandle eine leere Liste deklarierter Stufen als „keine Stufen“. Standardmäßig deaktiviert: Der Modellkatalog und die ermittelten Aufwandsstufen bleiben exakt wie bisher.", "featureFlagAutoRequestResolvedCapabilitiesDescription": "Lässt auto/*-Anfragen den Kandidatenpool mit aufgelösten Modellfähigkeiten (Vision, Reasoning, Thinking) und beobachteten Qualitätswerten aufbauen, die in einem einzigen Sammellesevorgang geladen werden. Standardmäßig aus: Die Kandidatengewichte zur Anfragezeit bleiben exakt wie bisher. An: Diese Signale fließen in die Snapshot-Gewichte ein, sodass sich Kandidatengewichte und Auswahl ändern können.", "featureFlagCredentialHealthRetestAutoDisabledDescription": "Lassen Sie die Überprüfung des Anmeldedatenstatus Verbindungen erneut testen, die das Gateway selbst deaktiviert hat (jeweils höchstens einmal alle 6 Stunden, 5 pro Überprüfung), und sie wieder aktivieren, wenn der erneute Test erfolgreich ist. Standardmäßig deaktiviert: Solche Verbindungen bleiben ausgeschaltet, bis ein Operator eingreift. Von einem Operator deaktivierte und dauerhaft gesperrte Verbindungen werden nie erneut getestet.", diff --git a/src/i18n/messages/el.json b/src/i18n/messages/el.json index 74f0fb442558..1c7639b4ba2c 100644 --- a/src/i18n/messages/el.json +++ b/src/i18n/messages/el.json @@ -1028,7 +1028,6 @@ "featureFlagOpencodeV2ClientFingerprintEnabledDescription": "Αποστολή του αποτυπώματος πελάτη OpenCode v2 CLI στα αιτήματα OpenCode Zen/Go: του User-Agent opencode/latest/<2.x>/cli και του x-opencode-client cli, καθώς και, στα αιτήματα δωρεάν βαθμίδας, των κεφαλίδων συνεδρίας v2, των σχημάτων εργαλείων read/shell του v2 CLI και χωρίς tool_choice \"none\". Απενεργοποιημένο από προεπιλογή: κάθε αίτημα OpenCode διατηρεί αμετάβλητα την τρέχουσα ταυτότητα, τις κεφαλίδες και το σώμα.", "featureFlagComboEmptyPoolNoEligibleTargetsEnabledDescription": "Όταν η δεξαμενή υποψηφίων ενός αυτόματου συνδυασμού είναι ήδη κενή πριν εκτελεστεί το προφίλτρο κλήσης εργαλείων (κάθε στόχος παραλείφθηκε λόγω περιόδων αναμονής, αποκλεισμών ή εξάντλησης), επέστρεψε 503 no_eligible_targets αντί για το παλαιού τύπου 400 «Κανένας στόχος δεν υποστηρίζει την κλήση εργαλείων» capability_mismatch. Απενεργοποιημένο από προεπιλογή: η κενή δεξαμενή διατηρεί το παλαιού τύπου 400.", "featureFlagModelEndpointCapabilityGateEnabledDescription": "Επιβολή αξιόπιστων ετικετών τελικών σημείων μοντέλων (οι οποίες δηλώνονται από τον κατάλογο του παρόχου, συνάγονται από το αναγνωριστικό ενός εξειδικευμένου μοντέλου ή ορίζονται από τον χειριστή): απόρριψη, με HTTP 400 πριν από την αποστολή, αιτήματος που αποστέλλεται σε τελικό σημείο το οποίο δεν εξυπηρετείται από το μοντέλο, στα /v1/chat/completions, /v1/embeddings, /v1/rerank, /v1/images/generations, /v1/videos/generations, /v1/music/generations και /v1/audio/*· απόκρυψη από το /v1/models των μοντέλων των οποίων τα τελικά σημεία με ετικέτα δεν διαθέτουν προσαρμογέα OmniRoute· και απόκριση στη δοκιμή μοντέλου του πίνακα ελέγχου με 422 για αυτά τα μοντέλα. Απενεργοποιημένο από προεπιλογή: οι ετικέτες τελικών σημείων παραμένουν πληροφοριακές και τα αιτήματα αποστέλλονται ακριβώς όπως πριν.", - "featureFlagAnthropicCompatibleMidConversationSystemDescription": "Σε απλούς αναμεταδότες συμβατούς με το Anthropic, διατηρήστε τα μηνύματα συστήματος που εμφανίζονται στο μέσο της συνομιλίας μέσα στη λίστα μηνυμάτων, αντί να τα μετακινείτε στην προτροπή συστήματος ανώτατου επιπέδου, ώστε το αποθηκευμένο στην κρυφή μνήμη πρόθεμα της προτροπής να παραμένει σταθερό μεταξύ των γύρων. Ισχύει μόνο όταν ο ίδιος ο πελάτης έχει στείλει την έκδοση beta mid-conversation-system-2026-04-07. Απενεργοποιημένο από προεπιλογή: κάθε μήνυμα συστήματος μετακινείται στην προτροπή συστήματος ακριβώς όπως πριν, επειδή οι αναμεταδότες χωρίς αυτήν την έκδοση beta απορρίπτουν τα μηνύματα συστήματος στη λίστα μηνυμάτων.", "featureFlagModelsDevEffortTiersEnabledDescription": "Χρησιμοποιήστε τα επίπεδα έντασης συλλογισμού που δημοσιεύει το models.dev όταν ούτε μια παράκαμψη τελεστή ούτε το μητρώο παρόχων δηλώνει κάποια, διατηρώντας μόνο τα γνωστά επίπεδα έντασης, και αντιμετωπίστε μια κενή δηλωμένη λίστα επιπέδων ως «χωρίς επίπεδα». Απενεργοποιημένο από προεπιλογή: ο κατάλογος μοντέλων και τα επιλυμένα επίπεδα έντασης παραμένουν ακριβώς όπως πριν.", "featureFlagAutoRequestResolvedCapabilitiesDescription": "Επιτρέπει στα αιτήματα auto/* να δημιουργούν τη δεξαμενή υποψηφίων με επιλυμένες δυνατότητες μοντέλων (όραση, συλλογισμός, σκέψη) και παρατηρούμενες βαθμολογίες ποιότητας, που διαβάζονται σε μία μαζική ανάγνωση. Απενεργοποιημένο από προεπιλογή: τα βάρη των υποψηφίων κατά το αίτημα παραμένουν ακριβώς όπως πριν. Ενεργοποιημένο: αυτά τα σήματα τροφοδοτούν τα βάρη του στιγμιότυπου, οπότε τα βάρη των υποψηφίων και η επιλογή μπορεί να αλλάξουν.", "featureFlagCredentialHealthRetestAutoDisabledDescription": "Επιτρέψτε στον έλεγχο κατάστασης διαπιστευτηρίων να επανελέγχει τις συνδέσεις που απενεργοποίησε αυτόματα η πύλη (το πολύ μία φορά κάθε 6 ώρες για καθεμία, 5 ανά έλεγχο) και να ενεργοποιεί ξανά μια σύνδεση όταν περάσει επιτυχώς τον επανέλεγχο. Απενεργοποιημένο από προεπιλογή: αυτές οι συνδέσεις παραμένουν απενεργοποιημένες μέχρι να παρέμβει κάποιος χειριστής. Οι συνδέσεις που έχουν απενεργοποιηθεί από χειριστή ή έχουν αποκλειστεί μόνιμα δεν επανελέγχονται ποτέ.", diff --git a/src/i18n/messages/en.json b/src/i18n/messages/en.json index 5889c4f92188..f6e1818903b0 100644 --- a/src/i18n/messages/en.json +++ b/src/i18n/messages/en.json @@ -1028,7 +1028,6 @@ "featureFlagOpencodeV2ClientFingerprintEnabledDescription": "Send the OpenCode v2 CLI client fingerprint on OpenCode Zen/Go requests: the opencode/latest/<2.x>/cli User-Agent and x-opencode-client cli, and on free-tier requests the v2 session headers, the v2 CLI read/shell tool schemas and no tool_choice \"none\". Off by default: every OpenCode request keeps the current identity, headers and body unchanged.", "featureFlagComboEmptyPoolNoEligibleTargetsEnabledDescription": "When an auto combo's candidate pool is already empty before the tool-calling pre-filter runs (every target skipped by cooldowns, lockouts or exhaustion), answer 503 no_eligible_targets instead of the legacy 400 \"No target supports tool calling\" capability_mismatch. Off by default: the empty pool keeps the legacy 400.", "featureFlagModelEndpointCapabilityGateEnabledDescription": "Enforce trusted model endpoint tags (declared by the provider catalog, inferred from a specialty model id, or set by the operator): reject a request sent to an endpoint the model does not serve with HTTP 400 before dispatch on /v1/chat/completions, /v1/embeddings, /v1/rerank, /v1/images/generations, /v1/videos/generations, /v1/music/generations and /v1/audio/*; hide models whose tagged endpoints have no OmniRoute adapter from /v1/models; and answer the dashboard model test with 422 for such models. Off by default: endpoint tags stay informational and requests are dispatched exactly as before.", - "featureFlagAnthropicCompatibleMidConversationSystemDescription": "On plain anthropic-compatible relays, keep mid-conversation system turns inside the message list instead of moving them into the top-level system prompt, so the cached prompt prefix stays stable across turns. Applies only when the client itself sent the mid-conversation-system-2026-04-07 beta. Off by default: every system turn is moved into the system prompt exactly as before, because relays without that beta reject system turns in the message list.", "featureFlagModelsDevEffortTiersEnabledDescription": "Use the reasoning effort tiers models.dev publishes when neither an operator override nor the provider registry declares any, keeping only known effort levels, and treat an empty declared tier list as \"no tiers\". Off by default: the model catalog and resolved effort tiers stay exactly as before.", "featureFlagAutoRequestResolvedCapabilitiesDescription": "Let auto/* requests build the candidate pool with resolved model capabilities (vision, reasoning, thinking) and observed quality scores, read in one bulk pass. Off by default: request-time candidate weights stay exactly as before. On: those signals feed the snapshot weights, so candidate weights and selection can change.", "featureFlagCredentialHealthRetestAutoDisabledDescription": "Let the credential health sweep retest connections the gateway switched off by itself (at most once every 6 hours each, 5 per sweep) and switch one back on when its retest passes. Off by default: such connections stay off until an operator acts. Operator-disabled and permanently banned connections are never retested.", @@ -6094,6 +6093,8 @@ "allowCodexPaidCreditsDescription": "Allow this account to use paid credits after its subscription quota reaches the cutoff. Requires available credits reported by OpenAI; may incur charges. Spark limits and upstream cooldowns still apply. Credit availability is checked even if quota preflight is disabled, unless local quota filtering is fully turned off (quota preflight and limit policy both disabled).", "disableCoolingDescription": "Skip the transient cooldown so this connection stays eligible even after recoverable errors (terminal states like banned/expired still apply).", "disableCoolingLabel": "Disable cooldown for this connection", + "keepMidConversationSystemDescription": "Leave system messages sent mid-conversation inside the message list instead of moving them into the top-level system prompt, so the prompt cache keeps hitting across turns. Enable only if this upstream accepts role \"system\" inside messages (otherwise it answers 400).", + "keepMidConversationSystemLabel": "Keep mid-conversation system messages in place", "bulkPasteAdded": "{count, plural, one {1 key added} other {# keys added}}", "bulkPasteDuplicatesIgnored": "{count, plural, one {1 duplicate skipped} other {# duplicates skipped}}", "bulkPasteHint": "Paste one API key per line. Empty lines are ignored and duplicate keys are skipped.", diff --git a/src/i18n/messages/es.json b/src/i18n/messages/es.json index 3fffbdd18b34..b4cb96427dcb 100644 --- a/src/i18n/messages/es.json +++ b/src/i18n/messages/es.json @@ -1028,7 +1028,6 @@ "featureFlagOpencodeV2ClientFingerprintEnabledDescription": "Envía la huella digital del cliente CLI OpenCode v2 en las solicitudes de OpenCode Zen/Go: el User-Agent opencode/latest/<2.x>/cli y x-opencode-client cli y, en las solicitudes del nivel gratuito, los encabezados de sesión v2, los esquemas de las herramientas read/shell de la CLI v2 y ningún tool_choice \"none\". Desactivado de forma predeterminada: cada solicitud de OpenCode conserva sin cambios la identidad, los encabezados y el cuerpo actuales.", "featureFlagComboEmptyPoolNoEligibleTargetsEnabledDescription": "Cuando el grupo de candidatos de un combo automático ya está vacío antes de que se ejecute el prefiltro de llamadas a herramientas (todos los destinos se han omitido debido a periodos de espera, bloqueos o agotamiento), se responde con 503 no_eligible_targets en lugar del error 400 heredado \"Ningún destino admite llamadas a herramientas\" capability_mismatch. Desactivado de forma predeterminada: el grupo vacío mantiene el error 400 heredado.", "featureFlagModelEndpointCapabilityGateEnabledDescription": "Aplicar etiquetas de endpoints de modelo de confianza (declaradas por el catálogo del proveedor, inferidas a partir del id de un modelo especializado o configuradas por el operador): rechazar con HTTP 400, antes de enviarla, una solicitud dirigida a un endpoint que el modelo no admite en /v1/chat/completions, /v1/embeddings, /v1/rerank, /v1/images/generations, /v1/videos/generations, /v1/music/generations y /v1/audio/*; ocultar de /v1/models los modelos cuyos endpoints etiquetados no tengan un adaptador de OmniRoute; y responder con 422 a la prueba de modelos del panel para dichos modelos. Desactivado de forma predeterminada: las etiquetas de endpoints siguen siendo informativas y las solicitudes se envían exactamente como antes.", - "featureFlagAnthropicCompatibleMidConversationSystemDescription": "En relés estándar compatibles con Anthropic, mantén los turnos de sistema que aparecen en mitad de la conversación dentro de la lista de mensajes, en lugar de moverlos al mensaje de sistema de nivel superior, para que el prefijo de la solicitud almacenado en caché permanezca estable entre turnos. Solo se aplica cuando el propio cliente ha enviado la beta mid-conversation-system-2026-04-07. Desactivado de forma predeterminada: cada turno de sistema se mueve al mensaje de sistema exactamente como antes, porque los relés que no admiten esa beta rechazan los turnos de sistema incluidos en la lista de mensajes.", "featureFlagModelsDevEffortTiersEnabledDescription": "Usa los niveles de esfuerzo de razonamiento que publica models.dev cuando ni una anulación del operador ni el registro del proveedor declaren ninguno; conserva únicamente los niveles de esfuerzo conocidos y considera una lista vacía de niveles declarados como «sin niveles». Desactivado de forma predeterminada: el catálogo de modelos y los niveles de esfuerzo resueltos permanecen exactamente como antes.", "featureFlagAutoRequestResolvedCapabilitiesDescription": "Permite que las solicitudes auto/* construyan el grupo de candidatos con las capacidades de modelo resueltas (visión, razonamiento, pensamiento) y las puntuaciones de calidad observadas, leídas en una sola lectura masiva. Desactivado por defecto: los pesos de los candidatos en el momento de la solicitud se mantienen exactamente como antes. Activado: esas señales alimentan los pesos de la instantánea, por lo que los pesos de los candidatos y la selección pueden cambiar.", "featureFlagCredentialHealthRetestAutoDisabledDescription": "Permite que el análisis de estado de credenciales vuelva a probar las conexiones que la puerta de enlace desactivó automáticamente (como máximo una vez cada 6 horas cada una y 5 por análisis) y reactive una cuando supere la prueba. Desactivado de forma predeterminada: dichas conexiones permanecen desactivadas hasta que intervenga un operador. Las conexiones desactivadas por un operador y las bloqueadas permanentemente nunca vuelven a probarse.", diff --git a/src/i18n/messages/et.json b/src/i18n/messages/et.json index 9e75c743f4c2..671f338238ad 100644 --- a/src/i18n/messages/et.json +++ b/src/i18n/messages/et.json @@ -1028,7 +1028,6 @@ "featureFlagOpencodeV2ClientFingerprintEnabledDescription": "Saada OpenCode Zen/Go päringutes OpenCode v2 CLI-kliendi sõrmejälg: opencode/latest/<2.x>/cli User-Agent ja x-opencode-client cli ning tasuta paketi päringutes v2 seansipäised, v2 CLI read/shell-tööriistaskeemid ja mitte tool_choice \"none\". Vaikimisi välja lülitatud: iga OpenCode'i päringu praegune identiteet, päised ja keha jäävad muutmata.", "featureFlagComboEmptyPoolNoEligibleTargetsEnabledDescription": "Kui automaatse kombinatsiooni kandidaatide kogum on juba enne tööriistakutse eelfiltri käivitamist tühi (kõik sihtmärgid jäetakse ooteaegade, lukustuste või ammendumise tõttu vahele), tagasta pärandkäitumise 400 „No target supports tool calling“ capability_mismatch asemel 503 no_eligible_targets. Vaikimisi välja lülitatud: tühja kogumi korral säilib pärandkäitumise 400.", "featureFlagModelEndpointCapabilityGateEnabledDescription": "Jõusta usaldusväärsed mudeli lõpp-punktide sildid (teenusepakkuja kataloogis deklareeritud, erimudeli ID põhjal tuletatud või operaatori määratud): lükka HTTP 400 vastusega enne edastamist tagasi päring, mis saadeti lõpp-punktile, mida mudel ei teeninda, teedel /v1/chat/completions, /v1/embeddings, /v1/rerank, /v1/images/generations, /v1/videos/generations, /v1/music/generations ja /v1/audio/*; peida mudelid, mille sildistatud lõpp-punktidel puudub OmniRoute'i adapter, teelt /v1/models; ning vasta selliste mudelite juhtpaneeli mudelitestile koodiga 422. Vaikimisi väljas: lõpp-punktide sildid jäävad informatiivseks ja päringud edastatakse täpselt nagu varem.", - "featureFlagAnthropicCompatibleMidConversationSystemDescription": "Tavalistes anthropic-ühilduvates vaheserverites hoitakse vestluse keskel olevaid süsteemisõnumeid sõnumiloendis, selle asemel et teisaldada need ülataseme süsteemiviipa, nii et vahemällu salvestatud viiba prefiks püsib voorude lõikes muutumatuna. Kehtib ainult siis, kui klient ise saatis beetalipu mid-conversation-system-2026-04-07. Vaikimisi välja lülitatud: iga süsteemisõnum teisaldatakse süsteemiviipa täpselt nagu varem, sest ilma selle beetafunktsioonita vaheserverid lükkavad sõnumiloendis olevad süsteemisõnumid tagasi.", "featureFlagModelsDevEffortTiersEnabledDescription": "Kasuta models.dev-i avaldatud arutluspingutuse tasemeid, kui neid pole määratud ei operaatori alistusega ega teenusepakkujate registris, säilitades ainult teadaolevad pingutustasemed, ning käsitle tühja deklareeritud tasemete loendit kui „tasemed puuduvad“. Vaikimisi välja lülitatud: mudelikataloog ja tuvastatud pingutustasemed jäävad täpselt endiseks.", "featureFlagAutoRequestResolvedCapabilitiesDescription": "Võimaldab auto/* päringutel koostada kandidaatide kogumi lahendatud mudelivõimekuste (nägemine, arutlemine, mõtlemine) ja täheldatud kvaliteediskooridega, mis loetakse ühe hulgilugemisega. Vaikimisi väljas: kandidaatide kaalud päringu ajal jäävad täpselt samaks nagu varem. Sees: need signaalid lähevad hetktõmmise kaaludesse, nii et kandidaatide kaalud ja valik võivad muutuda.", "featureFlagCredentialHealthRetestAutoDisabledDescription": "Luba mandaatide seisundi kontrollil uuesti testida ühendusi, mille lüüs ise välja lülitas (iga ühendust kõige rohkem kord 6 tunni jooksul, 5 ühendust kontrolli kohta), ja lülitada ühendus uuesti sisse, kui kordustest õnnestub. Vaikimisi väljas: sellised ühendused jäävad väljalülitatuks, kuni operaator sekkub. Operaatori poolt keelatud ja jäädavalt blokeeritud ühendusi ei testita kunagi uuesti.", diff --git a/src/i18n/messages/fa.json b/src/i18n/messages/fa.json index 7106c4bf36a6..72ad967f414a 100644 --- a/src/i18n/messages/fa.json +++ b/src/i18n/messages/fa.json @@ -1028,7 +1028,6 @@ "featureFlagOpencodeV2ClientFingerprintEnabledDescription": "اثر انگشت کلاینت CLI نسخه ۲ OpenCode را در درخواستهای OpenCode Zen/Go ارسال کنید: User-Agent با مقدار opencode/latest/<2.x>/cli و x-opencode-client با مقدار cli، و در درخواستهای سطح رایگان، هدرهای نشست نسخه ۲، طرحوارههای ابزار read/shell در CLI نسخه ۲ و بدون tool_choice \"none\". بهطور پیشفرض غیرفعال است: هویت، هدرها و بدنه فعلی تمام درخواستهای OpenCode بدون تغییر باقی میمانند.", "featureFlagComboEmptyPoolNoEligibleTargetsEnabledDescription": "وقتی مجموعه نامزدهای یک ترکیب خودکار، پیش از اجرای پیشفیلتر فراخوانی ابزار، از قبل خالی باشد (همه مقصدها بهدلیل دورههای انتظار، قفلشدگی یا اتمام ظرفیت کنار گذاشته شده باشند)، بهجای خطای قدیمی 400 با پیام \"No target supports tool calling\" و کد capability_mismatch، پاسخ 503 با کد no_eligible_targets را برگردانید. این گزینه بهطور پیشفرض غیرفعال است: مجموعه خالی همچنان خطای قدیمی 400 را برمیگرداند.", "featureFlagModelEndpointCapabilityGateEnabledDescription": "برچسبهای مورد اعتمادِ نقطه پایانی مدل را اعمال کنید (برچسبهایی که در کاتالوگ ارائهدهنده اعلام شدهاند، از شناسه مدل تخصصی استنباط شدهاند یا توسط اپراتور تنظیم شدهاند): درخواست ارسالشده به نقطهای پایانی که مدل آن را ارائه نمیکند، پیش از ارسال در /v1/chat/completions، /v1/embeddings، /v1/rerank، /v1/images/generations، /v1/videos/generations، /v1/music/generations و /v1/audio/* با HTTP 400 رد شود؛ مدلهایی که نقاط پایانی برچسبخورده آنها فاقد آداپتور OmniRoute هستند، از /v1/models پنهان شوند؛ و آزمون مدل داشبورد برای چنین مدلهایی با 422 پاسخ داده شود. این قابلیت بهطور پیشفرض غیرفعال است: برچسبهای نقطه پایانی صرفاً اطلاعرسان باقی میمانند و درخواستها دقیقاً مانند قبل ارسال میشوند.", - "featureFlagAnthropicCompatibleMidConversationSystemDescription": "در رلههای ساده سازگار با Anthropic، پیامهای سیستمی میانگفتوگو را بهجای انتقال به پرامپت سیستمی سطحبالا، در فهرست پیامها نگه دارید تا پیشوند پرامپت کششده در بین نوبتها ثابت بماند. این رفتار فقط زمانی اعمال میشود که خود کلاینت نسخه بتای mid-conversation-system-2026-04-07 را ارسال کرده باشد. این گزینه بهطور پیشفرض غیرفعال است: درست مانند قبل، همه پیامهای سیستمی به پرامپت سیستمی منتقل میشوند، زیرا رلههایی که از آن نسخه بتا پشتیبانی نمیکنند، پیامهای سیستمی موجود در فهرست پیامها را رد میکنند.", "featureFlagModelsDevEffortTiersEnabledDescription": "وقتی نه بازنویسیِ اپراتور و نه رجیستری ارائهدهنده هیچ سطحی را اعلام نکردهاند، از سطوح میزان استدلالی که models.dev منتشر میکند استفاده کنید، فقط سطوح شناختهشده را نگه دارید و فهرست خالیِ سطوح اعلامشده را بهمعنای «بدون سطح» در نظر بگیرید. این قابلیت بهطور پیشفرض غیرفعال است: کاتالوگ مدل و سطوح میزان استدلال نهایی دقیقاً مانند قبل باقی میمانند.", "featureFlagAutoRequestResolvedCapabilitiesDescription": "به درخواست‌های auto/* اجازه می‌دهد مجموعه نامزدها را با قابلیت‌های حل‌شده مدل (بینایی، استدلال، تفکر) و امتیازهای کیفیت مشاهده‌شده، که در یک خوانش انبوه بارگذاری می‌شوند، بسازند. به‌طور پیش‌فرض خاموش: وزن‌های نامزدها در زمان درخواست دقیقاً مانند قبل باقی می‌مانند. روشن: این سیگنال‌ها وارد وزن‌های اسنپ‌شات می‌شوند، بنابراین وزن نامزدها و انتخاب ممکن است تغییر کند.", "featureFlagCredentialHealthRetestAutoDisabledDescription": "اجازه دهید پایش سلامت اطلاعات احراز هویت، اتصالهایی را که دروازه بهطور خودکار خاموش کرده است دوباره آزمایش کند (هرکدام حداکثر یکبار در هر ۶ ساعت، ۵ مورد در هر پایش) و در صورت موفقیتآمیز بودن آزمایش مجدد، آنها را دوباره روشن کند. این گزینه بهطور پیشفرض خاموش است: چنین اتصالهایی تا زمانی که اپراتور اقدامی نکند، خاموش میمانند. اتصالهای غیرفعالشده توسط اپراتور و اتصالهای مسدودشده دائمی هرگز دوباره آزمایش نمیشوند.", diff --git a/src/i18n/messages/fi.json b/src/i18n/messages/fi.json index c298bbf5045e..ec1317d907e5 100644 --- a/src/i18n/messages/fi.json +++ b/src/i18n/messages/fi.json @@ -1028,7 +1028,6 @@ "featureFlagOpencodeV2ClientFingerprintEnabledDescription": "Lähetä OpenCode v2 CLI -asiakasohjelman sormenjälki OpenCode Zen/Go -pyynnöissä: opencode/latest/<2.x>/cli User-Agent ja x-opencode-client cli sekä ilmaistason pyynnöissä v2-istunto-otsakkeet, v2 CLI:n read/shell-työkaluskeemat eikä tool_choice \"none\". Oletuksena pois käytöstä: jokaisen OpenCode-pyynnön nykyinen identiteetti, otsakkeet ja runko säilyvät muuttumattomina.", "featureFlagComboEmptyPoolNoEligibleTargetsEnabledDescription": "Kun automaattisen yhdistelmän ehdokasjoukko on tyhjä jo ennen työkalukutsujen esisuodatusta (jokainen kohde ohitetaan jäähyaikojen, lukitusten tai ehtymisen vuoksi), vastaa virheellä 503 no_eligible_targets vanhan 400-virheen ”Mikään kohde ei tue työkalukutsuja” capability_mismatch sijaan. Oletusarvoisesti pois käytöstä: tyhjä joukko säilyttää vanhan 400-virheen.", "featureFlagModelEndpointCapabilityGateEnabledDescription": "Pakota luotetut mallipäätepistetunnisteet (jotka palveluntarjoajaluettelo on ilmoittanut, jotka on päätelty erikoismallin tunnuksesta tai jotka operaattori on määrittänyt): hylkää pyyntö HTTP 400 -vastauksella ennen välitystä, jos se lähetetään päätepisteeseen, jota malli ei palvele, reiteillä /v1/chat/completions, /v1/embeddings, /v1/rerank, /v1/images/generations, /v1/videos/generations, /v1/music/generations ja /v1/audio/*; piilota reitiltä /v1/models mallit, joiden tunnisteilla merkityille päätepisteille ei ole OmniRoute-sovitinta; ja vastaa koontinäytön mallitestiin tilakoodilla 422 tällaisten mallien kohdalla. Oletusarvoisesti pois käytöstä: päätepistetunnisteet säilyvät vain tiedoksi, ja pyynnöt välitetään täsmälleen kuten ennenkin.", - "featureFlagAnthropicCompatibleMidConversationSystemDescription": "Tavallisissa anthropic-yhteensopivissa välityspalveluissa keskustelun keskellä olevat järjestelmävuorot pidetään viestiluettelossa sen sijaan, että ne siirrettäisiin ylimmän tason järjestelmäkehotteeseen, jotta välimuistiin tallennetun kehotteen etuliite pysyy vakaana vuorojen välillä. Tätä sovelletaan vain, kun asiakas itse lähetti mid-conversation-system-2026-04-07-betaominaisuuden. Oletusarvoisesti pois käytöstä: jokainen järjestelmävuoro siirretään järjestelmäkehotteeseen täsmälleen kuten ennenkin, koska ilman kyseistä betaominaisuutta toimivat välityspalvelut hylkäävät viestiluettelossa olevat järjestelmävuorot.", "featureFlagModelsDevEffortTiersEnabledDescription": "Käytä models.dev-palvelun julkaisemia päättelypanoksen tasoja, kun operaattorin ohitus tai palveluntarjoajarekisteri ei määritä niitä. Säilytä vain tunnetut panostustasot ja käsittele tyhjää määritettyjen tasojen luetteloa niin, ettei tasoja ole. Oletusarvoisesti pois käytöstä: malliluettelo ja ratkaistut panostustasot säilyvät täsmälleen ennallaan.", "featureFlagAutoRequestResolvedCapabilitiesDescription": "Antaa auto/*-pyyntöjen koota ehdokasjoukon ratkaistuilla mallin ominaisuuksilla (näkö, päättely, ajattelu) ja havaituilla laatupisteillä, jotka luetaan yhdellä kertalukemisella. Oletuksena pois: ehdokkaiden painot pyyntöhetkellä pysyvät täsmälleen ennallaan. Päällä: nämä signaalit vaikuttavat tilannekuvan painoihin, joten ehdokkaiden painot ja valinta voivat muuttua.", "featureFlagCredentialHealthRetestAutoDisabledDescription": "Salli tunnistetietojen kuntotarkistuksen testata uudelleen yhteydet, jotka yhdyskäytävä on poistanut käytöstä automaattisesti (kukin enintään kerran 6 tunnissa, 5 tarkistusta kohden), ja ottaa yhteys takaisin käyttöön, kun uudelleentestaus onnistuu. Oletusarvoisesti pois käytöstä: tällaiset yhteydet pysyvät pois käytöstä, kunnes operaattori toimii. Operaattorin käytöstä poistamia ja pysyvästi estettyjä yhteyksiä ei koskaan testata uudelleen.", diff --git a/src/i18n/messages/fr.json b/src/i18n/messages/fr.json index d984f084fda4..57a35290bbb2 100644 --- a/src/i18n/messages/fr.json +++ b/src/i18n/messages/fr.json @@ -1028,7 +1028,6 @@ "featureFlagOpencodeV2ClientFingerprintEnabledDescription": "Envoyer l’empreinte du client CLI OpenCode v2 dans les requêtes OpenCode Zen/Go : le User-Agent opencode/latest/<2.x>/cli et x-opencode-client cli, ainsi que, dans les requêtes de l’offre gratuite, les en-têtes de session v2, les schémas des outils read/shell du CLI v2, et aucun tool_choice \"none\". Désactivé par défaut : chaque requête OpenCode conserve l’identité, les en-têtes et le corps actuels sans modification.", "featureFlagComboEmptyPoolNoEligibleTargetsEnabledDescription": "Lorsque le pool de candidats d’un combo automatique est déjà vide avant l’exécution du préfiltre d’appel d’outil (toutes les cibles ayant été ignorées en raison de délais de récupération, de verrouillages ou de leur épuisement), renvoyer l’erreur 503 no_eligible_targets au lieu de l’ancienne erreur 400 « Aucune cible ne prend en charge l’appel d’outil » capability_mismatch. Désactivé par défaut : lorsque le pool est vide, l’ancienne erreur 400 est conservée.", "featureFlagModelEndpointCapabilityGateEnabledDescription": "Appliquer les étiquettes d’endpoint de modèle approuvées (déclarées par le catalogue du fournisseur, déduites d’un identifiant de modèle spécialisé ou définies par l’opérateur) : rejeter avec le code HTTP 400, avant acheminement, toute requête envoyée à un endpoint non pris en charge par le modèle sur /v1/chat/completions, /v1/embeddings, /v1/rerank, /v1/images/generations, /v1/videos/generations, /v1/music/generations et /v1/audio/* ; masquer dans /v1/models les modèles dont les endpoints étiquetés ne disposent d’aucun adaptateur OmniRoute ; et répondre avec le code 422 au test de modèle du tableau de bord pour ces modèles. Désactivé par défaut : les étiquettes d’endpoint restent informatives et les requêtes sont acheminées exactement comme auparavant.", - "featureFlagAnthropicCompatibleMidConversationSystemDescription": "Sur les relais simplement compatibles avec Anthropic, conservez les tours système en milieu de conversation dans la liste des messages au lieu de les déplacer vers l’invite système de premier niveau, afin que le préfixe d’invite mis en cache reste stable d’un tour à l’autre. Cela s’applique uniquement lorsque le client lui-même a envoyé la version bêta mid-conversation-system-2026-04-07. Désactivé par défaut : chaque tour système est déplacé vers l’invite système exactement comme auparavant, car les relais ne prenant pas en charge cette version bêta rejettent les tours système présents dans la liste des messages.", "featureFlagModelsDevEffortTiersEnabledDescription": "Utiliser les niveaux d’effort de raisonnement publiés par models.dev lorsque ni une dérogation de l’opérateur ni le registre des fournisseurs n’en déclare, en ne conservant que les niveaux d’effort connus et en traitant une liste de niveaux déclarée vide comme « aucun niveau ». Désactivé par défaut : le catalogue de modèles et les niveaux d’effort résolus restent exactement comme avant.", "featureFlagAutoRequestResolvedCapabilitiesDescription": "Permet aux requêtes auto/* de constituer le pool de candidats avec les capacités de modèle résolues (vision, raisonnement, réflexion) et les scores de qualité observés, lus en une seule lecture groupée. Désactivé par défaut : les poids des candidats au moment de la requête restent exactement comme avant. Activé : ces signaux alimentent les poids de l'instantané, de sorte que les poids des candidats et la sélection peuvent changer.", "featureFlagCredentialHealthRetestAutoDisabledDescription": "Autoriser l’analyse de l’état des identifiants à tester de nouveau les connexions que la passerelle a désactivées d’elle-même (au maximum une fois toutes les 6 heures pour chacune, 5 par analyse) et à en réactiver une lorsque son nouveau test réussit. Désactivé par défaut : ces connexions restent désactivées jusqu’à l’intervention d’un opérateur. Les connexions désactivées par un opérateur et celles bannies définitivement ne sont jamais testées de nouveau.", diff --git a/src/i18n/messages/ga.json b/src/i18n/messages/ga.json index c1369438f4f7..627dd5a3eb19 100644 --- a/src/i18n/messages/ga.json +++ b/src/i18n/messages/ga.json @@ -1028,7 +1028,6 @@ "featureFlagOpencodeV2ClientFingerprintEnabledDescription": "Seol méarlorg cliant CLI OpenCode v2 ar iarratais OpenCode Zen/Go: an User-Agent opencode/latest/<2.x>/cli agus x-opencode-client cli, agus ar iarratais sraithe saor in aisce, ceanntásca seisiúin v2, scéimeanna uirlisí read/shell CLI v2 agus gan tool_choice \"none\". Díchumasaithe de réir réamhshocraithe: coimeádann gach iarratas OpenCode an t-aitheantas, na ceanntásca agus an corp reatha gan athrú.", "featureFlagComboEmptyPoolNoEligibleTargetsEnabledDescription": "Nuair atá linn iarrthóirí teaglaim uathoibríoch folamh cheana féin sula ritear réamhscagaire ghlaoch na huirlise (gach sprioc scipeáilte mar gheall ar thréimhsí athshocraithe, frithdhúntaí nó ídiú), tabhair freagra 503 no_eligible_targets in ionad an fhreagra oidhreachta 400 \"Ní thacaíonn aon sprioc le glaoch uirlise\" capability_mismatch. Díchumasaithe de réir réamhshocraithe: coinníonn an linn fholamh an freagra oidhreachta 400.", "featureFlagModelEndpointCapabilityGateEnabledDescription": "Cuir clibeanna iontaofa chríochphointí na samhla i bhfeidhm (arna ndearbhú i gcatalóg an tsoláthraí, arna dtátal ó aitheantas sainmhodhla, nó arna socrú ag an oibreoir): diúltaigh, le HTTP 400 roimh sheoladh, d’iarratas a seoladh chuig críochphointe nach bhfreastalaíonn an tsamhail air ar /v1/chat/completions, /v1/embeddings, /v1/rerank, /v1/images/generations, /v1/videos/generations, /v1/music/generations agus /v1/audio/*; folaigh ó /v1/models samhlacha nach bhfuil aon chuibheoir OmniRoute ann dá gcríochphointí clibeáilte; agus freagair tástáil samhla an deais le 422 i gcás samhlacha den sórt sin. Díchumasaithe de réir réamhshocraithe: fanann clibeanna críochphointí mar fhaisnéis amháin agus seoltar iarratais díreach mar a seoladh roimhe seo iad.", - "featureFlagAnthropicCompatibleMidConversationSystemDescription": "Ar athsheachadáin shimplí atá comhoiriúnach le Anthropic, coinnigh sealanna córais i lár an chomhrá laistigh den liosta teachtaireachtaí seachas iad a bhogadh isteach sa leid chórais ardleibhéil, ionas go bhfanfaidh réimír na leide taiscthe cobhsaí ó sheal go seal. Ní bhaineann sé seo ach le cásanna inar sheol an cliant féin an béite mid-conversation-system-2026-04-07. Díchumasaithe de réir réamhshocraithe: bogtar gach seal córais isteach sa leid chórais díreach mar a rinneadh roimhe seo, toisc go ndiúltaíonn athsheachadáin nach bhfuil an béite sin acu do shealanna córais sa liosta teachtaireachtaí.", "featureFlagModelsDevEffortTiersEnabledDescription": "Úsáid na sraitheanna iarrachta réasúnaíochta a fhoilsíonn models.dev nuair nach ndearbhaíonn sárú oibreora ná clárlann an tsoláthraí aon cheann, agus ná coinnigh ach leibhéil iarrachta aitheanta; caith le liosta folamh de shraitheanna dearbhaithe mar \"gan sraitheanna\". Díchumasaithe de réir réamhshocraithe: fanann catalóg na samhlacha agus na sraitheanna iarrachta réitithe díreach mar a bhí roimhe seo.", "featureFlagAutoRequestResolvedCapabilitiesDescription": "Ligeann sé d'iarratais auto/* an linn iarrthóirí a thógáil le cumais réitithe múnla (fís, réasúnaíocht, smaointeoireacht) agus scóir cháilíochta breathnaithe, léite in aon léamh mórchóir amháin. As de réir réamhshocraithe: fanann meáchain na n-iarrthóirí ag am an iarratais díreach mar a bhí. Ar siúl: cuireann na comharthaí sin le meáchain an ghrianghraif, mar sin is féidir le meáchain na n-iarrthóirí agus an rogha athrú.", "featureFlagCredentialHealthRetestAutoDisabledDescription": "Lig do scuabadh sláinte na ndintiúr naisc a mhúch an geata leis féin a atástáil (uair amháin ar a mhéad gach 6 huaire an chloig do gach ceann acu, 5 cinn in aghaidh an scuabtha) agus ceann acu a chur ar siúl arís nuair a éiríonn leis an atástáil. Díchumasaithe de réir réamhshocraithe: fanann naisc den sórt sin múchta go dtí go ndéanann oibreoir beart. Ní dhéantar naisc atá díchumasaithe ag oibreoir ná naisc atá coiscthe go buan a atástáil riamh.", diff --git a/src/i18n/messages/gu.json b/src/i18n/messages/gu.json index d6a8599ee3eb..c9755760d82a 100644 --- a/src/i18n/messages/gu.json +++ b/src/i18n/messages/gu.json @@ -1028,7 +1028,6 @@ "featureFlagOpencodeV2ClientFingerprintEnabledDescription": "OpenCode Zen/Go વિનંતીઓ પર OpenCode v2 CLI ક્લાયન્ટ ફિંગરપ્રિન્ટ મોકલો: opencode/latest/<2.x>/cli User-Agent અને x-opencode-client cli, તેમજ મફત-સ્તરની વિનંતીઓ પર v2 સત્ર હેડર્સ, v2 CLI read/shell ટૂલ સ્કીમા અને tool_choice \"none\" નહીં. ડિફૉલ્ટ રૂપે બંધ: દરેક OpenCode વિનંતી વર્તમાન ઓળખ, હેડર્સ અને બૉડીને યથાવત્ રાખે છે.", "featureFlagComboEmptyPoolNoEligibleTargetsEnabledDescription": "જ્યારે ટૂલ-કૉલિંગ પ્રી-ફિલ્ટર ચાલે તે પહેલાં જ ઑટો કોમ્બોનો ઉમેદવાર પૂલ ખાલી હોય (કૂલડાઉન, લૉકઆઉટ અથવા ક્ષમતા ખૂટી જવાને કારણે દરેક ટાર્ગેટ છોડવામાં આવ્યો હોય), ત્યારે જૂના 400 \"કોઈ ટાર્ગેટ ટૂલ કૉલિંગને સપોર્ટ કરતું નથી\" capability_mismatch ને બદલે 503 no_eligible_targets જવાબ આપો. ડિફૉલ્ટ રૂપે બંધ: ખાલી પૂલ માટે જૂનો 400 જ રાખવામાં આવે છે.", "featureFlagModelEndpointCapabilityGateEnabledDescription": "વિશ્વસનીય મોડેલ એન્ડપોઇન્ટ ટૅગ્સ (પ્રદાતા કેટલોગ દ્વારા જાહેર કરાયેલા, વિશિષ્ટ મોડેલ id પરથી અનુમાનિત, અથવા ઑપરેટર દ્વારા સેટ કરાયેલા) લાગુ કરો: મોડેલ જે એન્ડપોઇન્ટ પર સેવા આપતું નથી ત્યાં મોકલાયેલી વિનંતીને /v1/chat/completions, /v1/embeddings, /v1/rerank, /v1/images/generations, /v1/videos/generations, /v1/music/generations અને /v1/audio/* પર ડિસ્પૅચ પહેલાં HTTP 400 સાથે નકારો; જેનાં ટૅગ કરેલા એન્ડપોઇન્ટ માટે કોઈ OmniRoute ઍડૅપ્ટર નથી તેવા મોડેલોને /v1/models માંથી છુપાવો; અને આવા મોડેલો માટે ડૅશબોર્ડ મોડેલ પરીક્ષણનો જવાબ 422 સાથે આપો. ડિફૉલ્ટ રૂપે બંધ: એન્ડપોઇન્ટ ટૅગ્સ માત્ર માહિતીપ્રદ રહે છે અને વિનંતીઓ પહેલાંની જેમ જ ડિસ્પૅચ થાય છે.", - "featureFlagAnthropicCompatibleMidConversationSystemDescription": "સામાન્ય anthropic-સુસંગત રિલે પર, વાતચીતની વચ્ચે આવતા system turns ને ટોચ-સ્તરના system prompt માં ખસેડવાને બદલે message list માં જ રાખો, જેથી કૅશ કરાયેલ prompt prefix દરેક turn દરમિયાન સ્થિર રહે. આ ફક્ત ત્યારે લાગુ પડે છે જ્યારે ક્લાયન્ટે પોતે mid-conversation-system-2026-04-07 beta મોકલ્યું હોય. ડિફૉલ્ટ રૂપે બંધ: પહેલાની જેમ દરેક system turn ને ચોક્કસપણે system prompt માં ખસેડવામાં આવે છે, કારણ કે તે beta વિનાના રિલે message list માં system turns ને નકારે છે.", "featureFlagModelsDevEffortTiersEnabledDescription": "જ્યારે ન તો ઑપરેટર ઓવરરાઇડ અને ન તો પ્રદાતા રજિસ્ટ્રી કોઈ રીઝનિંગ એફર્ટ ટિયર જાહેર કરે, ત્યારે models.dev દ્વારા પ્રકાશિત ટિયરનો ઉપયોગ કરો, માત્ર જાણીતા એફર્ટ લેવલ જ રાખો અને જાહેર કરેલી ખાલી ટિયર સૂચિને \"કોઈ ટિયર નથી\" તરીકે ગણો. ડિફૉલ્ટ રૂપે બંધ: મોડેલ કેટલોગ અને નિર્ધારિત એફર્ટ ટિયર પહેલાં જેવા હતા બરાબર તેવા જ રહે છે.", "featureFlagAutoRequestResolvedCapabilitiesDescription": "auto/* વિનંતીઓને ઉકેલાયેલી મોડેલ ક્ષમતાઓ (વિઝન, રીઝનિંગ, થિંકિંગ) અને અવલોકિત ગુણવત્તા સ્કોર સાથે એક જ બલ્ક વાંચનમાં ઉમેદવાર પૂલ બનાવવા દે છે. ડિફોલ્ટ રીતે બંધ: વિનંતી સમયના ઉમેદવાર વજન બરાબર પહેલાં જેવા જ રહે છે. ચાલુ હોય ત્યારે: આ સંકેતો સ્નેપશોટ વજનમાં ઉમેરાય છે, તેથી ઉમેદવાર વજન અને પસંદગી બદલાઈ શકે છે.", "featureFlagCredentialHealthRetestAutoDisabledDescription": "ક્રેડેન્શિયલ હેલ્થ સ્વીપને ગેટવેએ પોતાની મેળે બંધ કરેલાં કનેક્શનોનું ફરી પરીક્ષણ કરવા દો (દરેક કનેક્શનનું વધુમાં વધુ દર 6 કલાકે એક વાર, પ્રત્યેક સ્વીપમાં 5) અને ફરી પરીક્ષણ સફળ થાય ત્યારે તેને ફરી ચાલુ કરવા દો. ડિફૉલ્ટ રૂપે બંધ: ઑપરેટર કાર્યવાહી ન કરે ત્યાં સુધી આવાં કનેક્શનો બંધ જ રહે છે. ઑપરેટરે બંધ કરેલાં અને કાયમી રીતે પ્રતિબંધિત કનેક્શનોનું ફરી પરીક્ષણ ક્યારેય કરવામાં આવતું નથી.", diff --git a/src/i18n/messages/ha.json b/src/i18n/messages/ha.json index 1af133a86a59..c5ad69b7a9e6 100644 --- a/src/i18n/messages/ha.json +++ b/src/i18n/messages/ha.json @@ -1028,7 +1028,6 @@ "featureFlagOpencodeV2ClientFingerprintEnabledDescription": "Aika sawun yatsan abokin cinikin OpenCode v2 CLI a kan buƙatun OpenCode Zen/Go: wato opencode/latest/<2.x>/cli User-Agent da x-opencode-client cli, sannan a kan buƙatun matakin kyauta a aika taken zaman v2, tsarin kayan aikin karantawa/shell na v2 CLI, ba tare da tool_choice \"none\" ba. A kashe yake ta tsohuwa: kowace buƙatar OpenCode tana riƙe asali, take da jiki na yanzu ba tare da canji ba.", "featureFlagComboEmptyPoolNoEligibleTargetsEnabledDescription": "Idan tarin ’yan takarar combo na atomatik ya riga ya zama fanko kafin matatar farko ta kiran kayan aiki ta fara aiki (an tsallake kowace manufa saboda lokutan jira, kullewa ko ƙarewa), a mayar da 503 no_eligible_targets maimakon tsohon 400 \"Babu manufa da ke goyon bayan kiran kayan aiki\" capability_mismatch. A kashe yake ta tsohuwa: tarin da yake fanko zai ci gaba da mayar da tsohon 400.", "featureFlagModelEndpointCapabilityGateEnabledDescription": "Tilasta amintattun alamun wuraren ƙarshen samfurin (waɗanda kundin mai samarwa ya ayyana, aka gano daga id na samfurin ƙwarewa, ko mai gudanarwa ya saita): ƙi buƙatar da aka aika zuwa wurin ƙarshe da samfurin ba ya tallafawa da HTTP 400 kafin aikawa a /v1/chat/completions, /v1/embeddings, /v1/rerank, /v1/images/generations, /v1/videos/generations, /v1/music/generations da /v1/audio/*; ɓoye samfuran da wuraren ƙarshensu masu alama ba su da adaftan OmniRoute daga /v1/models; sannan a amsa gwajin samfurin dashboard da 422 ga irin waɗannan samfuran. A kashe yake ta tsohuwa: alamun wuraren ƙarshen za su ci gaba da zama na bayani kawai, kuma za a aika buƙatu daidai kamar yadda ake yi a baya.", - "featureFlagAnthropicCompatibleMidConversationSystemDescription": "A kan relay masu dacewa da anthropic na yau da kullum, a bar saƙonnin system da ke tsakiyar tattaunawa a cikin jerin saƙonni maimakon matsar da su zuwa system prompt na babban mataki, domin prefix ɗin prompt da aka adana a cache ya ci gaba da kasancewa daidai a duk juyin tattaunawa. Wannan yana aiki ne kawai idan client ɗin da kansa ya aika beta na mid-conversation-system-2026-04-07. A kashe yake ta tsohuwa: ana matsar da kowane saƙon system zuwa system prompt kamar yadda ake yi a baya, saboda relay waɗanda ba su da wannan beta suna ƙin karɓar saƙonnin system a cikin jerin saƙonni.", "featureFlagModelsDevEffortTiersEnabledDescription": "Yi amfani da matakan ƙoƙarin tunani da models.dev ke wallafawa idan babu saitin maye gurbi na mai gudanarwa ko na rajistar mai bayarwa da ya ayyana wasu, tare da riƙe sanannun matakan ƙoƙari kawai, sannan a ɗauki jerin matakan da aka ayyana amma babu komai a ciki a matsayin \"babu matakai\". A kashe yake ta tsohuwa: kundin samfura da matakan ƙoƙarin da aka tantance za su kasance daidai yadda suke a baya.", "featureFlagAutoRequestResolvedCapabilitiesDescription": "Yana bari buƙatun auto/* su gina rukunin ’yan takara da ƙwarewar samfurin da aka warware (gani, tunani mai zurfi, tunani) da makin inganci da aka lura, waɗanda aka karanta a karatu ɗaya na gaba ɗaya. A kashe ta tsohuwa: nauyin ’yan takara a lokacin buƙata yana nan daidai kamar da. A kunne: waɗannan alamomi suna shiga nauyin hoton lokaci, don haka nauyin ’yan takara da zaɓi na iya canzawa.", "featureFlagCredentialHealthRetestAutoDisabledDescription": "Bari binciken lafiyar bayanan shaidarka ya sake gwada haɗin da gateway ya kashe da kansa (aƙalla sau ɗaya kawai a cikin kowane awa 6 ga kowane haɗi, haɗi 5 a kowane bincike), sannan ya sake kunna ɗaya idan ya ci gwajin. Wannan a kashe yake ta tsohuwa: irin waɗannan haɗin za su ci gaba da kasancewa a kashe har sai ma'aikaci ya ɗauki mataki. Ba a taɓa sake gwada haɗin da ma'aikaci ya kashe ko aka haramta na dindindin ba.", diff --git a/src/i18n/messages/he.json b/src/i18n/messages/he.json index 0d354a1a5fef..980a47cbe560 100644 --- a/src/i18n/messages/he.json +++ b/src/i18n/messages/he.json @@ -1028,7 +1028,6 @@ "featureFlagOpencodeV2ClientFingerprintEnabledDescription": "שליחת טביעת האצבע של לקוח ה-CLI של OpenCode v2 בבקשות OpenCode Zen/Go: ה-User-Agent ‏opencode/latest/<2.x>/cli והכותרת x-opencode-client cli, ובבקשות במסלול החינמי — כותרות ההפעלה של v2, סכמות כלי הקריאה/מעטפת של ה-CLI ב-v2 וללא tool_choice \"none\". האפשרות כבויה כברירת מחדל: כל בקשת OpenCode שומרת על הזהות, הכותרות וגוף הבקשה הנוכחיים ללא שינוי.", "featureFlagComboEmptyPoolNoEligibleTargetsEnabledDescription": "כאשר מאגר המועמדים של קומבו אוטומטי כבר ריק לפני הפעלת הסינון המקדים של קריאות לכלים (כל היעדים דולגו עקב תקופות צינון, נעילות או מיצוי), יש להחזיר 503 no_eligible_targets במקום שגיאת 400 הישנה \"אין יעד שתומך בקריאות לכלים\" מסוג capability_mismatch. האפשרות מושבתת כברירת מחדל: כאשר המאגר ריק, ממשיכה להיות מוחזרת שגיאת 400 הישנה.", "featureFlagModelEndpointCapabilityGateEnabledDescription": "אכיפת תגיות מהימנות של נקודות קצה למודלים (שהוצהרו בקטלוג הספק, הוסקו ממזהה של מודל ייעודי או הוגדרו על ידי המפעיל): דחיית בקשה שנשלחה לנקודת קצה שהמודל אינו מספק, עם HTTP 400 לפני הניתוב ב־/v1/chat/completions, /v1/embeddings, /v1/rerank, /v1/images/generations, /v1/videos/generations, /v1/music/generations ו־/v1/audio/*; הסתרת מודלים שלנקודות הקצה המתויגות שלהם אין מתאם OmniRoute מ־/v1/models; ומענה לבדיקת המודל בלוח הבקרה עם 422 עבור מודלים כאלה. מושבת כברירת מחדל: תגיות נקודות הקצה נשארות למטרות מידע, והבקשות מנותבות בדיוק כפי שנותבו קודם לכן.", - "featureFlagAnthropicCompatibleMidConversationSystemDescription": "בממסרים רגילים התואמים ל-anthropic, יש להשאיר תורי מערכת באמצע השיחה בתוך רשימת ההודעות במקום להעביר אותם להנחיית המערכת ברמה העליונה, כך שקידומת ההנחיה שבמטמון תישאר יציבה בין התורים. חל רק כאשר הלקוח עצמו שלח את גרסת הבטא mid-conversation-system-2026-04-07. מושבת כברירת מחדל: כל תור מערכת מועבר להנחיית המערכת בדיוק כמו קודם, משום שממסרים ללא גרסת בטא זו דוחים תורי מערכת ברשימת ההודעות.", "featureFlagModelsDevEffortTiersEnabledDescription": "השתמש ברמות מאמץ ההסקה שמפרסם models.dev כאשר לא הוגדרת רמה כלשהי בעקיפת מפעיל או במרשם הספק, תוך שמירה על רמות מאמץ מוכרות בלבד, והתייחס לרשימת רמות מוצהרת ריקה כאל \"ללא רמות\". האפשרות מושבתת כברירת מחדל: קטלוג המודלים ורמות המאמץ שנקבעו נשארים בדיוק כפי שהיו.", "featureFlagAutoRequestResolvedCapabilitiesDescription": "מאפשר לבקשות auto/* לבנות את מאגר המועמדים עם יכולות מודל פתורות (ראייה, הסקה, חשיבה) וציוני איכות שנצפו, הנקראים בקריאה מרוכזת אחת. כבוי כברירת מחדל: משקלי המועמדים בזמן הבקשה נשארים בדיוק כמו קודם. מופעל: אותות אלה מוזנים למשקלי תמונת המצב, ולכן משקלי המועמדים והבחירה עשויים להשתנות.", "featureFlagCredentialHealthRetestAutoDisabledDescription": "אפשרו לסריקת תקינות פרטי הכניסה לבדוק מחדש חיבורים שהשער השבית בעצמו (לכל היותר פעם אחת בכל 6 שעות לכל חיבור, 5 בכל סריקה) ולהפעיל מחדש חיבור כאשר הבדיקה החוזרת שלו מצליחה. מושבת כברירת מחדל: חיבורים כאלה נשארים מושבתים עד להתערבות מפעיל. חיבורים שהושבתו על ידי מפעיל וחיבורים שנחסמו לצמיתות לעולם אינם נבדקים מחדש.", diff --git a/src/i18n/messages/hi.json b/src/i18n/messages/hi.json index c6ab58038a75..666c254f02cb 100644 --- a/src/i18n/messages/hi.json +++ b/src/i18n/messages/hi.json @@ -1028,7 +1028,6 @@ "featureFlagOpencodeV2ClientFingerprintEnabledDescription": "OpenCode Zen/Go अनुरोधों पर OpenCode v2 CLI क्लाइंट फ़िंगरप्रिंट भेजें: opencode/latest/<2.x>/cli User-Agent और x-opencode-client cli, तथा फ़्री-टियर अनुरोधों पर v2 सेशन हेडर, v2 CLI read/shell टूल स्कीमा और कोई tool_choice \"none\" नहीं। डिफ़ॉल्ट रूप से बंद: प्रत्येक OpenCode अनुरोध की मौजूदा पहचान, हेडर और बॉडी अपरिवर्तित रहती है।", "featureFlagComboEmptyPoolNoEligibleTargetsEnabledDescription": "जब टूल-कॉलिंग प्री-फ़िल्टर चलने से पहले ही किसी ऑटो कॉम्बो का कैंडिडेट पूल खाली हो (कूलडाउन, लॉकआउट या समाप्ति के कारण प्रत्येक टार्गेट छोड़ दिया गया हो), तो पुराने 400 \"कोई भी टार्गेट टूल कॉलिंग का समर्थन नहीं करता\" capability_mismatch के बजाय 503 no_eligible_targets उत्तर दें। डिफ़ॉल्ट रूप से बंद: खाली पूल पुराने 400 को बनाए रखता है।", "featureFlagModelEndpointCapabilityGateEnabledDescription": "विश्वसनीय मॉडल एंडपॉइंट टैग लागू करें (प्रोवाइडर कैटलॉग द्वारा घोषित, किसी विशेष मॉडल id से अनुमानित, या ऑपरेटर द्वारा सेट): /v1/chat/completions, /v1/embeddings, /v1/rerank, /v1/images/generations, /v1/videos/generations, /v1/music/generations और /v1/audio/* पर डिस्पैच से पहले, ऐसे एंडपॉइंट को भेजे गए अनुरोध को HTTP 400 के साथ अस्वीकार करें जिसे मॉडल सर्व नहीं करता; जिन मॉडल के टैग किए गए एंडपॉइंट के लिए कोई OmniRoute अडैप्टर नहीं है, उन्हें /v1/models से छिपाएँ; और ऐसे मॉडल के लिए डैशबोर्ड मॉडल परीक्षण का उत्तर 422 के साथ दें। डिफ़ॉल्ट रूप से बंद: एंडपॉइंट टैग केवल सूचनात्मक रहते हैं और अनुरोध पहले की तरह ही डिस्पैच किए जाते हैं।", - "featureFlagAnthropicCompatibleMidConversationSystemDescription": "सामान्य Anthropic-संगत रिले पर, बातचीत के बीच आने वाले सिस्टम टर्न को शीर्ष-स्तरीय सिस्टम प्रॉम्प्ट में ले जाने के बजाय संदेश सूची में ही रखें, ताकि कैश किया गया प्रॉम्प्ट प्रीफ़िक्स सभी टर्न में स्थिर रहे। यह केवल तभी लागू होता है, जब क्लाइंट ने स्वयं mid-conversation-system-2026-04-07 बीटा भेजा हो। डिफ़ॉल्ट रूप से बंद: प्रत्येक सिस्टम टर्न को पहले की तरह ही सिस्टम प्रॉम्प्ट में ले जाया जाता है, क्योंकि इस बीटा के बिना रिले संदेश सूची में सिस्टम टर्न को अस्वीकार कर देते हैं।", "featureFlagModelsDevEffortTiersEnabledDescription": "जब न तो कोई ऑपरेटर ओवरराइड और न ही प्रदाता रजिस्ट्री किसी स्तर की घोषणा करे, तब models.dev द्वारा प्रकाशित रीजनिंग प्रयास स्तरों का उपयोग करें, केवल ज्ञात प्रयास स्तरों को बनाए रखें, और घोषित स्तरों की खाली सूची को \"कोई स्तर नहीं\" मानें। यह डिफ़ॉल्ट रूप से बंद है: मॉडल कैटलॉग और निर्धारित प्रयास स्तर पहले जैसे ही बने रहते हैं।", "featureFlagAutoRequestResolvedCapabilitiesDescription": "auto/* अनुरोधों को हल की गई मॉडल क्षमताओं (विज़न, रीज़निंग, थिंकिंग) और देखे गए गुणवत्ता स्कोर के साथ, एक ही बल्क रीड में, उम्मीदवार पूल बनाने देता है। डिफ़ॉल्ट रूप से बंद: अनुरोध के समय उम्मीदवारों के वेट बिल्कुल पहले जैसे रहते हैं। चालू होने पर: ये संकेत स्नैपशॉट वेट में शामिल होते हैं, इसलिए उम्मीदवारों के वेट और चयन बदल सकते हैं।", "featureFlagCredentialHealthRetestAutoDisabledDescription": "क्रेडेंशियल हेल्थ स्वीप को उन कनेक्शनों का फिर से परीक्षण करने दें जिन्हें गेटवे ने स्वयं बंद किया था (प्रत्येक का अधिकतम हर 6 घंटे में एक बार, प्रति स्वीप 5) और पुनः परीक्षण सफल होने पर उन्हें फिर से चालू कर दें। डिफ़ॉल्ट रूप से बंद: ऐसे कनेक्शन तब तक बंद रहते हैं जब तक कोई ऑपरेटर कार्रवाई नहीं करता। ऑपरेटर द्वारा अक्षम और स्थायी रूप से प्रतिबंधित कनेक्शनों का कभी भी पुनः परीक्षण नहीं किया जाता।", diff --git a/src/i18n/messages/hr.json b/src/i18n/messages/hr.json index a97326d47984..b9ef9ad6b128 100644 --- a/src/i18n/messages/hr.json +++ b/src/i18n/messages/hr.json @@ -1028,7 +1028,6 @@ "featureFlagOpencodeV2ClientFingerprintEnabledDescription": "Šalji otisak CLI klijenta OpenCode v2 u zahtjevima za OpenCode Zen/Go: User-Agent opencode/latest/<2.x>/cli i x-opencode-client cli, a u zahtjevima besplatne razine zaglavlja sesije v2, sheme alata read/shell za CLI v2 i bez tool_choice \"none\". Prema zadanim postavkama isključeno: svaki zahtjev za OpenCode zadržava trenutačni identitet, zaglavlja i tijelo bez promjena.", "featureFlagComboEmptyPoolNoEligibleTargetsEnabledDescription": "Kada je skup kandidata automatske kombinacije već prazan prije pokretanja predfiltra za pozivanje alata (svaki je cilj preskočen zbog razdoblja čekanja, blokada ili iscrpljenosti), vrati 503 no_eligible_targets umjesto zastarjele pogreške 400 „Nijedan cilj ne podržava pozivanje alata” capability_mismatch. Prema zadanim je postavkama isključeno: prazan skup zadržava zastarjelu pogrešku 400.", "featureFlagModelEndpointCapabilityGateEnabledDescription": "Nametnite pouzdane oznake krajnjih točaka modela (koje je deklarirao katalog pružatelja usluge, koje su izvedene iz ID-a specijaliziranog modela ili koje je postavio operator): odbijte zahtjev poslan krajnjoj točki koju model ne podržava uz HTTP 400 prije prosljeđivanja na /v1/chat/completions, /v1/embeddings, /v1/rerank, /v1/images/generations, /v1/videos/generations, /v1/music/generations i /v1/audio/*; sakrijte modele čije označene krajnje točke nemaju OmniRoute adapter iz /v1/models; te odgovorite na test modela na nadzornoj ploči statusom 422 za takve modele. Prema zadanim postavkama isključeno: oznake krajnjih točaka ostaju informativne, a zahtjevi se prosljeđuju kao i prije.", - "featureFlagAnthropicCompatibleMidConversationSystemDescription": "Na običnim anthropic-compatible relejima zadržite sistemske unose usred razgovora na popisu poruka umjesto da ih premještate u sistemski prompt najviše razine kako bi predmemorirani prefiks prompta ostao stabilan između unosa. Primjenjuje se samo kada je sam klijent poslao beta-verziju mid-conversation-system-2026-04-07. Zadano je isključeno: svaki se sistemski unos premješta u sistemski prompt točno kao i prije jer releji bez te beta-verzije odbijaju sistemske unose na popisu poruka.", "featureFlagModelsDevEffortTiersEnabledDescription": "Upotrijebi razine intenziteta zaključivanja koje objavljuje models.dev kada ih ne navodi ni nadjačavanje operatora ni registar pružatelja usluga, zadržavajući samo poznate razine intenziteta, a prazan popis navedenih razina tretiraj kao „bez razina”. Prema zadanim postavkama isključeno: katalog modela i razriješene razine intenziteta ostaju potpuno isti kao i prije.", "featureFlagAutoRequestResolvedCapabilitiesDescription": "Omogućuje zahtjevima auto/* da izgrade skup kandidata s razriješenim mogućnostima modela (vid, rasuđivanje, razmišljanje) i uočenim ocjenama kvalitete, učitanima u jednom skupnom čitanju. Isključeno prema zadanim postavkama: težine kandidata u trenutku zahtjeva ostaju točno kao prije. Uključeno: ti signali ulaze u težine snimke, pa se težine kandidata i odabir mogu promijeniti.", "featureFlagCredentialHealthRetestAutoDisabledDescription": "Dopustite provjeri valjanosti vjerodajnica da ponovno testira veze koje je pristupnik sam isključio (svaku najviše jednom svakih 6 sati, 5 po provjeri) te ponovno uključi vezu kada uspješno prođe ponovno testiranje. Prema zadanim postavkama isključeno: takve veze ostaju isključene dok operater ne poduzme radnju. Veze koje je operater isključio i trajno zabranjene veze nikada se ponovno ne testiraju.", diff --git a/src/i18n/messages/hu.json b/src/i18n/messages/hu.json index bb9709c175c0..3825bb72680a 100644 --- a/src/i18n/messages/hu.json +++ b/src/i18n/messages/hu.json @@ -1028,7 +1028,6 @@ "featureFlagOpencodeV2ClientFingerprintEnabledDescription": "Az OpenCode v2 CLI-kliens ujjlenyomatának küldése az OpenCode Zen/Go kéréseknél: az opencode/latest/<2.x>/cli User-Agent és az x-opencode-client cli, az ingyenes csomag kéréseinél pedig a v2 munkamenet-fejlécek, a v2 CLI read/shell eszközsémák, tool_choice \"none\" nélkül. Alapértelmezés szerint kikapcsolva: minden OpenCode-kérés változatlanul megtartja a jelenlegi identitást, fejléceket és törzset.", "featureFlagComboEmptyPoolNoEligibleTargetsEnabledDescription": "Ha egy automatikus kombináció jelöltkészlete már az eszközhívási előszűrő futtatása előtt üres (minden célpont kimarad a várakozási idők, zárolások vagy kimerülés miatt), a korábbi 400-as „No target supports tool calling” capability_mismatch helyett 503-as no_eligible_targets választ adjon. Alapértelmezés szerint ki van kapcsolva: üres jelöltkészlet esetén megmarad a korábbi 400-as válasz.", "featureFlagModelEndpointCapabilityGateEnabledDescription": "A megbízható modellvégpont-címkék (amelyeket a szolgáltatói katalógus deklarál, egy speciális modellazonosítóból következtetünk ki, vagy az üzemeltető állít be) kikényszerítése: a /v1/chat/completions, /v1/embeddings, /v1/rerank, /v1/images/generations, /v1/videos/generations, /v1/music/generations és /v1/audio/* útvonalakon még a továbbítás előtt HTTP 400 válasszal elutasítja az olyan végpontra küldött kéréseket, amelyet a modell nem szolgál ki; elrejti a /v1/models listából azokat a modelleket, amelyek címkézett végpontjaihoz nincs OmniRoute-adapter; és az ilyen modellek irányítópulti tesztjére 422-es választ ad. Alapértelmezés szerint ki van kapcsolva: a végpontcímkék tájékoztató jellegűek maradnak, és a kérések továbbítása pontosan úgy történik, mint korábban.", - "featureFlagAnthropicCompatibleMidConversationSystemDescription": "Egyszerű, Anthropic-kompatibilis átjátszók esetén a beszélgetés közbeni rendszerszerepű üzenetek maradjanak az üzenetlistában ahelyett, hogy a legfelső szintű rendszerpromptba kerülnének, így a gyorsítótárazott promptelőtag stabil marad a beszélgetési fordulók között. Ez csak akkor érvényes, ha maga a kliens küldte el a mid-conversation-system-2026-04-07 bétát. Alapértelmezés szerint ki van kapcsolva: minden rendszerszerepű üzenet pontosan úgy kerül át a rendszerpromptba, mint korábban, mert az ezt a bétát nem támogató átjátszók elutasítják az üzenetlistában szereplő rendszerszerepű üzeneteket.", "featureFlagModelsDevEffortTiersEnabledDescription": "Használja a models.dev által közzétett következtetési ráfordítási szinteket, ha sem operátori felülbírálás, sem a szolgáltatói nyilvántartás nem határoz meg ilyeneket; csak az ismert ráfordítási szinteket tartsa meg, az üresen deklarált szintlistát pedig kezelje úgy, mintha „nem lennének szintek”. Alapértelmezés szerint kikapcsolva: a modellkatalógus és a feloldott ráfordítási szintek pontosan a korábbiak maradnak.", "featureFlagAutoRequestResolvedCapabilitiesDescription": "Lehetővé teszi, hogy az auto/* kérések a jelöltkészletet feloldott modellképességekkel (látás, következtetés, gondolkodás) és megfigyelt minőségi pontszámokkal állítsák össze, egyetlen tömeges olvasással betöltve. Alapértelmezés szerint ki: a jelöltek súlyai a kérés idején pontosan a korábbiak maradnak. Be: ezek a jelek bekerülnek a pillanatkép súlyaiba, így a jelöltek súlyai és a kiválasztás változhat.", "featureFlagCredentialHealthRetestAutoDisabledDescription": "Engedélyezze, hogy a hitelesítő adatok állapotának ellenőrzése újratesztelje az átjáró által automatikusan kikapcsolt kapcsolatokat (kapcsolatonként legfeljebb 6 óránként egyszer, ellenőrzésenként 5-öt), és újra bekapcsolja őket, ha sikeresen teljesítik az újratesztelést. Alapértelmezés szerint ki van kapcsolva: az ilyen kapcsolatok mindaddig kikapcsolva maradnak, amíg egy operátor nem intézkedik. Az operátor által letiltott és a véglegesen kitiltott kapcsolatokat a rendszer soha nem teszteli újra.", diff --git a/src/i18n/messages/hy.json b/src/i18n/messages/hy.json index 05dbc1202d2b..e6537d3ea049 100644 --- a/src/i18n/messages/hy.json +++ b/src/i18n/messages/hy.json @@ -1028,7 +1028,6 @@ "featureFlagOpencodeV2ClientFingerprintEnabledDescription": "OpenCode Zen/Go հարցումներում ուղարկել OpenCode v2 CLI հաճախորդի մատնահետքը՝ opencode/latest/<2.x>/cli User-Agent-ը և x-opencode-client cli-ն, իսկ անվճար մակարդակի հարցումներում՝ v2 աշխատաշրջանի վերնագրերը, v2 CLI read/shell գործիքների սխեմաները և առանց tool_choice \"none\"-ի։ Լռելյայն անջատված է․ OpenCode-ի յուրաքանչյուր հարցում պահպանում է ընթացիկ նույնականացումը, վերնագրերն ու մարմինն անփոփոխ։", "featureFlagComboEmptyPoolNoEligibleTargetsEnabledDescription": "Երբ ավտո կոմբոյի թեկնածուների ավազանն արդեն դատարկ է՝ նախքան գործիքի կանչի նախնական զտիչի գործարկումը (բոլոր թիրախները բաց են թողնվել սառեցման ժամանակահատվածների, արգելափակումների կամ սպառման պատճառով), հին 400 «Ոչ մի թիրախ չի աջակցում գործիքի կանչը» capability_mismatch պատասխանի փոխարեն վերադարձնել 503 no_eligible_targets։ Լռելյայն անջատված է․ դատարկ ավազանի դեպքում պահպանվում է հին 400 պատասխանը։", "featureFlagModelEndpointCapabilityGateEnabledDescription": "Պարտադրել վստահելի մոդելային վերջնակետերի թեգերը (հայտարարված մատակարարի կատալոգում, եզրակացված մասնագիտացված մոդելի ID-ից կամ սահմանված օպերատորի կողմից)․ մերժել այն վերջնակետին ուղարկված հարցումը, որը մոդելը չի սպասարկում՝ վերադարձնելով HTTP 400 մինչև /v1/chat/completions, /v1/embeddings, /v1/rerank, /v1/images/generations, /v1/videos/generations, /v1/music/generations և /v1/audio/* ուղարկելը, /v1/models-ից թաքցնել այն մոդելները, որոնց թեգավորված վերջնակետերի համար OmniRoute ադապտեր չկա, և կառավարման վահանակի մոդելի թեստին նման մոդելների դեպքում պատասխանել 422 կոդով։ Լռելյայն անջատված է․ վերջնակետերի թեգերը մնում են տեղեկատվական, իսկ հարցումներն ուղարկվում են ճիշտ այնպես, ինչպես նախկինում։", - "featureFlagAnthropicCompatibleMidConversationSystemDescription": "Սովորական anthropic-համատեղելի միջնորդների դեպքում զրույցի կեսում հանդիպող system դերային հաղորդագրությունները պահեք հաղորդագրությունների ցանկում՝ դրանք վերին մակարդակի system հուշման մեջ տեղափոխելու փոխարեն, որպեսզի քեշավորված հուշման նախածանցը կայուն մնա տարբեր հաղորդագրությունների միջև։ Կիրառվում է միայն այն դեպքում, երբ հաճախորդն ինքն է ուղարկել mid-conversation-system-2026-04-07 բետա տարբերակը։ Լռելյայն անջատված է․ յուրաքանչյուր system դերային հաղորդագրություն տեղափոխվում է system հուշման մեջ՝ ճիշտ այնպես, ինչպես նախկինում, քանի որ այդ բետա տարբերակը չաջակցող միջնորդները մերժում են հաղորդագրությունների ցանկում առկա system դերային հաղորդագրությունները։", "featureFlagModelsDevEffortTiersEnabledDescription": "Օգտագործել models.dev-ի հրապարակած դատողության ջանքի մակարդակները, երբ դրանք սահմանված չեն ո՛չ օպերատորի վերասահմանմամբ, ո՛չ էլ մատակարարների ռեեստրում՝ պահպանելով միայն հայտնի ջանքի մակարդակները, իսկ հայտարարված մակարդակների դատարկ ցանկը համարել «մակարդակներ չկան»։ Լռելյայն անջատված է․ մոդելների կատալոգը և որոշված ջանքի մակարդակները մնում են ճիշտ այնպես, ինչպես նախկինում։", "featureFlagAutoRequestResolvedCapabilitiesDescription": "Թույլ է տալիս auto/* հարցումներին կառուցել թեկնածուների խումբը մոդելի լուծված հնարավորություններով (տեսողություն, դատողություն, մտածողություն) և դիտարկված որակի միավորներով՝ կարդացված մեկ զանգվածային ընթերցմամբ։ Լռելյայն անջատված է. հարցման պահին թեկնածուների կշիռները մնում են ճիշտ նույնը, ինչ նախկինում։ Միացված. այդ ազդանշանները մտնում են ակնթարթային կշիռների մեջ, ուստի թեկնածուների կշիռներն ու ընտրությունը կարող են փոխվել։", "featureFlagCredentialHealthRetestAutoDisabledDescription": "Թույլատրել, որ հավատարմագրերի վիճակի պարբերական ստուգումը կրկին փորձարկի այն կապերը, որոնք անցուղին ինքնուրույն անջատել է (յուրաքանչյուրը՝ առավելագույնը 6 ժամը մեկ անգամ, յուրաքանչյուր ստուգման ընթացքում՝ 5 կապ), և կրկին միացնի կապը, երբ այն հաջողությամբ անցնի վերափորձարկումը։ Լռելյայն անջատված է․ նման կապերը մնում են անջատված, մինչև օպերատորը միջամտի։ Օպերատորի կողմից անջատված և մշտապես արգելափակված կապերը երբեք չեն վերափորձարկվում։", diff --git a/src/i18n/messages/id.json b/src/i18n/messages/id.json index bcbf393d8440..2ae8c6ec29bb 100644 --- a/src/i18n/messages/id.json +++ b/src/i18n/messages/id.json @@ -1028,7 +1028,6 @@ "featureFlagOpencodeV2ClientFingerprintEnabledDescription": "Kirim sidik jari klien CLI OpenCode v2 pada permintaan OpenCode Zen/Go: User-Agent opencode/latest/<2.x>/cli dan x-opencode-client cli, serta pada permintaan tingkat gratis, header sesi v2, skema alat baca/shell CLI v2, dan tanpa tool_choice \"none\". Dinonaktifkan secara default: setiap permintaan OpenCode mempertahankan identitas, header, dan isi saat ini tanpa perubahan.", "featureFlagComboEmptyPoolNoEligibleTargetsEnabledDescription": "Ketika kumpulan kandidat kombo otomatis sudah kosong sebelum pra-filter pemanggilan alat dijalankan (setiap target dilewati karena cooldown, penguncian, atau kehabisan), berikan respons 503 no_eligible_targets, bukan respons lama 400 \"No target supports tool calling\" capability_mismatch. Nonaktif secara default: kumpulan yang kosong tetap memberikan respons lama 400.", "featureFlagModelEndpointCapabilityGateEnabledDescription": "Terapkan tag endpoint model tepercaya (dideklarasikan oleh katalog penyedia, disimpulkan dari ID model khusus, atau ditetapkan oleh operator): tolak permintaan yang dikirim ke endpoint yang tidak dilayani model dengan HTTP 400 sebelum diteruskan pada /v1/chat/completions, /v1/embeddings, /v1/rerank, /v1/images/generations, /v1/videos/generations, /v1/music/generations, dan /v1/audio/*; sembunyikan model yang endpoint bertagnya tidak memiliki adaptor OmniRoute dari /v1/models; dan respons pengujian model dasbor dengan 422 untuk model tersebut. Nonaktif secara default: tag endpoint tetap bersifat informatif dan permintaan diteruskan persis seperti sebelumnya.", - "featureFlagAnthropicCompatibleMidConversationSystemDescription": "Pada relay biasa yang kompatibel dengan anthropic, pertahankan giliran sistem di tengah percakapan dalam daftar pesan alih-alih memindahkannya ke prompt sistem tingkat atas, sehingga prefiks prompt yang di-cache tetap stabil di setiap giliran. Hanya berlaku jika klien itu sendiri mengirimkan beta mid-conversation-system-2026-04-07. Dinonaktifkan secara default: setiap giliran sistem dipindahkan ke prompt sistem persis seperti sebelumnya, karena relay tanpa beta tersebut menolak giliran sistem dalam daftar pesan.", "featureFlagModelsDevEffortTiersEnabledDescription": "Gunakan tingkat upaya penalaran yang dipublikasikan oleh models.dev ketika tidak ada penggantian oleh operator maupun deklarasi dari registri penyedia, dengan hanya mempertahankan tingkat upaya yang dikenal, dan perlakukan daftar tingkat yang dideklarasikan tetapi kosong sebagai \"tanpa tingkat\". Dinonaktifkan secara default: katalog model dan tingkat upaya yang telah ditentukan tetap sama persis seperti sebelumnya.", "featureFlagAutoRequestResolvedCapabilitiesDescription": "Memungkinkan permintaan auto/* menyusun kumpulan kandidat dengan kapabilitas model yang telah di-resolve (vision, reasoning, thinking) dan skor kualitas yang teramati, dibaca dalam satu pembacaan massal. Nonaktif secara default: bobot kandidat saat permintaan tetap persis seperti sebelumnya. Aktif: sinyal-sinyal tersebut masuk ke bobot snapshot, sehingga bobot kandidat dan pemilihan dapat berubah.", "featureFlagCredentialHealthRetestAutoDisabledDescription": "Izinkan pemindaian kesehatan kredensial menguji ulang koneksi yang dinonaktifkan sendiri oleh gateway (maksimal sekali setiap 6 jam untuk tiap koneksi, 5 koneksi per pemindaian) dan mengaktifkan kembali koneksi saat pengujian ulangnya berhasil. Secara default dinonaktifkan: koneksi tersebut tetap nonaktif hingga operator mengambil tindakan. Koneksi yang dinonaktifkan oleh operator dan diblokir secara permanen tidak pernah diuji ulang.", diff --git a/src/i18n/messages/ig.json b/src/i18n/messages/ig.json index 293094f3659b..a2b96c25a14d 100644 --- a/src/i18n/messages/ig.json +++ b/src/i18n/messages/ig.json @@ -1028,7 +1028,6 @@ "featureFlagOpencodeV2ClientFingerprintEnabledDescription": "Ziga njirimara onye ahịa OpenCode v2 CLI na arịrịọ OpenCode Zen/Go: opencode/latest/<2.x>/cli User-Agent na x-opencode-client cli, ma na arịrịọ ọkwa efu, ziga nkụnye isi nnọkọ v2, atụmatụ ngwaọrụ read/shell nke v2 CLI, ma etinyekwala tool_choice \"none\". Agbanyụrụ ya na ndabara: arịrịọ OpenCode ọ bụla na-edobe njirimara, nkụnye isi na body dị ugbu a na-enweghị mgbanwe.", "featureFlagComboEmptyPoolNoEligibleTargetsEnabledDescription": "Mgbe nchịkọta ndị a ga-ahọrọ nke auto combo tọgbọrịrị tupu pre-filter nke ịkpọ ngwá ọrụ amalite (a mafere target niile n'ihi cooldowns, lockouts ma ọ bụ exhaustion), zaghachi 503 no_eligible_targets kama 400 ochie \"Ọ dịghị target na-akwado ịkpọ ngwá ọrụ\" capability_mismatch. Agbanyụghị ya na ndabara: nchịkọta tọgbọ chakoo ga-anọgide na-eji 400 ochie.", "featureFlagModelEndpointCapabilityGateEnabledDescription": "Manye mkpado endpoint ụdịdị a tụkwasịrị obi (nke katalọgụ provider kwupụtara, nke e si na ID ụdịdị ọpụrụiche chọpụta, ma ọ bụ nke operator debere): jụ arịrịọ ezitere na endpoint nke ụdịdị ahụ anaghị akwado site na HTTP 400 tupu iziga ya na /v1/chat/completions, /v1/embeddings, /v1/rerank, /v1/images/generations, /v1/videos/generations, /v1/music/generations na /v1/audio/*; zoo ụdịdị ndị endpoint ha nwere mkpado enweghị adapter OmniRoute na /v1/models; ma jiri 422 zaa ule ụdịdị dashboard maka ụdịdị ndị dị otu a. A gbanyụrụ ya na ndabara: mkpado endpoint ga-anọgide bụrụ naanị ozi, a ga-ezigakwa arịrịọ dịka ọ dị na mbụ.", - "featureFlagAnthropicCompatibleMidConversationSystemDescription": "Na relays nkịtị dakọtara na Anthropic, debe ntụgharị system ndị dị n’etiti mkparịta ụka n’ime ndepụta ozi kama ibuga ha na system prompt nke ọkwa kachasị elu, ka prefix nke prompt echekwara na cache wee nọgide otu ihe n’ofe ntụgharị. Nke a na-emetụta naanị mgbe client n’onwe ya zitere beta mid-conversation-system-2026-04-07. A gbanyụrụ ya na ndabara: a na-ebuga ntụgharị system ọ bụla n’ime system prompt kpọmkwem dịka ọ dị na mbụ, n’ihi na relays na-enweghị beta ahụ na-ajụ ntụgharị system dị na ndepụta ozi.", "featureFlagModelsDevEffortTiersEnabledDescription": "Jiri ọkwa mgbalị ntụgharị uche ndị models.dev na-ebipụta mgbe ma ntọala onye njikwa nke karịrị ndị ọzọ ma ndekọ onye na-eweta ọrụ ekwughị nke ọ bụla, na-edobe naanị ọkwa mgbalị ndị a maara, ma were ndepụta ọkwa ekwuputara nke tọgbọ chakoo dị ka \"enweghị ọkwa\". Agbanyụrụ ya na ndabara: katalọgụ ụdị na ọkwa mgbalị edoziri na-anọgide kpọmkwem dịka ha dịbu.", "featureFlagAutoRequestResolvedCapabilitiesDescription": "Na-ekwe ka arịrịọ auto/* wuo ọdọ ndị a na-atụle site na ikike ụdị ndị edoziri (ọhụhụ, ntụgharị uche, iche echiche) na akara ogo a hụrụ, a gụrụ n'otu ọgụgụ nnukwu. Agbanyụrụ na ndabara: ibu ndị a na-atụle n'oge arịrịọ ka dị kpọmkwem ka ọ dị na mbụ. Agbanyere: akara ndị ahụ na-abanye n'ibu foto oge ahụ, ya mere ibu ndị a na-atụle na nhọrọ nwere ike ịgbanwe.", "featureFlagCredentialHealthRetestAutoDisabledDescription": "Kwe ka nyocha ahụike nzere nwalee njikọ ndị ọnụ ụzọ ahụ gbanyụrụ n'onwe ya ọzọ (nke ọ bụla, ọ kachasị otu ugboro n'ime awa 6 ọ bụla; njikọ 5 n'otu nyocha) ma gbanye otu n'ime ha ọzọ mgbe ule ya gara nke ọma. A na-agbanyụ nke a na ndabara: njikọ ndị dị otu ahụ ga-anọgide na-agbanyụ ruo mgbe onye na-ahụ maka sistemụ mere ihe. A naghị anwale ọzọ njikọ ndị onye na-ahụ maka sistemụ gbanyụrụ ma ọ bụ ndị amachibidoro kpamkpam.", diff --git a/src/i18n/messages/it.json b/src/i18n/messages/it.json index 52aec8108baa..6a9e4b47ae4c 100644 --- a/src/i18n/messages/it.json +++ b/src/i18n/messages/it.json @@ -1028,7 +1028,6 @@ "featureFlagOpencodeV2ClientFingerprintEnabledDescription": "Invia l'impronta digitale del client CLI OpenCode v2 nelle richieste OpenCode Zen/Go: lo User-Agent opencode/latest/<2.x>/cli e x-opencode-client cli e, nelle richieste del livello gratuito, le intestazioni di sessione v2, gli schemi degli strumenti read/shell della CLI v2 e nessun tool_choice \"none\". Disattivato per impostazione predefinita: ogni richiesta OpenCode mantiene invariati l'identità, le intestazioni e il corpo attuali.", "featureFlagComboEmptyPoolNoEligibleTargetsEnabledDescription": "Quando il pool di candidati di una combo automatica è già vuoto prima dell'esecuzione del pre-filtro per la chiamata di strumenti (tutti i target sono stati esclusi a causa di periodi di attesa, blocchi o esaurimento), restituisce 503 no_eligible_targets anziché il precedente 400 \"Nessun target supporta la chiamata di strumenti\" capability_mismatch. Disattivato per impostazione predefinita: se il pool è vuoto, viene mantenuto il precedente errore 400.", "featureFlagModelEndpointCapabilityGateEnabledDescription": "Applica i tag attendibili degli endpoint del modello (dichiarati dal catalogo del provider, dedotti dall'ID di un modello specializzato o impostati dall'operatore): rifiuta con HTTP 400, prima dell'inoltro, una richiesta inviata a un endpoint non supportato dal modello per /v1/chat/completions, /v1/embeddings, /v1/rerank, /v1/images/generations, /v1/videos/generations, /v1/music/generations e /v1/audio/*; nasconde da /v1/models i modelli i cui endpoint contrassegnati non dispongono di un adattatore OmniRoute; e restituisce 422 al test del modello nella dashboard per tali modelli. Disattivato per impostazione predefinita: i tag degli endpoint rimangono informativi e le richieste vengono inoltrate esattamente come prima.", - "featureFlagAnthropicCompatibleMidConversationSystemDescription": "Sui relay standard compatibili con Anthropic, mantieni i turni di sistema a metà conversazione nell'elenco dei messaggi anziché spostarli nel prompt di sistema di primo livello, in modo che il prefisso del prompt memorizzato nella cache rimanga stabile tra i turni. Si applica solo quando il client stesso ha inviato la beta mid-conversation-system-2026-04-07. Disattivato per impostazione predefinita: ogni turno di sistema viene spostato nel prompt di sistema esattamente come prima, perché i relay che non supportano tale beta rifiutano i turni di sistema nell'elenco dei messaggi.", "featureFlagModelsDevEffortTiersEnabledDescription": "Usa i livelli di impegno di ragionamento pubblicati da models.dev quando né una sostituzione dell'operatore né il registro del provider ne dichiarano alcuno, mantenendo solo i livelli di impegno noti e considerando un elenco vuoto di livelli dichiarati come \"nessun livello\". Disattivato per impostazione predefinita: il catalogo dei modelli e i livelli di impegno risolti rimangono esattamente come prima.", "featureFlagAutoRequestResolvedCapabilitiesDescription": "Consente alle richieste auto/* di costruire il pool di candidati con le capacità del modello risolte (visione, ragionamento, thinking) e i punteggi di qualità osservati, letti in un'unica lettura in blocco. Disattivato per impostazione predefinita: i pesi dei candidati al momento della richiesta restano esattamente come prima. Attivato: questi segnali alimentano i pesi dello snapshot, quindi i pesi dei candidati e la selezione possono cambiare.", "featureFlagCredentialHealthRetestAutoDisabledDescription": "Consenti alla verifica periodica dello stato delle credenziali di testare nuovamente le connessioni disattivate automaticamente dal gateway (al massimo una volta ogni 6 ore per ciascuna connessione, 5 per verifica) e di riattivarle quando superano il nuovo test. Opzione disattivata per impostazione predefinita: tali connessioni rimangono disattivate finché non interviene un operatore. Le connessioni disattivate da un operatore e quelle bloccate in modo permanente non vengono mai testate nuovamente.", diff --git a/src/i18n/messages/ja.json b/src/i18n/messages/ja.json index b4e5b1328b02..4202602f3f09 100644 --- a/src/i18n/messages/ja.json +++ b/src/i18n/messages/ja.json @@ -1028,7 +1028,6 @@ "featureFlagOpencodeV2ClientFingerprintEnabledDescription": "OpenCode Zen/Go リクエストで OpenCode v2 CLI クライアントフィンガープリントを送信します。具体的には、opencode/latest/<2.x>/cli User-Agent と x-opencode-client cli を送信し、無料枠のリクエストでは v2 セッションヘッダーと v2 CLI の read/shell ツールスキーマを使用し、tool_choice \"none\" は使用しません。デフォルトではオフです。すべての OpenCode リクエストで、現在の ID、ヘッダー、本文が変更されずに維持されます。", "featureFlagComboEmptyPoolNoEligibleTargetsEnabledDescription": "自動コンボの候補プールがツール呼び出しの事前フィルター実行前にすでに空の場合(クールダウン、ロックアウト、または枯渇によりすべてのターゲットがスキップされた場合)、従来の 400「ツール呼び出しをサポートするターゲットがありません」という capability_mismatch の代わりに、503 no_eligible_targets を返します。デフォルトではオフです。オフの場合、候補プールが空でも従来の 400 が維持されます。", "featureFlagModelEndpointCapabilityGateEnabledDescription": "信頼済みモデルエンドポイントタグ(プロバイダーカタログで宣言されたもの、特殊用途モデルIDから推論されたもの、または運用者が設定したもの)を適用します。モデルを提供していないエンドポイントに送信されたリクエストは、/v1/chat/completions、/v1/embeddings、/v1/rerank、/v1/images/generations、/v1/videos/generations、/v1/music/generations、/v1/audio/* へのディスパッチ前に HTTP 400 で拒否します。タグ付けされたエンドポイントに対応する OmniRoute アダプターがないモデルは /v1/models で非表示にし、そのようなモデルに対するダッシュボードのモデルテストには 422 を返します。デフォルトではオフです。エンドポイントタグは引き続き参考情報としてのみ扱われ、リクエストは従来どおりディスパッチされます。", - "featureFlagAnthropicCompatibleMidConversationSystemDescription": "標準の anthropic 互換リレーでは、会話途中のシステムターンをトップレベルのシステムプロンプトへ移動せず、メッセージリスト内に保持します。これにより、キャッシュされたプロンプトのプレフィックスがターン間で安定します。クライアント自身が mid-conversation-system-2026-04-07 ベータを送信した場合にのみ適用されます。デフォルトでは無効です。そのベータに対応していないリレーはメッセージリスト内のシステムターンを拒否するため、従来どおり、すべてのシステムターンがシステムプロンプトへ移動されます。", "featureFlagModelsDevEffortTiersEnabledDescription": "オペレーターによる上書きもプロバイダーレジストリによる宣言もない場合は、models.dev が公開する推論エフォートのティアを使用し、既知のエフォートレベルのみを保持します。また、宣言されたティアのリストが空の場合は「ティアなし」として扱います。デフォルトでは無効です。モデルカタログと解決済みのエフォートティアは従来どおり変更されません。", "featureFlagAutoRequestResolvedCapabilitiesDescription": "auto/* リクエストが、解決済みのモデル機能(ビジョン、推論、思考)と観測された品質スコアを使って候補プールを構築できるようにします。これらは一括読み込みで取得されます。既定ではオフ:リクエスト時の候補の重みは以前とまったく同じままです。オン:これらのシグナルがスナップショットの重みに反映されるため、候補の重みと選択が変わる可能性があります。", "featureFlagCredentialHealthRetestAutoDisabledDescription": "認証情報の健全性スイープが、ゲートウェイによって自動的にオフにされた接続を再テストし(各接続につき最大6時間に1回、1回のスイープにつき5件)、再テストに合格した接続を再びオンにできるようにします。デフォルトではオフです。この場合、オペレーターが対応するまで該当する接続はオフのままになります。オペレーターによって無効化された接続と、恒久的に禁止された接続は再テストされません。", diff --git a/src/i18n/messages/ka.json b/src/i18n/messages/ka.json index 4f31ee32a998..a583d7774b35 100644 --- a/src/i18n/messages/ka.json +++ b/src/i18n/messages/ka.json @@ -1028,7 +1028,6 @@ "featureFlagOpencodeV2ClientFingerprintEnabledDescription": "OpenCode Zen/Go მოთხოვნებზე გააგზავნეთ OpenCode v2 CLI კლიენტის ციფრული ანაბეჭდი: opencode/latest/<2.x>/cli User-Agent და x-opencode-client cli, ხოლო უფასო ტარიფის მოთხოვნებზე — v2 სესიის სათაურები, v2 CLI read/shell ხელსაწყოების სქემები და არა tool_choice \"none\". ნაგულისხმევად გამორთულია: OpenCode-ის ყოველი მოთხოვნის მიმდინარე იდენტობა, სათაურები და სხეული უცვლელი რჩება.", "featureFlagComboEmptyPoolNoEligibleTargetsEnabledDescription": "როდესაც ავტომატური კომბინაციის კანდიდატთა პული უკვე ცარიელია ხელსაწყოს გამოძახების წინასწარი ფილტრის გაშვებამდე (ყველა სამიზნე გამოტოვებულია დაყოვნების პერიოდების, ბლოკირებების ან ამოწურვის გამო), ძველი 400 \"No target supports tool calling\" capability_mismatch-ის ნაცვლად უპასუხეთ 503 no_eligible_targets-ით. ნაგულისხმევად გამორთულია: ცარიელი პული ინარჩუნებს ძველ 400-ს.", "featureFlagModelEndpointCapabilityGateEnabledDescription": "სანდო მოდელის endpoint-ის ტეგების სავალდებულო გამოყენება (რომლებიც გამოცხადებულია პროვაიდერის კატალოგის მიერ, განსაზღვრულია სპეციალიზებული მოდელის id-დან ან დაყენებულია ოპერატორის მიერ): მოთხოვნა, რომელიც გაგზავნილია endpoint-ზე, რომელსაც მოდელი არ ემსახურება, უარყოფილი იქნება HTTP 400-ით გადაგზავნამდე /v1/chat/completions, /v1/embeddings, /v1/rerank, /v1/images/generations, /v1/videos/generations, /v1/music/generations და /v1/audio/* მისამართებზე; /v1/models-დან დაიმალება მოდელები, რომელთა ტეგირებულ endpoint-ებს OmniRoute-ის ადაპტერი არ აქვთ; ხოლო ასეთ მოდელებზე დაფის მოდელის ტესტი უპასუხებს 422-ით. ნაგულისხმევად გამორთულია: endpoint-ის ტეგები მხოლოდ საინფორმაციოდ რჩება და მოთხოვნები ზუსტად ისე გადაიგზავნება, როგორც ადრე.", - "featureFlagAnthropicCompatibleMidConversationSystemDescription": "ჩვეულებრივ Anthropic-თან თავსებად რელეებზე, საუბრის შუაში არსებული სისტემური შეტყობინებები შეტყობინებების სიაში დატოვეთ და ზედა დონის სისტემურ პრომპტში არ გადაიტანოთ, რათა ქეშირებული პრომპტის პრეფიქსი სვლებს შორის უცვლელი დარჩეს. ეს ვრცელდება მხოლოდ იმ შემთხვევაზე, როცა თავად კლიენტმა გაგზავნა mid-conversation-system-2026-04-07 ბეტა. ნაგულისხმევად გამორთულია: თითოეული სისტემური შეტყობინება კვლავ ზუსტად ისე გადაიტანება სისტემურ პრომპტში, როგორც ადრე, რადგან რელეები, რომლებსაც ეს ბეტა არ აქვთ, შეტყობინებების სიაში სისტემურ შეტყობინებებს უარყოფენ.", "featureFlagModelsDevEffortTiersEnabledDescription": "გამოიყენეთ models.dev-ის მიერ გამოქვეყნებული მსჯელობის ძალისხმევის დონეები, როდესაც არც ოპერატორის ჩანაცვლება და არც პროვაიდერის რეესტრი არ განსაზღვრავს მათ; შეინარჩუნეთ მხოლოდ ცნობილი ძალისხმევის დონეები და დონეების ცარიელი გამოცხადებული სია აღიქვით როგორც „დონეები არ არის“. ნაგულისხმევად გამორთულია: მოდელების კატალოგი და განსაზღვრული ძალისხმევის დონეები ზუსტად უცვლელი რჩება.", "featureFlagAutoRequestResolvedCapabilitiesDescription": "საშუალებას აძლევს auto/* მოთხოვნებს, ააგონ კანდიდატების ფონდი მოდელის გადაწყვეტილი შესაძლებლობებით (ხედვა, მსჯელობა, ფიქრი) და დაკვირვებული ხარისხის ქულებით, ერთი ჯგუფური წაკითხვით. ნაგულისხმევად გამორთულია: კანდიდატების წონები მოთხოვნის დროს ზუსტად ისეთივე რჩება, როგორც ადრე. ჩართული: ეს სიგნალები შედის სნეპშოტის წონებში, ამიტომ კანდიდატების წონები და არჩევანი შეიძლება შეიცვალოს.", "featureFlagCredentialHealthRetestAutoDisabledDescription": "მიეცით ავტორიზაციის მონაცემების მდგომარეობის შემოწმების პროცესს უფლება, ხელახლა შეამოწმოს კავშირები, რომლებიც კარიბჭემ თავად გამორთო (თითოეული — მაქსიმუმ ერთხელ ყოველ 6 საათში, თითო შემოწმებაზე 5 კავშირი) და წარმატებული ხელახალი შემოწმების შემთხვევაში კვლავ ჩართოს შესაბამისი კავშირი. ნაგულისხმევად გამორთულია: ასეთი კავშირები გამორთული რჩება, სანამ ოპერატორი არ ჩაერევა. ოპერატორის მიერ გამორთული და სამუდამოდ დაბლოკილი კავშირები არასოდეს მოწმდება ხელახლა.", diff --git a/src/i18n/messages/km.json b/src/i18n/messages/km.json index 32d2fe0b104f..3e5f347d7225 100644 --- a/src/i18n/messages/km.json +++ b/src/i18n/messages/km.json @@ -1028,7 +1028,6 @@ "featureFlagOpencodeV2ClientFingerprintEnabledDescription": "ផ្ញើស្នាមសម្គាល់របស់កម្មវិធី CLI OpenCode v2 នៅលើសំណើ OpenCode Zen/Go៖ User-Agent opencode/latest/<2.x>/cli និង x-opencode-client cli ហើយនៅលើសំណើកម្រិតឥតគិតថ្លៃ សូមផ្ញើបឋមកថាសម័យ v2 គ្រោងការណ៍ឧបករណ៍អាន/shell របស់ CLI v2 និងមិនប្រើ tool_choice \"none\"។ តាមលំនាំដើម វាត្រូវបានបិទ៖ រាល់សំណើ OpenCode រក្សាអត្តសញ្ញាណ បឋមកថា និងខ្លឹមសារសំណើបច្ចុប្បន្នឱ្យនៅដដែល។", "featureFlagComboEmptyPoolNoEligibleTargetsEnabledDescription": "នៅពេលបញ្ជីបេក្ខជនរបស់ auto combo ទទេរួចហើយ មុនពេល pre-filter សម្រាប់ tool-calling ដំណើរការ (រាល់ target ត្រូវបានរំលងដោយសារ cooldowns, lockouts ឬ exhaustion) សូមឆ្លើយតបដោយ 503 no_eligible_targets ជំនួសឱ្យ 400 \"គ្មាន target ណាមួយគាំទ្រ tool calling ទេ\" capability_mismatch បែបចាស់។ បិទតាមលំនាំដើម៖ បញ្ជីទទេនឹងបន្តប្រើ 400 បែបចាស់។", "featureFlagModelEndpointCapabilityGateEnabledDescription": "អនុវត្តស្លាក endpoint ម៉ូដែលដែលទុកចិត្តបាន (ដែលបានប្រកាសដោយកាតាឡុកអ្នកផ្តល់សេវា, បានសន្និដ្ឋានពី ID ម៉ូដែលឯកទេស ឬកំណត់ដោយប្រតិបត្តិករ)៖ បដិសេធសំណើដែលបានផ្ញើទៅ endpoint ដែលម៉ូដែលមិនផ្តល់សេវា ដោយប្រើ HTTP 400 មុនពេលបញ្ជូនបន្តនៅលើ /v1/chat/completions, /v1/embeddings, /v1/rerank, /v1/images/generations, /v1/videos/generations, /v1/music/generations និង /v1/audio/*; លាក់ម៉ូដែលដែល endpoint មានស្លាករបស់វាមិនមានអាដាប់ទ័រ OmniRoute ពី /v1/models; និងឆ្លើយតបការធ្វើតេស្តម៉ូដែលនៅលើផ្ទាំងគ្រប់គ្រងដោយ 422 សម្រាប់ម៉ូដែលបែបនេះ។ បិទតាមលំនាំដើម៖ ស្លាក endpoint នៅតែសម្រាប់ផ្តល់ព័ត៌មាន ហើយសំណើត្រូវបានបញ្ជូនបន្តដូចមុនទាំងស្រុង។", - "featureFlagAnthropicCompatibleMidConversationSystemDescription": "នៅលើ relay ដែលឆបគ្នាជាមួយ Anthropic ធម្មតា សូមរក្សា system turn នៅកណ្ដាលការសន្ទនាទុកក្នុងបញ្ជីសារ ជំនួសឱ្យការផ្លាស់ទីវាទៅកាន់ system prompt កម្រិតកំពូល ដើម្បីឱ្យបុព្វបទ prompt ដែលបានរក្សាទុកក្នុងឃ្លាំងសម្ងាត់មានស្ថិរភាពនៅគ្រប់ turn។ វាអនុវត្តតែនៅពេល client ខ្លួនឯងបានផ្ញើ beta mid-conversation-system-2026-04-07 ប៉ុណ្ណោះ។ តាមលំនាំដើម វាត្រូវបានបិទ៖ រាល់ system turn ត្រូវបានផ្លាស់ទីទៅក្នុង system prompt ដូចពីមុន ព្រោះ relay ដែលគ្មាន beta នោះនឹងបដិសេធ system turn នៅក្នុងបញ្ជីសារ។", "featureFlagModelsDevEffortTiersEnabledDescription": "ប្រើកម្រិតនៃកិច្ចខិតខំប្រឹងប្រែងក្នុងការវែកញែកដែល models.dev បោះពុម្ពផ្សាយ នៅពេលដែលទាំងការកំណត់ជំនួសរបស់ប្រតិបត្តិករ និងបញ្ជីចុះឈ្មោះរបស់អ្នកផ្តល់សេវា មិនបានប្រកាសកម្រិតណាមួយ ដោយរក្សាទុកតែកម្រិតកិច្ចខិតខំប្រឹងប្រែងដែលស្គាល់ប៉ុណ្ណោះ ហើយចាត់ទុកបញ្ជីកម្រិតដែលបានប្រកាសទទេថាជា \"គ្មានកម្រិត\"។ បានបិទតាមលំនាំដើម៖ កាតាឡុកម៉ូដែល និងកម្រិតកិច្ចខិតខំប្រឹងប្រែងដែលបានដោះស្រាយ នឹងនៅដដែលដូចពីមុន។", "featureFlagAutoRequestResolvedCapabilitiesDescription": "អនុញ្ញាតឱ្យសំណើ auto/* បង្កើតក្រុមបេក្ខជនដោយប្រើសមត្ថភាពម៉ូដែលដែលបានដោះស្រាយ (ចក្ខុវិស័យ ការវែកញែក ការគិត) និងពិន្ទុគុណភាពដែលបានសង្កេត ដែលអានក្នុងការអានជាដុំតែមួយ។ បិទតាមលំនាំដើម៖ ទម្ងន់បេក្ខជននៅពេលសំណើនៅដដែលដូចមុនទាំងស្រុង។ បើក៖ សញ្ញាទាំងនោះបញ្ចូលទៅក្នុងទម្ងន់ snapshot ដូច្នេះទម្ងន់បេក្ខជន និងការជ្រើសរើសអាចផ្លាស់ប្តូរ។", "featureFlagCredentialHealthRetestAutoDisabledDescription": "អនុញ្ញាតឱ្យការស្កេនសុខភាពព័ត៌មានសម្ងាត់សាកល្បងការតភ្ជាប់ឡើងវិញ ដែល gateway បានបិទដោយស្វ័យប្រវត្តិ (ការតភ្ជាប់នីមួយៗច្រើនបំផុតម្តងរៀងរាល់ 6 ម៉ោង និង 5 ការតភ្ជាប់ក្នុងមួយការស្កេន) ហើយបើកការតភ្ជាប់ណាមួយឡើងវិញ នៅពេលការសាកល្បងឡើងវិញរបស់វាជោគជ័យ។ តាមលំនាំដើម វាត្រូវបានបិទ៖ ការតភ្ជាប់បែបនេះនៅតែបិទ រហូតដល់ប្រតិបត្តិករចាត់វិធានការ។ ការតភ្ជាប់ដែលត្រូវបានបិទដោយប្រតិបត្តិករ និងការតភ្ជាប់ដែលត្រូវបានហាមឃាត់ជាអចិន្ត្រៃយ៍ មិនត្រូវបានសាកល្បងឡើងវិញឡើយ។", diff --git a/src/i18n/messages/kn.json b/src/i18n/messages/kn.json index bbcd00477abb..606dd1c60fe8 100644 --- a/src/i18n/messages/kn.json +++ b/src/i18n/messages/kn.json @@ -1028,7 +1028,6 @@ "featureFlagOpencodeV2ClientFingerprintEnabledDescription": "OpenCode Zen/Go ವಿನಂತಿಗಳಲ್ಲಿ OpenCode v2 CLI ಕ್ಲೈಂಟ್ ಫಿಂಗರ್ಪ್ರಿಂಟ್ ಅನ್ನು ಕಳುಹಿಸಿ: opencode/latest/<2.x>/cli User-Agent ಮತ್ತು x-opencode-client cli; ಹಾಗೆಯೇ ಉಚಿತ-ಶ್ರೇಣಿಯ ವಿನಂತಿಗಳಲ್ಲಿ v2 ಸೆಷನ್ ಹೆಡರ್ಗಳು, v2 CLI read/shell ಟೂಲ್ ಸ್ಕೀಮಾಗಳು ಮತ್ತು tool_choice \"none\" ಇಲ್ಲದೆ. ಪೂರ್ವನಿಯೋಜಿತವಾಗಿ ಆಫ್ ಆಗಿರುತ್ತದೆ: ಪ್ರತಿಯೊಂದು OpenCode ವಿನಂತಿಯೂ ಪ್ರಸ್ತುತ ಗುರುತು, ಹೆಡರ್ಗಳು ಮತ್ತು ಬಾಡಿಯನ್ನು ಬದಲಾವಣೆ ಇಲ್ಲದೆ ಉಳಿಸಿಕೊಳ್ಳುತ್ತದೆ.", "featureFlagComboEmptyPoolNoEligibleTargetsEnabledDescription": "ಟೂಲ್-ಕಾಲಿಂಗ್ ಪ್ರೀ-ಫಿಲ್ಟರ್ ಚಾಲನೆಯಾಗುವ ಮೊದಲೇ ಆಟೋ ಕಾಂಬೋದ ಅಭ್ಯರ್ಥಿ ಪೂಲ್ ಖಾಲಿಯಾಗಿದ್ದರೆ (ಕೂಲ್ಡೌನ್ಗಳು, ಲಾಕ್ಔಟ್ಗಳು ಅಥವಾ ಬಳಲಿ ಮುಗಿದಿರುವುದರಿಂದ ಪ್ರತಿಯೊಂದು ಟಾರ್ಗೆಟ್ ಅನ್ನು ಬಿಟ್ಟುಬಿಟ್ಟಿದ್ದರೆ), ಹಳೆಯ 400 \"ಯಾವುದೇ ಟಾರ್ಗೆಟ್ ಟೂಲ್ ಕಾಲಿಂಗ್ ಅನ್ನು ಬೆಂಬಲಿಸುವುದಿಲ್ಲ\" capability_mismatch ಬದಲಿಗೆ 503 no_eligible_targets ಅನ್ನು ಉತ್ತರಿಸಿ. ಡೀಫಾಲ್ಟ್ ಆಗಿ ಆಫ್: ಖಾಲಿ ಪೂಲ್ ಹಳೆಯ 400 ಅನ್ನು ಉಳಿಸಿಕೊಳ್ಳುತ್ತದೆ.", "featureFlagModelEndpointCapabilityGateEnabledDescription": "ವಿಶ್ವಾಸಾರ್ಹ ಮಾದರಿ ಎಂಡ್ಪಾಯಿಂಟ್ ಟ್ಯಾಗ್ಗಳನ್ನು (ಪೂರೈಕೆದಾರರ ಕ್ಯಾಟಲಾಗ್ನಿಂದ ಘೋಷಿಸಲಾದ, ವಿಶೇಷ ಮಾದರಿ id ಯಿಂದ ಊಹಿಸಲಾದ, ಅಥವಾ ನಿರ್ವಾಹಕರು ಹೊಂದಿಸಿದ) ಕಡ್ಡಾಯಗೊಳಿಸಿ: /v1/chat/completions, /v1/embeddings, /v1/rerank, /v1/images/generations, /v1/videos/generations, /v1/music/generations ಮತ್ತು /v1/audio/* ನಲ್ಲಿ ಮಾದರಿಯು ಸೇವೆ ನೀಡದ ಎಂಡ್ಪಾಯಿಂಟ್ಗೆ ಕಳುಹಿಸಲಾದ ವಿನಂತಿಯನ್ನು ರವಾನಿಸುವ ಮೊದಲು HTTP 400 ನೊಂದಿಗೆ ತಿರಸ್ಕರಿಸಿ; ಟ್ಯಾಗ್ ಮಾಡಲಾದ ಎಂಡ್ಪಾಯಿಂಟ್ಗಳಿಗೆ OmniRoute ಅಡಾಪ್ಟರ್ ಇಲ್ಲದ ಮಾದರಿಗಳನ್ನು /v1/models ನಿಂದ ಮರೆಮಾಡಿ; ಮತ್ತು ಅಂತಹ ಮಾದರಿಗಳ ಡ್ಯಾಶ್ಬೋರ್ಡ್ ಮಾದರಿ ಪರೀಕ್ಷೆಗೆ 422 ನೊಂದಿಗೆ ಪ್ರತಿಕ್ರಿಯಿಸಿ. ಪೂರ್ವನಿಯೋಜಿತವಾಗಿ ಆಫ್: ಎಂಡ್ಪಾಯಿಂಟ್ ಟ್ಯಾಗ್ಗಳು ಮಾಹಿತಿಗಾಗಿ ಮಾತ್ರ ಉಳಿಯುತ್ತವೆ ಮತ್ತು ವಿನಂತಿಗಳನ್ನು ಹಿಂದಿನಂತೆಯೇ ರವಾನಿಸಲಾಗುತ್ತದೆ.", - "featureFlagAnthropicCompatibleMidConversationSystemDescription": "ಸರಳ anthropic-ಹೊಂದಾಣಿಕೆಯ ರಿಲೇಗಳಲ್ಲಿ, ಸಂಭಾಷಣೆಯ ಮಧ್ಯದ ಸಿಸ್ಟಮ್ ಟರ್ನ್ಗಳನ್ನು ಉನ್ನತ-ಮಟ್ಟದ ಸಿಸ್ಟಮ್ ಪ್ರಾಂಪ್ಟ್ಗೆ ಸ್ಥಳಾಂತರಿಸುವ ಬದಲು ಸಂದೇಶಗಳ ಪಟ್ಟಿಯಲ್ಲೇ ಇರಿಸಿ, ಇದರಿಂದ ಕ್ಯಾಶ್ ಮಾಡಲಾದ ಪ್ರಾಂಪ್ಟ್ ಪೂರ್ವಪ್ರತ್ಯಯವು ಟರ್ನ್ಗಳಾದ್ಯಂತ ಸ್ಥಿರವಾಗಿರುತ್ತದೆ. ಕ್ಲೈಂಟ್ ಸ್ವತಃ mid-conversation-system-2026-04-07 ಬೀಟಾವನ್ನು ಕಳುಹಿಸಿದಾಗ ಮಾತ್ರ ಇದು ಅನ್ವಯಿಸುತ್ತದೆ. ಪೂರ್ವನಿಯೋಜಿತವಾಗಿ ಆಫ್ ಆಗಿರುತ್ತದೆ: ಆ ಬೀಟಾ ಇಲ್ಲದ ರಿಲೇಗಳು ಸಂದೇಶಗಳ ಪಟ್ಟಿಯಲ್ಲಿರುವ ಸಿಸ್ಟಮ್ ಟರ್ನ್ಗಳನ್ನು ತಿರಸ್ಕರಿಸುವುದರಿಂದ, ಮೊದಲಿನಂತೆಯೇ ಪ್ರತಿಯೊಂದು ಸಿಸ್ಟಮ್ ಟರ್ನ್ ಅನ್ನು ನಿಖರವಾಗಿ ಸಿಸ್ಟಮ್ ಪ್ರಾಂಪ್ಟ್ಗೆ ಸ್ಥಳಾಂತರಿಸಲಾಗುತ್ತದೆ.", "featureFlagModelsDevEffortTiersEnabledDescription": "ಆಪರೇಟರ್ ಓವರ್ರೈಡ್ ಅಥವಾ ಪೂರೈಕೆದಾರರ ರಿಜಿಸ್ಟ್ರಿ ಯಾವುದನ್ನೂ ಘೋಷಿಸದಿದ್ದಾಗ models.dev ಪ್ರಕಟಿಸುವ ತಾರ್ಕಿಕ ಪ್ರಯತ್ನದ ಹಂತಗಳನ್ನು ಬಳಸಿ, ತಿಳಿದಿರುವ ಪ್ರಯತ್ನದ ಮಟ್ಟಗಳನ್ನು ಮಾತ್ರ ಉಳಿಸಿಕೊಳ್ಳಿ ಮತ್ತು ಘೋಷಿಸಲಾದ ಹಂತಗಳ ಪಟ್ಟಿ ಖಾಲಿಯಾಗಿದ್ದರೆ ಅದನ್ನು \"ಯಾವುದೇ ಹಂತಗಳಿಲ್ಲ\" ಎಂದು ಪರಿಗಣಿಸಿ. ಪೂರ್ವನಿಯೋಜಿತವಾಗಿ ಆಫ್ ಆಗಿರುತ್ತದೆ: ಮಾದರಿ ಕ್ಯಾಟಲಾಗ್ ಮತ್ತು ನಿರ್ಧರಿಸಲಾದ ಪ್ರಯತ್ನದ ಹಂತಗಳು ಮೊದಲಿನಂತೆಯೇ ಇರುತ್ತವೆ.", "featureFlagAutoRequestResolvedCapabilitiesDescription": "auto/* ವಿನಂತಿಗಳಿಗೆ ಪರಿಹರಿಸಿದ ಮಾದರಿ ಸಾಮರ್ಥ್ಯಗಳು (ವಿಷನ್, ರೀಸನಿಂಗ್, ಥಿಂಕಿಂಗ್) ಮತ್ತು ಗಮನಿಸಿದ ಗುಣಮಟ್ಟದ ಅಂಕಗಳೊಂದಿಗೆ, ಒಂದೇ ಬಲ್ಕ್ ಓದಿನಲ್ಲಿ, ಅಭ್ಯರ್ಥಿ ಪೂಲ್ ನಿರ್ಮಿಸಲು ಅನುಮತಿಸುತ್ತದೆ. ಡೀಫಾಲ್ಟ್ ಆಗಿ ಆಫ್: ವಿನಂತಿ ಸಮಯದ ಅಭ್ಯರ್ಥಿ ತೂಕಗಳು ಹಿಂದಿನಂತೆಯೇ ಇರುತ್ತವೆ. ಆನ್ ಆಗಿದ್ದಾಗ: ಈ ಸಂಕೇತಗಳು ಸ್ನ್ಯಾಪ್‌ಶಾಟ್ ತೂಕಗಳಿಗೆ ಸೇರುತ್ತವೆ, ಆದ್ದರಿಂದ ಅಭ್ಯರ್ಥಿ ತೂಕಗಳು ಮತ್ತು ಆಯ್ಕೆ ಬದಲಾಗಬಹುದು.", "featureFlagCredentialHealthRetestAutoDisabledDescription": "ಗೇಟ್ವೇ ಸ್ವಯಂ ಆಫ್ ಮಾಡಿದ ಸಂಪರ್ಕಗಳನ್ನು ಕ್ರೆಡೆನ್ಶಿಯಲ್ ಆರೋಗ್ಯ ಪರಿಶೀಲನೆಯು ಮರುಪರೀಕ್ಷಿಸಲು (ಪ್ರತಿಯೊಂದನ್ನು ಹೆಚ್ಚೆಂದರೆ ಪ್ರತಿ 6 ಗಂಟೆಗಳಿಗೊಮ್ಮೆ, ಪ್ರತಿ ಪರಿಶೀಲನೆಯಲ್ಲಿ 5) ಮತ್ತು ಮರುಪರೀಕ್ಷೆಯಲ್ಲಿ ಯಶಸ್ವಿಯಾದಾಗ ಒಂದನ್ನು ಮತ್ತೆ ಆನ್ ಮಾಡಲು ಅನುಮತಿಸಿ. ಡೀಫಾಲ್ಟ್ ಆಗಿ ಆಫ್: ಆಪರೇಟರ್ ಕ್ರಮ ಕೈಗೊಳ್ಳುವವರೆಗೆ ಅಂತಹ ಸಂಪರ್ಕಗಳು ಆಫ್ ಆಗಿಯೇ ಇರುತ್ತವೆ. ಆಪರೇಟರ್ ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಿದ ಮತ್ತು ಶಾಶ್ವತವಾಗಿ ನಿಷೇಧಿಸಿದ ಸಂಪರ್ಕಗಳನ್ನು ಎಂದಿಗೂ ಮರುಪರೀಕ್ಷಿಸಲಾಗುವುದಿಲ್ಲ.", diff --git a/src/i18n/messages/ko.json b/src/i18n/messages/ko.json index ea108f4d26bf..ed5254ced1e6 100644 --- a/src/i18n/messages/ko.json +++ b/src/i18n/messages/ko.json @@ -1028,7 +1028,6 @@ "featureFlagOpencodeV2ClientFingerprintEnabledDescription": "OpenCode Zen/Go 요청에 OpenCode v2 CLI 클라이언트 핑거프린트를 전송합니다. 즉, opencode/latest/<2.x>/cli User-Agent와 x-opencode-client cli를 전송하고, 무료 티어 요청에는 v2 세션 헤더와 v2 CLI read/shell 도구 스키마를 사용하며 tool_choice \"none\"은 사용하지 않습니다. 기본값은 꺼짐입니다. 모든 OpenCode 요청은 현재 식별 정보, 헤더 및 본문을 변경 없이 유지합니다.", "featureFlagComboEmptyPoolNoEligibleTargetsEnabledDescription": "도구 호출 사전 필터가 실행되기 전에 자동 콤보의 후보 풀이 이미 비어 있는 경우(쿨다운, 잠금 또는 소진으로 인해 모든 대상이 제외된 경우), 기존의 400 \"도구 호출을 지원하는 대상이 없습니다\" capability_mismatch 대신 503 no_eligible_targets로 응답합니다. 기본값은 꺼짐이며, 후보 풀이 비어 있으면 기존 400 응답이 유지됩니다.", "featureFlagModelEndpointCapabilityGateEnabledDescription": "신뢰할 수 있는 모델 엔드포인트 태그(프로바이더 카탈로그에서 선언되거나, 특수 모델 ID에서 추론되거나, 운영자가 설정한 태그)를 적용합니다. 모델이 제공하지 않는 엔드포인트로 전송된 요청은 /v1/chat/completions, /v1/embeddings, /v1/rerank, /v1/images/generations, /v1/videos/generations, /v1/music/generations 및 /v1/audio/*에서 디스패치 전에 HTTP 400으로 거부합니다. 태그가 지정된 엔드포인트에 OmniRoute 어댑터가 없는 모델은 /v1/models에서 숨기고, 해당 모델에 대한 대시보드 모델 테스트에는 422로 응답합니다. 기본적으로 꺼져 있습니다. 엔드포인트 태그는 정보 제공용으로 유지되며 요청은 이전과 정확히 동일하게 디스패치됩니다.", - "featureFlagAnthropicCompatibleMidConversationSystemDescription": "일반 Anthropic 호환 릴레이에서는 대화 중간의 시스템 턴을 최상위 시스템 프롬프트로 옮기지 않고 메시지 목록 안에 유지하여, 캐시된 프롬프트 접두사가 여러 턴에 걸쳐 안정적으로 유지되도록 합니다. 클라이언트 자체에서 mid-conversation-system-2026-04-07 베타를 전송한 경우에만 적용됩니다. 기본값은 꺼짐입니다. 해당 베타를 지원하지 않는 릴레이는 메시지 목록의 시스템 턴을 거부하므로, 이전과 동일하게 모든 시스템 턴이 시스템 프롬프트로 이동됩니다.", "featureFlagModelsDevEffortTiersEnabledDescription": "운영자 재정의나 제공자 레지스트리에 추론 노력 단계가 선언되어 있지 않은 경우 models.dev에서 게시한 단계를 사용하되, 알려진 노력 수준만 유지하고 선언된 단계 목록이 비어 있으면 \"단계 없음\"으로 처리합니다. 기본값은 꺼짐입니다. 모델 카탈로그와 확인된 노력 단계는 이전과 정확히 동일하게 유지됩니다.", "featureFlagAutoRequestResolvedCapabilitiesDescription": "auto/* 요청이 확인된 모델 기능(비전, 추론, 사고)과 관측된 품질 점수를 한 번의 일괄 읽기로 불러와 후보 풀을 구성하도록 합니다. 기본값은 꺼짐: 요청 시점의 후보 가중치는 이전과 정확히 동일하게 유지됩니다. 켜짐: 이 신호들이 스냅샷 가중치에 반영되므로 후보 가중치와 선택이 달라질 수 있습니다.", "featureFlagCredentialHealthRetestAutoDisabledDescription": "자격 증명 상태 점검에서 게이트웨이가 자체적으로 비활성화한 연결을 다시 테스트하고(각 연결당 최대 6시간에 한 번, 점검당 5개), 재테스트를 통과하면 다시 활성화하도록 허용합니다. 기본값은 꺼짐입니다. 이 경우 운영자가 조치할 때까지 해당 연결은 비활성화 상태로 유지됩니다. 운영자가 비활성화했거나 영구 차단된 연결은 재테스트하지 않습니다.", diff --git a/src/i18n/messages/lt.json b/src/i18n/messages/lt.json index cd63fc8b2863..d1e07cb33666 100644 --- a/src/i18n/messages/lt.json +++ b/src/i18n/messages/lt.json @@ -1028,7 +1028,6 @@ "featureFlagOpencodeV2ClientFingerprintEnabledDescription": "Siųsti „OpenCode v2“ CLI kliento identifikavimo duomenis „OpenCode Zen/Go“ užklausose: opencode/latest/<2.x>/cli User-Agent ir x-opencode-client cli, o nemokamo plano užklausose – v2 seanso antraštes, v2 CLI read/shell įrankių schemas ir be tool_choice \"none\". Pagal numatytuosius nustatymus išjungta: kiekvienos „OpenCode“ užklausos dabartinė tapatybė, antraštės ir turinys lieka nepakeisti.", "featureFlagComboEmptyPoolNoEligibleTargetsEnabledDescription": "Kai automatinio derinio kandidatų telkinys būna tuščias dar prieš įrankių iškvietimo pirminį filtravimą (visi tikslai praleisti dėl laukimo laikotarpių, blokavimų arba išnaudojimo), vietoj ankstesnės 400 klaidos „Nė vienas tikslas nepalaiko įrankių iškvietimo“ capability_mismatch grąžinti 503 no_eligible_targets. Pagal numatytuosius nustatymus išjungta: esant tuščiam telkiniui paliekama ankstesnė 400 klaida.", "featureFlagModelEndpointCapabilityGateEnabledDescription": "Taikyti patikimas modelio galinio taško žymas (deklaruotas teikėjo kataloge, nustatytas pagal specializuoto modelio ID arba nustatytas operatoriaus): prieš persiunčiant užklausą atmesti su HTTP 400, jei ji išsiųsta į galinį tašką, kurio modelis neaptarnauja, keliuose /v1/chat/completions, /v1/embeddings, /v1/rerank, /v1/images/generations, /v1/videos/generations, /v1/music/generations ir /v1/audio/*; nerodyti /v1/models modelių, kurių žymėtiems galiniams taškams nėra OmniRoute adapterio; ir į tokių modelių valdymo skydelio testą atsakyti kodu 422. Pagal numatytąsias nuostatas išjungta: galinio taško žymos lieka tik informacinės, o užklausos persiunčiamos lygiai taip pat kaip anksčiau.", - "featureFlagAnthropicCompatibleMidConversationSystemDescription": "Įprastuose su Anthropic suderinamuose perdavimo serveriuose pokalbio viduryje esančius sistemos pranešimus palikite pranešimų sąraše, užuot perkėlę juos į aukščiausio lygio sistemos raginimą, kad podėlyje saugomas raginimo prefiksas išliktų stabilus visuose pokalbio etapuose. Taikoma tik tada, kai pats klientas išsiuntė mid-conversation-system-2026-04-07 beta versiją. Pagal numatytuosius nustatymus išjungta: kiekvienas sistemos pranešimas perkeliamas į sistemos raginimą lygiai taip pat kaip anksčiau, nes šios beta versijos nepalaikantys perdavimo serveriai atmeta pranešimų sąraše esančius sistemos pranešimus.", "featureFlagModelsDevEffortTiersEnabledDescription": "Kai nei operatoriaus perrašymas, nei teikėjo registras nenurodo samprotavimo pastangų lygių, naudokite models.dev skelbiamus lygius, palikdami tik žinomus pastangų lygius, o tuščią nurodytų lygių sąrašą laikykite reikšme „lygių nėra“. Pagal numatytuosius nustatymus išjungta: modelių katalogas ir nustatyti pastangų lygiai išlieka visiškai tokie patys kaip anksčiau.", "featureFlagAutoRequestResolvedCapabilitiesDescription": "Leidžia auto/* užklausoms sudaryti kandidatų telkinį su išspręstomis modelių galimybėmis (regėjimas, samprotavimas, mąstymas) ir stebėtais kokybės balais, nuskaitytais vienu masiniu skaitymu. Pagal numatytuosius nustatymus išjungta: kandidatų svoriai užklausos metu lieka lygiai tokie patys kaip anksčiau. Įjungta: šie signalai patenka į momentinės kopijos svorius, todėl kandidatų svoriai ir pasirinkimas gali pasikeisti.", "featureFlagCredentialHealthRetestAutoDisabledDescription": "Leiskite prisijungimo duomenų būklės patikrai iš naujo išbandyti ryšius, kuriuos tinklų sietuvas išjungė automatiškai (kiekvieną ryšį ne dažniau kaip kartą per 6 valandas, po 5 per patikrą), ir vėl įjungti ryšį, jei pakartotinis bandymas sėkmingas. Pagal numatytuosius nustatymus išjungta: tokie ryšiai lieka išjungti, kol operatorius imasi veiksmų. Operatoriaus išjungti ir visam laikui užblokuoti ryšiai niekada pakartotinai nebandomi.", diff --git a/src/i18n/messages/lv.json b/src/i18n/messages/lv.json index 202f4972b311..bf10ec9e3a0a 100644 --- a/src/i18n/messages/lv.json +++ b/src/i18n/messages/lv.json @@ -1028,7 +1028,6 @@ "featureFlagOpencodeV2ClientFingerprintEnabledDescription": "OpenCode Zen/Go pieprasījumos sūtīt OpenCode v2 CLI klienta identifikācijas nospiedumu: opencode/latest/<2.x>/cli User-Agent un x-opencode-client cli, bet bezmaksas līmeņa pieprasījumos — v2 sesijas galvenes, v2 CLI read/shell rīku shēmas un bez tool_choice \"none\". Pēc noklusējuma izslēgts: katrs OpenCode pieprasījums saglabā pašreizējo identitāti, galvenes un pamattekstu nemainītu.", "featureFlagComboEmptyPoolNoEligibleTargetsEnabledDescription": "Ja automātiskās kombinācijas kandidātu kopa ir tukša jau pirms rīku izsaukšanas priekšfiltra izpildes (visi mērķi izlaisti gaidīšanas periodu, bloķējumu vai izsmelšanas dēļ), atgriezt 503 no_eligible_targets, nevis mantoto 400 \"Neviens mērķis neatbalsta rīku izsaukšanu\" capability_mismatch. Pēc noklusējuma izslēgts: tukšai kopai joprojām tiek atgriezts mantotais 400.", "featureFlagModelEndpointCapabilityGateEnabledDescription": "Ieviest uzticamo modeļu galapunktu tagu (kurus deklarējis pakalpojumu sniedzēja katalogs, kuri secināti no specializēta modeļa ID vai kurus iestatījis operators) obligātu pārbaudi: pirms nosūtīšanas noraidīt ar HTTP 400 pieprasījumu, kas nosūtīts uz galapunktu, kuru modelis neapkalpo, ceļos /v1/chat/completions, /v1/embeddings, /v1/rerank, /v1/images/generations, /v1/videos/generations, /v1/music/generations un /v1/audio/*; paslēpt no /v1/models modeļus, kuru atzīmētajiem galapunktiem nav OmniRoute adaptera; un informācijas paneļa modeļa testā šādiem modeļiem atbildēt ar 422. Pēc noklusējuma izslēgts: galapunktu tagi paliek informatīvi, un pieprasījumi tiek nosūtīti tieši tāpat kā iepriekš.", - "featureFlagAnthropicCompatibleMidConversationSystemDescription": "Vienkāršos ar anthropic saderīgos starpniekserveros sarunas vidū ievietotos sistēmas ziņojumus paturiet ziņojumu sarakstā, nevis pārvietojiet tos uz augstākā līmeņa sistēmas uzvedni, lai kešatmiņā saglabātais uzvednes prefikss starp sarunas posmiem paliktu nemainīgs. Attiecas tikai tad, ja klients pats ir nosūtījis mid-conversation-system-2026-04-07 beta. Pēc noklusējuma izslēgts: katrs sistēmas ziņojums tiek pārvietots uz sistēmas uzvedni tieši tāpat kā iepriekš, jo starpniekserveri bez šīs beta versijas noraida sistēmas ziņojumus ziņojumu sarakstā.", "featureFlagModelsDevEffortTiersEnabledDescription": "Izmantojiet models.dev publicētos spriešanas piepūles līmeņus, ja ne operatora pārrakstījums, ne pakalpojumu sniedzēju reģistrs tādus nedeklarē, saglabājot tikai zināmos piepūles līmeņus, un uzskatiet tukšu deklarēto līmeņu sarakstu par \"nav līmeņu\". Pēc noklusējuma izslēgts: modeļu katalogs un noteiktie piepūles līmeņi paliek tieši tādi paši kā iepriekš.", "featureFlagAutoRequestResolvedCapabilitiesDescription": "Ļauj auto/* pieprasījumiem veidot kandidātu kopu ar atrisinātām modeļu iespējām (redze, spriešana, domāšana) un novērotiem kvalitātes rādītājiem, kas nolasīti vienā lielapjoma nolasījumā. Pēc noklusējuma izslēgts: kandidātu svari pieprasījuma laikā paliek tieši tādi paši kā iepriekš. Ieslēgts: šie signāli tiek iekļauti momentuzņēmuma svaros, tāpēc kandidātu svari un izvēle var mainīties.", "featureFlagCredentialHealthRetestAutoDisabledDescription": "Ļaut akreditācijas datu stāvokļa pārbaudei atkārtoti testēt savienojumus, kurus vārteja pati izslēdza (katru ne biežāk kā reizi 6 stundās, 5 vienā pārbaudes reizē), un atkal ieslēgt savienojumu, ja atkārtotais tests ir sekmīgs. Pēc noklusējuma izslēgts: šādi savienojumi paliek izslēgti, līdz operators veic darbību. Operatora izslēgti un pastāvīgi bloķēti savienojumi nekad netiek atkārtoti testēti.", diff --git a/src/i18n/messages/ml.json b/src/i18n/messages/ml.json index 8184fc9d2275..bb9903a45796 100644 --- a/src/i18n/messages/ml.json +++ b/src/i18n/messages/ml.json @@ -1028,7 +1028,6 @@ "featureFlagOpencodeV2ClientFingerprintEnabledDescription": "OpenCode Zen/Go അഭ്യർത്ഥനകളിൽ OpenCode v2 CLI ക്ലയന്റ് ഫിംഗർപ്രിന്റ് അയയ്ക്കുക: opencode/latest/<2.x>/cli User-Agent, x-opencode-client cli എന്നിവയും, ഫ്രീ-ടയർ അഭ്യർത്ഥനകളിൽ v2 സെഷൻ ഹെഡറുകൾ, v2 CLI read/shell ടൂൾ സ്കീമകൾ എന്നിവയും അയയ്ക്കുക; tool_choice \"none\" അയയ്ക്കരുത്. ഡിഫോൾട്ടായി ഓഫാണ്: എല്ലാ OpenCode അഭ്യർത്ഥനകളും നിലവിലെ ഐഡന്റിറ്റി, ഹെഡറുകൾ, ബോഡി എന്നിവ മാറ്റമില്ലാതെ നിലനിർത്തുന്നു.", "featureFlagComboEmptyPoolNoEligibleTargetsEnabledDescription": "ടൂൾ-കോളിംഗ് പ്രീ-ഫിൽട്ടർ പ്രവർത്തിക്കുന്നതിന് മുമ്പുതന്നെ ഒരു auto combo-യുടെ കാൻഡിഡേറ്റ് പൂൾ ശൂന്യമാണെങ്കിൽ (കൂൾഡൗണുകൾ, ലോക്കൗട്ടുകൾ, അല്ലെങ്കിൽ ശേഷി തീർന്നത് കാരണം എല്ലാ ടാർഗെറ്റുകളും ഒഴിവാക്കിയിട്ടുണ്ടെങ്കിൽ), പഴയ 400 \"No target supports tool calling\" capability_mismatch-ന് പകരം 503 no_eligible_targets എന്ന മറുപടി നൽകുക. ഡിഫോൾട്ടായി ഓഫാണ്: ശൂന്യമായ പൂൾ പഴയ 400 നിലനിർത്തും.", "featureFlagModelEndpointCapabilityGateEnabledDescription": "വിശ്വസനീയമായ മോഡൽ എൻഡ്പോയിന്റ് ടാഗുകൾ (പ്രൊവൈഡർ കാറ്റലോഗ് പ്രഖ്യാപിച്ചതോ, ഒരു പ്രത്യേക മോഡൽ id-യിൽ നിന്ന് അനുമാനിച്ചതോ, അല്ലെങ്കിൽ ഓപ്പറേറ്റർ സജ്ജമാക്കിയതോ) നിർബന്ധമാക്കുക: /v1/chat/completions, /v1/embeddings, /v1/rerank, /v1/images/generations, /v1/videos/generations, /v1/music/generations, /v1/audio/* എന്നിവയിൽ മോഡൽ സേവനം നൽകാത്ത ഒരു എൻഡ്പോയിന്റിലേക്ക് അയച്ച അഭ്യർത്ഥന ഡിസ്പാച്ച് ചെയ്യുന്നതിന് മുമ്പ് HTTP 400 ഉപയോഗിച്ച് നിരസിക്കുക; ടാഗ് ചെയ്ത എൻഡ്പോയിന്റുകൾക്ക് OmniRoute അഡാപ്റ്റർ ഇല്ലാത്ത മോഡലുകൾ /v1/models-ൽ നിന്ന് മറയ്ക്കുക; അത്തരം മോഡലുകൾക്കായുള്ള ഡാഷ്ബോർഡ് മോഡൽ പരിശോധനയ്ക്ക് 422 ഉപയോഗിച്ച് മറുപടി നൽകുക. ഡിഫോൾട്ടായി ഓഫാണ്: എൻഡ്പോയിന്റ് ടാഗുകൾ വിവരദായകമായി തുടരുകയും അഭ്യർത്ഥനകൾ മുമ്പത്തേതുപോലെ തന്നെ ഡിസ്പാച്ച് ചെയ്യപ്പെടുകയും ചെയ്യും.", - "featureFlagAnthropicCompatibleMidConversationSystemDescription": "സാധാരണ anthropic-അനുയോജ്യമായ റിലേകളിൽ, സംഭാഷണത്തിനിടയിലെ system ടേണുകളെ ഉയർന്ന തലത്തിലുള്ള system prompt-ലേക്ക് മാറ്റുന്നതിനുപകരം സന്ദേശപ്പട്ടികയ്ക്കുള്ളിൽ തന്നെ നിലനിർത്തുക; ഇതുവഴി കാഷ് ചെയ്ത prompt prefix ടേണുകളിലുടനീളം സ്ഥിരമായി തുടരും. ക്ലയന്റ് തന്നെ mid-conversation-system-2026-04-07 ബീറ്റ അയച്ചപ്പോൾ മാത്രമാണ് ഇത് ബാധകമാകുന്നത്. ഡിഫോൾട്ടായി ഓഫാണ്: ആ ബീറ്റയില്ലാത്ത റിലേകൾ സന്ദേശപ്പട്ടികയിലെ system ടേണുകൾ നിരസിക്കുന്നതിനാൽ, മുമ്പത്തെപ്പോലെതന്നെ ഓരോ system ടേണും system prompt-ലേക്ക് മാറ്റപ്പെടും.", "featureFlagModelsDevEffortTiersEnabledDescription": "ഓപ്പറേറ്റർ ഓവർറൈഡോ പ്രൊവൈഡർ രജിസ്ട്രിയോ ഒന്നും പ്രഖ്യാപിക്കാത്തപ്പോൾ models.dev പ്രസിദ്ധീകരിക്കുന്ന റീസണിംഗ് എഫർട്ട് ടിയറുകൾ ഉപയോഗിക്കുക, അറിയപ്പെടുന്ന എഫർട്ട് ലെവലുകൾ മാത്രം നിലനിർത്തുക, കൂടാതെ ശൂന്യമായി പ്രഖ്യാപിച്ച ടിയർ ലിസ്റ്റിനെ \"ടിയറുകളൊന്നുമില്ല\" എന്ന് കണക്കാക്കുക. ഡിഫോൾട്ടായി ഓഫാണ്: മോഡൽ കാറ്റലോഗും നിർണ്ണയിക്കപ്പെട്ട എഫർട്ട് ടിയറുകളും മുമ്പുണ്ടായിരുന്നതുപോലെ തന്നെ തുടരും.", "featureFlagAutoRequestResolvedCapabilitiesDescription": "auto/* അഭ്യർത്ഥനകളെ പരിഹരിച്ച മോഡൽ കഴിവുകളും (വിഷൻ, റീസണിംഗ്, തിങ്കിംഗ്) നിരീക്ഷിച്ച ഗുണനിലവാര സ്കോറുകളും ഉപയോഗിച്ച്, ഒരൊറ്റ ബൾക്ക് റീഡിൽ, കാൻഡിഡേറ്റ് പൂൾ നിർമ്മിക്കാൻ അനുവദിക്കുന്നു. സ്ഥിരമായി ഓഫ്: അഭ്യർത്ഥന സമയത്തെ കാൻഡിഡേറ്റ് വെയ്റ്റുകൾ മുമ്പത്തെപ്പോലെ തന്നെ തുടരും. ഓൺ ആയാൽ: ഈ സിഗ്നലുകൾ സ്നാപ്ഷോട്ട് വെയ്റ്റുകളിലേക്ക് ചേരുന്നു, അതിനാൽ കാൻഡിഡേറ്റ് വെയ്റ്റുകളും തിരഞ്ഞെടുപ്പും മാറാം.", "featureFlagCredentialHealthRetestAutoDisabledDescription": "ഗേറ്റ്വേ സ്വയം ഓഫാക്കിയ കണക്ഷനുകൾ ക്രെഡൻഷ്യൽ ആരോഗ്യ പരിശോധന വീണ്ടും പരിശോധിക്കാനും (ഓരോന്നും പരമാവധി 6 മണിക്കൂറിൽ ഒരിക്കൽ, ഓരോ പരിശോധനയിലും 5 എണ്ണം), പുനഃപരിശോധന വിജയിക്കുമ്പോൾ അവയിൽ ഒന്നിനെ വീണ്ടും ഓണാക്കാനും അനുവദിക്കുക. ഡിഫോൾട്ടായി ഓഫ്: ഒരു ഓപ്പറേറ്റർ നടപടി എടുക്കുന്നതുവരെ അത്തരം കണക്ഷനുകൾ ഓഫായിത്തന്നെ തുടരും. ഓപ്പറേറ്റർ പ്രവർത്തനരഹിതമാക്കിയതും ശാശ്വതമായി നിരോധിച്ചതുമായ കണക്ഷനുകൾ ഒരിക്കലും വീണ്ടും പരിശോധിക്കില്ല.", diff --git a/src/i18n/messages/mr.json b/src/i18n/messages/mr.json index afd686396d0e..693568195776 100644 --- a/src/i18n/messages/mr.json +++ b/src/i18n/messages/mr.json @@ -1028,7 +1028,6 @@ "featureFlagOpencodeV2ClientFingerprintEnabledDescription": "OpenCode Zen/Go विनंत्यांवर OpenCode v2 CLI क्लायंट फिंगरप्रिंट पाठवा: opencode/latest/<2.x>/cli User-Agent आणि x-opencode-client cli, तसेच मोफत-स्तरावरील विनंत्यांवर v2 सत्र हेडर्स, v2 CLI read/shell टूल स्कीमा आणि tool_choice \"none\" नाही. डीफॉल्टनुसार बंद: प्रत्येक OpenCode विनंतीची सध्याची ओळख, हेडर्स आणि बॉडी अपरिवर्तित राहतात.", "featureFlagComboEmptyPoolNoEligibleTargetsEnabledDescription": "टूल-कॉलिंग प्री-फिल्टर चालण्यापूर्वीच ऑटो कॉम्बोचा उमेदवार पूल रिकामा असल्यास (कूलडाउन, लॉकआउट किंवा उपलब्धता संपल्यामुळे प्रत्येक लक्ष्य वगळले असल्यास), जुन्या 400 \"No target supports tool calling\" capability_mismatch ऐवजी 503 no_eligible_targets उत्तर द्या. डीफॉल्टनुसार बंद: रिकामा पूल जुना 400 प्रतिसाद कायम ठेवतो.", "featureFlagModelEndpointCapabilityGateEnabledDescription": "विश्वसनीय मॉडेल एंडपॉइंट टॅगची (प्रदाता कॅटलॉगद्वारे घोषित केलेले, विशेष मॉडेल id वरून अनुमानित केलेले किंवा ऑपरेटरने सेट केलेले) सक्ती करा: मॉडेल सेवा देत नसलेल्या एंडपॉइंटवर पाठवलेली विनंती /v1/chat/completions, /v1/embeddings, /v1/rerank, /v1/images/generations, /v1/videos/generations, /v1/music/generations आणि /v1/audio/* वर डिस्पॅच करण्यापूर्वी HTTP 400 सह नाकारा; ज्यांच्या टॅग केलेल्या एंडपॉइंटसाठी OmniRoute अडॅप्टर नाही अशी मॉडेल्स /v1/models मधून लपवा; आणि अशा मॉडेल्ससाठी डॅशबोर्ड मॉडेल चाचणीला 422 ने उत्तर द्या. डीफॉल्टनुसार बंद: एंडपॉइंट टॅग केवळ माहितीपर राहतात आणि विनंत्या पूर्वीप्रमाणेच डिस्पॅच केल्या जातात.", - "featureFlagAnthropicCompatibleMidConversationSystemDescription": "साध्या anthropic-सुसंगत रिलेवर, संभाषणाच्या मध्यातील सिस्टम टर्न्स शीर्ष-स्तरीय सिस्टम प्रॉम्प्टमध्ये हलवण्याऐवजी संदेश सूचीमध्येच ठेवा, जेणेकरून कॅश केलेला प्रॉम्प्ट उपसर्ग सर्व टर्न्समध्ये स्थिर राहील. हे केवळ क्लायंटने स्वतः mid-conversation-system-2026-04-07 बीटा पाठवला असेल तेव्हाच लागू होते. डीफॉल्टनुसार बंद: पूर्वीप्रमाणे प्रत्येक सिस्टम टर्न सिस्टम प्रॉम्प्टमध्ये हलवला जातो, कारण त्या बीटाशिवायचे रिले संदेश सूचीतील सिस्टम टर्न्स नाकारतात.", "featureFlagModelsDevEffortTiersEnabledDescription": "ऑपरेटर ओव्हरराइड किंवा प्रदाता रजिस्ट्री यांपैकी कोणीही रीझनिंग एफर्ट टियर्स घोषित केले नसल्यास, models.dev ने प्रकाशित केलेले टियर्स वापरा, फक्त ज्ञात एफर्ट पातळ्याच ठेवा आणि घोषित केलेली रिकामी टियर सूची म्हणजे \"कोणतेही टियर्स नाहीत\" असे समजा. डीफॉल्टनुसार बंद: मॉडेल कॅटलॉग आणि निर्धारित एफर्ट टियर्स पूर्वीप्रमाणेच राहतात.", "featureFlagAutoRequestResolvedCapabilitiesDescription": "auto/* विनंत्यांना निराकरण केलेल्या मॉडेल क्षमता (व्हिजन, रीझनिंग, थिंकिंग) आणि निरीक्षित गुणवत्ता स्कोअरसह, एकाच बल्क वाचनात, उमेदवार पूल तयार करू देते. डीफॉल्टनुसार बंद: विनंतीच्या वेळचे उमेदवार वजन अगदी पूर्वीसारखेच राहते. चालू असताना: हे संकेत स्नॅपशॉट वजनांमध्ये जातात, त्यामुळे उमेदवार वजन आणि निवड बदलू शकते.", "featureFlagCredentialHealthRetestAutoDisabledDescription": "क्रेडेन्शियल आरोग्य तपासणीला गेटवेने स्वतःहून बंद केलेल्या कनेक्शनची पुन्हा चाचणी करू द्या (प्रत्येक कनेक्शनची जास्तीत जास्त दर 6 तासांतून एकदा, प्रत्येक तपासणीत 5 कनेक्शन) आणि पुनर्चाचणी यशस्वी झाल्यास ते कनेक्शन पुन्हा सुरू करू द्या. हे डीफॉल्टनुसार बंद असते: ऑपरेटर कारवाई करेपर्यंत अशी कनेक्शन बंदच राहतात. ऑपरेटरने बंद केलेल्या आणि कायमस्वरूपी प्रतिबंधित केलेल्या कनेक्शनची कधीही पुन्हा चाचणी केली जात नाही.", diff --git a/src/i18n/messages/ms.json b/src/i18n/messages/ms.json index 71494d4bf3f1..2cc3f980ae05 100644 --- a/src/i18n/messages/ms.json +++ b/src/i18n/messages/ms.json @@ -1028,7 +1028,6 @@ "featureFlagOpencodeV2ClientFingerprintEnabledDescription": "Hantar cap jari klien CLI OpenCode v2 pada permintaan OpenCode Zen/Go: User-Agent opencode/latest/<2.x>/cli dan x-opencode-client cli, serta pada permintaan peringkat percuma, pengepala sesi v2, skema alat baca/shell CLI v2 dan tanpa tool_choice \"none\". Dimatikan secara lalai: setiap permintaan OpenCode mengekalkan identiti, pengepala dan isi semasa tanpa perubahan.", "featureFlagComboEmptyPoolNoEligibleTargetsEnabledDescription": "Apabila kumpulan calon kombo automatik sudah kosong sebelum pra-penapis pemanggilan alat dijalankan (setiap sasaran dilangkau disebabkan tempoh bertenang, sekatan atau kehabisan), berikan respons 503 no_eligible_targets dan bukannya respons 400 legasi \"Tiada sasaran menyokong pemanggilan alat\" capability_mismatch. Dilumpuhkan secara lalai: kumpulan kosong mengekalkan respons 400 legasi.", "featureFlagModelEndpointCapabilityGateEnabledDescription": "Kuatkuasakan teg titik akhir model yang dipercayai (diisytiharkan oleh katalog penyedia, disimpulkan daripada ID model khusus, atau ditetapkan oleh pengendali): tolak permintaan yang dihantar ke titik akhir yang tidak disokong oleh model dengan HTTP 400 sebelum penghantaran pada /v1/chat/completions, /v1/embeddings, /v1/rerank, /v1/images/generations, /v1/videos/generations, /v1/music/generations dan /v1/audio/*; sembunyikan model yang titik akhir bertegnya tidak mempunyai penyesuai OmniRoute daripada /v1/models; dan balas ujian model papan pemuka dengan 422 untuk model sedemikian. Dimatikan secara lalai: teg titik akhir kekal sebagai maklumat dan permintaan dihantar tepat seperti sebelumnya.", - "featureFlagAnthropicCompatibleMidConversationSystemDescription": "Pada geganti biasa yang serasi dengan anthropic, kekalkan giliran sistem di pertengahan perbualan dalam senarai mesej dan bukannya memindahkannya ke gesaan sistem peringkat teratas, supaya awalan gesaan yang dicache kekal stabil merentas giliran. Hanya terpakai apabila klien itu sendiri menghantar beta mid-conversation-system-2026-04-07. Dilumpuhkan secara lalai: setiap giliran sistem dipindahkan ke dalam gesaan sistem sama seperti sebelum ini, kerana geganti tanpa beta tersebut menolak giliran sistem dalam senarai mesej.", "featureFlagModelsDevEffortTiersEnabledDescription": "Gunakan peringkat usaha penaakulan yang diterbitkan oleh models.dev apabila penggantian operator mahupun daftar penyedia tidak mengisytiharkan sebarang peringkat, dengan hanya mengekalkan tahap usaha yang diketahui, dan anggap senarai peringkat yang diisytiharkan tetapi kosong sebagai \"tiada peringkat\". Dilumpuhkan secara lalai: katalog model dan peringkat usaha yang ditentukan kekal sama seperti sebelumnya.", "featureFlagAutoRequestResolvedCapabilitiesDescription": "Membenarkan permintaan auto/* membina kumpulan calon dengan keupayaan model yang telah diselesaikan (penglihatan, penaakulan, pemikiran) dan skor kualiti yang diperhatikan, dibaca dalam satu bacaan pukal. Dimatikan secara lalai: pemberat calon pada masa permintaan kekal sama seperti sebelum ini. Dihidupkan: isyarat tersebut dimasukkan ke dalam pemberat snapshot, jadi pemberat calon dan pemilihan boleh berubah.", "featureFlagCredentialHealthRetestAutoDisabledDescription": "Benarkan imbasan kesihatan kelayakan menguji semula sambungan yang dimatikan sendiri oleh get laluan (paling banyak sekali setiap 6 jam bagi setiap sambungan, 5 bagi setiap imbasan) dan menghidupkan semula sambungan apabila ujian semulanya berjaya. Dilumpuhkan secara lalai: sambungan sedemikian kekal dimatikan sehingga pengendali mengambil tindakan. Sambungan yang dilumpuhkan oleh pengendali dan dilarang secara kekal tidak akan diuji semula.", diff --git a/src/i18n/messages/mt.json b/src/i18n/messages/mt.json index c44f0f1c2405..88573f77f0ac 100644 --- a/src/i18n/messages/mt.json +++ b/src/i18n/messages/mt.json @@ -1028,7 +1028,6 @@ "featureFlagOpencodeV2ClientFingerprintEnabledDescription": "Ibgħat il-marki ta’ identifikazzjoni tal-klijent OpenCode v2 CLI fuq it-talbiet OpenCode Zen/Go: il-User-Agent opencode/latest/<2.x>/cli u x-opencode-client cli, u fuq it-talbiet tal-livell bla ħlas, l-intestaturi tas-sessjoni v2, l-iskemi tal-għodod read/shell tal-v2 u l-ebda tool_choice \"none\". Mitfi b’mod awtomatiku: kull talba OpenCode żżomm l-identità, l-intestaturi u l-korp attwali mingħajr tibdil.", "featureFlagComboEmptyPoolNoEligibleTargetsEnabledDescription": "Meta l-ġabra ta’ kandidati ta’ combo awtomatiku tkun diġà vojta qabel ma jitħaddem il-pre-filter tas-sejħa tal-għodod (kull mira tkun inqabżet minħabba cooldowns, lockouts jew eżawriment), wieġeb b’503 no_eligible_targets minflok il-400 legat \"L-ebda mira ma tappoġġja s-sejħa tal-għodod\" capability_mismatch. Mitfi b’mod awtomatiku: il-ġabra vojta żżomm il-400 legat.", "featureFlagModelEndpointCapabilityGateEnabledDescription": "Infurza t-tikketti fdati tal-punti finali tal-mudell (iddikjarati mill-katalgu tal-fornitur, inferiti minn ID ta’ mudell speċjalizzat, jew issettjati mill-operatur): irrifjuta b’HTTP 400, qabel id-dispaċċ, talba mibgħuta lil punt finali li l-mudell ma jservix fuq /v1/chat/completions, /v1/embeddings, /v1/rerank, /v1/images/generations, /v1/videos/generations, /v1/music/generations u /v1/audio/*; aħbi minn /v1/models il-mudelli li l-punti finali ttikkettati tagħhom ma għandhomx adattatur OmniRoute; u wieġeb b’422 għat-test tal-mudell tad-dashboard għal mudelli bħal dawn. Mitfi b’mod awtomatiku: it-tikketti tal-punti finali jibqgħu informattivi u t-talbiet jintbagħtu eżattament bħal qabel.", - "featureFlagAnthropicCompatibleMidConversationSystemDescription": "Fuq relays sempliċi kompatibbli ma’ Anthropic, żomm it-turns tas-sistema f’nofs il-konverżazzjoni fil-lista tal-messaġġi minflok tmexxihom lejn il-prompt tas-sistema fl-ogħla livell, sabiex il-prefiss tal-prompt fil-cache jibqa’ stabbli bejn it-turns. Dan japplika biss meta l-klijent innifsu jkun bagħat il-beta mid-conversation-system-2026-04-07. Diżattivat b’mod awtomatiku: kull turn tas-sistema jitmexxa lejn il-prompt tas-sistema eżattament bħal qabel, għax relays mingħajr dik il-beta jirrifjutaw turns tas-sistema fil-lista tal-messaġġi.", "featureFlagModelsDevEffortTiersEnabledDescription": "Uża l-livelli ta’ sforz ta’ raġunament li jippubblika models.dev meta la override tal-operatur u lanqas ir-reġistru tal-fornitur ma jiddikjaraw livelli, filwaqt li żżomm biss livelli ta’ sforz magħrufa, u tqis lista ddikjarata vojta ta’ livelli bħala \"l-ebda livell\". Diżattivat b’mod awtomatiku: il-katalgu tal-mudelli u l-livelli ta’ sforz riżolti jibqgħu eżatt kif kienu qabel.", "featureFlagAutoRequestResolvedCapabilitiesDescription": "Jippermetti lit-talbiet auto/* jibnu l-grupp ta' kandidati b'kapaċitajiet tal-mudell solvuti (viżjoni, raġunament, ħsieb) u punteġġi tal-kwalità osservati, moqrija f'qari wieħed bil-massa. Mitfi b'mod awtomatiku: il-piżijiet tal-kandidati fil-ħin tat-talba jibqgħu eżattament bħal qabel. Mixgħul: dawk is-sinjali jidħlu fil-piżijiet tal-istantanja, għalhekk il-piżijiet tal-kandidati u l-għażla jistgħu jinbidlu.", "featureFlagCredentialHealthRetestAutoDisabledDescription": "Ħalli l-iskann tal-istat tal-kredenzjali jerġa’ jittestja l-konnessjonijiet li l-gateway ikun tefa waħdu (mhux aktar minn darba kull 6 sigħat għal kull waħda, 5 għal kull skann) u jerġa’ jixgħel konnessjoni meta t-test mill-ġdid tagħha jirnexxi. Mitfi awtomatikament: konnessjonijiet bħal dawn jibqgħu mitfija sakemm jieħu azzjoni operatur. Konnessjonijiet diżattivati minn operatur u dawk ipprojbiti b’mod permanenti qatt ma jerġgħu jiġu ttestjati.", diff --git a/src/i18n/messages/my.json b/src/i18n/messages/my.json index 4da492fda48b..39180f96eb3b 100644 --- a/src/i18n/messages/my.json +++ b/src/i18n/messages/my.json @@ -1028,7 +1028,6 @@ "featureFlagOpencodeV2ClientFingerprintEnabledDescription": "OpenCode Zen/Go တောင်းဆိုမှုများတွင် OpenCode v2 CLI client fingerprint ကို ပေးပို့ပါ- opencode/latest/<2.x>/cli User-Agent နှင့် x-opencode-client cli ကို ပေးပို့ပြီး၊ free-tier တောင်းဆိုမှုများတွင် v2 session headers၊ v2 CLI read/shell tool schemas တို့ကို ပေးပို့ကာ tool_choice \"none\" ကို မသုံးပါ။ မူလအားဖြင့် ပိတ်ထားသည်- OpenCode တောင်းဆိုမှုတိုင်း၏ လက်ရှိ identity၊ headers နှင့် body ကို မပြောင်းလဲဘဲ ထားရှိသည်။", "featureFlagComboEmptyPoolNoEligibleTargetsEnabledDescription": "ကိရိယာခေါ်ယူမှု ကြိုတင်စစ်ထုတ်ခြင်း မလုပ်ဆောင်မီ auto combo တစ်ခု၏ ကိုယ်စားလှယ်လောင်းအစုသည် ဗလာဖြစ်နေပြီးသားဖြစ်ပါက (cooldown၊ lockout သို့မဟုတ် အသုံးပြုနိုင်မှု ကုန်ဆုံးခြင်းကြောင့် ပစ်မှတ်တိုင်းကို ကျော်သွားသောအခါ) ယခင် 400 \"မည်သည့်ပစ်မှတ်ကမျှ ကိရိယာခေါ်ယူမှုကို မပံ့ပိုးပါ\" capability_mismatch အစား 503 no_eligible_targets ဖြင့် တုံ့ပြန်ပါ။ မူလအတိုင်း ပိတ်ထားသည်- အစုဗလာဖြစ်နေပါက ယခင် 400 ကို ဆက်လက်အသုံးပြုသည်။", "featureFlagModelEndpointCapabilityGateEnabledDescription": "ယုံကြည်ရသော မော်ဒယ် endpoint တဂ်များ (ပံ့ပိုးသူ catalog က ကြေညာထားသည့်၊ အထူးပြု မော်ဒယ် ID တစ်ခုမှ ကောက်ချက်ချထားသည့် သို့မဟုတ် အော်ပရေတာက သတ်မှတ်ထားသည့် တဂ်များ) ကို မဖြစ်မနေ လိုက်နာစေပါ- မော်ဒယ်က ပံ့ပိုးမထားသော endpoint သို့ ပေးပို့သည့် တောင်းဆိုမှုကို /v1/chat/completions၊ /v1/embeddings၊ /v1/rerank၊ /v1/images/generations၊ /v1/videos/generations၊ /v1/music/generations နှင့် /v1/audio/* တို့တွင် dispatch မလုပ်မီ HTTP 400 ဖြင့် ပယ်ချပါ၊ တဂ်ထားသော endpoint များအတွက် OmniRoute adapter မရှိသည့် မော်ဒယ်များကို /v1/models မှ ဖျောက်ထားပါ၊ ထိုကဲ့သို့သော မော်ဒယ်များအတွက် dashboard မော်ဒယ်စမ်းသပ်မှုကို 422 ဖြင့် တုံ့ပြန်ပါ။ မူလအားဖြင့် ပိတ်ထားသည်- endpoint တဂ်များသည် အချက်အလက်ပေးရန်သာ ဆက်လက်အသုံးပြုပြီး တောင်းဆိုမှုများကို ယခင်အတိုင်း အတိအကျ dispatch လုပ်ပါသည်။", - "featureFlagAnthropicCompatibleMidConversationSystemDescription": "ရိုးရိုး anthropic-compatible relay များတွင် cached prompt prefix သည် turn များတစ်လျှောက် တည်ငြိမ်နေစေရန်၊ စကားဝိုင်းအလယ်ရှိ system turn များကို ထိပ်ဆုံးအဆင့် system prompt ထဲသို့ ရွှေ့မည့်အစား message list အတွင်းတွင်သာ ထားပါ။ client ကိုယ်တိုင် mid-conversation-system-2026-04-07 beta ကို ပေးပို့ထားသည့်အခါတွင်သာ သက်ရောက်သည်။ မူလအားဖြင့် ပိတ်ထားသည်။ ထို beta ကို မပံ့ပိုးသော relay များသည် message list ထဲရှိ system turn များကို ငြင်းပယ်သောကြောင့်၊ ယခင်အတိုင်း system turn တိုင်းကို system prompt ထဲသို့ ရွှေ့ပေးသည်။", "featureFlagModelsDevEffortTiersEnabledDescription": "အော်ပရေတာ၏ အစားထိုးသတ်မှတ်ချက် သို့မဟုတ် ဝန်ဆောင်မှုပေးသူ registry တို့က အဆင့်များကို မကြေညာထားသည့်အခါ models.dev က ထုတ်ပြန်ထားသော reasoning effort အဆင့်များကို အသုံးပြုပါ။ သိရှိထားသော effort အဆင့်များကိုသာ ထားရှိပြီး၊ ကြေညာထားသော အဆင့်စာရင်းအလွတ်ကို \"အဆင့်မရှိ\" ဟု သတ်မှတ်ပါ။ မူလအားဖြင့် ပိတ်ထားသည်- မော်ဒယ် catalog နှင့် ဖြေရှင်းသတ်မှတ်ပြီးသော effort အဆင့်များသည် ယခင်အတိုင်း အတိအကျ ဆက်ရှိနေမည်။", "featureFlagAutoRequestResolvedCapabilitiesDescription": "auto/* တောင်းဆိုမှုများကို ဖြေရှင်းပြီးသော မော်ဒယ်စွမ်းရည်များ (ရူပါရုံ၊ ဆင်ခြင်ခြင်း၊ စဉ်းစားခြင်း) နှင့် တွေ့ရှိရသော အရည်အသွေးရမှတ်များဖြင့် တစ်ကြိမ်တည်း အစုလိုက်ဖတ်ယူ၍ ကိုယ်စားလှယ်အုပ်စုကို တည်ဆောက်ခွင့်ပြုသည်။ မူလအားဖြင့် ပိတ်ထားသည်- တောင်းဆိုချိန်ရှိ ကိုယ်စားလှယ်အလေးချိန်များသည် ယခင်အတိုင်း အတိအကျ ရှိနေသည်။ ဖွင့်ထားလျှင်- ထိုအချက်ပြမှုများသည် snapshot အလေးချိန်များထဲသို့ ဝင်ရောက်သောကြောင့် ကိုယ်စားလှယ်အလေးချိန်များနှင့် ရွေးချယ်မှု ပြောင်းလဲနိုင်သည်။", "featureFlagCredentialHealthRetestAutoDisabledDescription": "အထောက်အထား အခြေအနေစစ်ဆေးမှုက gateway မှ အလိုအလျောက် ပိတ်ထားသော ချိတ်ဆက်မှုများကို ပြန်လည်စမ်းသပ်ခွင့်ပြုပါ (တစ်ခုစီကို ၆ နာရီလျှင် အများဆုံးတစ်ကြိမ်၊ စစ်ဆေးမှုတစ်ကြိမ်လျှင် ၅ ခု)၊ ပြန်လည်စမ်းသပ်မှု အောင်မြင်ပါက ယင်းချိတ်ဆက်မှုကို ပြန်ဖွင့်ပါ။ မူလအတိုင်း ပိတ်ထားသည်- ထိုချိတ်ဆက်မှုများသည် အော်ပရေတာက လုပ်ဆောင်ချက်တစ်ခု မပြုလုပ်မချင်း ပိတ်ထားမည်ဖြစ်သည်။ အော်ပရေတာက ပိတ်ထားသော ချိတ်ဆက်မှုများနှင့် အပြီးအပိုင် ပိတ်ပင်ထားသော ချိတ်ဆက်မှုများကို မည်သည့်အခါမျှ ပြန်လည်စမ်းသပ်မည်မဟုတ်ပါ။", diff --git a/src/i18n/messages/ne.json b/src/i18n/messages/ne.json index 273e3a260524..038ad1f850e1 100644 --- a/src/i18n/messages/ne.json +++ b/src/i18n/messages/ne.json @@ -1028,7 +1028,6 @@ "featureFlagOpencodeV2ClientFingerprintEnabledDescription": "OpenCode Zen/Go अनुरोधहरूमा OpenCode v2 CLI क्लाइन्ट फिङ्गरप्रिन्ट पठाउनुहोस्: opencode/latest/<2.x>/cli User-Agent र x-opencode-client cli, तथा निःशुल्क-टियर अनुरोधहरूमा v2 सत्र हेडरहरू, v2 CLI read/shell उपकरण स्किमाहरू र tool_choice \"none\" नपठाउनुहोस्। पूर्वनिर्धारित रूपमा बन्द: प्रत्येक OpenCode अनुरोधले हालको पहिचान, हेडरहरू र बडीलाई अपरिवर्तित राख्छ।", "featureFlagComboEmptyPoolNoEligibleTargetsEnabledDescription": "tool-calling pre-filter चल्नुअघि नै auto combo को candidate pool खाली भएमा (cooldown, lockout वा exhaustion का कारण प्रत्येक target छोडिएको अवस्थामा), पुरानो 400 \"No target supports tool calling\" capability_mismatch को सट्टा 503 no_eligible_targets जवाफ दिनुहोस्। पूर्वनिर्धारित रूपमा बन्द: खाली pool ले पुरानै 400 कायम राख्छ।", "featureFlagModelEndpointCapabilityGateEnabledDescription": "विश्वसनीय मोडेल एन्डपोइन्ट ट्यागहरू (प्रदायक क्याटलगद्वारा घोषित, विशेष मोडेल ID बाट अनुमानित, वा अपरेटरद्वारा सेट गरिएका) लागू गर्नुहोस्: मोडेलले सेवा नदिने एन्डपोइन्टमा पठाइएको अनुरोधलाई /v1/chat/completions, /v1/embeddings, /v1/rerank, /v1/images/generations, /v1/videos/generations, /v1/music/generations र /v1/audio/* मा डिस्प्याच गर्नुअघि HTTP 400 सहित अस्वीकार गर्नुहोस्; ट्याग गरिएका एन्डपोइन्टहरूका लागि OmniRoute एडाप्टर नभएका मोडेलहरूलाई /v1/models बाट लुकाउनुहोस्; र त्यस्ता मोडेलहरूको ड्यासबोर्ड मोडेल परीक्षणमा 422 सहित जवाफ दिनुहोस्। पूर्वनिर्धारित रूपमा बन्द: एन्डपोइन्ट ट्यागहरू सूचनामूलक नै रहन्छन् र अनुरोधहरू पहिलेझैँ ठ्याक्कै डिस्प्याच गरिन्छन्।", - "featureFlagAnthropicCompatibleMidConversationSystemDescription": "साधारण anthropic-संगत रिलेहरूमा, कुराकानीको बीचमा आउने system टर्नहरूलाई शीर्ष-स्तरको system prompt मा सार्नुको सट्टा सन्देश सूचीभित्रै राख्नुहोस्, ताकि क्यास गरिएको prompt prefix टर्नहरूबीच स्थिर रहोस्। यो client ले स्वयं mid-conversation-system-2026-04-07 beta पठाएको अवस्थामा मात्र लागू हुन्छ। पूर्वनिर्धारित रूपमा बन्द हुन्छ: पहिलेझैँ प्रत्येक system टर्नलाई system prompt मा सारिन्छ, किनभने उक्त beta नभएका रिलेहरूले सन्देश सूचीमा भएका system टर्नहरू अस्वीकार गर्छन्।", "featureFlagModelsDevEffortTiersEnabledDescription": "अपरेटर ओभरराइड वा प्रदायक रजिस्ट्रीमध्ये कुनैले पनि घोषणा नगरेको अवस्थामा models.dev ले प्रकाशित गर्ने तर्क प्रयासका तहहरू प्रयोग गर्नुहोस्, ज्ञात प्रयास स्तरहरू मात्र राख्नुहोस्, र घोषणा गरिएको खाली तह सूचीलाई \"कुनै तह छैन\" का रूपमा लिनुहोस्। पूर्वनिर्धारित रूपमा बन्द: मोडेल क्याटलग र समाधान गरिएका प्रयासका तहहरू पहिले जस्तै ठ्याक्कै रहन्छन्।", "featureFlagAutoRequestResolvedCapabilitiesDescription": "auto/* अनुरोधहरूलाई समाधान गरिएका मोडेल क्षमताहरू (भिजन, रिजनिङ, थिंकिङ) र अवलोकन गरिएका गुणस्तर स्कोरहरूसहित, एउटै बल्क पठनमा, उम्मेदवार पूल बनाउन दिन्छ। पूर्वनिर्धारित रूपमा बन्द: अनुरोधको समयका उम्मेदवार तौलहरू ठ्याक्कै पहिलेजस्तै रहन्छन्। सक्रिय हुँदा: यी संकेतहरू स्न्यापसट तौलहरूमा समावेश हुन्छन्, त्यसैले उम्मेदवार तौल र छनोट परिवर्तन हुन सक्छन्।", "featureFlagCredentialHealthRetestAutoDisabledDescription": "क्रेडेन्सियल स्वास्थ्य जाँचलाई गेटवेले आफैँ बन्द गरेका कनेक्सनहरू पुनः परीक्षण गर्न दिनुहोस् (प्रत्येकलाई बढीमा हरेक ६ घण्टामा एक पटक, प्रत्येक जाँचमा ५ वटा) र पुनः परीक्षण सफल हुँदा कुनै एकलाई फेरि सक्रिय गर्नुहोस्। पूर्वनिर्धारित रूपमा बन्द: अपरेटरले कारबाही नगरेसम्म त्यस्ता कनेक्सनहरू बन्द नै रहन्छन्। अपरेटरद्वारा निष्क्रिय गरिएका र स्थायी रूपमा प्रतिबन्धित कनेक्सनहरू कहिल्यै पुनः परीक्षण गरिँदैनन्।", diff --git a/src/i18n/messages/nl.json b/src/i18n/messages/nl.json index a3205e4ba559..cc1b9b9fad3b 100644 --- a/src/i18n/messages/nl.json +++ b/src/i18n/messages/nl.json @@ -1028,7 +1028,6 @@ "featureFlagOpencodeV2ClientFingerprintEnabledDescription": "Stuur de fingerprint van de OpenCode v2 CLI-client mee bij OpenCode Zen/Go-verzoeken: de User-Agent opencode/latest/<2.x>/cli en x-opencode-client cli, en bij verzoeken binnen de gratis laag de v2-sessieheaders, de v2 CLI-schema's voor de read/shell-tools en geen tool_choice \"none\". Standaard uitgeschakeld: elk OpenCode-verzoek behoudt ongewijzigd de huidige identiteit, headers en body.", "featureFlagComboEmptyPoolNoEligibleTargetsEnabledDescription": "Wanneer de kandidatenpool van een automatische combinatie al leeg is voordat het voorfilter voor toolaanroepen wordt uitgevoerd (elk doel is overgeslagen vanwege afkoelperiodes, blokkeringen of uitputting), antwoord dan met 503 no_eligible_targets in plaats van de verouderde 400-fout \"Geen enkel doel ondersteunt toolaanroepen\" capability_mismatch. Standaard uitgeschakeld: bij een lege pool blijft de verouderde 400-fout behouden.", "featureFlagModelEndpointCapabilityGateEnabledDescription": "Dwing vertrouwde modeleindpunttags af (gedeclareerd door de providercatalogus, afgeleid uit een gespecialiseerd model-ID of ingesteld door de operator): weiger een verzoek dat wordt verzonden naar een eindpunt dat niet door het model wordt aangeboden vóór verzending met HTTP 400 op /v1/chat/completions, /v1/embeddings, /v1/rerank, /v1/images/generations, /v1/videos/generations, /v1/music/generations en /v1/audio/*; verberg modellen waarvan de getagde eindpunten geen OmniRoute-adapter hebben in /v1/models; en beantwoord de modeltest van het dashboard met 422 voor dergelijke modellen. Standaard uitgeschakeld: eindpunttags blijven informatief en verzoeken worden exact zoals voorheen verzonden.", - "featureFlagAnthropicCompatibleMidConversationSystemDescription": "Bij gewone Anthropic-compatibele relays blijven systeembeurten halverwege het gesprek in de berichtenlijst staan in plaats van ze naar de systeemprompt op het hoogste niveau te verplaatsen, zodat het in de cache opgeslagen promptvoorvoegsel tussen beurten stabiel blijft. Dit is alleen van toepassing wanneer de client zelf de bèta mid-conversation-system-2026-04-07 heeft verzonden. Standaard uitgeschakeld: elke systeembeurt wordt precies zoals voorheen naar de systeemprompt verplaatst, omdat relays zonder die bèta systeembeurten in de berichtenlijst weigeren.", "featureFlagModelsDevEffortTiersEnabledDescription": "Gebruik de niveaus voor redeneerinspanning die models.dev publiceert wanneer noch een operatoroverride, noch het providerregister niveaus opgeeft. Behoud alleen bekende inspanningsniveaus en behandel een lege lijst met opgegeven niveaus als \"geen niveaus\". Standaard uitgeschakeld: de modelcatalogus en de vastgestelde inspanningsniveaus blijven precies zoals voorheen.", "featureFlagAutoRequestResolvedCapabilitiesDescription": "Laat auto/*-verzoeken de kandidatenpool opbouwen met opgeloste modelmogelijkheden (vision, redeneren, denken) en waargenomen kwaliteitsscores, ingelezen in één bulkleesactie. Standaard uit: kandidaatgewichten op het moment van het verzoek blijven precies zoals voorheen. Aan: deze signalen voeden de snapshotgewichten, zodat kandidaatgewichten en selectie kunnen veranderen.", "featureFlagCredentialHealthRetestAutoDisabledDescription": "Laat de controle van de status van inloggegevens verbindingen opnieuw testen die de gateway zelf heeft uitgeschakeld (elk maximaal één keer per 6 uur, 5 per controle) en schakel een verbinding weer in wanneer deze de hertest doorstaat. Standaard uitgeschakeld: zulke verbindingen blijven uitgeschakeld totdat een operator ingrijpt. Door een operator uitgeschakelde en permanent geblokkeerde verbindingen worden nooit opnieuw getest.", diff --git a/src/i18n/messages/no.json b/src/i18n/messages/no.json index 8efec7e7ccce..82727c8900a3 100644 --- a/src/i18n/messages/no.json +++ b/src/i18n/messages/no.json @@ -1028,7 +1028,6 @@ "featureFlagOpencodeV2ClientFingerprintEnabledDescription": "Send fingeravtrykket til OpenCode v2 CLI-klienten i OpenCode Zen/Go-forespørsler: opencode/latest/<2.x>/cli som User-Agent og x-opencode-client cli, og i forespørsler på gratisnivå v2-øktshodene, v2 CLI-verktøyskjemaene for read/shell og ingen tool_choice \"none\". Deaktivert som standard: Alle OpenCode-forespørsler beholder gjeldende identitet, hoder og brødtekst uendret.", "featureFlagComboEmptyPoolNoEligibleTargetsEnabledDescription": "Når kandidatpoolen til en automatisk kombinasjon allerede er tom før forhåndsfilteret for verktøykall kjøres (alle mål er hoppet over på grunn av nedkjølingsperioder, sperrer eller oppbrukte kvoter), returneres 503 no_eligible_targets i stedet for den eldre 400-feilen «No target supports tool calling» capability_mismatch. Deaktivert som standard: En tom pool beholder den eldre 400-feilen.", "featureFlagModelEndpointCapabilityGateEnabledDescription": "Håndhev klarerte tagger for modellendepunkter (deklarert av leverandørkatalogen, utledet fra en spesialisert modell-ID eller angitt av operatøren): Avvis en forespørsel sendt til et endepunkt som modellen ikke støtter, med HTTP 400 før videresending på /v1/chat/completions, /v1/embeddings, /v1/rerank, /v1/images/generations, /v1/videos/generations, /v1/music/generations og /v1/audio/*; skjul modeller med taggede endepunkter som ikke har noen OmniRoute-adapter, fra /v1/models; og svar med 422 på modelltesten i kontrollpanelet for slike modeller. Av som standard: Endepunkttagger forblir kun informative, og forespørsler videresendes nøyaktig som før.", - "featureFlagAnthropicCompatibleMidConversationSystemDescription": "På vanlige anthropic-kompatible reléer beholdes systemmeldinger midt i samtalen i meldingslisten i stedet for å flyttes til systeminstruksen på toppnivå, slik at det bufrede instruksjonsprefikset forblir stabilt på tvers av meldingene. Gjelder bare når klienten selv har sendt betaen mid-conversation-system-2026-04-07. Deaktivert som standard: Hver systemmelding flyttes til systeminstruksen akkurat som før, fordi reléer uten denne betaen avviser systemmeldinger i meldingslisten.", "featureFlagModelsDevEffortTiersEnabledDescription": "Bruk nivåene for resonneringsinnsats som models.dev publiserer når verken en operatøroverstyring eller leverandørregisteret angir noen, behold bare kjente innsatsnivåer, og behandle en tom liste over angitte nivåer som «ingen nivåer». Deaktivert som standard: Modellkatalogen og de fastsatte innsatsnivåene forblir nøyaktig som før.", "featureFlagAutoRequestResolvedCapabilitiesDescription": "Lar auto/*-forespørsler bygge kandidatpoolen med avklarte modellegenskaper (syn, resonnering, tenkning) og observerte kvalitetspoeng, lest i én samlet lesing. Av som standard: kandidatvektene ved forespørselstidspunktet forblir nøyaktig som før. På: disse signalene inngår i øyeblikksbildevektene, så kandidatvekter og valg kan endres.", "featureFlagCredentialHealthRetestAutoDisabledDescription": "La helsekontrollen for legitimasjon teste på nytt tilkoblinger som gatewayen har slått av automatisk (maksimalt én gang hver 6. time per tilkobling, 5 per kontroll), og slå dem på igjen når den nye testen er vellykket. Av som standard: Slike tilkoblinger forblir av til en operatør griper inn. Tilkoblinger som er deaktivert av en operatør eller permanent utestengt, testes aldri på nytt.", diff --git a/src/i18n/messages/or.json b/src/i18n/messages/or.json index 700d6528d72a..3a2156f3f708 100644 --- a/src/i18n/messages/or.json +++ b/src/i18n/messages/or.json @@ -1028,7 +1028,6 @@ "featureFlagOpencodeV2ClientFingerprintEnabledDescription": "OpenCode Zen/Go ଅନୁରୋଧଗୁଡ଼ିକରେ OpenCode v2 CLI କ୍ଲାଏଣ୍ଟ ଫିଙ୍ଗରପ୍ରିଣ୍ଟ ପଠାନ୍ତୁ: opencode/latest/<2.x>/cli User-Agent ଏବଂ x-opencode-client cli, ଏବଂ ମାଗଣା-ସ୍ତରର ଅନୁରୋଧଗୁଡ଼ିକରେ v2 ସେସନ୍ ହେଡର୍, v2 CLI read/shell ଟୁଲ୍ ସ୍କିମା ଏବଂ tool_choice \"none\" ବିନା। ଡିଫଲ୍ଟ ଭାବେ ବନ୍ଦ: ପ୍ରତ୍ୟେକ OpenCode ଅନୁରୋଧ ବର୍ତ୍ତମାନର ପରିଚୟ, ହେଡର୍ ଏବଂ ବଡିକୁ ଅପରିବର୍ତ୍ତିତ ରଖେ।", "featureFlagComboEmptyPoolNoEligibleTargetsEnabledDescription": "ଟୁଲ୍-କଲିଂ ପ୍ରି-ଫିଲ୍ଟର୍ ଚାଲିବା ପୂର୍ବରୁ ଯଦି କୌଣସି ଅଟୋ କମ୍ବୋର ପ୍ରାର୍ଥୀ ପୁଲ୍ ପୂର୍ବରୁ ହିଁ ଖାଲି ଥାଏ (କୁଲଡାଉନ୍, ଲକ୍ଆଉଟ୍ କିମ୍ବା ସମ୍ପୂର୍ଣ୍ଣ ବ୍ୟବହୃତ ହୋଇଯିବା କାରଣରୁ ପ୍ରତ୍ୟେକ ଟାର୍ଗେଟ୍କୁ ଏଡ଼ାଇ ଦିଆଯାଇଥାଏ), ତେବେ ପୁରୁଣା 400 \"No target supports tool calling\" capability_mismatch ପରିବର୍ତ୍ତେ 503 no_eligible_targets ଉତ୍ତର ଦିଅନ୍ତୁ। ଡିଫଲ୍ଟ ଭାବେ ବନ୍ଦ: ପୁଲ୍ ଖାଲି ଥିଲେ ପୁରୁଣା 400 ହିଁ ରହେ।", "featureFlagModelEndpointCapabilityGateEnabledDescription": "ବିଶ୍ୱସନୀୟ ମଡେଲ୍ ଏଣ୍ଡପଏଣ୍ଟ ଟ୍ୟାଗ୍ଗୁଡ଼ିକୁ ବାଧ୍ୟତାମୂଳକ କରନ୍ତୁ (ପ୍ରଦାନକାରୀ କ୍ୟାଟାଲଗ୍ ଦ୍ୱାରା ଘୋଷିତ, କୌଣସି ବିଶେଷୀକୃତ ମଡେଲ୍ IDରୁ ଅନୁମାନିତ କିମ୍ବା ଅପରେଟର୍ଙ୍କ ଦ୍ୱାରା ସେଟ୍ କରାଯାଇଥିବା): ମଡେଲ୍ ଯେଉଁ ଏଣ୍ଡପଏଣ୍ଟରେ ସେବା ପ୍ରଦାନ କରେ ନାହିଁ, ସେଠାକୁ ପଠାଯାଇଥିବା ଅନୁରୋଧକୁ /v1/chat/completions, /v1/embeddings, /v1/rerank, /v1/images/generations, /v1/videos/generations, /v1/music/generations ଏବଂ /v1/audio/* ରେ ଡିସ୍ପାଚ୍ କରିବା ପୂର୍ବରୁ HTTP 400 ସହ ପ୍ରତ୍ୟାଖ୍ୟାନ କରନ୍ତୁ; ଯେଉଁ ମଡେଲ୍ଗୁଡ଼ିକର ଟ୍ୟାଗ୍ ହୋଇଥିବା ଏଣ୍ଡପଏଣ୍ଟ ପାଇଁ OmniRoute ଆଡାପ୍ଟର୍ ନାହିଁ, ସେଗୁଡ଼ିକୁ /v1/models ରୁ ଲୁଚାନ୍ତୁ; ଏବଂ ଏପରି ମଡେଲ୍ଗୁଡ଼ିକ ପାଇଁ ଡ୍ୟାସ୍ବୋର୍ଡ ମଡେଲ୍ ପରୀକ୍ଷାର ଉତ୍ତର 422 ସହ ଦିଅନ୍ତୁ। ଡିଫଲ୍ଟ ଭାବେ ବନ୍ଦ: ଏଣ୍ଡପଏଣ୍ଟ ଟ୍ୟାଗ୍ଗୁଡ଼ିକ କେବଳ ସୂଚନାମୂଳକ ରହେ ଏବଂ ଅନୁରୋଧଗୁଡ଼ିକୁ ପୂର୍ବପରି ଠିକ୍ ସେହିଭଳି ଡିସ୍ପାଚ୍ କରାଯାଏ।", - "featureFlagAnthropicCompatibleMidConversationSystemDescription": "ସାଧାରଣ anthropic-ସୁସଙ୍ଗତ ରିଲେଗୁଡ଼ିକରେ, କଥୋପକଥନର ମଧ୍ୟଭାଗର system ଟର୍ନଗୁଡ଼ିକୁ ଶୀର୍ଷ-ସ୍ତରୀୟ system promptକୁ ସ୍ଥାନାନ୍ତର କରିବା ପରିବର୍ତ୍ତେ message ତାଲିକା ଭିତରେ ରଖନ୍ତୁ, ଯାହାଦ୍ୱାରା କ୍ୟାଶ୍ ହୋଇଥିବା prompt prefix ପ୍ରତ୍ୟେକ ଟର୍ନରେ ସ୍ଥିର ରହିବ। ଏହା କେବଳ ସେତେବେଳେ ପ୍ରଯୁଜ୍ୟ, ଯେତେବେଳେ client ନିଜେ mid-conversation-system-2026-04-07 beta ପଠାଇଥାଏ। ଡିଫଲ୍ଟ ଭାବେ ବନ୍ଦ: ପୂର୍ବପରି ପ୍ରତ୍ୟେକ system ଟର୍ନକୁ ଠିକ୍ ସେହିଭଳି system promptକୁ ସ୍ଥାନାନ୍ତର କରାଯାଏ, କାରଣ ସେହି beta ନଥିବା ରିଲେଗୁଡ଼ିକ message ତାଲିକାରେ system ଟର୍ନଗୁଡ଼ିକୁ ପ୍ରତ୍ୟାଖ୍ୟାନ କରନ୍ତି।", "featureFlagModelsDevEffortTiersEnabledDescription": "ଅପରେଟର୍ ଓଭରରାଇଡ୍ କିମ୍ବା ପ୍ରଦାତା ରେଜିଷ୍ଟ୍ରି—କୌଣସିଟି ମଧ୍ୟ ରିଜନିଂ ଏଫର୍ଟ ଟିୟର୍ ଘୋଷଣା କରିନଥିଲେ, models.dev ପ୍ରକାଶ କରୁଥିବା ଟିୟର୍ଗୁଡ଼ିକ ବ୍ୟବହାର କରନ୍ତୁ; କେବଳ ଜଣାଶୁଣା ଏଫର୍ଟ ସ୍ତରଗୁଡ଼ିକୁ ରଖନ୍ତୁ ଏବଂ ଘୋଷିତ ଟିୟର୍ ତାଲିକା ଖାଲି ଥିଲେ ତାହାକୁ \"କୌଣସି ଟିୟର୍ ନାହିଁ\" ବୋଲି ଧରନ୍ତୁ। ଡିଫଲ୍ଟ ଭାବେ ବନ୍ଦ: ମଡେଲ୍ କ୍ୟାଟାଲଗ୍ ଏବଂ ସମାଧାନ କରାଯାଇଥିବା ଏଫର୍ଟ ଟିୟର୍ଗୁଡ଼ିକ ପୂର୍ବପରି ଠିକ୍ ସେହିଭଳି ରହିବ।", "featureFlagAutoRequestResolvedCapabilitiesDescription": "auto/* ଅନୁରୋଧଗୁଡ଼ିକୁ ସମାଧାନ ହୋଇଥିବା ମଡେଲ କ୍ଷମତା (ଭିଜନ, ରିଜନିଂ, ଥିଙ୍କିଂ) ଏବଂ ପର୍ଯ୍ୟବେକ୍ଷିତ ଗୁଣବତ୍ତା ସ୍କୋର ସହିତ, ଗୋଟିଏ ବଲ୍କ ପଠନରେ, ପ୍ରାର୍ଥୀ ପୁଲ୍ ଗଠନ କରିବାକୁ ଦିଏ। ଡିଫଲ୍ଟ ଭାବେ ବନ୍ଦ: ଅନୁରୋଧ ସମୟର ପ୍ରାର୍ଥୀ ଓଜନ ଠିକ୍ ପୂର୍ବ ପରି ରହେ। ଚାଲୁ ଥିଲେ: ଏହି ସଙ୍କେତଗୁଡ଼ିକ ସ୍ନାପସଟ୍ ଓଜନରେ ଯୋଗ ହୁଏ, ତେଣୁ ପ୍ରାର୍ଥୀ ଓଜନ ଓ ଚୟନ ବଦଳିପାରେ।", "featureFlagCredentialHealthRetestAutoDisabledDescription": "କ୍ରେଡେନ୍ସିଆଲ୍ ସ୍ୱାସ୍ଥ୍ୟ ଯାଞ୍ଚକୁ ଗେଟୱେ ନିଜେ ବନ୍ଦ କରିଥିବା ସଂଯୋଗଗୁଡ଼ିକୁ ପୁନଃପରୀକ୍ଷା କରିବାକୁ ଦିଅନ୍ତୁ (ପ୍ରତ୍ୟେକଟିକୁ ସର୍ବାଧିକ ପ୍ରତି 6 ଘଣ୍ଟାରେ ଥରେ, ପ୍ରତି ଯାଞ୍ଚରେ 5ଟି) ଏବଂ ପୁନଃପରୀକ୍ଷା ସଫଳ ହେଲେ ସେଗୁଡ଼ିକ ମଧ୍ୟରୁ ଗୋଟିଏକୁ ପୁଣି ସକ୍ରିୟ କରନ୍ତୁ। ଡିଫଲ୍ଟ ଭାବେ ବନ୍ଦ: କୌଣସି ଅପରେଟର୍ ପଦକ୍ଷେପ ନେବା ପର୍ଯ୍ୟନ୍ତ ଏପରି ସଂଯୋଗଗୁଡ଼ିକ ବନ୍ଦ ରହିବ। ଅପରେଟର୍ଙ୍କ ଦ୍ୱାରା ଅକ୍ଷମ କରାଯାଇଥିବା ଏବଂ ସ୍ଥାୟୀ ଭାବେ ନିଷିଦ୍ଧ ସଂଯୋଗଗୁଡ଼ିକୁ କେବେ ମଧ୍ୟ ପୁନଃପରୀକ୍ଷା କରାଯାଏ ନାହିଁ।", diff --git a/src/i18n/messages/pa.json b/src/i18n/messages/pa.json index e5d593b5a673..40c42b903700 100644 --- a/src/i18n/messages/pa.json +++ b/src/i18n/messages/pa.json @@ -1028,7 +1028,6 @@ "featureFlagOpencodeV2ClientFingerprintEnabledDescription": "OpenCode Zen/Go ਬੇਨਤੀਆਂ 'ਤੇ OpenCode v2 CLI ਕਲਾਇੰਟ ਫਿੰਗਰਪ੍ਰਿੰਟ ਭੇਜੋ: opencode/latest/<2.x>/cli User-Agent ਅਤੇ x-opencode-client cli, ਅਤੇ ਮੁਫ਼ਤ-ਟੀਅਰ ਬੇਨਤੀਆਂ 'ਤੇ v2 ਸੈਸ਼ਨ ਹੈਡਰ, v2 CLI read/shell ਟੂਲ ਸਕੀਮਾਵਾਂ ਅਤੇ ਕੋਈ tool_choice \"none\" ਨਹੀਂ। ਮੂਲ ਰੂਪ ਵਿੱਚ ਬੰਦ: ਹਰ OpenCode ਬੇਨਤੀ ਦੀ ਮੌਜੂਦਾ ਪਛਾਣ, ਹੈਡਰ ਅਤੇ ਬਾਡੀ ਬਿਨਾਂ ਬਦਲਾਅ ਦੇ ਰਹਿੰਦੇ ਹਨ।", "featureFlagComboEmptyPoolNoEligibleTargetsEnabledDescription": "ਜਦੋਂ tool-calling ਪ੍ਰੀ-ਫਿਲਟਰ ਦੇ ਚੱਲਣ ਤੋਂ ਪਹਿਲਾਂ ਹੀ ਕਿਸੇ auto combo ਦਾ ਉਮੀਦਵਾਰ ਪੂਲ ਖਾਲੀ ਹੋਵੇ (ਹਰੇਕ ਟਾਰਗੇਟ cooldowns, lockouts ਜਾਂ exhaustion ਕਾਰਨ ਛੱਡ ਦਿੱਤਾ ਗਿਆ ਹੋਵੇ), ਤਾਂ ਪੁਰਾਣੇ 400 \"ਕੋਈ ਵੀ ਟਾਰਗੇਟ tool calling ਦਾ ਸਮਰਥਨ ਨਹੀਂ ਕਰਦਾ\" capability_mismatch ਦੀ ਬਜਾਏ 503 no_eligible_targets ਜਵਾਬ ਦਿਓ। ਡਿਫੌਲਟ ਤੌਰ 'ਤੇ ਬੰਦ: ਖਾਲੀ ਪੂਲ ਲਈ ਪੁਰਾਣਾ 400 ਹੀ ਜਾਰੀ ਰਹਿੰਦਾ ਹੈ।", "featureFlagModelEndpointCapabilityGateEnabledDescription": "ਭਰੋਸੇਯੋਗ ਮਾਡਲ ਐਂਡਪੌਇੰਟ ਟੈਗ ਲਾਗੂ ਕਰੋ (ਜੋ ਪ੍ਰਦਾਤਾ ਕੈਟਾਲਾਗ ਵੱਲੋਂ ਘੋਸ਼ਿਤ, ਕਿਸੇ ਵਿਸ਼ੇਸ਼ ਮਾਡਲ id ਤੋਂ ਅਨੁਮਾਨਿਤ, ਜਾਂ ਓਪਰੇਟਰ ਵੱਲੋਂ ਸੈੱਟ ਕੀਤੇ ਗਏ ਹਨ): /v1/chat/completions, /v1/embeddings, /v1/rerank, /v1/images/generations, /v1/videos/generations, /v1/music/generations ਅਤੇ /v1/audio/* ਉੱਤੇ ਡਿਸਪੈਚ ਤੋਂ ਪਹਿਲਾਂ ਉਸ ਐਂਡਪੌਇੰਟ ਨੂੰ ਭੇਜੀ ਬੇਨਤੀ HTTP 400 ਨਾਲ ਰੱਦ ਕਰੋ ਜਿਸਨੂੰ ਮਾਡਲ ਸੇਵਾ ਨਹੀਂ ਦਿੰਦਾ; /v1/models ਤੋਂ ਉਹ ਮਾਡਲ ਲੁਕਾਓ ਜਿਨ੍ਹਾਂ ਦੇ ਟੈਗ ਕੀਤੇ ਐਂਡਪੌਇੰਟਾਂ ਲਈ ਕੋਈ OmniRoute ਅਡੈਪਟਰ ਨਹੀਂ ਹੈ; ਅਤੇ ਅਜਿਹੇ ਮਾਡਲਾਂ ਲਈ ਡੈਸ਼ਬੋਰਡ ਮਾਡਲ ਟੈਸਟ ਦਾ ਜਵਾਬ 422 ਨਾਲ ਦਿਓ। ਮੂਲ ਰੂਪ ਵਿੱਚ ਬੰਦ: ਐਂਡਪੌਇੰਟ ਟੈਗ ਸਿਰਫ਼ ਜਾਣਕਾਰੀ ਲਈ ਰਹਿੰਦੇ ਹਨ ਅਤੇ ਬੇਨਤੀਆਂ ਪਹਿਲਾਂ ਵਾਂਗ ਹੀ ਡਿਸਪੈਚ ਕੀਤੀਆਂ ਜਾਂਦੀਆਂ ਹਨ।", - "featureFlagAnthropicCompatibleMidConversationSystemDescription": "ਸਧਾਰਨ anthropic-compatible ਰਿਲੇਅਾਂ 'ਤੇ, ਗੱਲਬਾਤ ਦੇ ਵਿਚਕਾਰਲੇ ਸਿਸਟਮ ਟਰਨਾਂ ਨੂੰ ਸਿਖਰ-ਪੱਧਰੀ ਸਿਸਟਮ ਪ੍ਰੌਂਪਟ ਵਿੱਚ ਭੇਜਣ ਦੀ ਬਜਾਏ ਸੁਨੇਹਾ ਸੂਚੀ ਵਿੱਚ ਹੀ ਰੱਖੋ, ਤਾਂ ਜੋ ਕੈਸ਼ ਕੀਤਾ ਪ੍ਰੌਂਪਟ ਪ੍ਰੀਫਿਕਸ ਵੱਖ-ਵੱਖ ਟਰਨਾਂ ਦੌਰਾਨ ਸਥਿਰ ਰਹੇ। ਇਹ ਸਿਰਫ਼ ਉਦੋਂ ਲਾਗੂ ਹੁੰਦਾ ਹੈ ਜਦੋਂ ਕਲਾਇੰਟ ਨੇ ਖੁਦ mid-conversation-system-2026-04-07 ਬੀਟਾ ਭੇਜਿਆ ਹੋਵੇ। ਮੂਲ ਰੂਪ ਵਿੱਚ ਬੰਦ: ਹਰ ਸਿਸਟਮ ਟਰਨ ਨੂੰ ਪਹਿਲਾਂ ਵਾਂਗ ਹੀ ਸਿਸਟਮ ਪ੍ਰੌਂਪਟ ਵਿੱਚ ਭੇਜਿਆ ਜਾਂਦਾ ਹੈ, ਕਿਉਂਕਿ ਉਸ ਬੀਟਾ ਤੋਂ ਬਿਨਾਂ ਰਿਲੇਅ ਸੁਨੇਹਾ ਸੂਚੀ ਵਿੱਚ ਸਿਸਟਮ ਟਰਨਾਂ ਨੂੰ ਅਸਵੀਕਾਰ ਕਰਦੇ ਹਨ।", "featureFlagModelsDevEffortTiersEnabledDescription": "ਜਦੋਂ ਨਾ ਤਾਂ ਕੋਈ ਓਪਰੇਟਰ ਓਵਰਰਾਈਡ ਅਤੇ ਨਾ ਹੀ ਪ੍ਰਦਾਤਾ ਰਜਿਸਟਰੀ ਕੋਈ ਰੀਜ਼ਨਿੰਗ ਐਫ਼ਰਟ ਟੀਅਰ ਘੋਸ਼ਿਤ ਕਰਦੀ ਹੋਵੇ, ਤਾਂ models.dev ਵੱਲੋਂ ਪ੍ਰਕਾਸ਼ਿਤ ਟੀਅਰ ਵਰਤੋ, ਸਿਰਫ਼ ਜਾਣੇ-ਪਛਾਣੇ ਐਫ਼ਰਟ ਪੱਧਰ ਹੀ ਰੱਖੋ, ਅਤੇ ਘੋਸ਼ਿਤ ਕੀਤੀ ਖਾਲੀ ਟੀਅਰ ਸੂਚੀ ਨੂੰ \"ਕੋਈ ਟੀਅਰ ਨਹੀਂ\" ਮੰਨੋ। ਮੂਲ ਰੂਪ ਵਿੱਚ ਬੰਦ: ਮਾਡਲ ਕੈਟਾਲਾਗ ਅਤੇ ਨਿਰਧਾਰਤ ਐਫ਼ਰਟ ਟੀਅਰ ਬਿਲਕੁਲ ਪਹਿਲਾਂ ਵਾਂਗ ਹੀ ਰਹਿੰਦੇ ਹਨ।", "featureFlagAutoRequestResolvedCapabilitiesDescription": "auto/* ਬੇਨਤੀਆਂ ਨੂੰ ਹੱਲ ਕੀਤੀਆਂ ਮਾਡਲ ਸਮਰੱਥਾਵਾਂ (ਵਿਜ਼ਨ, ਰੀਜ਼ਨਿੰਗ, ਥਿੰਕਿੰਗ) ਅਤੇ ਦੇਖੇ ਗਏ ਗੁਣਵੱਤਾ ਸਕੋਰਾਂ ਨਾਲ, ਇੱਕੋ ਬਲਕ ਪੜ੍ਹਤ ਵਿੱਚ, ਉਮੀਦਵਾਰ ਪੂਲ ਬਣਾਉਣ ਦਿੰਦਾ ਹੈ। ਡਿਫੌਲਟ ਤੌਰ 'ਤੇ ਬੰਦ: ਬੇਨਤੀ ਸਮੇਂ ਦੇ ਉਮੀਦਵਾਰ ਭਾਰ ਬਿਲਕੁਲ ਪਹਿਲਾਂ ਵਾਂਗ ਰਹਿੰਦੇ ਹਨ। ਚਾਲੂ ਹੋਣ 'ਤੇ: ਇਹ ਸੰਕੇਤ ਸਨੈਪਸ਼ਾਟ ਭਾਰਾਂ ਵਿੱਚ ਸ਼ਾਮਲ ਹੁੰਦੇ ਹਨ, ਇਸ ਲਈ ਉਮੀਦਵਾਰ ਭਾਰ ਅਤੇ ਚੋਣ ਬਦਲ ਸਕਦੇ ਹਨ।", "featureFlagCredentialHealthRetestAutoDisabledDescription": "ਕ੍ਰੈਡੈਂਸ਼ਲ ਸਿਹਤ ਜਾਂਚ ਨੂੰ ਉਹਨਾਂ ਕਨੈਕਸ਼ਨਾਂ ਦੀ ਮੁੜ ਜਾਂਚ ਕਰਨ ਦਿਓ ਜਿਨ੍ਹਾਂ ਨੂੰ ਗੇਟਵੇ ਨੇ ਆਪਣੇ ਆਪ ਬੰਦ ਕੀਤਾ ਸੀ (ਹਰੇਕ ਦੀ ਵੱਧ ਤੋਂ ਵੱਧ ਹਰ 6 ਘੰਟਿਆਂ ਵਿੱਚ ਇੱਕ ਵਾਰ, ਪ੍ਰਤੀ ਜਾਂਚ 5) ਅਤੇ ਜਦੋਂ ਕਿਸੇ ਕਨੈਕਸ਼ਨ ਦੀ ਮੁੜ ਜਾਂਚ ਸਫਲ ਹੋਵੇ ਤਾਂ ਉਸਨੂੰ ਮੁੜ ਚਾਲੂ ਕਰ ਦਿਓ। ਮੂਲ ਰੂਪ ਵਿੱਚ ਬੰਦ: ਅਜਿਹੇ ਕਨੈਕਸ਼ਨ ਉਦੋਂ ਤੱਕ ਬੰਦ ਰਹਿੰਦੇ ਹਨ ਜਦੋਂ ਤੱਕ ਕੋਈ ਓਪਰੇਟਰ ਕਾਰਵਾਈ ਨਹੀਂ ਕਰਦਾ। ਓਪਰੇਟਰ ਵੱਲੋਂ ਬੰਦ ਕੀਤੇ ਅਤੇ ਸਥਾਈ ਤੌਰ 'ਤੇ ਪਾਬੰਦੀਸ਼ੁਦਾ ਕਨੈਕਸ਼ਨਾਂ ਦੀ ਕਦੇ ਵੀ ਮੁੜ ਜਾਂਚ ਨਹੀਂ ਕੀਤੀ ਜਾਂਦੀ।", diff --git a/src/i18n/messages/phi.json b/src/i18n/messages/phi.json index 53a7ba8fa3d4..1c37c9daea36 100644 --- a/src/i18n/messages/phi.json +++ b/src/i18n/messages/phi.json @@ -1028,7 +1028,6 @@ "featureFlagOpencodeV2ClientFingerprintEnabledDescription": "Ipadala ang fingerprint ng OpenCode v2 CLI client sa mga request ng OpenCode Zen/Go: ang opencode/latest/<2.x>/cli User-Agent at x-opencode-client cli, at sa mga free-tier na request, ang mga v2 session header, ang mga v2 CLI read/shell tool schema, at walang tool_choice na \"none\". Naka-off bilang default: pinananatili ng bawat request ng OpenCode na hindi nababago ang kasalukuyang identity, mga header, at body.", "featureFlagComboEmptyPoolNoEligibleTargetsEnabledDescription": "Kapag wala nang laman ang pool ng mga kandidato ng isang awtomatikong combo bago tumakbo ang pre-filter para sa pagtawag sa tool (nilaktawan ang bawat target dahil sa mga cooldown, lockout, o pagkaubos), ibalik ang 503 no_eligible_targets sa halip na ang dating 400 \"Walang target na sumusuporta sa pagtawag sa tool\" capability_mismatch. Naka-off bilang default: pananatilihin ng walang-lamang pool ang dating 400.", "featureFlagModelEndpointCapabilityGateEnabledDescription": "Ipatupad ang mga pinagkakatiwalaang tag ng endpoint ng modelo (idineklara ng catalog ng provider, natukoy mula sa isang specialty model id, o itinakda ng operator): tanggihan nang may HTTP 400 bago ang dispatch ang kahilingang ipinadala sa endpoint na hindi sinusuportahan ng modelo sa /v1/chat/completions, /v1/embeddings, /v1/rerank, /v1/images/generations, /v1/videos/generations, /v1/music/generations at /v1/audio/*; itago sa /v1/models ang mga modelong ang mga naka-tag na endpoint ay walang OmniRoute adapter; at sagutin nang may 422 ang pagsubok ng modelo sa dashboard para sa mga naturang modelo. Naka-off bilang default: mananatiling pang-impormasyon ang mga tag ng endpoint at idi-dispatch ang mga kahilingan gaya ng dati.", - "featureFlagAnthropicCompatibleMidConversationSystemDescription": "Sa mga karaniwang anthropic-compatible relay, panatilihin ang mga system turn sa gitna ng pag-uusap sa loob ng listahan ng mensahe sa halip na ilipat ang mga ito sa top-level na system prompt, upang manatiling hindi nagbabago ang naka-cache na prompt prefix sa bawat turn. Nalalapat lang ito kapag ang client mismo ang nagpadala ng mid-conversation-system-2026-04-07 beta. Naka-off bilang default: inililipat ang bawat system turn sa system prompt gaya mismo ng dati, dahil tinatanggihan ng mga relay na walang beta na iyon ang mga system turn sa listahan ng mensahe.", "featureFlagModelsDevEffortTiersEnabledDescription": "Gamitin ang mga antas ng reasoning effort na inilalathala ng models.dev kapag walang idinedeklarang anuman ang override ng operator o ang registry ng provider, panatilihin lamang ang mga kilalang antas ng effort, at ituring ang walang lamang listahan ng mga idineklarang antas bilang \"walang mga antas\". Naka-off bilang default: mananatiling eksaktong gaya ng dati ang catalog ng modelo at mga nalutas na antas ng effort.", "featureFlagAutoRequestResolvedCapabilitiesDescription": "Pinapayagan ang mga auto/* request na buuin ang pool ng mga kandidato gamit ang na-resolve na kakayahan ng modelo (vision, reasoning, thinking) at ang naobserbahang quality score, na binabasa sa iisang bulk na pagbasa. Naka-off bilang default: ang timbang ng mga kandidato sa oras ng request ay nananatiling eksaktong tulad ng dati. Naka-on: pumapasok ang mga signal na ito sa timbang ng snapshot, kaya maaaring magbago ang timbang ng mga kandidato at ang pagpili.", "featureFlagCredentialHealthRetestAutoDisabledDescription": "Payagan ang pagsusuri sa kalagayan ng kredensyal na muling subukan ang mga koneksiyong awtomatikong pinatay ng gateway (hindi hihigit sa isang beses bawat 6 na oras para sa bawat isa, 5 sa bawat pagsusuri) at muling buksan ang isang koneksiyon kapag pumasa ito sa muling pagsubok. Naka-off bilang default: mananatiling naka-off ang mga naturang koneksiyon hanggang sa kumilos ang isang operator. Hindi kailanman muling sinusubukan ang mga koneksiyong pinatay ng operator at ang mga permanenteng naka-ban.", diff --git a/src/i18n/messages/pl.json b/src/i18n/messages/pl.json index 6b7650ee6112..0513df821047 100644 --- a/src/i18n/messages/pl.json +++ b/src/i18n/messages/pl.json @@ -1028,7 +1028,6 @@ "featureFlagOpencodeV2ClientFingerprintEnabledDescription": "Wysyłaj dane identyfikacyjne klienta OpenCode v2 CLI w żądaniach OpenCode Zen/Go: User-Agent opencode/latest/<2.x>/cli oraz x-opencode-client cli, a w żądaniach warstwy bezpłatnej — nagłówki sesji v2, schematy narzędzi read/shell interfejsu CLI v2 i bez tool_choice \"none\". Domyślnie wyłączone: każde żądanie OpenCode zachowuje obecną tożsamość, nagłówki i treść bez zmian.", "featureFlagComboEmptyPoolNoEligibleTargetsEnabledDescription": "Gdy pula kandydatów automatycznej kombinacji jest już pusta przed uruchomieniem filtra wstępnego wywoływania narzędzi (każdy cel został pominięty z powodu okresów karencji, blokad lub wyczerpania), zwróć 503 no_eligible_targets zamiast starszego błędu 400 „No target supports tool calling” capability_mismatch. Domyślnie wyłączone: w przypadku pustej puli nadal zwracany jest starszy błąd 400.", "featureFlagModelEndpointCapabilityGateEnabledDescription": "Wymuszaj zaufane tagi endpointów modeli (zadeklarowane w katalogu dostawcy, wywnioskowane na podstawie identyfikatora modelu specjalistycznego lub ustawione przez operatora): odrzucaj z kodem HTTP 400 przed przekazaniem żądania wysłane do endpointu, którego model nie obsługuje, na ścieżkach /v1/chat/completions, /v1/embeddings, /v1/rerank, /v1/images/generations, /v1/videos/generations, /v1/music/generations i /v1/audio/*; ukrywaj w /v1/models modele, dla których otagowanych endpointów nie ma adaptera OmniRoute; oraz zwracaj kod 422 podczas testu modelu w panelu dla takich modeli. Domyślnie wyłączone: tagi endpointów pozostają wyłącznie informacyjne, a żądania są przekazywane dokładnie tak jak dotychczas.", - "featureFlagAnthropicCompatibleMidConversationSystemDescription": "W przypadku zwykłych przekaźników zgodnych z anthropic zachowuj systemowe komunikaty w środku konwersacji na liście wiadomości, zamiast przenosić je do systemowego promptu najwyższego poziomu, dzięki czemu buforowany prefiks promptu pozostaje stabilny między turami. Ma zastosowanie tylko wtedy, gdy klient sam wysłał wersję beta mid-conversation-system-2026-04-07. Domyślnie wyłączone: każdy komunikat systemowy jest przenoszony do promptu systemowego dokładnie tak jak wcześniej, ponieważ przekaźniki bez tej wersji beta odrzucają komunikaty systemowe na liście wiadomości.", "featureFlagModelsDevEffortTiersEnabledDescription": "Używaj poziomów intensywności rozumowania publikowanych przez models.dev, gdy nie określono ich ani w nadpisaniu operatora, ani w rejestrze dostawcy. Zachowuj wyłącznie znane poziomy intensywności, a pustą zadeklarowaną listę poziomów traktuj jako „brak poziomów”. Domyślnie wyłączone: katalog modeli i wynikowe poziomy intensywności pozostają dokładnie takie jak wcześniej.", "featureFlagAutoRequestResolvedCapabilitiesDescription": "Pozwala żądaniom auto/* budować pulę kandydatów z rozwiązanymi możliwościami modeli (wizja, rozumowanie, myślenie) i zaobserwowanymi ocenami jakości, wczytanymi jednym zbiorczym odczytem. Domyślnie wyłączone: wagi kandydatów w momencie żądania pozostają dokładnie takie jak wcześniej. Włączone: te sygnały trafiają do wag migawki, więc wagi kandydatów i wybór mogą się zmienić.", "featureFlagCredentialHealthRetestAutoDisabledDescription": "Zezwól, aby kontrola stanu danych uwierzytelniających ponownie testowała połączenia automatycznie wyłączone przez bramę (każde najwyżej raz na 6 godzin, po 5 w ramach jednej kontroli) i ponownie włączała połączenie, jeśli test zakończy się pomyślnie. Domyślnie wyłączone: takie połączenia pozostają wyłączone do czasu interwencji operatora. Połączenia wyłączone przez operatora i trwale zablokowane nigdy nie są ponownie testowane.", diff --git a/src/i18n/messages/pt-BR.json b/src/i18n/messages/pt-BR.json index f3beac6e8016..d768b799c679 100644 --- a/src/i18n/messages/pt-BR.json +++ b/src/i18n/messages/pt-BR.json @@ -1028,7 +1028,6 @@ "featureFlagOpencodeV2ClientFingerprintEnabledDescription": "Envie a impressão digital do cliente CLI OpenCode v2 nas solicitações do OpenCode Zen/Go: o User-Agent opencode/latest/<2.x>/cli e x-opencode-client cli e, nas solicitações do nível gratuito, os cabeçalhos de sessão v2, os esquemas das ferramentas read/shell da CLI v2 e nenhum tool_choice \"none\". Desativado por padrão: todas as solicitações do OpenCode mantêm a identidade, os cabeçalhos e o corpo atuais inalterados.", "featureFlagComboEmptyPoolNoEligibleTargetsEnabledDescription": "Quando o conjunto de candidatos de uma combinação automática já estiver vazio antes da execução do pré-filtro de chamadas de ferramentas (todos os destinos ignorados devido a períodos de espera, bloqueios ou esgotamento), responder com 503 no_eligible_targets em vez do 400 legado \"Nenhum destino oferece suporte a chamadas de ferramentas\" capability_mismatch. Desativado por padrão: o conjunto vazio mantém o 400 legado.", "featureFlagModelEndpointCapabilityGateEnabledDescription": "Imponha tags confiáveis de endpoint de modelo (declaradas pelo catálogo do provedor, inferidas a partir do ID de um modelo especializado ou definidas pelo operador): rejeite com HTTP 400, antes do encaminhamento, uma solicitação enviada a um endpoint que não é atendido pelo modelo em /v1/chat/completions, /v1/embeddings, /v1/rerank, /v1/images/generations, /v1/videos/generations, /v1/music/generations e /v1/audio/*; oculte de /v1/models os modelos cujos endpoints marcados não tenham um adaptador do OmniRoute; e responda ao teste de modelo do painel com 422 para esses modelos. Desativado por padrão: as tags de endpoint permanecem informativas e as solicitações são encaminhadas exatamente como antes.", - "featureFlagAnthropicCompatibleMidConversationSystemDescription": "Em retransmissores simples compatíveis com a Anthropic, mantenha as mensagens de sistema no meio da conversa dentro da lista de mensagens, em vez de movê-las para o prompt de sistema de nível superior, para que o prefixo do prompt armazenado em cache permaneça estável entre as interações. Aplica-se somente quando o próprio cliente tiver enviado a versão beta mid-conversation-system-2026-04-07. Desativado por padrão: todas as mensagens de sistema são movidas para o prompt de sistema exatamente como antes, pois retransmissores sem essa versão beta rejeitam mensagens de sistema na lista de mensagens.", "featureFlagModelsDevEffortTiersEnabledDescription": "Use os níveis de esforço de raciocínio publicados pelo models.dev quando nem uma substituição do operador nem o registro do provedor declarar algum, mantendo apenas os níveis de esforço conhecidos e tratando uma lista vazia de níveis declarados como \"sem níveis\". Desativado por padrão: o catálogo de modelos e os níveis de esforço resolvidos permanecem exatamente como antes.", "featureFlagAutoRequestResolvedCapabilitiesDescription": "Permite que as requisições auto/* montem o pool de candidatos com as capacidades de modelo resolvidas (visão, raciocínio, pensamento) e as pontuações de qualidade observadas, lidas em uma única leitura em lote. Desativado por padrão: os pesos dos candidatos no momento da requisição permanecem exatamente como antes. Ativado: esses sinais alimentam os pesos do snapshot, então os pesos dos candidatos e a seleção podem mudar.", "featureFlagCredentialHealthRetestAutoDisabledDescription": "Permita que a verificação de integridade das credenciais teste novamente as conexões que o gateway desativou automaticamente (no máximo uma vez a cada 6 horas por conexão, 5 por verificação) e reative uma delas quando o novo teste for bem-sucedido. Desativado por padrão: essas conexões permanecem desativadas até que um operador tome uma ação. Conexões desativadas por um operador e conexões banidas permanentemente nunca são testadas novamente.", diff --git a/src/i18n/messages/pt.json b/src/i18n/messages/pt.json index 1ed5ad28858d..3d7f578458e4 100644 --- a/src/i18n/messages/pt.json +++ b/src/i18n/messages/pt.json @@ -1028,7 +1028,6 @@ "featureFlagOpencodeV2ClientFingerprintEnabledDescription": "Enviar a identificação do cliente CLI OpenCode v2 nos pedidos OpenCode Zen/Go: o User-Agent opencode/latest/<2.x>/cli e x-opencode-client cli e, nos pedidos do nível gratuito, os cabeçalhos de sessão v2, os esquemas das ferramentas read/shell da CLI v2 e sem tool_choice \"none\". Desativado por predefinição: todos os pedidos OpenCode mantêm a identidade, os cabeçalhos e o corpo atuais inalterados.", "featureFlagComboEmptyPoolNoEligibleTargetsEnabledDescription": "Quando o conjunto de candidatos de uma combinação automática já estiver vazio antes da execução do pré-filtro de chamada de ferramentas (todos os destinos foram ignorados devido a períodos de espera, bloqueios ou esgotamento), devolver 503 no_eligible_targets em vez do erro 400 legado «No target supports tool calling» capability_mismatch. Desativado por predefinição: o conjunto vazio mantém o erro 400 legado.", "featureFlagModelEndpointCapabilityGateEnabledDescription": "Aplicar as etiquetas de endpoints de modelos fidedignas (declaradas pelo catálogo do fornecedor, inferidas a partir do ID de um modelo especializado ou definidas pelo operador): rejeitar, com HTTP 400 e antes do encaminhamento, um pedido enviado para um endpoint que o modelo não disponibiliza em /v1/chat/completions, /v1/embeddings, /v1/rerank, /v1/images/generations, /v1/videos/generations, /v1/music/generations e /v1/audio/*; ocultar de /v1/models os modelos cujos endpoints etiquetados não tenham um adaptador OmniRoute; e responder ao teste de modelo do painel com 422 para esses modelos. Desativado por predefinição: as etiquetas de endpoints permanecem informativas e os pedidos são encaminhados exatamente como antes.", - "featureFlagAnthropicCompatibleMidConversationSystemDescription": "Em relays simples compatíveis com a Anthropic, mantenha as mensagens de sistema a meio da conversa na lista de mensagens, em vez de as mover para o prompt de sistema de nível superior, para que o prefixo do prompt em cache permaneça estável entre interações. Aplica-se apenas quando o próprio cliente tiver enviado a beta mid-conversation-system-2026-04-07. Desativado por predefinição: todas as mensagens de sistema são movidas para o prompt de sistema exatamente como antes, porque os relays sem essa beta rejeitam mensagens de sistema na lista de mensagens.", "featureFlagModelsDevEffortTiersEnabledDescription": "Utilize os níveis de esforço de raciocínio publicados por models.dev quando nem uma substituição do operador nem o registo do fornecedor declararem quaisquer níveis, mantendo apenas os níveis de esforço conhecidos e tratando uma lista de níveis declarada vazia como \"sem níveis\". Desativado por predefinição: o catálogo de modelos e os níveis de esforço determinados permanecem exatamente como antes.", "featureFlagAutoRequestResolvedCapabilitiesDescription": "Permite que os pedidos auto/* construam o conjunto de candidatos com as capacidades de modelo resolvidas (visão, raciocínio, pensamento) e as pontuações de qualidade observadas, lidas numa única leitura em lote. Desativado por predefinição: os pesos dos candidatos no momento do pedido mantêm-se exatamente como antes. Ativado: esses sinais alimentam os pesos do instantâneo, pelo que os pesos dos candidatos e a seleção podem mudar.", "featureFlagCredentialHealthRetestAutoDisabledDescription": "Permitir que a verificação do estado das credenciais volte a testar as ligações que o gateway desativou automaticamente (no máximo uma vez a cada 6 horas por ligação, 5 por verificação) e reative uma ligação quando o novo teste for bem-sucedido. Desativado por predefinição: essas ligações permanecem desativadas até que um operador intervenha. As ligações desativadas por um operador e as ligações permanentemente banidas nunca são testadas novamente.", diff --git a/src/i18n/messages/ro.json b/src/i18n/messages/ro.json index 9b79f3b8b9a7..08bee0d7a5c9 100644 --- a/src/i18n/messages/ro.json +++ b/src/i18n/messages/ro.json @@ -1028,7 +1028,6 @@ "featureFlagOpencodeV2ClientFingerprintEnabledDescription": "Trimite amprenta clientului CLI OpenCode v2 în solicitările OpenCode Zen/Go: User-Agent-ul opencode/latest/<2.x>/cli și x-opencode-client cli, iar în solicitările din nivelul gratuit, antetele de sesiune v2, schemele instrumentelor read/shell ale CLI-ului v2 și fără tool_choice \"none\". Dezactivat implicit: fiecare solicitare OpenCode păstrează neschimbate identitatea, antetele și corpul actuale.", "featureFlagComboEmptyPoolNoEligibleTargetsEnabledDescription": "Atunci când grupul de candidați al unui combo automat este deja gol înainte de rularea prefiltrului pentru apelarea instrumentelor (fiecare țintă fiind omisă din cauza perioadelor de așteptare, a blocărilor sau a epuizării), răspunde cu 503 no_eligible_targets în locul vechiului răspuns 400 „Nicio țintă nu acceptă apelarea instrumentelor” capability_mismatch. Dezactivat în mod implicit: dacă grupul este gol, se păstrează vechiul răspuns 400.", "featureFlagModelEndpointCapabilityGateEnabledDescription": "Impune etichetele de încredere pentru endpointurile modelelor (declarate de catalogul furnizorului, deduse din ID-ul unui model specializat sau setate de operator): respinge cu HTTP 400, înainte de expediere, o cerere trimisă către un endpoint pe care modelul nu îl deservește, pentru /v1/chat/completions, /v1/embeddings, /v1/rerank, /v1/images/generations, /v1/videos/generations, /v1/music/generations și /v1/audio/*; ascunde din /v1/models modelele ale căror endpointuri etichetate nu au un adaptor OmniRoute; și răspunde cu 422 la testul de model din panoul de control pentru astfel de modele. Dezactivat în mod implicit: etichetele endpointurilor rămân informative, iar cererile sunt expediate exact ca înainte.", - "featureFlagAnthropicCompatibleMidConversationSystemDescription": "Pentru releele simple compatibile cu Anthropic, păstrează mesajele de sistem din mijlocul conversației în lista de mesaje, în loc să le muți în promptul de sistem de nivel superior, astfel încât prefixul promptului memorat în cache să rămână stabil de la un schimb la altul. Se aplică numai atunci când clientul însuși a trimis versiunea beta mid-conversation-system-2026-04-07. Opțiunea este dezactivată în mod implicit: fiecare mesaj de sistem este mutat în promptul de sistem exact ca înainte, deoarece releele fără acea versiune beta resping mesajele de sistem din lista de mesaje.", "featureFlagModelsDevEffortTiersEnabledDescription": "Folosește nivelurile de efort de raționament publicate de models.dev atunci când nici o suprascriere a operatorului, nici registrul furnizorului nu declară vreunul, păstrând doar nivelurile de efort cunoscute și tratând o listă goală de niveluri declarate drept „fără niveluri”. Dezactivat în mod implicit: catalogul de modele și nivelurile de efort determinate rămân exact ca înainte.", "featureFlagAutoRequestResolvedCapabilitiesDescription": "Permite cererilor auto/* să construiască grupul de candidați cu capabilitățile de model rezolvate (viziune, raționament, gândire) și scorurile de calitate observate, citite într-o singură citire în bloc. Dezactivat implicit: ponderile candidaților la momentul cererii rămân exact ca înainte. Activat: aceste semnale intră în ponderile instantaneului, astfel încât ponderile candidaților și selecția se pot schimba.", "featureFlagCredentialHealthRetestAutoDisabledDescription": "Permite verificării stării credențialelor să retesteze conexiunile pe care gateway-ul le-a dezactivat automat (cel mult o dată la fiecare 6 ore pentru fiecare conexiune, câte 5 per verificare) și să reactiveze o conexiune atunci când aceasta trece retestarea. Dezactivată implicit: astfel de conexiuni rămân dezactivate până când intervine un operator. Conexiunile dezactivate de operator și cele blocate permanent nu sunt retestate niciodată.", diff --git a/src/i18n/messages/ru.json b/src/i18n/messages/ru.json index a460b1c49e45..8d085d452c03 100644 --- a/src/i18n/messages/ru.json +++ b/src/i18n/messages/ru.json @@ -1028,7 +1028,6 @@ "featureFlagOpencodeV2ClientFingerprintEnabledDescription": "Отправлять отпечаток клиента OpenCode v2 CLI в запросах OpenCode Zen/Go: User-Agent opencode/latest/<2.x>/cli и x-opencode-client cli, а в запросах бесплатного уровня — заголовки сеанса v2, схемы инструментов чтения/оболочки v2 CLI и без tool_choice \"none\". По умолчанию отключено: каждый запрос OpenCode сохраняет текущие идентификационные данные, заголовки и тело без изменений.", "featureFlagComboEmptyPoolNoEligibleTargetsEnabledDescription": "Если пул кандидатов автокомбо уже пуст до запуска предварительного фильтра вызова инструментов (все цели пропущены из-за периодов ожидания, блокировок или исчерпания), возвращать ошибку 503 no_eligible_targets вместо прежней ошибки 400 «Ни одна цель не поддерживает вызов инструментов» capability_mismatch. По умолчанию отключено: при пустом пуле сохраняется прежняя ошибка 400.", "featureFlagModelEndpointCapabilityGateEnabledDescription": "Принудительно учитывать доверенные теги конечных точек модели (объявленные в каталоге провайдера, выведенные из идентификатора специализированной модели или заданные оператором): отклонять с HTTP 400 до маршрутизации запрос, отправленный на конечную точку, которую модель не обслуживает, для /v1/chat/completions, /v1/embeddings, /v1/rerank, /v1/images/generations, /v1/videos/generations, /v1/music/generations и /v1/audio/*; скрывать из /v1/models модели, для конечных точек с тегами которых отсутствует адаптер OmniRoute; и возвращать 422 при проверке таких моделей на панели управления. По умолчанию отключено: теги конечных точек остаются информационными, а запросы маршрутизируются точно так же, как и раньше.", - "featureFlagAnthropicCompatibleMidConversationSystemDescription": "На обычных ретрансляторах, совместимых с anthropic, промежуточные системные сообщения остаются в списке сообщений, а не переносятся в системный промпт верхнего уровня, благодаря чему кэшированный префикс промпта остаётся неизменным между репликами. Применяется только в том случае, если клиент сам отправил бета-флаг mid-conversation-system-2026-04-07. По умолчанию отключено: каждое системное сообщение, как и раньше, переносится в системный промпт, поскольку ретрансляторы без этого бета-флага отклоняют системные сообщения в списке сообщений.", "featureFlagModelsDevEffortTiersEnabledDescription": "Использовать уровни глубины рассуждений, публикуемые models.dev, если они не заданы ни переопределением оператора, ни реестром провайдера; учитывать только известные уровни глубины и считать пустой объявленный список уровней отсутствием уровней. По умолчанию отключено: каталог моделей и итоговые уровни глубины рассуждений остаются в точности такими же, как прежде.", "featureFlagAutoRequestResolvedCapabilitiesDescription": "Позволяет запросам auto/* формировать пул кандидатов с разрешёнными возможностями моделей (зрение, рассуждение, мышление) и наблюдаемыми оценками качества, считанными одним пакетным чтением. По умолчанию выключено: веса кандидатов во время запроса остаются точно такими же, как раньше. Включено: эти сигналы поступают в веса снимка, поэтому веса кандидатов и выбор могут измениться.", "featureFlagCredentialHealthRetestAutoDisabledDescription": "Разрешить проверке состояния учётных данных повторно тестировать подключения, которые шлюз отключил автоматически (каждое — не чаще одного раза в 6 часов, не более 5 за одну проверку), и снова включать подключение, если повторный тест пройден успешно. По умолчанию отключено: такие подключения остаются отключёнными до вмешательства оператора. Подключения, отключённые оператором или заблокированные навсегда, никогда не тестируются повторно.", @@ -6094,6 +6093,8 @@ "allowCodexPaidCreditsDescription": "Allow this account to use paid credits after its subscription quota reaches the cutoff. Requires available credits reported by OpenAI; may incur charges. Spark limits and upstream cooldowns still apply. Credit availability is checked even if quota preflight is disabled.", "disableCoolingDescription": "Пропускать временный кулдаун, чтобы это подключение оставалось доступным даже после устранимых ошибок (терминальные состояния, такие как banned/expired, по-прежнему применяются).", "disableCoolingLabel": "Отключить кулдаун для этого подключения", + "keepMidConversationSystemDescription": "Не переносить system-сообщения из середины диалога в верхний system, а оставлять их в списке сообщений, чтобы кеш промпта попадал от хода к ходу. Включайте, только если upstream принимает role \"system\" внутри messages (иначе он ответит 400).", + "keepMidConversationSystemLabel": "Оставлять system-сообщения из середины диалога на месте", "bulkPasteAdded": "{count, plural, one {Добавлен 1 ключ} few {Добавлено # ключа} many {Добавлено # ключей} other {Добавлено # ключа}}", "bulkPasteDuplicatesIgnored": "{count, plural, one {Пропущен 1 дубликат} few {Пропущено # дубликата} many {Пропущено # дубликатов} other {Пропущено # дубликата}}", "bulkPasteHint": "Вставьте по одному API-ключу в каждой строке. Пустые строки игнорируются, а повторяющиеся ключи пропускаются.", diff --git a/src/i18n/messages/si.json b/src/i18n/messages/si.json index 8abea3c8387e..31acb901b068 100644 --- a/src/i18n/messages/si.json +++ b/src/i18n/messages/si.json @@ -1028,7 +1028,6 @@ "featureFlagOpencodeV2ClientFingerprintEnabledDescription": "OpenCode Zen/Go ඉල්ලීම්වලදී OpenCode v2 CLI සේවාලාභී ඇඟිලි සලකුණ යවන්න: opencode/latest/<2.x>/cli User-Agent සහ x-opencode-client cli යවන්න; නොමිලේ මට්ටමේ ඉල්ලීම්වලදී v2 සැසි ශීර්ෂ, v2 CLI read/shell මෙවලම් යෝජනා ක්රම යවන්න සහ tool_choice \"none\" නොයවන්න. පෙරනිමියෙන් අක්රියයි: සෑම OpenCode ඉල්ලීමක්ම වත්මන් අනන්යතාව, ශීර්ෂ සහ අන්තර්ගතය වෙනස් නොකර තබා ගනී.", "featureFlagComboEmptyPoolNoEligibleTargetsEnabledDescription": "මෙවලම්-ඇමතුම් පෙරහන් කිරීම ක්රියාත්මක වීමට පෙරම ස්වයංක්රීය සංයෝජනයක අපේක්ෂක සංචිතය හිස් නම් (cooldowns, lockouts හෝ exhaustion නිසා සෑම ඉලක්කයක්ම මඟහැරී ඇත්නම්), පැරණි 400 \"කිසිදු ඉලක්කයක් මෙවලම් ඇමතීමට සහාය නොදක්වයි\" capability_mismatch වෙනුවට 503 no_eligible_targets ලෙස පිළිතුරු දෙන්න. පෙරනිමියෙන් අක්රියයි: හිස් සංචිතය පැරණි 400 ප්රතිචාරයම තබා ගනී.", "featureFlagModelEndpointCapabilityGateEnabledDescription": "විශ්වාසදායී මොඩල් අන්තලක්ෂ්ය ටැග් (සපයන්නාගේ නාමාවලිය මඟින් ප්රකාශිත, විශේෂිත මොඩල් id එකකින් අනුමාන කළ, හෝ ක්රියාකරු විසින් සැකසූ) බලාත්මක කරන්න: /v1/chat/completions, /v1/embeddings, /v1/rerank, /v1/images/generations, /v1/videos/generations, /v1/music/generations සහ /v1/audio/* වෙත, මොඩලය සේවය නොකරන අන්තලක්ෂ්යයකට යවන ලද ඉල්ලීමක් යොමු කිරීමට පෙර HTTP 400 සමඟ ප්රතික්ෂේප කරන්න; ටැග් කළ අන්තලක්ෂ්ය සඳහා OmniRoute ඇඩැප්ටරයක් නොමැති මොඩල් /v1/models වෙතින් සඟවන්න; එවැනි මොඩල් සඳහා උපකරණ පුවරුවේ මොඩල් පරීක්ෂණයට 422 සමඟ පිළිතුරු දෙන්න. පෙරනිමියෙන් අක්රියයි: අන්තලක්ෂ්ය ටැග් තොරතුරුමය ලෙස පවතින අතර ඉල්ලීම් පෙර පරිදිම යොමු කෙරේ.", - "featureFlagAnthropicCompatibleMidConversationSystemDescription": "සාමාන්ය anthropic-අනුකූල රිලේවලදී, සංවාදය මැද ඇති system turns ඉහළ මට්ටමේ system prompt වෙත ගෙන යාම වෙනුවට message ලැයිස්තුව තුළම තබන්න. එමඟින් cache කළ prompt prefix එක turns අතර ස්ථාවරව පවතී. මෙය අදාළ වන්නේ client විසින්ම mid-conversation-system-2026-04-07 බීටාව යවා ඇති විට පමණි. පෙරනිමියෙන් අක්රියයි: එම බීටාව නොමැති රිලේ message ලැයිස්තුවේ ඇති system turns ප්රතික්ෂේප කරන බැවින්, පෙර පරිදිම සෑම system turn එකක්ම system prompt වෙත ගෙන යනු ලැබේ.", "featureFlagModelsDevEffortTiersEnabledDescription": "ක්රියාකරු අතික්රමණයක් හෝ සපයන්නාගේ රෙජිස්ට්රියක් කිසිවක් ප්රකාශ නොකරන විට models.dev ප්රකාශයට පත් කරන තාර්කික ප්රයත්න මට්ටම් භාවිත කරන්න, දන්නා ප්රයත්න මට්ටම් පමණක් තබා ගන්න, සහ හිස් ලෙස ප්රකාශිත මට්ටම් ලැයිස්තුවක් \"මට්ටම් නැත\" ලෙස සලකන්න. පෙරනිමියෙන් අක්රියයි: ආකෘති නාමාවලිය සහ නිර්ණය කළ ප්රයත්න මට්ටම් පෙර තිබූ ආකාරයටම පවතී.", "featureFlagAutoRequestResolvedCapabilitiesDescription": "auto/* ඉල්ලීම්වලට විසඳන ලද මාදිලි හැකියාවන් (දර්ශනය, තර්කනය, චින්තනය) සහ නිරීක්ෂණය කළ ගුණාත්මක ලකුණු සමඟ, එක් තොග කියවීමකින්, අපේක්ෂක සංචිතය ගොඩනැගීමට ඉඩ දෙයි. පෙරනිමියෙන් අක්‍රියයි: ඉල්ලීම් අවස්ථාවේ අපේක්ෂක බර පෙර පරිදිම හරියටම පවතී. සක්‍රිය විට: මෙම සංඥා ස්නැප්ෂොට් බරට එක් වන බැවින් අපේක්ෂක බර සහ තේරීම වෙනස් විය හැක.", "featureFlagCredentialHealthRetestAutoDisabledDescription": "අක්තපත්ර සෞඛ්ය පිරික්සුමට, ගේට්වේ විසින් ස්වයංක්රීයව අක්රිය කළ සම්බන්ධතා නැවත පරීක්ෂා කිරීමට (එක් එක් සම්බන්ධතාව උපරිම වශයෙන් පැය 6කට වරක්, එක් පිරික්සුමකට සම්බන්ධතා 5ක්) සහ නැවත පරීක්ෂාව සමත් වූ විට ඒවායින් එකක් යළි සක්රිය කිරීමට ඉඩ දෙන්න. පෙරනිමියෙන් අක්රියයි: ක්රියාකරුවෙකු ක්රියා කරන තෙක් එවැනි සම්බන්ධතා අක්රියව පවතී. ක්රියාකරු විසින් අක්රිය කළ සහ ස්ථිරව තහනම් කළ සම්බන්ධතා කිසිවිටෙක නැවත පරීක්ෂා නොකෙරේ.", diff --git a/src/i18n/messages/sk.json b/src/i18n/messages/sk.json index 43c885f27c49..7ed1aa8a7724 100644 --- a/src/i18n/messages/sk.json +++ b/src/i18n/messages/sk.json @@ -1028,7 +1028,6 @@ "featureFlagOpencodeV2ClientFingerprintEnabledDescription": "Odosielať odtlačok klienta OpenCode v2 CLI v požiadavkách OpenCode Zen/Go: User-Agent opencode/latest/<2.x>/cli a x-opencode-client cli a pri požiadavkách bezplatnej úrovne hlavičky relácie v2, schémy nástrojov read/shell rozhrania v2 CLI a bez tool_choice \"none\". Predvolene vypnuté: každá požiadavka OpenCode si zachová aktuálnu identitu, hlavičky a telo bez zmien.", "featureFlagComboEmptyPoolNoEligibleTargetsEnabledDescription": "Keď je množina kandidátov automatickej kombinácie prázdna už pred spustením predfiltra volania nástrojov (každý cieľ je preskočený z dôvodu čakacej lehoty, uzamknutia alebo vyčerpania), vráti sa odpoveď 503 no_eligible_targets namiesto pôvodnej odpovede 400 „Žiadny cieľ nepodporuje volanie nástrojov“ capability_mismatch. Predvolene je táto možnosť vypnutá: pri prázdnej množine sa zachová pôvodná odpoveď 400.", "featureFlagModelEndpointCapabilityGateEnabledDescription": "Vynútiť dôveryhodné značky koncových bodov modelu (deklarované katalógom poskytovateľa, odvodené z ID špecializovaného modelu alebo nastavené operátorom): odmietnuť požiadavku odoslanú na koncový bod, ktorý model neobsluhuje, s chybou HTTP 400 ešte pred jej odoslaním na /v1/chat/completions, /v1/embeddings, /v1/rerank, /v1/images/generations, /v1/videos/generations, /v1/music/generations a /v1/audio/*; skryť z /v1/models modely, ktorých označené koncové body nemajú adaptér OmniRoute; a pri teste modelu na ovládacom paneli vrátiť pre takéto modely stav 422. Predvolene vypnuté: značky koncových bodov zostávajú iba informatívne a požiadavky sa odosielajú presne ako predtým.", - "featureFlagAnthropicCompatibleMidConversationSystemDescription": "Pri bežných relayoch kompatibilných s Anthropic ponechajte systémové vstupy uprostred konverzácie v zozname správ namiesto ich presúvania do systémovej výzvy najvyššej úrovne, aby prefix výzvy uložený vo vyrovnávacej pamäti zostal medzi jednotlivými vstupmi stabilný. Platí to iba vtedy, keď samotný klient odoslal beta príznak mid-conversation-system-2026-04-07. Predvolene je táto možnosť vypnutá: každý systémový vstup sa presunie do systémovej výzvy presne ako predtým, pretože relaye bez tohto beta príznaku odmietajú systémové vstupy v zozname správ.", "featureFlagModelsDevEffortTiersEnabledDescription": "Použiť úrovne úsilia pri uvažovaní, ktoré zverejňuje models.dev, ak žiadne neurčuje ani prepísanie operátorom, ani register poskytovateľa; ponechať iba známe úrovne úsilia a prázdny zoznam deklarovaných úrovní považovať za „žiadne úrovne“. Predvolene vypnuté: katalóg modelov a výsledné úrovne úsilia zostanú presne také ako predtým.", "featureFlagAutoRequestResolvedCapabilitiesDescription": "Umožňuje požiadavkám auto/* zostaviť skupinu kandidátov s vyriešenými schopnosťami modelov (videnie, uvažovanie, premýšľanie) a pozorovaným skóre kvality, načítanými jedným hromadným čítaním. Predvolene vypnuté: váhy kandidátov v čase požiadavky zostávajú presne ako predtým. Zapnuté: tieto signály vstupujú do váh snímky, takže váhy kandidátov a výber sa môžu zmeniť.", "featureFlagCredentialHealthRetestAutoDisabledDescription": "Povoľte kontrole stavu poverení opätovne testovať pripojenia, ktoré brána sama vypla (každé najviac raz za 6 hodín, 5 na jednu kontrolu), a znova zapnúť pripojenie, keď úspešne prejde opätovným testom. Predvolene je táto možnosť vypnutá: takéto pripojenia zostanú vypnuté, kým nezasiahne operátor. Pripojenia vypnuté operátorom a natrvalo zakázané pripojenia sa nikdy opätovne netestujú.", diff --git a/src/i18n/messages/sl.json b/src/i18n/messages/sl.json index 0736b0d6ec3c..ba63b9ccc03f 100644 --- a/src/i18n/messages/sl.json +++ b/src/i18n/messages/sl.json @@ -1028,7 +1028,6 @@ "featureFlagOpencodeV2ClientFingerprintEnabledDescription": "Pošiljaj prstni odtis odjemalca OpenCode v2 CLI pri zahtevah OpenCode Zen/Go: User-Agent opencode/latest/<2.x>/cli in x-opencode-client cli, pri zahtevah brezplačne ravni pa glave sej v2, sheme orodij v2 CLI read/shell in brez tool_choice \"none\". Privzeto izklopljeno: vsaka zahteva OpenCode ohrani trenutno identiteto, glave in telo nespremenjene.", "featureFlagComboEmptyPoolNoEligibleTargetsEnabledDescription": "Ko je nabor kandidatov samodejne kombinacije prazen že pred izvedbo predfiltra za klicanje orodij (vsi cilji so preskočeni zaradi obdobij ohlajanja, zaklepanja ali izčrpanosti), vrnite odgovor 503 no_eligible_targets namesto podedovanega odgovora 400 »Noben cilj ne podpira klicanja orodij« capability_mismatch. Privzeto je izklopljeno: pri praznem naboru se ohrani podedovani odgovor 400.", "featureFlagModelEndpointCapabilityGateEnabledDescription": "Uveljavi zaupanja vredne oznake končnih točk modela (ki jih navede katalog ponudnika, ki so razbrane iz ID-ja specializiranega modela ali ki jih nastavi upravljavec): zahtevo, poslano na končno točko, ki je model ne podpira, pred posredovanjem zavrni z odgovorom HTTP 400 na /v1/chat/completions, /v1/embeddings, /v1/rerank, /v1/images/generations, /v1/videos/generations, /v1/music/generations in /v1/audio/*; skrij modele, katerih označene končne točke nimajo vmesnika OmniRoute, iz /v1/models; ter na preskus modela na nadzorni plošči za takšne modele odgovori s 422. Privzeto izklopljeno: oznake končnih točk ostanejo informativne, zahteve pa se posredujejo povsem enako kot prej.", - "featureFlagAnthropicCompatibleMidConversationSystemDescription": "Pri običajnih posrednikih, združljivih z anthropic, naj sistemski vnosi sredi pogovora ostanejo na seznamu sporočil, namesto da se premaknejo v sistemski poziv na najvišji ravni, tako da predpomnjena predpona poziva ostane nespremenjena med posameznimi vnosi. Velja samo, če je odjemalec sam poslal različico beta mid-conversation-system-2026-04-07. Privzeto je izklopljeno: vsak sistemski vnos se tako kot prej premakne v sistemski poziv, ker posredniki brez te različice beta zavrnejo sistemske vnose na seznamu sporočil.", "featureFlagModelsDevEffortTiersEnabledDescription": "Uporabi stopnje napora sklepanja, ki jih objavlja models.dev, kadar jih ne določa niti preglasitev operaterja niti register ponudnikov, pri čemer ohrani le znane stopnje napora, prazen seznam navedenih stopenj pa obravnavaj kot »brez stopenj«. Privzeto izklopljeno: katalog modelov in razrešene stopnje napora ostanejo povsem enaki kot prej.", "featureFlagAutoRequestResolvedCapabilitiesDescription": "Zahtevam auto/* omogoči, da sestavijo nabor kandidatov z razrešenimi zmožnostmi modelov (vid, sklepanje, razmišljanje) in opaženimi ocenami kakovosti, prebranimi v enem skupnem branju. Privzeto izklopljeno: uteži kandidatov ob zahtevi ostanejo natanko takšne kot prej. Vklopljeno: ti signali vstopijo v uteži posnetka, zato se lahko uteži kandidatov in izbira spremenijo.", "featureFlagCredentialHealthRetestAutoDisabledDescription": "Dovolite, da preverjanje veljavnosti poverilnic znova preizkusi povezave, ki jih je prehod samodejno izklopil (vsako največ enkrat na 6 ur, 5 na preverjanje), in znova vklopi povezavo, ko uspešno prestane ponovni preizkus. Privzeto je izklopljeno: takšne povezave ostanejo izklopljene, dokler ne ukrepa skrbnik. Povezave, ki jih je onemogočil skrbnik, in trajno prepovedane povezave se nikoli ne preizkusijo znova.", diff --git a/src/i18n/messages/sr.json b/src/i18n/messages/sr.json index f3160d262cfd..d59ec357a9dd 100644 --- a/src/i18n/messages/sr.json +++ b/src/i18n/messages/sr.json @@ -1028,7 +1028,6 @@ "featureFlagOpencodeV2ClientFingerprintEnabledDescription": "Шаљи отисак CLI клијента OpenCode v2 у захтевима за OpenCode Zen/Go: opencode/latest/<2.x>/cli User-Agent и x-opencode-client cli, а у захтевима бесплатног нивоа заглавља v2 сесије, шеме алатки за читање/љуску v2 CLI-ја и без tool_choice \"none\". Подразумевано искључено: сваки OpenCode захтев задржава тренутни идентитет, заглавља и тело непромењеним.", "featureFlagComboEmptyPoolNoEligibleTargetsEnabledDescription": "Када је скуп кандидата аутоматске комбинације већ празан пре покретања предфилтера за позивање алата (сваки циљ је прескочен због периода чекања, блокада или исцрпљености), вратите 503 no_eligible_targets уместо застареле грешке 400 „Ниједан циљ не подржава позивање алата“ capability_mismatch. Подразумевано је искључено: празан скуп задржава застарелу грешку 400.", "featureFlagModelEndpointCapabilityGateEnabledDescription": "Наметните поуздане ознаке крајњих тачака модела (декларисане у каталогу добављача, изведене из ID-а специјализованог модела или подешене од стране оператера): одбијте захтев послат крајњој тачки коју модел не опслужује са HTTP 400 пре прослеђивања на /v1/chat/completions, /v1/embeddings, /v1/rerank, /v1/images/generations, /v1/videos/generations, /v1/music/generations и /v1/audio/*; сакријте из /v1/models моделе чије означене крајње тачке немају OmniRoute адаптер; и одговорите на тест модела на контролној табли са 422 за такве моделе. Подразумевано је искључено: ознаке крајњих тачака остају информативне, а захтеви се прослеђују на исти начин као и раније.", - "featureFlagAnthropicCompatibleMidConversationSystemDescription": "На обичним посредничким сервисима компатибилним са Anthropic-ом, задржите системске поруке усред разговора у листи порука уместо да их премештате у системски упит највишег нивоа, како би кеширани префикс упита остао стабилан између порука. Примењује се само када је сам клијент послао бета-верзију mid-conversation-system-2026-04-07. Подразумевано је искључено: свака системска порука премешта се у системски упит тачно као и раније, јер посреднички сервиси без те бета-верзије одбијају системске поруке у листи порука.", "featureFlagModelsDevEffortTiersEnabledDescription": "Користите нивое напора при резоновању које објављује models.dev када ни операторско поништавање ни регистар добављача не наводе ниједан, задржавајући само познате нивое напора, а празну листу декларисаних нивоа третирајте као „без нивоа“. Подразумевано је искључено: каталог модела и разрешени нивои напора остају потпуно непромењени.", "featureFlagAutoRequestResolvedCapabilitiesDescription": "Омогућава захтевима auto/* да изграде скуп кандидата са разрешеним могућностима модела (вид, резоновање, размишљање) и уоченим оценама квалитета, учитаним у једном скупном читању. Подразумевано искључено: тежине кандидата у тренутку захтева остају потпуно исте као раније. Укључено: ти сигнали улазе у тежине снимка, па се тежине кандидата и избор могу променити.", "featureFlagCredentialHealthRetestAutoDisabledDescription": "Дозволите провери исправности акредитива да поново тестира везе које је мрежни пролаз сам искључио (највише једном на сваких 6 сати за сваку везу, 5 по провери) и да поново укључи везу када успешно прође поновни тест. Подразумевано је искључено: такве везе остају искључене док оператер не предузме нешто. Везе које је оператер онемогућио и трајно забрањене везе никада се не тестирају поново.", diff --git a/src/i18n/messages/sv.json b/src/i18n/messages/sv.json index baa84df515a9..790d846b7e5a 100644 --- a/src/i18n/messages/sv.json +++ b/src/i18n/messages/sv.json @@ -1028,7 +1028,6 @@ "featureFlagOpencodeV2ClientFingerprintEnabledDescription": "Skicka OpenCode v2 CLI-klientens fingeravtryck vid OpenCode Zen/Go-förfrågningar: User-Agent opencode/latest/<2.x>/cli och x-opencode-client cli, och vid förfrågningar på kostnadsfri nivå v2-sessionsrubrikerna, v2 CLI-verktygsschemana för read/shell och inget tool_choice \"none\". Av som standard: varje OpenCode-förfrågan behåller nuvarande identitet, rubriker och brödtext oförändrade.", "featureFlagComboEmptyPoolNoEligibleTargetsEnabledDescription": "När kandidatpoolen för en automatisk kombination redan är tom innan förfiltret för verktygsanrop körs (alla mål har hoppats över på grund av vänteperioder, låsningar eller uttömning), returnera 503 no_eligible_targets i stället för det äldre 400-felet ”Inget mål stöder verktygsanrop” capability_mismatch. Inaktiverat som standard: om poolen är tom behålls det äldre 400-felet.", "featureFlagModelEndpointCapabilityGateEnabledDescription": "Framtvinga betrodda taggar för modellslutpunkter (deklarerade av leverantörskatalogen, härledda från ett specialiserat modell-id eller angivna av operatören): avvisa en begäran som skickas till en slutpunkt som modellen inte betjänar med HTTP 400 före vidarebefordran på /v1/chat/completions, /v1/embeddings, /v1/rerank, /v1/images/generations, /v1/videos/generations, /v1/music/generations och /v1/audio/*; dölj modeller vars taggade slutpunkter saknar en OmniRoute-adapter från /v1/models; och besvara instrumentpanelens modelltest med 422 för sådana modeller. Av som standard: slutpunktstaggar förblir endast informativa och begäranden vidarebefordras precis som tidigare.", - "featureFlagAnthropicCompatibleMidConversationSystemDescription": "På vanliga anthropic-kompatibla reläer behålls systemturer mitt i konversationen i meddelandelistan i stället för att flyttas till systemprompten på toppnivå, så att det cachade promptprefixet förblir stabilt mellan turerna. Gäller endast när klienten själv skickade betaversionen mid-conversation-system-2026-04-07. Av som standard: varje systemtur flyttas till systemprompten precis som tidigare, eftersom reläer utan den betaversionen avvisar systemturer i meddelandelistan.", "featureFlagModelsDevEffortTiersEnabledDescription": "Använd nivåerna för resonemangsinsats som models.dev publicerar när varken en operatörsåsidosättning eller leverantörsregistret anger några, behåll endast kända insatsnivåer och behandla en tom lista över angivna nivåer som \"inga nivåer\". Av som standard: modellkatalogen och de fastställda insatsnivåerna förblir exakt som tidigare.", "featureFlagAutoRequestResolvedCapabilitiesDescription": "Låter auto/*-förfrågningar bygga kandidatpoolen med fastställda modellförmågor (vision, resonemang, tänkande) och observerade kvalitetspoäng, inlästa i en enda massläsning. Av som standard: kandidatvikterna vid förfrågningstillfället förblir exakt som tidigare. På: dessa signaler matas in i ögonblicksbildens vikter, så kandidatvikter och urval kan ändras.", "featureFlagCredentialHealthRetestAutoDisabledDescription": "Låt hälsokontrollen för autentiseringsuppgifter testa anslutningar som gatewayen själv har stängt av på nytt (högst en gång var sjätte timme per anslutning, fem per kontroll) och slå på en anslutning igen när omtestet lyckas. Av som standard: sådana anslutningar förblir avstängda tills en operatör ingriper. Anslutningar som har stängts av av en operatör eller blockerats permanent testas aldrig på nytt.", diff --git a/src/i18n/messages/sw.json b/src/i18n/messages/sw.json index 1122f34dd582..3ec9879402a5 100644 --- a/src/i18n/messages/sw.json +++ b/src/i18n/messages/sw.json @@ -1028,7 +1028,6 @@ "featureFlagOpencodeV2ClientFingerprintEnabledDescription": "Tuma alama ya utambulisho ya kiteja cha OpenCode v2 CLI kwenye maombi ya OpenCode Zen/Go: User-Agent ya opencode/latest/<2.x>/cli na x-opencode-client cli, na kwenye maombi ya kiwango cha bure, vichwa vya kipindi vya v2, miundo ya zana za read/shell za v2 CLI, na bila tool_choice \"none\". Imezimwa kwa chaguo-msingi: kila ombi la OpenCode huhifadhi utambulisho, vichwa na mwili wa sasa bila kubadilishwa.", "featureFlagComboEmptyPoolNoEligibleTargetsEnabledDescription": "Wakati kundi la wagombea la mchanganyiko otomatiki tayari ni tupu kabla ya kichujio cha awali cha uitaji wa zana kuendeshwa (kila lengwa limerukwa kwa sababu ya vipindi vya kusubiri, vizuizi au kuishiwa), rudisha 503 no_eligible_targets badala ya jibu la zamani la 400 \"Hakuna lengwa linalotumia uitaji wa zana\" capability_mismatch. Huzimwa kwa chaguo-msingi: kundi tupu huendelea kutumia jibu la zamani la 400.", "featureFlagModelEndpointCapabilityGateEnabledDescription": "Tekeleza lebo zinazoaminika za endpointi za modeli (zilizotangazwa na katalogi ya mtoa huduma, zilizobainishwa kutokana na kitambulisho cha modeli maalum, au zilizowekwa na mwendeshaji): kataa kwa HTTP 400 kabla ya kutuma ombi lililotumwa kwa endpointi ambayo modeli haihudumii kwenye /v1/chat/completions, /v1/embeddings, /v1/rerank, /v1/images/generations, /v1/videos/generations, /v1/music/generations na /v1/audio/*; ficha modeli ambazo endpointi zake zilizo na lebo hazina adapta ya OmniRoute kutoka /v1/models; na ujibu jaribio la modeli la dashibodi kwa 422 kwa modeli kama hizo. Hali hii imezimwa kwa chaguo-msingi: lebo za endpointi hubaki za taarifa na maombi hutumwa kama hapo awali.", - "featureFlagAnthropicCompatibleMidConversationSystemDescription": "Kwenye relays za kawaida zinazooana na anthropic, weka ujumbe wa mfumo wa katikati ya mazungumzo ndani ya orodha ya ujumbe badala ya kuuhamishia kwenye kidokezo cha mfumo cha kiwango cha juu, ili kiambishi awali cha kidokezo kilichohifadhiwa kwenye akiba kisibadilike kati ya zamu. Hii inatumika tu wakati mteja mwenyewe ametuma beta ya mid-conversation-system-2026-04-07. Hali hii imezimwa kwa chaguo-msingi: kila ujumbe wa mfumo huhamishwa hadi kwenye kidokezo cha mfumo kama ilivyokuwa awali, kwa sababu relays zisizo na beta hiyo hukataa ujumbe wa mfumo ulio kwenye orodha ya ujumbe.", "featureFlagModelsDevEffortTiersEnabledDescription": "Tumia viwango vya juhudi za kufikiri vinavyochapishwa na models.dev wakati ambapo hakuna ubatilishaji wa opereta wala sajili ya mtoa huduma inayovitaja, ukihifadhi viwango vya juhudi vinavyojulikana pekee, na uchukulie orodha tupu ya viwango vilivyotajwa kama \"hakuna viwango\". Imezimwa kwa chaguo-msingi: katalogi ya modeli na viwango vya juhudi vilivyobainishwa hubaki kama vilivyokuwa awali.", "featureFlagAutoRequestResolvedCapabilitiesDescription": "Huruhusu maombi ya auto/* kujenga kundi la wagombea kwa kutumia uwezo wa modeli uliotatuliwa (maono, hoja, kufikiri) na alama za ubora zilizoonekana, zinazosomwa kwa usomaji mmoja wa jumla. Imezimwa kwa chaguo-msingi: uzito wa wagombea wakati wa ombi unabaki kama ulivyokuwa hapo awali. Ikiwashwa: ishara hizo huingia kwenye uzito wa picha ya papo hapo, hivyo uzito wa wagombea na uteuzi unaweza kubadilika.", "featureFlagCredentialHealthRetestAutoDisabledDescription": "Ruhusu ukaguzi wa afya ya vitambulisho ujaribu upya miunganisho ambayo lango lilizima lenyewe (angalau mara moja tu kila baada ya saa 6 kwa kila muunganisho, miunganisho 5 kwa kila ukaguzi) na uiwashe tena inapofaulu jaribio hilo. Hali hii imezimwa kwa chaguo-msingi: miunganisho hiyo husalia imezimwa hadi opereta achukue hatua. Miunganisho iliyozimwa na opereta na iliyopigwa marufuku kabisa haijaribiwi tena kamwe.", diff --git a/src/i18n/messages/ta.json b/src/i18n/messages/ta.json index 68050279cb52..4ffa6e462759 100644 --- a/src/i18n/messages/ta.json +++ b/src/i18n/messages/ta.json @@ -1028,7 +1028,6 @@ "featureFlagOpencodeV2ClientFingerprintEnabledDescription": "OpenCode Zen/Go கோரிக்கைகளில் OpenCode v2 CLI கிளையன்ட் கைரேகையை அனுப்பவும்: opencode/latest/<2.x>/cli User-Agent மற்றும் x-opencode-client cli; இலவச அடுக்குக் கோரிக்கைகளில் v2 அமர்வு தலைப்புகள், v2 CLI read/shell கருவித் திட்டவடிவங்கள் ஆகியவற்றை அனுப்பி, tool_choice \"none\" என்பதை அனுப்ப வேண்டாம். இயல்பாக முடக்கப்பட்டிருக்கும்: ஒவ்வொரு OpenCode கோரிக்கையும் தற்போதைய அடையாளம், தலைப்புகள் மற்றும் உடலை மாற்றமின்றி வைத்திருக்கும்.", "featureFlagComboEmptyPoolNoEligibleTargetsEnabledDescription": "கருவி அழைப்பு முன்-வடிகட்டி இயங்குவதற்கு முன்பே ஓர் ஆட்டோ காம்போவின் பரிந்துரைக்கப்பட்ட இலக்குகளின் தொகுப்பு காலியாக இருந்தால் (cooldown-கள், lockout-கள் அல்லது வரம்புத் தீர்வு காரணமாக ஒவ்வொரு இலக்கும் தவிர்க்கப்பட்டிருந்தால்), பழைய 400 \"No target supports tool calling\" capability_mismatch என்பதற்குப் பதிலாக 503 no_eligible_targets எனப் பதிலளிக்கவும். இயல்பாக இது முடக்கப்பட்டிருக்கும்: தொகுப்பு காலியாக இருக்கும்போது பழைய 400 பதிலே தொடரும்.", "featureFlagModelEndpointCapabilityGateEnabledDescription": "நம்பகமான மாதிரி முனைப்புள்ளிக் குறிச்சொற்களை (வழங்குநர் பட்டியலால் அறிவிக்கப்பட்டவை, சிறப்பு மாதிரி id-இலிருந்து ஊகிக்கப்பட்டவை, அல்லது இயக்குநரால் அமைக்கப்பட்டவை) கட்டாயப்படுத்தவும்: /v1/chat/completions, /v1/embeddings, /v1/rerank, /v1/images/generations, /v1/videos/generations, /v1/music/generations மற்றும் /v1/audio/* ஆகியவற்றில் மாதிரி சேவை வழங்காத முனைப்புள்ளிக்கு அனுப்பப்படும் கோரிக்கையை அனுப்புவதற்கு முன் HTTP 400 உடன் நிராகரிக்கவும்; குறியிடப்பட்ட முனைப்புள்ளிகளுக்கு OmniRoute adapter இல்லாத மாதிரிகளை /v1/models இலிருந்து மறைக்கவும்; அத்தகைய மாதிரிகளுக்கான dashboard மாதிரிச் சோதனைக்கு 422 எனப் பதிலளிக்கவும். இயல்பாக முடக்கப்பட்டுள்ளது: முனைப்புள்ளிக் குறிச்சொற்கள் தகவலுக்காக மட்டுமே இருக்கும், மேலும் கோரிக்கைகள் முன்பைப் போலவே அனுப்பப்படும்.", - "featureFlagAnthropicCompatibleMidConversationSystemDescription": "சாதாரண anthropic-இணக்கமான ரிலேக்களில், உரையாடலின் நடுவே வரும் system turn-களை மேல்நிலை system prompt-க்கு நகர்த்துவதற்குப் பதிலாக message பட்டியலுக்குள்ளேயே வைத்திருங்கள்; இதனால் தற்காலிகச் சேமிப்பில் உள்ள prompt முன்னொட்டு ஒவ்வொரு turn-லும் நிலையாக இருக்கும். கிளையன்ட் தானே mid-conversation-system-2026-04-07 beta-வை அனுப்பியிருக்கும்போது மட்டுமே இது பொருந்தும். இயல்பாக இது முடக்கப்பட்டிருக்கும்: அந்த beta இல்லாத ரிலேக்கள் message பட்டியலிலுள்ள system turn-களை நிராகரிப்பதால், முன்பு இருந்ததைப் போலவே ஒவ்வொரு system turn-மும் system prompt-க்கு நகர்த்தப்படும்.", "featureFlagModelsDevEffortTiersEnabledDescription": "ஆபரேட்டர் மேலெழுதலோ வழங்குநர் பதிவகத்தின் அறிவிப்போ இல்லாதபோது models.dev வெளியிடும் பகுத்தறிவு முயற்சி அடுக்குகளைப் பயன்படுத்தவும்; அறியப்பட்ட முயற்சி நிலைகளை மட்டும் வைத்துக்கொண்டு, காலியான அறிவிக்கப்பட்ட அடுக்குப் பட்டியலை \"அடுக்குகள் இல்லை\" எனக் கருதவும். இயல்பாக முடக்கப்பட்டிருக்கும்: மாதிரி பட்டியலும் தீர்மானிக்கப்பட்ட முயற்சி அடுக்குகளும் முன்பிருந்ததைப் போலவே இருக்கும்.", "featureFlagAutoRequestResolvedCapabilitiesDescription": "auto/* கோரிக்கைகள், தீர்க்கப்பட்ட மாடல் திறன்கள் (விஷன், ரீசனிங், திங்கிங்) மற்றும் கவனிக்கப்பட்ட தர மதிப்பெண்களுடன், ஒரே மொத்த வாசிப்பில், வேட்பாளர் தொகுப்பை உருவாக்க அனுமதிக்கிறது. இயல்பாக முடக்கப்பட்டுள்ளது: கோரிக்கை நேர வேட்பாளர் எடைகள் முன்பு இருந்தபடியே இருக்கும். இயக்கப்பட்டால்: இந்தச் சமிக்ஞைகள் ஸ்னாப்ஷாட் எடைகளில் சேர்கின்றன, எனவே வேட்பாளர் எடைகளும் தேர்வும் மாறலாம்.", "featureFlagCredentialHealthRetestAutoDisabledDescription": "நற்சான்று நிலை ஆய்வு, நுழைவாயில் தானாகவே முடக்கிய இணைப்புகளை மீண்டும் சோதிக்கவும் (ஒவ்வொன்றையும் அதிகபட்சம் 6 மணிநேரத்திற்கு ஒருமுறை, ஓர் ஆய்வுக்கு 5 இணைப்புகள்), மறுசோதனையில் தேர்ச்சி பெறும் இணைப்பை மீண்டும் இயக்கவும் அனுமதிக்கவும். இயல்பாக இது முடக்கப்பட்டிருக்கும்: ஓர் இயக்குநர் நடவடிக்கை எடுக்கும் வரை அத்தகைய இணைப்புகள் முடக்கப்பட்ட நிலையிலேயே இருக்கும். இயக்குநரால் முடக்கப்பட்ட மற்றும் நிரந்தரமாகத் தடைசெய்யப்பட்ட இணைப்புகள் ஒருபோதும் மீண்டும் சோதிக்கப்படாது.", diff --git a/src/i18n/messages/te.json b/src/i18n/messages/te.json index e2757167b1a0..85f4888084f8 100644 --- a/src/i18n/messages/te.json +++ b/src/i18n/messages/te.json @@ -1028,7 +1028,6 @@ "featureFlagOpencodeV2ClientFingerprintEnabledDescription": "OpenCode Zen/Go అభ్యర్థనలపై OpenCode v2 CLI క్లయింట్ ఫింగర్ప్రింట్ను పంపండి: opencode/latest/<2.x>/cli User-Agent మరియు x-opencode-client cli; అలాగే ఉచిత-స్థాయి అభ్యర్థనలపై v2 సెషన్ హెడర్లు, v2 CLI read/shell టూల్ స్కీమాలను పంపండి, కానీ tool_choice \"none\"ను పంపవద్దు. డిఫాల్ట్గా ఆఫ్లో ఉంటుంది: ప్రతి OpenCode అభ్యర్థన ప్రస్తుత గుర్తింపు, హెడర్లు మరియు బాడీని మార్పు లేకుండా ఉంచుతుంది.", "featureFlagComboEmptyPoolNoEligibleTargetsEnabledDescription": "టూల్-కాలింగ్ ప్రీ-ఫిల్టర్ అమలయ్యే ముందే ఆటో కాంబో యొక్క అభ్యర్థి పూల్ ఖాళీగా ఉంటే (కూల్డౌన్లు, లాక్అవుట్లు లేదా పరిమితి ముగియడం వల్ల ప్రతి లక్ష్యం దాటవేయబడితే), పాత 400 \"ఏ లక్ష్యమూ టూల్ కాలింగ్కు మద్దతు ఇవ్వదు\" capability_mismatchకు బదులుగా 503 no_eligible_targetsతో సమాధానం ఇవ్వండి. డిఫాల్ట్గా ఆఫ్లో ఉంటుంది: ఖాళీ పూల్కు పాత 400నే కొనసాగుతుంది.", "featureFlagModelEndpointCapabilityGateEnabledDescription": "విశ్వసనీయ మోడల్ ఎండ్పాయింట్ ట్యాగ్లను (ప్రొవైడర్ కేటలాగ్ ద్వారా ప్రకటించబడినవి, ప్రత్యేక మోడల్ id నుండి ఊహించబడినవి లేదా ఆపరేటర్ ద్వారా సెట్ చేయబడినవి) అమలు చేయండి: మోడల్ సేవలందించని ఎండ్పాయింట్కు పంపిన అభ్యర్థనను /v1/chat/completions, /v1/embeddings, /v1/rerank, /v1/images/generations, /v1/videos/generations, /v1/music/generations మరియు /v1/audio/* పై డిస్పాచ్కు ముందు HTTP 400తో తిరస్కరించండి; ట్యాగ్ చేయబడిన ఎండ్పాయింట్లకు OmniRoute అడాప్టర్ లేని మోడల్లను /v1/models నుండి దాచండి; అలాగే అటువంటి మోడల్ల కోసం డ్యాష్బోర్డ్ మోడల్ పరీక్షకు 422తో ప్రతిస్పందించండి. డిఫాల్ట్గా ఆఫ్లో ఉంటుంది: ఎండ్పాయింట్ ట్యాగ్లు సమాచారాత్మకంగానే ఉంటాయి మరియు అభ్యర్థనలు మునుపటిలాగే డిస్పాచ్ చేయబడతాయి.", - "featureFlagAnthropicCompatibleMidConversationSystemDescription": "సాధారణ anthropic-అనుకూల రిలేలలో, సంభాషణ మధ్యలోని system టర్న్లను అగ్ర-స్థాయి system ప్రాంప్ట్కు తరలించకుండా సందేశాల జాబితాలోనే ఉంచండి, తద్వారా క్యాష్ చేసిన ప్రాంప్ట్ ప్రిఫిక్స్ టర్న్ల మధ్య స్థిరంగా ఉంటుంది. క్లయింట్ స్వయంగా mid-conversation-system-2026-04-07 బీటాను పంపినప్పుడు మాత్రమే ఇది వర్తిస్తుంది. డిఫాల్ట్గా ఆఫ్లో ఉంటుంది: ఆ బీటా లేని రిలేలు సందేశాల జాబితాలోని system టర్న్లను తిరస్కరిస్తాయి కాబట్టి, మునుపటిలాగే ప్రతి system టర్న్ను యథాతథంగా system ప్రాంప్ట్లోకి తరలిస్తారు.", "featureFlagModelsDevEffortTiersEnabledDescription": "ఆపరేటర్ ఓవర్రైడ్ లేదా ప్రొవైడర్ రిజిస్ట్రీ ఏ స్థాయిలనూ ప్రకటించనప్పుడు models.dev ప్రచురించే రీజనింగ్ ఎఫర్ట్ టియర్లను ఉపయోగించండి; తెలిసిన ఎఫర్ట్ స్థాయిలను మాత్రమే ఉంచి, ఖాళీగా ప్రకటించిన టియర్ జాబితాను \"టియర్లు లేవు\"గా పరిగణించండి. డిఫాల్ట్గా ఆఫ్లో ఉంటుంది: మోడల్ కేటలాగ్ మరియు పరిష్కరించబడిన ఎఫర్ట్ టియర్లు మునుపటిలాగే యథాతథంగా ఉంటాయి.", "featureFlagAutoRequestResolvedCapabilitiesDescription": "auto/* అభ్యర్థనలు పరిష్కరించిన మోడల్ సామర్థ్యాలు (విజన్, రీజనింగ్, థింకింగ్) మరియు గమనించిన నాణ్యత స్కోర్లతో, ఒకే బల్క్ రీడ్‌లో, అభ్యర్థి పూల్‌ను నిర్మించడానికి అనుమతిస్తుంది. డిఫాల్ట్‌గా ఆఫ్: అభ్యర్థన సమయంలోని అభ్యర్థి బరువులు సరిగ్గా మునుపటిలాగే ఉంటాయి. ఆన్‌లో ఉంటే: ఈ సంకేతాలు స్నాప్‌షాట్ బరువుల్లో చేరతాయి, కాబట్టి అభ్యర్థి బరువులు మరియు ఎంపిక మారవచ్చు.", "featureFlagCredentialHealthRetestAutoDisabledDescription": "క్రెడెన్షియల్ హెల్త్ స్వీప్, గేట్వే స్వయంగా ఆఫ్ చేసిన కనెక్షన్లను మళ్లీ పరీక్షించడానికి (ఒక్కోదాన్ని గరిష్ఠంగా ప్రతి 6 గంటలకు ఒకసారి, ప్రతి స్వీప్కు 5 చొప్పున) మరియు మళ్లీ పరీక్షలో ఉత్తీర్ణమైన కనెక్షన్ను తిరిగి ఆన్ చేయడానికి అనుమతించండి. డిఫాల్ట్గా ఆఫ్లో ఉంటుంది: ఆపరేటర్ చర్య తీసుకునే వరకు అటువంటి కనెక్షన్లు ఆఫ్లోనే ఉంటాయి. ఆపరేటర్ డిసేబుల్ చేసిన మరియు శాశ్వతంగా నిషేధించిన కనెక్షన్లు ఎప్పటికీ మళ్లీ పరీక్షించబడవు.", diff --git a/src/i18n/messages/th.json b/src/i18n/messages/th.json index 1f4686501617..4e8ae5f5452e 100644 --- a/src/i18n/messages/th.json +++ b/src/i18n/messages/th.json @@ -1028,7 +1028,6 @@ "featureFlagOpencodeV2ClientFingerprintEnabledDescription": "ส่งลายนิ้วมือไคลเอนต์ OpenCode v2 CLI ในคำขอ OpenCode Zen/Go ได้แก่ User-Agent opencode/latest/<2.x>/cli และ x-opencode-client cli และสำหรับคำขอระดับฟรี ให้ส่งส่วนหัวเซสชัน v2 และสคีมาเครื่องมือ read/shell ของ v2 CLI โดยไม่มี tool_choice \"none\" ปิดใช้งานโดยค่าเริ่มต้น: คำขอ OpenCode ทุกรายการจะคงข้อมูลระบุตัวตน ส่วนหัว และเนื้อหาคำขอปัจจุบันไว้โดยไม่เปลี่ยนแปลง", "featureFlagComboEmptyPoolNoEligibleTargetsEnabledDescription": "เมื่อพูลตัวเลือกของคอมโบอัตโนมัติว่างเปล่าอยู่แล้วก่อนที่ตัวกรองล่วงหน้าสำหรับการเรียกใช้เครื่องมือจะทำงาน (ทุกเป้าหมายถูกข้ามเนื่องจากช่วงพัก การล็อกไม่ให้ใช้งาน หรือการใช้งานจนหมด) ให้ตอบกลับด้วย 503 no_eligible_targets แทน 400 \"ไม่มีเป้าหมายใดรองรับการเรียกใช้เครื่องมือ\" capability_mismatch แบบเดิม โดยค่าเริ่มต้นจะปิดอยู่: พูลที่ว่างเปล่าจะยังคงใช้ 400 แบบเดิม", "featureFlagModelEndpointCapabilityGateEnabledDescription": "บังคับใช้แท็กเอนด์พอยต์ของโมเดลที่เชื่อถือได้ (ประกาศโดยแค็ตตาล็อกผู้ให้บริการ อนุมานจากรหัสโมเดลเฉพาะทาง หรือตั้งค่าโดยผู้ดำเนินการ): ปฏิเสธคำขอที่ส่งไปยังเอนด์พอยต์ซึ่งโมเดลไม่ได้ให้บริการด้วย HTTP 400 ก่อนส่งต่อบน /v1/chat/completions, /v1/embeddings, /v1/rerank, /v1/images/generations, /v1/videos/generations, /v1/music/generations และ /v1/audio/*; ซ่อนโมเดลที่เอนด์พอยต์ซึ่งมีแท็กไม่มีอะแดปเตอร์ OmniRoute จาก /v1/models; และตอบกลับการทดสอบโมเดลบนแดชบอร์ดด้วย 422 สำหรับโมเดลดังกล่าว ปิดใช้งานโดยค่าเริ่มต้น: แท็กเอนด์พอยต์ยังคงมีไว้เพื่อให้ข้อมูล และคำขอจะถูกส่งต่อตามเดิมทุกประการ", - "featureFlagAnthropicCompatibleMidConversationSystemDescription": "สำหรับรีเลย์แบบธรรมดาที่รองรับ anthropic ให้เก็บเทิร์น system ที่อยู่ระหว่างการสนทนาไว้ในรายการข้อความ แทนที่จะย้ายไปยังพรอมต์ system ระดับบนสุด เพื่อให้คำนำหน้าพรอมต์ที่แคชไว้คงที่ตลอดแต่ละเทิร์น ใช้เฉพาะเมื่อไคลเอ็นต์ส่งเบต้า mid-conversation-system-2026-04-07 มาเองเท่านั้น ค่าเริ่มต้นคือปิด: ทุกเทิร์น system จะถูกย้ายไปยังพรอมต์ system เหมือนเดิมทุกประการ เนื่องจากรีเลย์ที่ไม่มีเบต้านี้จะปฏิเสธเทิร์น system ในรายการข้อความ", "featureFlagModelsDevEffortTiersEnabledDescription": "ใช้ระดับขั้นของความพยายามในการให้เหตุผลที่ models.dev เผยแพร่ เมื่อทั้งการตั้งค่าแทนที่โดยผู้ดำเนินการและรีจิสทรีของผู้ให้บริการไม่ได้ระบุไว้ โดยคงไว้เฉพาะระดับความพยายามที่รู้จัก และถือว่ารายการระดับขั้นที่ประกาศไว้แต่เป็นค่าว่างหมายถึง \"ไม่มีระดับขั้น\" ปิดใช้งานโดยค่าเริ่มต้น: แค็ตตาล็อกโมเดลและระดับขั้นของความพยายามที่ผ่านการกำหนดค่าแล้วยังคงเหมือนเดิมทุกประการ", "featureFlagAutoRequestResolvedCapabilitiesDescription": "ให้คำขอ auto/* สร้างกลุ่มตัวเลือกด้วยความสามารถของโมเดลที่ระบุแล้ว (การมองเห็น การให้เหตุผล การคิด) และคะแนนคุณภาพที่สังเกตได้ ซึ่งอ่านมาในการอ่านแบบรวมครั้งเดียว ปิดโดยค่าเริ่มต้น: น้ำหนักของตัวเลือก ณ เวลาที่ส่งคำขอยังคงเหมือนเดิมทุกประการ เมื่อเปิด: สัญญาณเหล่านั้นจะถูกนำไปใช้ในน้ำหนักของสแนปช็อต ดังนั้นน้ำหนักของตัวเลือกและการเลือกอาจเปลี่ยนไป", "featureFlagCredentialHealthRetestAutoDisabledDescription": "อนุญาตให้การตรวจสอบสถานะข้อมูลรับรองทดสอบการเชื่อมต่อที่เกตเวย์ปิดใช้งานเองซ้ำอีกครั้ง (แต่ละการเชื่อมต่อไม่เกินหนึ่งครั้งทุก 6 ชั่วโมง และไม่เกิน 5 รายการต่อรอบ) และเปิดใช้งานการเชื่อมต่อนั้นอีกครั้งเมื่อผ่านการทดสอบซ้ำ ค่าเริ่มต้นคือปิด: การเชื่อมต่อดังกล่าวจะยังคงปิดอยู่จนกว่าผู้ดูแลระบบจะดำเนินการ การเชื่อมต่อที่ผู้ดูแลระบบปิดใช้งานและการเชื่อมต่อที่ถูกแบนถาวรจะไม่ถูกทดสอบซ้ำ", diff --git a/src/i18n/messages/tr.json b/src/i18n/messages/tr.json index eabed776a9f5..9cebe4a712d3 100644 --- a/src/i18n/messages/tr.json +++ b/src/i18n/messages/tr.json @@ -1028,7 +1028,6 @@ "featureFlagOpencodeV2ClientFingerprintEnabledDescription": "OpenCode Zen/Go isteklerinde OpenCode v2 CLI istemci parmak izini gönderir: opencode/latest/<2.x>/cli User-Agent ve x-opencode-client cli; ücretsiz katman isteklerinde ise v2 oturum üstbilgilerini, v2 CLI read/shell araç şemalarını gönderir ve tool_choice \"none\" kullanmaz. Varsayılan olarak kapalıdır: her OpenCode isteğinin mevcut kimliği, üstbilgileri ve gövdesi değişmeden kalır.", "featureFlagComboEmptyPoolNoEligibleTargetsEnabledDescription": "Bir otomatik kombinasyonun aday havuzu, araç çağırma ön filtresi çalışmadan önce zaten boşsa (tüm hedefler bekleme süreleri, kilitlenmeler veya tükenme nedeniyle atlanmışsa), eski 400 \"Hiçbir hedef araç çağırmayı desteklemiyor\" capability_mismatch yanıtı yerine 503 no_eligible_targets yanıtını döndürün. Varsayılan olarak kapalıdır: boş havuz, eski 400 yanıtını döndürmeye devam eder.", "featureFlagModelEndpointCapabilityGateEnabledDescription": "Güvenilir model uç noktası etiketlerini (sağlayıcı kataloğu tarafından bildirilen, özel amaçlı bir model kimliğinden çıkarılan veya operatör tarafından ayarlanan) zorunlu kılın: modelin hizmet vermediği bir uç noktaya gönderilen isteği /v1/chat/completions, /v1/embeddings, /v1/rerank, /v1/images/generations, /v1/videos/generations, /v1/music/generations ve /v1/audio/* yollarında yönlendirmeden önce HTTP 400 ile reddedin; etiketli uç noktaları için OmniRoute bağdaştırıcısı bulunmayan modelleri /v1/models üzerinden gizleyin ve bu tür modeller için pano model testine 422 ile yanıt verin. Varsayılan olarak kapalıdır: uç noktası etiketleri yalnızca bilgilendirme amaçlı kalır ve istekler daha önce olduğu gibi yönlendirilir.", - "featureFlagAnthropicCompatibleMidConversationSystemDescription": "Standart Anthropic uyumlu aktarıcılarda, önbelleğe alınan istem önekinin turlar arasında sabit kalması için konuşmanın ortasındaki sistem turlarını üst düzey sistem istemine taşımak yerine mesaj listesinde tutar. Yalnızca istemcinin kendisi mid-conversation-system-2026-04-07 beta sürümünü gönderdiğinde uygulanır. Varsayılan olarak kapalıdır: Bu beta sürümünü desteklemeyen aktarıcılar mesaj listesindeki sistem turlarını reddettiğinden, her sistem turu daha önce olduğu gibi sistem istemine taşınır.", "featureFlagModelsDevEffortTiersEnabledDescription": "Ne bir operatör geçersiz kılması ne de sağlayıcı kayıt defteri herhangi bir seviye belirttiğinde, models.dev tarafından yayımlanan akıl yürütme eforu seviyelerini kullanın; yalnızca bilinen efor seviyelerini koruyun ve boş bir seviye listesini \"seviye yok\" olarak değerlendirin. Varsayılan olarak kapalıdır: model kataloğu ve çözümlenmiş efor seviyeleri tam olarak önceki gibi kalır.", "featureFlagAutoRequestResolvedCapabilitiesDescription": "auto/* isteklerinin aday havuzunu çözümlenmiş model yetenekleri (görme, akıl yürütme, düşünme) ve gözlemlenen kalite puanlarıyla, tek bir toplu okumada oluşturmasını sağlar. Varsayılan olarak kapalı: istek anındaki aday ağırlıkları tam olarak önceki gibi kalır. Açık: bu sinyaller anlık görüntü ağırlıklarına girer, bu nedenle aday ağırlıkları ve seçim değişebilir.", "featureFlagCredentialHealthRetestAutoDisabledDescription": "Kimlik bilgisi durumu taramasının, ağ geçidinin kendiliğinden kapattığı bağlantıları yeniden test etmesine (her biri en fazla 6 saatte bir, tarama başına 5 adet) ve yeniden testi geçen bir bağlantıyı tekrar açmasına izin verin. Varsayılan olarak kapalıdır: bu tür bağlantılar, bir operatör müdahale edene kadar kapalı kalır. Operatör tarafından devre dışı bırakılan ve kalıcı olarak yasaklanan bağlantılar hiçbir zaman yeniden test edilmez.", diff --git a/src/i18n/messages/uk-UA.json b/src/i18n/messages/uk-UA.json index 41019918e28d..19cd08e0e313 100644 --- a/src/i18n/messages/uk-UA.json +++ b/src/i18n/messages/uk-UA.json @@ -1028,7 +1028,6 @@ "featureFlagOpencodeV2ClientFingerprintEnabledDescription": "Надсилати ідентифікатор клієнта OpenCode v2 CLI у запитах OpenCode Zen/Go: User-Agent opencode/latest/<2.x>/cli і x-opencode-client cli, а в запитах безкоштовного рівня — заголовки сеансу v2, схеми інструментів read/shell для v2 і без tool_choice \"none\". За замовчуванням вимкнено: кожен запит OpenCode зберігає поточні ідентифікаційні дані, заголовки й тіло без змін.", "featureFlagComboEmptyPoolNoEligibleTargetsEnabledDescription": "Коли пул кандидатів автокомбінації вже порожній до запуску попереднього фільтра виклику інструментів (усі цілі пропущено через періоди очікування, блокування або вичерпання), повертати 503 no_eligible_targets замість застарілої помилки 400 «Жодна ціль не підтримує виклик інструментів» capability_mismatch. За замовчуванням вимкнено: для порожнього пулу повертається застаріла помилка 400.", "featureFlagModelEndpointCapabilityGateEnabledDescription": "Примусово застосовувати довірені теги кінцевих точок моделей (оголошені в каталозі провайдера, визначені на основі ідентифікатора спеціалізованої моделі або задані оператором): відхиляти із HTTP 400 до маршрутизації запит, надісланий до кінцевої точки, яку модель не обслуговує, для /v1/chat/completions, /v1/embeddings, /v1/rerank, /v1/images/generations, /v1/videos/generations, /v1/music/generations і /v1/audio/*; приховувати з /v1/models моделі, для позначених тегами кінцевих точок яких немає адаптера OmniRoute; і відповідати кодом 422 на тест моделі на панелі керування для таких моделей. Типово вимкнено: теги кінцевих точок залишаються інформаційними, а запити маршрутизуються так само, як і раніше.", - "featureFlagAnthropicCompatibleMidConversationSystemDescription": "У звичайних ретрансляторах, сумісних з Anthropic, залишати системні повідомлення посеред розмови у списку повідомлень, а не переносити їх до системного запиту верхнього рівня, щоб кешований префікс запиту залишався стабільним між ходами. Застосовується лише тоді, коли сам клієнт надіслав бета-параметр mid-conversation-system-2026-04-07. За замовчуванням вимкнено: кожне системне повідомлення переноситься до системного запиту, як і раніше, оскільки ретранслятори без цього бета-параметра відхиляють системні повідомлення у списку повідомлень.", "featureFlagModelsDevEffortTiersEnabledDescription": "Використовувати рівні зусиль для міркування, опубліковані models.dev, коли їх не вказано ані в перевизначенні оператора, ані в реєстрі постачальника, зберігаючи лише відомі рівні зусиль і вважаючи порожній список оголошених рівнів як «без рівнів». Типово вимкнено: каталог моделей і визначені рівні зусиль залишаються без змін.", "featureFlagAutoRequestResolvedCapabilitiesDescription": "Дозволяє запитам auto/* формувати пул кандидатів із розв’язаними можливостями моделей (зір, міркування, мислення) та спостереженими оцінками якості, зчитаними одним пакетним читанням. Типово вимкнено: ваги кандидатів під час запиту залишаються точно такими, як раніше. Увімкнено: ці сигнали потрапляють у ваги знімка, тому ваги кандидатів і вибір можуть змінитися.", "featureFlagCredentialHealthRetestAutoDisabledDescription": "Дозвольте перевірці стану облікових даних повторно тестувати з’єднання, які шлюз вимкнув самостійно (не частіше одного разу на 6 годин для кожного з’єднання, по 5 за одну перевірку), і повторно вмикати з’єднання, якщо воно пройшло тест. За замовчуванням вимкнено: такі з’єднання залишаються вимкненими, доки оператор не втрутиться. З’єднання, вимкнені оператором або назавжди заблоковані, ніколи не тестуються повторно.", diff --git a/src/i18n/messages/ur.json b/src/i18n/messages/ur.json index 1ecd145d4ba7..149471d8c774 100644 --- a/src/i18n/messages/ur.json +++ b/src/i18n/messages/ur.json @@ -1028,7 +1028,6 @@ "featureFlagOpencodeV2ClientFingerprintEnabledDescription": "OpenCode Zen/Go کی درخواستوں پر OpenCode v2 CLI کلائنٹ فنگرپرنٹ بھیجیں: opencode/latest/<2.x>/cli User-Agent اور x-opencode-client cli، اور فری ٹیر کی درخواستوں پر v2 سیشن ہیڈرز، v2 CLI read/shell ٹول اسکیمے، اور کوئی tool_choice \"none\" نہیں۔ بطور ڈیفالٹ بند: ہر OpenCode درخواست موجودہ شناخت، ہیڈرز اور باڈی کو بغیر کسی تبدیلی کے برقرار رکھتی ہے۔", "featureFlagComboEmptyPoolNoEligibleTargetsEnabledDescription": "جب ٹول کالنگ پری فلٹر چلنے سے پہلے ہی کسی آٹو کومبو کا امیدوار پول خالی ہو (کول ڈاؤن، لاک آؤٹ یا وسائل ختم ہونے کی وجہ سے ہر ہدف چھوڑ دیا گیا ہو)، تو پرانے 400 \"کوئی ہدف ٹول کالنگ کو سپورٹ نہیں کرتا\" capability_mismatch کے بجائے 503 no_eligible_targets جواب دیں۔ بطور ڈیفالٹ غیر فعال: خالی پول کے لیے پرانا 400 ہی برقرار رہتا ہے۔", "featureFlagModelEndpointCapabilityGateEnabledDescription": "قابلِ اعتماد ماڈل اینڈ پوائنٹ ٹیگز (جو فراہم کنندہ کی کیٹلاگ میں درج ہوں، کسی مخصوص ماڈل id سے اخذ کیے گئے ہوں، یا آپریٹر نے مقرر کیے ہوں) نافذ کریں: /v1/chat/completions، /v1/embeddings، /v1/rerank، /v1/images/generations، /v1/videos/generations، /v1/music/generations اور /v1/audio/* پر ایسے اینڈ پوائنٹ کو بھیجی گئی درخواست، جسے ماڈل سپورٹ نہیں کرتا، روانگی سے پہلے HTTP 400 کے ساتھ مسترد کریں؛ جن ماڈلز کے ٹیگ شدہ اینڈ پوائنٹس کے لیے کوئی OmniRoute اڈاپٹر موجود نہ ہو، انہیں /v1/models سے چھپائیں؛ اور ایسے ماڈلز کے لیے ڈیش بورڈ ماڈل ٹیسٹ کا جواب 422 کے ساتھ دیں۔ بطور ڈیفالٹ بند: اینڈ پوائنٹ ٹیگز صرف معلوماتی رہیں گے اور درخواستیں بالکل پہلے کی طرح روانہ کی جائیں گی۔", - "featureFlagAnthropicCompatibleMidConversationSystemDescription": "سادہ anthropic-compatible ریلے پر، گفتگو کے درمیان آنے والے سسٹم ٹرنز کو اعلیٰ سطح کے سسٹم پرامپٹ میں منتقل کرنے کے بجائے پیغامات کی فہرست میں ہی رکھیں، تاکہ کیش شدہ پرامپٹ کا سابقہ تمام ٹرنز میں مستحکم رہے۔ یہ صرف اس وقت لاگو ہوتا ہے جب کلائنٹ نے خود mid-conversation-system-2026-04-07 بیٹا بھیجا ہو۔ بطور ڈیفالٹ بند ہے: پہلے کی طرح ہر سسٹم ٹرن کو بعینہٖ سسٹم پرامپٹ میں منتقل کیا جاتا ہے، کیونکہ اس بیٹا کے بغیر ریلے پیغامات کی فہرست میں موجود سسٹم ٹرنز کو مسترد کر دیتے ہیں۔", "featureFlagModelsDevEffortTiersEnabledDescription": "جب نہ کوئی آپریٹر اوور رائیڈ اور نہ ہی فراہم کنندہ رجسٹری کسی سطح کا اعلان کرے تو models.dev کی شائع کردہ استدلالی کوشش کی سطحیں استعمال کریں، صرف معلوم کوشش کی سطحیں برقرار رکھیں، اور اعلان کردہ سطحوں کی خالی فہرست کو \"کوئی سطح نہیں\" سمجھیں۔ بطورِ طے شدہ غیر فعال: ماڈل کیٹلاگ اور حل شدہ کوشش کی سطحیں بالکل پہلے جیسی رہیں گی۔", "featureFlagAutoRequestResolvedCapabilitiesDescription": "auto/* درخواستوں کو حل شدہ ماڈل صلاحیتوں (وژن، استدلال، سوچ) اور مشاہدہ شدہ معیار کے اسکور کے ساتھ، ایک ہی بلک ریڈ میں، امیدواروں کا پول بنانے دیتا ہے۔ بطور ڈیفالٹ بند: درخواست کے وقت امیدواروں کے وزن بالکل پہلے جیسے رہتے ہیں۔ آن ہونے پر: یہ سگنل اسنیپ شاٹ کے وزن میں شامل ہوتے ہیں، اس لیے امیدواروں کے وزن اور انتخاب بدل سکتے ہیں۔", "featureFlagCredentialHealthRetestAutoDisabledDescription": "کریڈینشل ہیلتھ سویپ کو ان کنکشنز کی دوبارہ جانچ کرنے دیں جنہیں گیٹ وے نے خود بند کیا تھا (ہر ایک کی زیادہ سے زیادہ ہر 6 گھنٹے میں ایک بار، اور فی سویپ 5 کنکشنز)، اور دوبارہ جانچ کامیاب ہونے پر کنکشن کو پھر سے فعال کر دیں۔ بطور ڈیفالٹ بند: ایسے کنکشنز اس وقت تک بند رہتے ہیں جب تک کوئی آپریٹر کارروائی نہ کرے۔ آپریٹر کی جانب سے غیر فعال کیے گئے اور مستقل طور پر پابندی زدہ کنکشنز کی کبھی دوبارہ جانچ نہیں کی جاتی۔", diff --git a/src/i18n/messages/uz.json b/src/i18n/messages/uz.json index 6ec6f022e040..cde11ff80826 100644 --- a/src/i18n/messages/uz.json +++ b/src/i18n/messages/uz.json @@ -1028,7 +1028,6 @@ "featureFlagOpencodeV2ClientFingerprintEnabledDescription": "OpenCode Zen/Go so‘rovlarida OpenCode v2 CLI mijoz fingerprintini yuborish: opencode/latest/<2.x>/cli User-Agent va x-opencode-client cli, bepul tarifdagi so‘rovlarda esa v2 sessiya sarlavhalari, v2 CLI read/shell vosita sxemalari hamda tool_choice \"none\" bo‘lmasin. Sukut bo‘yicha o‘chiq: har bir OpenCode so‘rovida joriy identifikatsiya, sarlavhalar va tana o‘zgarmay qoladi.", "featureFlagComboEmptyPoolNoEligibleTargetsEnabledDescription": "Avtomatik kombinatsiyaning nomzodlar puli vosita chaqiruvini dastlabki filtrlash ishga tushishidan oldin bo‘sh bo‘lsa (barcha nishonlar kutish muddatlari, blokirovkalar yoki limit tugashi sababli o‘tkazib yuborilgan bo‘lsa), eski 400 \"Hech bir nishon vosita chaqiruvini qo‘llab-quvvatlamaydi\" capability_mismatch o‘rniga 503 no_eligible_targets javobini qaytaring. Sukut bo‘yicha o‘chirilgan: bo‘sh pul uchun eski 400 javobi saqlanadi.", "featureFlagModelEndpointCapabilityGateEnabledDescription": "Ishonchli model endpoint teglariga rioya qilishni majburiy eting (provayder katalogida e’lon qilingan, maxsus model identifikatoridan aniqlangan yoki operator tomonidan belgilangan): model xizmat ko‘rsatmaydigan endpointga yuborilgan so‘rovni /v1/chat/completions, /v1/embeddings, /v1/rerank, /v1/images/generations, /v1/videos/generations, /v1/music/generations va /v1/audio/* yo‘nalishlarida jo‘natishdan oldin HTTP 400 bilan rad eting; teg bilan belgilangan endpointlari uchun OmniRoute adapteri mavjud bo‘lmagan modellarni /v1/models ro‘yxatidan yashiring; bunday modellar uchun boshqaruv panelidagi model sinoviga 422 bilan javob bering. Odatiy holatda o‘chirilgan: endpoint teglari faqat axborot uchun qoladi va so‘rovlar avvalgidek jo‘natiladi.", - "featureFlagAnthropicCompatibleMidConversationSystemDescription": "Oddiy Anthropic bilan mos retranslyatorlarda suhbat o‘rtasidagi system navbatlarini yuqori darajadagi system promptiga ko‘chirish o‘rniga xabarlar ro‘yxatida saqlang, shunda keshlangan prompt prefiksi navbatlar davomida barqaror qoladi. Bu faqat mijozning o‘zi mid-conversation-system-2026-04-07 beta-versiyasini yuborganida qo‘llanadi. Sukut bo‘yicha o‘chiq: ushbu beta-versiyani qo‘llab-quvvatlamaydigan retranslyatorlar xabarlar ro‘yxatidagi system navbatlarini rad etgani sababli, har bir system navbati avvalgidek system promptiga ko‘chiriladi.", "featureFlagModelsDevEffortTiersEnabledDescription": "Operator tomonidan qayta belgilash ham, provayder reyestri ham hech qanday darajani eʼlon qilmaganida models.dev eʼlon qilgan fikrlash sarfi darajalaridan foydalaning, bunda faqat maʼlum sarf darajalarini saqlab qoling va eʼlon qilingan darajalar roʻyxati boʻsh boʻlsa, uni «darajalar yoʻq» deb hisoblang. Sukut boʻyicha oʻchiq: model katalogi va aniqlangan sarf darajalari avvalgidek qoladi.", "featureFlagAutoRequestResolvedCapabilitiesDescription": "auto/* so‘rovlariga nomzodlar havzasini hal qilingan model imkoniyatlari (ko‘rish, mulohaza, fikrlash) va kuzatilgan sifat ballari bilan, bitta ommaviy o‘qishda, tuzish imkonini beradi. Standart holatda o‘chirilgan: so‘rov vaqtidagi nomzod vaznlari aynan avvalgidek qoladi. Yoqilganda: bu signallar snapshot vaznlariga kiradi, shuning uchun nomzod vaznlari va tanlov o‘zgarishi mumkin.", "featureFlagCredentialHealthRetestAutoDisabledDescription": "Hisob ma’lumotlari holatini tekshirish jarayoniga shlyuz o‘zi o‘chirgan ulanishlarni qayta sinashga (har birini ko‘pi bilan 6 soatda bir marta, har bir tekshiruvda 5 tadan) va qayta sinovdan muvaffaqiyatli o‘tgan ulanishni yana yoqishga ruxsat bering. Odatiy holatda o‘chiq: bunday ulanishlar operator chora ko‘rmaguncha o‘chiq qoladi. Operator o‘chirgan va doimiy ravishda taqiqlangan ulanishlar hech qachon qayta sinalmaydi.", diff --git a/src/i18n/messages/vi.json b/src/i18n/messages/vi.json index 48e6bdf4ddb8..3729ee9f6f3f 100644 --- a/src/i18n/messages/vi.json +++ b/src/i18n/messages/vi.json @@ -1028,7 +1028,6 @@ "featureFlagOpencodeV2ClientFingerprintEnabledDescription": "Gửi dấu vân tay ứng dụng CLI OpenCode v2 trong các yêu cầu OpenCode Zen/Go: User-Agent opencode/latest/<2.x>/cli và x-opencode-client cli; đồng thời, trong các yêu cầu gói miễn phí, gửi các tiêu đề phiên v2, lược đồ công cụ read/shell của CLI v2 và không gửi tool_choice \"none\". Mặc định tắt: mọi yêu cầu OpenCode giữ nguyên danh tính, tiêu đề và nội dung hiện tại.", "featureFlagComboEmptyPoolNoEligibleTargetsEnabledDescription": "Khi nhóm ứng viên của một tổ hợp tự động đã trống trước khi bộ lọc sơ bộ cho việc gọi công cụ chạy (mọi đích đều bị bỏ qua do thời gian chờ, khóa tạm thời hoặc cạn kiệt), hãy trả về 503 no_eligible_targets thay vì lỗi 400 cũ \"Không có đích nào hỗ trợ gọi công cụ\" capability_mismatch. Mặc định tắt: khi nhóm ứng viên trống, hệ thống vẫn trả về lỗi 400 cũ.", "featureFlagModelEndpointCapabilityGateEnabledDescription": "Thực thi các thẻ endpoint mô hình đáng tin cậy (được khai báo bởi danh mục nhà cung cấp, suy luận từ ID mô hình chuyên biệt hoặc do người vận hành thiết lập): từ chối bằng HTTP 400 trước khi điều phối đối với yêu cầu được gửi đến endpoint mà mô hình không phục vụ trên /v1/chat/completions, /v1/embeddings, /v1/rerank, /v1/images/generations, /v1/videos/generations, /v1/music/generations và /v1/audio/*; ẩn khỏi /v1/models các mô hình có endpoint được gắn thẻ nhưng không có bộ điều hợp OmniRoute; và trả về mã 422 cho phép kiểm thử mô hình trên bảng điều khiển đối với các mô hình như vậy. Mặc định tắt: các thẻ endpoint chỉ mang tính cung cấp thông tin và các yêu cầu được điều phối chính xác như trước đây.", - "featureFlagAnthropicCompatibleMidConversationSystemDescription": "Trên các relay thông thường tương thích với Anthropic, hãy giữ các lượt hệ thống giữa cuộc hội thoại trong danh sách tin nhắn thay vì chuyển chúng vào lời nhắc hệ thống cấp cao nhất, để tiền tố lời nhắc được lưu vào bộ nhớ đệm duy trì ổn định qua các lượt. Chỉ áp dụng khi chính máy khách đã gửi bản beta mid-conversation-system-2026-04-07. Mặc định tắt: mọi lượt hệ thống đều được chuyển vào lời nhắc hệ thống giống hệt như trước đây, vì các relay không có bản beta đó sẽ từ chối các lượt hệ thống trong danh sách tin nhắn.", "featureFlagModelsDevEffortTiersEnabledDescription": "Sử dụng các cấp độ nỗ lực suy luận do models.dev công bố khi cả cấu hình ghi đè của người vận hành lẫn sổ đăng ký nhà cung cấp đều không khai báo cấp độ nào, chỉ giữ lại các mức nỗ lực đã biết và coi danh sách cấp độ được khai báo nhưng để trống là \"không có cấp độ\". Tắt theo mặc định: danh mục mô hình và các cấp độ nỗ lực đã phân giải vẫn giữ nguyên như trước.", "featureFlagAutoRequestResolvedCapabilitiesDescription": "Cho phép các yêu cầu auto/* xây dựng nhóm ứng viên với các khả năng mô hình đã được phân giải (thị giác, suy luận, tư duy) và điểm chất lượng quan sát được, đọc trong một lần đọc hàng loạt. Tắt theo mặc định: trọng số ứng viên tại thời điểm yêu cầu giữ nguyên chính xác như trước. Bật: các tín hiệu đó được đưa vào trọng số của ảnh chụp nhanh, nên trọng số ứng viên và việc lựa chọn có thể thay đổi.", "featureFlagCredentialHealthRetestAutoDisabledDescription": "Cho phép quy trình quét tình trạng thông tin xác thực kiểm tra lại các kết nối mà cổng đã tự tắt (tối đa một lần mỗi 6 giờ cho từng kết nối, 5 kết nối mỗi lượt quét) và bật lại kết nối khi kiểm tra lại thành công. Mặc định tắt: các kết nối đó vẫn bị tắt cho đến khi nhân viên vận hành can thiệp. Các kết nối bị nhân viên vận hành vô hiệu hóa và các kết nối bị cấm vĩnh viễn sẽ không bao giờ được kiểm tra lại.", diff --git a/src/i18n/messages/yo.json b/src/i18n/messages/yo.json index 0590eea4027d..0e1263e969cc 100644 --- a/src/i18n/messages/yo.json +++ b/src/i18n/messages/yo.json @@ -1028,7 +1028,6 @@ "featureFlagOpencodeV2ClientFingerprintEnabledDescription": "Fi ìtẹ̀wọ́-ìka oníbàárà OpenCode v2 CLI ránṣẹ́ lórí àwọn ìbéèrè OpenCode Zen/Go: opencode/latest/<2.x>/cli User-Agent àti x-opencode-client cli; àti lórí àwọn ìbéèrè ipele ọ̀fẹ́, àwọn àkọlé ìgbà v2, àwọn ètò irinṣẹ́ kíkà/shell v2 CLI, láìsí tool_choice \"none\". Ó wà ní pípa ní àìyípadà: gbogbo ìbéèrè OpenCode máa ń pa ìdánimọ̀, àwọn àkọlé àti ara rẹ̀ lọ́wọ́lọ́wọ́ mọ́ láìyípadà.", "featureFlagComboEmptyPoolNoEligibleTargetsEnabledDescription": "Nígbà tí àkójọpọ̀ àwọn olùdíje ti àkópọ̀ aládàáṣe bá ti ṣófo kí àlẹ́mọ́-àkọ́kọ́ fún ìpè irinṣẹ́ tó ṣiṣẹ́ (gbogbo ibi-àfojúsùn ni a fo nítorí àwọn àkókò ìsinmi, ìdènà tàbí ìtáná tán), dáhùn pẹ̀lú 503 no_eligible_targets dípò 400 àtijọ́, \"Kò sí ibi-àfojúsùn tó ṣe àtìlẹ́yìn fún ìpè irinṣẹ́\" capability_mismatch. Ó wà ní pípa ní àìròtẹ́lẹ̀: àkójọpọ̀ tó ṣófo yóò máa lo 400 àtijọ́.", "featureFlagModelEndpointCapabilityGateEnabledDescription": "Fipá mú lílo àwọn àmì ojú-ọ̀nà ìpẹ̀kun àwòṣe tí a fọkàn tán (èyí tí àkójọ olupèsè kéde, tí a yọ láti inú ID àwòṣe àkànṣe, tàbí tí olùṣàkóso ṣètò): kọ ìbéèrè tí a fi ránṣẹ́ sí ojú-ọ̀nà ìpẹ̀kun tí àwòṣe náà kò ṣiṣẹ́ lórí pẹ̀lú HTTP 400 kí a tó pín in lórí /v1/chat/completions, /v1/embeddings, /v1/rerank, /v1/images/generations, /v1/videos/generations, /v1/music/generations àti /v1/audio/*; fi àwọn àwòṣe tí àwọn ojú-ọ̀nà ìpẹ̀kun tí a fi àmì sí wọn kò ní ohun ìbámu OmniRoute pamọ́ kúrò ní /v1/models; kí o sì dá ìdánwò àwòṣe pánẹ́ẹ̀lì padà pẹ̀lú 422 fún irú àwọn àwòṣe bẹ́ẹ̀. Ó wà ní pípa ní àìyẹsẹ̀: àwọn àmì ojú-ọ̀nà ìpẹ̀kun yóò jẹ́ ti ìfitonilétí nìkan, a ó sì pín àwọn ìbéèrè gẹ́gẹ́ bí ti tẹ́lẹ̀.", - "featureFlagAnthropicCompatibleMidConversationSystemDescription": "Lórí àwọn relay anthropic-compatible lásán, jẹ́ kí àwọn ìyípadà system àárín ìjíròrò wà nínú àkójọ ìfiránṣẹ́ dípò kí a gbé wọn lọ sínú system prompt ìpele-ókè, kí prefix prompt tí a fi pamọ́ sínú cache lè dúró ṣinṣin láàárín àwọn ìyípadà. Èyí kan nígbà tí client fúnra rẹ̀ bá fi beta mid-conversation-system-2026-04-07 ránṣẹ́ nìkan. Ó wà ní pípa ní àkọ́kọ́: gbogbo ìyípadà system ni a máa ń gbé lọ sínú system prompt gẹ́gẹ́ bí ti tẹ́lẹ̀ gan-an, nítorí àwọn relay tí kò ní beta yẹn máa ń kọ àwọn ìyípadà system nínú àkójọ ìfiránṣẹ́.", "featureFlagModelsDevEffortTiersEnabledDescription": "Lo àwọn ìpele ìsapá ìrònú tí models.dev ṣe àtẹ̀jáde nígbà tí àtúnṣe olùdarí àti ìforúkọsílẹ̀ olùpèsè kò bá sọ èyíkéyìí, kí o sì fi àwọn ipele ìsapá tí a mọ̀ nìkan sílẹ̀, kí o sì ka àtòjọ ìpele tí a sọ tí ó ṣófo sí \"kò sí ìpele\". Ó wà ní pípa ní àìṣe-déédé: àkójọ awoṣe àti àwọn ìpele ìsapá tí a ti pinnu dúró gẹ́gẹ́ bí wọ́n ti wà tẹ́lẹ̀ gan-an.", "featureFlagAutoRequestResolvedCapabilitiesDescription": "Ó jẹ́ kí àwọn ìbéèrè auto/* kọ́ àkójọ àwọn olùdíje pẹ̀lú àwọn agbára àwòṣe tí a ti yanjú (ìríran, ìrònújinlẹ̀, ìrònú) àti àwọn máàkì dídára tí a ṣàkíyèsí, tí a kà ní ìkàwé ńlá kan ṣoṣo. Ó wà ní pípa ní àìyípadà: ìwọ̀n àwọn olùdíje ní àkókò ìbéèrè dúró gẹ́lẹ́ bí ó ti wà tẹ́lẹ̀. Nígbà tí ó bá wà ní títàn: àwọn àmì wọ̀nyẹn máa ń wọ inú ìwọ̀n àwòrán ìṣẹ́jú, nítorí náà ìwọ̀n àwọn olùdíje àti yíyàn lè yípadà.", "featureFlagCredentialHealthRetestAutoDisabledDescription": "Jẹ́ kí àyẹ̀wò ìlera ìwé-ẹ̀rí tún dán àwọn àsopọ̀ tí gateway pa fúnra rẹ̀ wò (ó pọ̀jù lẹ́ẹ̀kan ní gbogbo wákàtí 6 fún ọ̀kọ̀ọ̀kan, márùn-ún fún àyẹ̀wò kọ̀ọ̀kan), kí ó sì tún ọ̀kan tan nígbà tí àyẹ̀wò rẹ̀ bá ṣàṣeyọrí. Ó wà ní pípa láti ìbẹ̀rẹ̀: irú àwọn àsopọ̀ bẹ́ẹ̀ máa dúró ní pípa títí tí olùṣiṣẹ́ yóò fi gbé ìgbésẹ̀. A kì í tún àwọn àsopọ̀ tí olùṣiṣẹ́ pa tàbí tí a fòfin dè pátápátá dán wò láéláé.", diff --git a/src/i18n/messages/zh-CN.json b/src/i18n/messages/zh-CN.json index 1de7e1a9f225..4f47bb18cc7c 100644 --- a/src/i18n/messages/zh-CN.json +++ b/src/i18n/messages/zh-CN.json @@ -1028,7 +1028,6 @@ "featureFlagOpencodeV2ClientFingerprintEnabledDescription": "在 OpenCode Zen/Go 请求中发送 OpenCode v2 CLI 客户端指纹:opencode/latest/<2.x>/cli User-Agent 和 x-opencode-client cli;在免费套餐请求中发送 v2 会话标头和 v2 CLI read/shell 工具架构,且不发送 tool_choice \"none\"。默认关闭:每个 OpenCode 请求均保持当前身份、标头和请求正文不变。", "featureFlagComboEmptyPoolNoEligibleTargetsEnabledDescription": "当自动组合的候选池在工具调用预筛选运行前已为空时(所有目标都因冷却、锁定或耗尽而被跳过),返回 503 no_eligible_targets,而不是旧版的 400“没有目标支持工具调用” capability_mismatch。默认关闭:候选池为空时仍返回旧版 400。", "featureFlagModelEndpointCapabilityGateEnabledDescription": "强制执行可信模型端点标签(由提供者目录声明、根据专用模型 ID 推断,或由运维人员设置):对于发送到模型不支持的端点的请求,在分派前返回 HTTP 400 并拒绝,适用于 /v1/chat/completions、/v1/embeddings、/v1/rerank、/v1/images/generations、/v1/videos/generations、/v1/music/generations 和 /v1/audio/*;从 /v1/models 中隐藏其带标签端点没有 OmniRoute 适配器的模型;对于此类模型,控制面板模型测试返回 422。默认关闭:端点标签仍仅供参考,请求将与之前完全相同地分派。", - "featureFlagAnthropicCompatibleMidConversationSystemDescription": "对于普通的 Anthropic 兼容中继,将对话中途的 system 轮次保留在消息列表中,而不是将其移至顶层 system 提示词,这样缓存的提示词前缀就能在各轮次间保持稳定。仅当客户端自身发送了 mid-conversation-system-2026-04-07 beta 时适用。默认关闭:与之前完全相同,每个 system 轮次都会被移入 system 提示词,因为不支持该 beta 的中继会拒绝消息列表中的 system 轮次。", "featureFlagModelsDevEffortTiersEnabledDescription": "当运维方覆盖配置和提供者注册表均未声明推理强度层级时,使用 models.dev 发布的层级;仅保留已知的强度级别,并将声明的空层级列表视为“无层级”。默认关闭:模型目录和解析后的推理强度层级将与之前完全一致。", "featureFlagAutoRequestResolvedCapabilitiesDescription": "让 auto/* 请求使用已解析的模型能力(视觉、推理、思考)和观测到的质量评分构建候选池,这些数据通过一次批量读取获得。默认关闭:请求时的候选权重与之前完全相同。开启后:这些信号会计入快照权重,因此候选权重和选择可能发生变化。", "featureFlagCredentialHealthRetestAutoDisabledDescription": "允许凭据健康扫描重新测试由网关自动关闭的连接(每个连接最多每 6 小时测试一次,每轮扫描测试 5 个),并在重新测试通过后重新启用该连接。默认关闭:此类连接会保持关闭状态,直到操作员采取措施。操作员禁用和永久封禁的连接不会被重新测试。", diff --git a/src/i18n/messages/zh-TW.json b/src/i18n/messages/zh-TW.json index 7e336409f865..18360a258a51 100644 --- a/src/i18n/messages/zh-TW.json +++ b/src/i18n/messages/zh-TW.json @@ -1028,7 +1028,6 @@ "featureFlagOpencodeV2ClientFingerprintEnabledDescription": "在 OpenCode Zen/Go 請求中傳送 OpenCode v2 CLI 用戶端指紋:opencode/latest/<2.x>/cli User-Agent 與 x-opencode-client cli;在免費方案請求中,則傳送 v2 工作階段標頭、v2 CLI read/shell 工具結構描述,且不使用 tool_choice \"none\"。預設關閉:每個 OpenCode 請求都會維持目前的識別資訊、標頭與主體不變。", "featureFlagComboEmptyPoolNoEligibleTargetsEnabledDescription": "當自動組合的候選池在工具呼叫預篩選執行前便已為空(所有目標皆因冷卻、鎖定或耗盡而被略過)時,回應 503 no_eligible_targets,而不是舊版的 400「沒有任何目標支援工具呼叫」capability_mismatch。預設為關閉:候選池為空時仍維持舊版的 400 回應。", "featureFlagModelEndpointCapabilityGateEnabledDescription": "強制執行受信任的模型端點標籤(由提供者目錄宣告、從特殊用途模型 ID 推斷,或由操作員設定):若請求傳送至模型不提供服務的端點,則在分派至 /v1/chat/completions、/v1/embeddings、/v1/rerank、/v1/images/generations、/v1/videos/generations、/v1/music/generations 和 /v1/audio/* 前,以 HTTP 400 拒絕該請求;在 /v1/models 中隱藏其已標記端點沒有 OmniRoute 轉接器的模型;並針對此類模型,以 422 回應控制面板模型測試。預設關閉:端點標籤仍僅供參考,請求會完全依照先前的方式分派。", - "featureFlagAnthropicCompatibleMidConversationSystemDescription": "在一般的 anthropic 相容中繼服務上,將對話中途的 system 回合保留在訊息清單內,而不是移至頂層 system 提示詞,讓快取的提示詞前綴在各回合之間保持穩定。僅在用戶端本身傳送了 mid-conversation-system-2026-04-07 beta 時適用。預設關閉:每個 system 回合都會像以往一樣移至 system 提示詞,因為不支援該 beta 的中繼服務會拒絕訊息清單中的 system 回合。", "featureFlagModelsDevEffortTiersEnabledDescription": "當操作員覆寫與提供者登錄檔皆未宣告任何推理投入程度分級時,使用 models.dev 發布的分級,僅保留已知的投入程度層級,並將空白的已宣告分級清單視為「無分級」。預設關閉:模型目錄與解析後的投入程度分級會與先前完全相同。", "featureFlagAutoRequestResolvedCapabilitiesDescription": "讓 auto/* 請求使用已解析的模型能力(視覺、推理、思考)與觀測到的品質分數建立候選池,這些資料透過一次批次讀取取得。預設關閉:請求時的候選權重與先前完全相同。開啟後:這些訊號會納入快照權重,因此候選權重與選擇可能會改變。", "featureFlagCredentialHealthRetestAutoDisabledDescription": "允許憑證健康狀態掃描重新測試閘道自行停用的連線(每個連線最多每 6 小時一次,每次掃描 5 個),並在重新測試通過時重新啟用連線。預設為關閉:這類連線會保持停用,直到操作員採取行動。由操作員停用及遭永久封鎖的連線一律不會重新測試。", diff --git a/src/lib/providers/requestDefaults.ts b/src/lib/providers/requestDefaults.ts index a2d9ab6f893b..545adba0bea0 100644 --- a/src/lib/providers/requestDefaults.ts +++ b/src/lib/providers/requestDefaults.ts @@ -255,6 +255,14 @@ export function normalizeProviderSpecificData( delete normalized.disableCooling; } + // Mid-conversation system turns in place (open-sse/services/claudeMidConversationSystem.ts). + if ( + "keepMidConversationSystem" in normalized && + typeof normalized.keepMidConversationSystem !== "boolean" + ) { + delete normalized.keepMidConversationSystem; + } + // Claude OAuth usage-wall opt-ins (open-sse/services/claudeLowPriority.ts) — only // persist real booleans; both default to off when absent. if ("lowPriorityMode" in normalized && typeof normalized.lowPriorityMode !== "boolean") { diff --git a/src/shared/constants/featureFlagDefinitions.ts b/src/shared/constants/featureFlagDefinitions.ts index 928a25154c02..94c5eb265470 100644 --- a/src/shared/constants/featureFlagDefinitions.ts +++ b/src/shared/constants/featureFlagDefinitions.ts @@ -1210,18 +1210,6 @@ export const FEATURE_FLAG_DEFINITIONS: FeatureFlagDefinition[] = [ requiresRestart: false, warningLevel: "caution", }, - { - key: "ANTHROPIC_COMPATIBLE_MID_CONVERSATION_SYSTEM", - label: "Anthropic-Compatible Mid-Conversation System Turns", - description: - "On plain anthropic-compatible-* relays (claude → claude passthrough), keep mid-conversation role:system turns inside messages[] instead of hoisting them into the top-level system parameter, so the cached prompt prefix stays stable across turns. Applies only when the client itself sent the mid-conversation-system-2026-04-07 anthropic-beta (forwarded upstream as-is). Off by default: every system turn is hoisted exactly as before, because relays that do not support the beta reject role:system messages with 400.", - descriptionI18nKey: "featureFlagAnthropicCompatibleMidConversationSystemDescription", - category: "runtime", - defaultValue: "false", - type: "boolean", - requiresRestart: false, - warningLevel: "caution", - }, { key: "DB_HEALTHCHECK_STARTUP_DEFERRED_ENABLED", label: "DB Health Check: Defer Startup Scan", diff --git a/src/shared/validation/providerSpecificData.ts b/src/shared/validation/providerSpecificData.ts index a4d2a3d09b80..d028b7df6aaa 100644 --- a/src/shared/validation/providerSpecificData.ts +++ b/src/shared/validation/providerSpecificData.ts @@ -322,6 +322,15 @@ export function validateProviderSpecificData( }); } + const keepMidConversationSystem = data.keepMidConversationSystem; + if (keepMidConversationSystem !== undefined && typeof keepMidConversationSystem !== "boolean") { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: "providerSpecificData.keepMidConversationSystem must be a boolean", + path: ["keepMidConversationSystem"], + }); + } + const disableStreamOptions = data.disableStreamOptions; if (disableStreamOptions !== undefined && typeof disableStreamOptions !== "boolean") { ctx.addIssue({ diff --git a/tests/unit/claude-mid-conversation-system-compatible-passthrough.test.ts b/tests/unit/claude-mid-conversation-system-compatible-passthrough.test.ts index ae218e181e05..ff663f399a20 100644 --- a/tests/unit/claude-mid-conversation-system-compatible-passthrough.test.ts +++ b/tests/unit/claude-mid-conversation-system-compatible-passthrough.test.ts @@ -10,9 +10,10 @@ * (observed: ~27K cache read / ~105K cache write per turn on a 133K prompt). * * Keeping those turns in place needs an upstream that accepts `role: "system"` inside - * messages[] (the `mid-conversation-system-2026-04-07` beta). Relays without it 400, so the - * in-place shape is opt-in: ANTHROPIC_COMPATIBLE_MID_CONVERSATION_SYSTEM (default off) AND the - * client's own beta. Flag off, or beta absent, keeps the legacy full hoist byte-for-byte. + * messages[]. Relays without it 400, so the in-place shape is a per-connection opt-in: + * providerSpecificData.keepMidConversationSystem (default off). It no longer depends on the + * client sending the `mid-conversation-system-2026-04-07` beta — some Claude Code builds keep + * the turns but never send it. Opt-in off keeps the legacy full hoist byte-for-byte. * These tests assert the FINAL body and headers handed to fetch, not the helper in isolation. */ import assert from "node:assert/strict"; @@ -31,12 +32,10 @@ const originalFetch = globalThis.fetch; const PROVIDER = "anthropic-compatible-relay-test"; const MODEL = "claude-opus-5-5"; const MID_SYSTEM_BETA = "mid-conversation-system-2026-04-07"; -const FLAG = "ANTHROPIC_COMPATIBLE_MID_CONVERSATION_SYSTEM"; -delete process.env[FLAG]; +let keepMidSystem = false; function setFlag(on) { - if (on) process.env[FLAG] = "true"; - else delete process.env[FLAG]; + keepMidSystem = on; } // Captured Claude Code shape: it negotiates the beta itself when it sends such turns. @@ -65,7 +64,7 @@ async function flushAsyncSideEffects() { } test.afterEach(async () => { - delete process.env[FLAG]; + keepMidSystem = false; globalThis.fetch = originalFetch; await flushAsyncSideEffects(); core.resetDbInstance(); @@ -74,7 +73,6 @@ test.afterEach(async () => { }); test.after(() => { - delete process.env[FLAG]; globalThis.fetch = originalFetch; core.resetDbInstance(); fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); @@ -83,7 +81,10 @@ test.after(() => { function credentials() { return { apiKey: "sk-relay-test", - providerSpecificData: { baseUrl: "https://relay.example.test/v1" }, + providerSpecificData: { + baseUrl: "https://relay.example.test/v1", + ...(keepMidSystem ? { keepMidConversationSystem: true } : {}), + }, }; } @@ -189,7 +190,7 @@ function assertHoisted(call) { // ── Flag off (default): byte-for-byte legacy hoist ─────────────────────────────────────── -test("flag off (default): every system turn is hoisted even when the client sent the beta", async () => { +test("opt-in off (default): every system turn is hoisted even when the client sent the beta", async () => { setFlag(false); const body = buildBody(3); const call = await send(body); @@ -202,35 +203,57 @@ test("flag off (default): every system turn is hoisted even when the client sent ); }); -test("flag off (default): a beta-less client is hoisted too", async () => { +test("opt-in off (default): a beta-less client is hoisted too", async () => { setFlag(false); assertHoisted(await send(buildBody(2), { headers: NO_BETA_HEADERS })); }); -// ── Flag on, but the client did not negotiate the beta: still the legacy hoist ───────────── +// ── Opt-in on, client without the beta: still in place (the relay, not the client, decides) ── -test("flag on, client without the beta: turns are hoisted and the beta is never invented", async () => { +test("opt-in on, client without the beta: turns stay in place and the beta is never invented", async () => { setFlag(true); - assertHoisted(await send(buildBody(2), { headers: NO_BETA_HEADERS })); + const body = buildBody(2); + const call = await send(body, { headers: NO_BETA_HEADERS }); + assertInPlace(call, body); + assert.ok(!betaTokens(call).includes(MID_SYSTEM_BETA), `beta: ${betaTokens(call)}`); }); -test("flag on, client without any anthropic-beta header: hoisted, no beta header invented", async () => { +test("opt-in on, client without any anthropic-beta header: in place, no beta header invented", async () => { setFlag(true); - assertHoisted(await send(buildBody(2), { headers: { "user-agent": "curl/8.0" } })); + const body = buildBody(2); + const call = await send(body, { headers: { "user-agent": "curl/8.0" } }); + assertInPlace(call, body); + assert.ok(!betaTokens(call).includes(MID_SYSTEM_BETA)); }); -test("flag on, a token that only looks like the beta: hoisted and not forwarded", async () => { +test("opt-in on, a token that only looks like the beta: in place, the look-alike is not forwarded", async () => { setFlag(true); - const call = await send(buildBody(2), { + const body = buildBody(2); + const call = await send(body, { headers: { ...SDK_HEADERS, "anthropic-beta": `${MID_SYSTEM_BETA}x` }, }); - assertHoisted(call); + assertInPlace(call, body); assert.ok(!betaTokens(call).some((t) => t.toLowerCase() === MID_SYSTEM_BETA)); }); +test("opt-in on, beta-less client: successive turns keep system stable and messages a prefix", async () => { + setFlag(true); + const bodies = []; + for (const turns of [1, 2, 3]) { + bodies.push((await send(buildBody(turns), { headers: NO_BETA_HEADERS })).body); + } + for (let i = 1; i < bodies.length; i++) { + assert.deepEqual(bodies[i].system, bodies[i - 1].system); + assert.deepEqual( + stripCacheControl(bodies[i].messages.slice(0, bodies[i - 1].messages.length)), + stripCacheControl(bodies[i - 1].messages) + ); + } +}); + // ── Flag on + client-negotiated beta: turns stay in place ────────────────────────────────── -test("flag on + client beta: mid-conversation system turns stay in place in the final body", async () => { +test("opt-in on + client beta: mid-conversation system turns stay in place in the final body", async () => { setFlag(true); const body = buildBody(3); const call = await send(body); @@ -258,7 +281,7 @@ test("flag on + client beta: mid-conversation system turns stay in place in the assert.ok(betaTokens(call).includes("per-turn-control-2026-07-01"), `beta: ${betaTokens(call)}`); }); -test("flag on + client beta: successive turns keep system stable and earlier messages a prefix", async () => { +test("opt-in on + client beta: successive turns keep system stable and earlier messages a prefix", async () => { setFlag(true); const bodies = []; for (const turns of [1, 2, 3, 4]) bodies.push((await send(buildBody(turns))).body); @@ -275,7 +298,7 @@ test("flag on + client beta: successive turns keep system stable and earlier mes } }); -test("flag on: a non-Claude Code client that sent the beta gets the same treatment", async () => { +test("opt-in on: a non-Claude Code client that sent the beta gets the same treatment", async () => { setFlag(true); const body = buildBody(2); const call = await send(body, { headers: SDK_HEADERS }); @@ -283,7 +306,7 @@ test("flag on: a non-Claude Code client that sent the beta gets the same treatme assert.ok(betaTokens(call).includes(MID_SYSTEM_BETA)); }); -test("flag on: the client's beta token is matched case-insensitively and with spaces", async () => { +test("opt-in on: the client's beta token is matched case-insensitively and with spaces", async () => { setFlag(true); const body = buildBody(2); const call = await send(body, { @@ -299,7 +322,7 @@ test("flag on: the client's beta token is matched case-insensitively and with sp ); }); -test("flag on + beta: a body carrying an OpenAI developer turn keeps the full legacy hoist", async () => { +test("opt-in on + beta: a body carrying an OpenAI developer turn keeps the full legacy hoist", async () => { setFlag(true); const body = buildBody(2); body.messages.splice(2, 0, { role: "developer", content: "developer note" }); @@ -309,7 +332,7 @@ test("flag on + beta: a body carrying an OpenAI developer turn keeps the full le assert.ok(systemTexts(call.body.system).includes("developer note")); }); -test("flag on + beta: leading text system turn is hoisted, directive-only one relocated", async () => { +test("opt-in on + beta: leading text system turn is hoisted, directive-only one relocated", async () => { setFlag(true); const body = buildBody(1); body.messages.unshift( @@ -330,7 +353,7 @@ test("flag on + beta: leading text system turn is hoisted, directive-only one re assert.match(msgs.at(-1).content, //); }); -test("flag on + beta: requests without system turns keep their shape and get no extra beta", async () => { +test("opt-in on + beta: requests without system turns keep their shape and get no extra beta", async () => { setFlag(true); const body = buildBody(1); body.messages = body.messages.filter((m) => m.role !== "system"); @@ -340,7 +363,7 @@ test("flag on + beta: requests without system turns keep their shape and get no assert.ok(!betaTokens(call).includes(MID_SYSTEM_BETA), "no beta without a surviving turn"); }); -test("flag on + beta: the caller's body is not mutated, so retries/combo attempts see the original", async () => { +test("opt-in on + beta: the caller's body is not mutated, so retries/combo attempts see the original", async () => { setFlag(true); const body = buildBody(2); const snapshot = structuredClone(body); @@ -371,7 +394,7 @@ test("flag on + beta: the caller's body is not mutated, so retries/combo attempt assert.deepEqual(body.system, snapshot.system); }); -test("policy: scope, client beta and fail-closed flag reader", async () => { +test("policy: scope and strict per-connection opt-in", async () => { const { resolveClaudeMidConversationSystemPolicy } = await import("../../open-sse/services/claudeMidConversationSystem.ts"); const resolve = (extra = {}) => @@ -379,24 +402,17 @@ test("policy: scope, client beta and fail-closed flag reader", async () => { provider: PROVIDER, sourceFormat: "claude", targetFormat: "claude", - clientAnthropicBeta: `effort-2025-11-24,${MID_SYSTEM_BETA}`, - isPassthroughEnabled: () => true, + providerSpecificData: { keepMidConversationSystem: true }, ...extra, }); assert.equal(resolve(), true, "plain anthropic-compatible claude passthrough, opted in"); - assert.equal(resolve({ isPassthroughEnabled: () => false }), false, "flag off"); - assert.equal(resolve({ isPassthroughEnabled: undefined }), false, "no reader = off"); + assert.equal(resolve({ providerSpecificData: {} }), false, "opt-in absent"); + assert.equal(resolve({ providerSpecificData: undefined }), false, "no providerSpecificData"); assert.equal( - resolve({ - isPassthroughEnabled: () => { - throw new Error("db down"); - }, - }), + resolve({ providerSpecificData: { keepMidConversationSystem: "true" } }), false, - "a failing flag read keeps the legacy hoist" + "only a real boolean opts in" ); - assert.equal(resolve({ clientAnthropicBeta: null }), false, "client did not negotiate"); - assert.equal(resolve({ clientAnthropicBeta: "effort-2025-11-24" }), false); assert.equal(resolve({ targetFormat: "openai" }), false); assert.equal(resolve({ provider: "claude" }), false, "native path keeps its own policy"); assert.equal( @@ -404,13 +420,28 @@ test("policy: scope, client beta and fail-closed flag reader", async () => { false, "CC bridge builds its own body" ); - let flagReads = 0; - resolve({ - provider: "openai", - isPassthroughEnabled: () => { - flagReads++; - return true; - }, - }); - assert.equal(flagReads, 0, "the flag is not read for providers outside the scope"); + assert.equal(resolve({ provider: "openai" }), false); +}); + +test("keepMidConversationSystem: validated and normalized as a strict boolean", async () => { + const { validateProviderSpecificData } = + await import("../../src/shared/validation/providerSpecificData.ts"); + const { normalizeProviderSpecificData } = + await import("../../src/lib/providers/requestDefaults.ts"); + const issues = []; + const ctx = { addIssue: (issue) => issues.push(issue) }; + validateProviderSpecificData({ keepMidConversationSystem: true }, ctx); + assert.equal(issues.length, 0); + validateProviderSpecificData({ keepMidConversationSystem: "yes" }, ctx); + assert.equal(issues.length, 1); + assert.equal( + normalizeProviderSpecificData(PROVIDER, { keepMidConversationSystem: true }) + .keepMidConversationSystem, + true + ); + assert.equal( + "keepMidConversationSystem" in + normalizeProviderSpecificData(PROVIDER, { keepMidConversationSystem: 1, baseUrl: "x" }), + false + ); }); diff --git a/tests/unit/feature-flags-settings.test.ts b/tests/unit/feature-flags-settings.test.ts index 5fcd05f15130..f0d6bad93277 100644 --- a/tests/unit/feature-flags-settings.test.ts +++ b/tests/unit/feature-flags-settings.test.ts @@ -58,14 +58,14 @@ const { // tool_call wrapper repair, default on) to 86. // OPENCODE_PUBLISHED_CATALOG_ROUTING_ENABLED (#16798, OpenCode model surface from the // published catalog, default off) takes it to 87. -const EXPECTED_FEATURE_FLAG_COUNT = 111; +const EXPECTED_FEATURE_FLAG_COUNT = 110; // tool_call wrapper repair, default on) to 86; OPENCODE_V2_CLIENT_FINGERPRINT_ENABLED // (#16571, OpenCode v2 client fingerprint, default off) to 87. // tool_call wrapper repair, default on) to 86; CLAUDE_OAUTH_SUMMARIZED_THINKING_UNREDACTED // (#17003, lets Claude OAuth drop the redact-thinking beta for summarized thinking, // default off) to 87. // MODEL_ENDPOINT_CAPABILITY_GATE_ENABLED (#16929/#16956, default off) takes it to 88. -// ANTHROPIC_COMPATIBLE_MID_CONVERSATION_SYSTEM (#16236, default off) takes it to 88. +// ANTHROPIC_COMPATIBLE_MID_CONVERSATION_SYSTEM (#16236, moved to the per-connection keepMidConversationSystem opt-in) — 111 back to 110. // MODELS_DEV_EFFORT_TIERS_ENABLED (#15944, default off) takes it to 88. // default off) to 87; AUTO_REQUEST_RESOLVED_CAPABILITIES (#16419, resolved-capability // scoring inputs on the auto/* request path, default off) to 88. diff --git a/tests/unit/server-owned-tool-loop-flag.test.ts b/tests/unit/server-owned-tool-loop-flag.test.ts index fe93f399a2c8..cfcf9ab307cd 100644 --- a/tests/unit/server-owned-tool-loop-flag.test.ts +++ b/tests/unit/server-owned-tool-loop-flag.test.ts @@ -77,7 +77,7 @@ describe("feature-flags-settings count update", () => { // REASONING_REPLAY_ENABLED (#12486, default on) to 84; COMBO_AUTO_PRUNE_STALE_STEPS // (#13505, default off) to 85; KIRO_TOOL_CALL_REPAIR_ENABLED (default on) to 86; // OPENCODE_PUBLISHED_CATALOG_ROUTING_ENABLED (#16798, default off) to 87. - assert.equal(FEATURE_FLAG_DEFINITIONS.length, 111); + assert.equal(FEATURE_FLAG_DEFINITIONS.length, 110); // OPENCODE_V2_CLIENT_FINGERPRINT_ENABLED (#16571, default off) to 87. // COMBO_EMPTY_POOL_NO_ELIGIBLE_TARGETS_ENABLED (#16256, default off) to 87. // OPENCODE_ZEN_EGRESS_IP_LOCK_ENABLED (#16797, default off) to 87; @@ -85,7 +85,7 @@ describe("feature-flags-settings count update", () => { // (#16797, default off) to 89. // CLAUDE_OAUTH_SUMMARIZED_THINKING_UNREDACTED (#17003, default off) to 87; // MODEL_ENDPOINT_CAPABILITY_GATE_ENABLED (#16929/#16956, default off) to 88. - // ANTHROPIC_COMPATIBLE_MID_CONVERSATION_SYSTEM (#16236, default off) to 88. + // ANTHROPIC_COMPATIBLE_MID_CONVERSATION_SYSTEM (#16236, moved to the per-connection keepMidConversationSystem opt-in) — 111 back to 110. // MODELS_DEV_EFFORT_TIERS_ENABLED (#15944, default off) to 88. // CREDENTIAL_HEALTH_RETEST_AUTO_DISABLED (#16190, default off) to 88. // CREDENTIAL_GATE_AUTH_DIAGNOSES_ONLY (#16599, default off) to 88. From bf710d1857cc0255d5896da487a0b2d46b335b01 Mon Sep 17 00:00:00 2001 From: Mike Tuev R9FT Date: Tue, 27 Oct 2026 16:49:55 +0500 Subject: [PATCH 02/20] docs(changelog): name the fragment after #18341 --- ...pic-compatible-keep-mid-conversation-system-per-connection.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename changelog.d/fixes/{anthropic-compatible-keep-mid-conversation-system-per-connection.md => 18341-anthropic-compatible-keep-mid-conversation-system-per-connection.md} (100%) diff --git a/changelog.d/fixes/anthropic-compatible-keep-mid-conversation-system-per-connection.md b/changelog.d/fixes/18341-anthropic-compatible-keep-mid-conversation-system-per-connection.md similarity index 100% rename from changelog.d/fixes/anthropic-compatible-keep-mid-conversation-system-per-connection.md rename to changelog.d/fixes/18341-anthropic-compatible-keep-mid-conversation-system-per-connection.md From 6add6033e765cce28b3de58946e4a452463e7783 Mon Sep 17 00:00:00 2001 From: Mike Tuev R9FT Date: Tue, 3 Nov 2026 00:31:41 -0300 Subject: [PATCH 03/20] i18n(providers): add the keepMidConversationSystem strings to every locale Fills providers.keepMidConversationSystemLabel and keepMidConversationSystemDescription in the 65 locales that lacked them so i18n:check-keys passes. Machine translated; the next i18n wave can polish them. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --- src/i18n/messages/am.json | 2 ++ src/i18n/messages/ar.json | 2 ++ src/i18n/messages/az.json | 2 ++ src/i18n/messages/bg.json | 2 ++ src/i18n/messages/bn.json | 2 ++ src/i18n/messages/bs.json | 2 ++ src/i18n/messages/cs.json | 2 ++ src/i18n/messages/da.json | 2 ++ src/i18n/messages/de.json | 2 ++ src/i18n/messages/el.json | 2 ++ src/i18n/messages/es.json | 2 ++ src/i18n/messages/et.json | 2 ++ src/i18n/messages/fa.json | 2 ++ src/i18n/messages/fi.json | 2 ++ src/i18n/messages/fr.json | 2 ++ src/i18n/messages/ga.json | 2 ++ src/i18n/messages/gu.json | 2 ++ src/i18n/messages/ha.json | 2 ++ src/i18n/messages/he.json | 2 ++ src/i18n/messages/hi.json | 2 ++ src/i18n/messages/hr.json | 2 ++ src/i18n/messages/hu.json | 2 ++ src/i18n/messages/hy.json | 2 ++ src/i18n/messages/id.json | 2 ++ src/i18n/messages/ig.json | 2 ++ src/i18n/messages/it.json | 2 ++ src/i18n/messages/ja.json | 2 ++ src/i18n/messages/ka.json | 2 ++ src/i18n/messages/km.json | 2 ++ src/i18n/messages/kn.json | 2 ++ src/i18n/messages/ko.json | 2 ++ src/i18n/messages/lt.json | 2 ++ src/i18n/messages/lv.json | 2 ++ src/i18n/messages/ml.json | 2 ++ src/i18n/messages/mr.json | 2 ++ src/i18n/messages/ms.json | 2 ++ src/i18n/messages/mt.json | 2 ++ src/i18n/messages/my.json | 2 ++ src/i18n/messages/ne.json | 2 ++ src/i18n/messages/nl.json | 2 ++ src/i18n/messages/no.json | 2 ++ src/i18n/messages/or.json | 2 ++ src/i18n/messages/pa.json | 2 ++ src/i18n/messages/phi.json | 2 ++ src/i18n/messages/pl.json | 2 ++ src/i18n/messages/pt-BR.json | 2 ++ src/i18n/messages/pt.json | 2 ++ src/i18n/messages/ro.json | 2 ++ src/i18n/messages/si.json | 2 ++ src/i18n/messages/sk.json | 2 ++ src/i18n/messages/sl.json | 2 ++ src/i18n/messages/sr.json | 2 ++ src/i18n/messages/sv.json | 2 ++ src/i18n/messages/sw.json | 2 ++ src/i18n/messages/ta.json | 2 ++ src/i18n/messages/te.json | 2 ++ src/i18n/messages/th.json | 2 ++ src/i18n/messages/tr.json | 2 ++ src/i18n/messages/uk-UA.json | 2 ++ src/i18n/messages/ur.json | 2 ++ src/i18n/messages/uz.json | 2 ++ src/i18n/messages/vi.json | 2 ++ src/i18n/messages/yo.json | 2 ++ src/i18n/messages/zh-CN.json | 2 ++ src/i18n/messages/zh-TW.json | 2 ++ 65 files changed, 130 insertions(+) diff --git a/src/i18n/messages/am.json b/src/i18n/messages/am.json index f810b8b2c0e8..771547e87aa7 100644 --- a/src/i18n/messages/am.json +++ b/src/i18n/messages/am.json @@ -6322,6 +6322,8 @@ "allowCodexPaidCreditsDescription": "Allow this account to use paid credits after its subscription quota reaches the cutoff. Requires available credits reported by OpenAI; may incur charges. Spark limits and upstream cooldowns still apply. Credit availability is checked even if quota preflight is disabled.", "disableCoolingDescription": "ይህ ግንኙነት ሊታረሙ ከሚችሉ ስህተቶች በኋላም ብቁ ሆኖ እንዲቆይ ጊዜያዊውን የማቀዝቀዝ ጊዜ ይዝለሉ (እንደ የታገደ/ጊዜው ያለፈ ያሉ የመጨረሻ ሁኔታዎች አሁንም ተፈጻሚ ናቸው)።", "disableCoolingLabel": "ለዚህ ግንኙነት የማቀዝቀዝ ጊዜን አሰናክል", + "keepMidConversationSystemDescription": "ወደ ከፍተኛ ደረጃ የስርዓት መጠየቂያ ከማስገባት ይልቅ በመልእክት ዝርዝሩ ውስጥ የተላኩትን የስርዓት መልእክቶች መካከለኛ ውይይት ውስጥ ይተው፣ ስለዚህ የጥያቄው መሸጎጫ ተራ በተራ መምታቱን ይቀጥላል። ይህ የላይኛው ዥረት በመልእክቶች ውስጥ ሚና \"ስርዓት\" የሚቀበል ከሆነ ብቻ ያንቁ (አለበለዚያ 400 ይመልሳል)።", + "keepMidConversationSystemLabel": "የመሃል የውይይት ስርዓት መልዕክቶችን በቦታቸው ያቆዩ", "bulkPasteAdded": "{count, plural, one {1 ቁልፍ ታክሏል} other {# ቁልፎች ታክለዋል}}", "bulkPasteDuplicatesIgnored": "{count, plural, one {1 የተደገመ ቁልፍ ታልፏል} other {# የተደገሙ ቁልፎች ታልፈዋል}}", "bulkPasteHint": "በእያንዳንዱ መስመር አንድ API ቁልፍ ይለጥፉ። ባዶ መስመሮች ችላ ይባላሉ፣ የተደገሙ ቁልፎችም ይዘለላሉ።", diff --git a/src/i18n/messages/ar.json b/src/i18n/messages/ar.json index cfee38c9247f..46d6fa7d1d3e 100644 --- a/src/i18n/messages/ar.json +++ b/src/i18n/messages/ar.json @@ -6322,6 +6322,8 @@ "allowCodexPaidCreditsDescription": "Allow this account to use paid credits after its subscription quota reaches the cutoff. Requires available credits reported by OpenAI; may incur charges. Spark limits and upstream cooldowns still apply. Credit availability is checked even if quota preflight is disabled.", "disableCoolingDescription": "تخطي فترة التهدئة المؤقتة حتى يظل هذا الاتصال مؤهلاً حتى بعد الأخطاء القابلة للاسترداد (لا تزال الحالات النهائية مثل banned/expired سارية).", "disableCoolingLabel": "تعطيل فترة التهدئة لهذا الاتصال", + "keepMidConversationSystemDescription": "اترك رسائل النظام مرسلة في منتصف المحادثة داخل قائمة الرسائل بدلاً من نقلها إلى مطالبة النظام ذات المستوى الأعلى، بحيث تستمر ذاكرة التخزين المؤقت للمطالبة في النقر عبر المنعطفات. قم بالتمكين فقط إذا كان هذا المنبع يقبل دور \"النظام\" داخل الرسائل (وإلا فإنه يجيب على 400).", + "keepMidConversationSystemLabel": "احتفظ برسائل نظام منتصف المحادثة في مكانها", "bulkPasteAdded": "المفاتيح المضافة: {count, plural, one {أُضيف مفتاح واحد} other {أُضيف # من المفاتيح}}", "bulkPasteDuplicatesIgnored": "التكرارات المتجاهلة: {count, plural, one {تُخطي تكرار واحد} other {تُخطي # من التكرارات}}", "bulkPasteHint": "ألصق مفتاح API واحدًا في كل سطر. تُتجاهل الأسطر الفارغة والمفاتيح المكررة.", diff --git a/src/i18n/messages/az.json b/src/i18n/messages/az.json index e92b9dabc2b7..ec0ee62dfa35 100644 --- a/src/i18n/messages/az.json +++ b/src/i18n/messages/az.json @@ -6322,6 +6322,8 @@ "allowCodexPaidCreditsDescription": "Allow this account to use paid credits after its subscription quota reaches the cutoff. Requires available credits reported by OpenAI; may incur charges. Spark limits and upstream cooldowns still apply. Credit availability is checked even if quota preflight is disabled.", "disableCoolingDescription": "Müvəqqəti soyuma müddətini atlayın ki, bu bağlantı bərpa edilə bilən xətalardan sonra belə uyğun qalsın (bloklanmış/vaxtı keçmiş kimi terminal vəziyyətlər hələ də tətbiq olunur).", "disableCoolingLabel": "Bu bağlantı üçün soyuma müddətini söndürün", + "keepMidConversationSystemDescription": "Söhbətin ortasında göndərilmiş sistem mesajlarını yuxarı səviyyəli sistem sorğusuna köçürmək əvəzinə mesaj siyahısında buraxın, beləliklə, sorğu keşi növbələr arasında vurmağa davam edir. Yalnız bu yuxarı axını mesajlar daxilində rol \"sistemini\" qəbul etdikdə aktivləşdirin (əks halda 400 cavab verir).", + "keepMidConversationSystemLabel": "Söhbətin ortasındakı sistem mesajlarını yerində saxlayın", "bulkPasteAdded": "{count, plural, one {1 açar əlavə edildi} other {# açar əlavə edildi}}", "bulkPasteDuplicatesIgnored": "{count, plural, one {1 dublikat nəzərə alınmadı} other {# dublikat nəzərə alınmadı}}", "bulkPasteHint": "Hər sətirə bir API açarı yapışdırın. Boş sətirlər və dublikat açarlar nəzərə alınmır.", diff --git a/src/i18n/messages/bg.json b/src/i18n/messages/bg.json index 35e0816320bc..5eff8eeaa1ef 100644 --- a/src/i18n/messages/bg.json +++ b/src/i18n/messages/bg.json @@ -6322,6 +6322,8 @@ "allowCodexPaidCreditsDescription": "Allow this account to use paid credits after its subscription quota reaches the cutoff. Requires available credits reported by OpenAI; may incur charges. Spark limits and upstream cooldowns still apply. Credit availability is checked even if quota preflight is disabled.", "disableCoolingDescription": "Пропускане на временния период на изчакване, така че тази връзка да остане допустима дори след възстановими грешки (терминалните състояния като блокиран/изтекъл все още се прилагат).", "disableCoolingLabel": "Деактивиране на периода на изчакване за тази връзка", + "keepMidConversationSystemDescription": "Оставете системните съобщения, изпратени по време на разговор, вътре в списъка със съобщения, вместо да ги преместите в системния подкана от най-високо ниво, така че кешът на подканите да продължава да удря наред. Активирайте само ако този upstream приема роля \"система\" в съобщенията (в противен случай отговаря на 400).", + "keepMidConversationSystemLabel": "Поддържайте системните съобщения по средата на разговора", "bulkPasteAdded": "{count, plural, one {Добавен е 1 ключ} other {Добавени са # ключа}}", "bulkPasteDuplicatesIgnored": "{count, plural, one {Пропуснат е 1 дублиран ключ} other {Пропуснати са # дублирани ключа}}", "bulkPasteHint": "Поставете по един API ключ на ред. Празните редове се игнорират, а дублираните ключове се пропускат.", diff --git a/src/i18n/messages/bn.json b/src/i18n/messages/bn.json index 7459bab280b7..b36ecc08fae5 100644 --- a/src/i18n/messages/bn.json +++ b/src/i18n/messages/bn.json @@ -6322,6 +6322,8 @@ "allowCodexPaidCreditsDescription": "Allow this account to use paid credits after its subscription quota reaches the cutoff. Requires available credits reported by OpenAI; may incur charges. Spark limits and upstream cooldowns still apply. Credit availability is checked even if quota preflight is disabled.", "disableCoolingDescription": "অস্থায়ী কুলডাউন এড়িয়ে যান যাতে পুনরুদ্ধারযোগ্য ত্রুটির পরেও এই সংযোগটি যোগ্য থাকে (নিষিদ্ধ/মেয়াদোত্তীর্ণের মতো টার্মিনাল অবস্থাগুলি এখনও প্রযোজ্য)।", "disableCoolingLabel": "এই সংযোগের জন্য কুলডাউন নিষ্ক্রিয় করুন", + "keepMidConversationSystemDescription": "সিস্টেম বার্তাগুলিকে শীর্ষ-স্তরের সিস্টেম প্রম্পটে স্থানান্তর করার পরিবর্তে বার্তা তালিকার মধ্যে মধ্য-কথোপকথন পাঠানো ছেড়ে দিন, তাই প্রম্পট ক্যাশে বার বার আঘাত করতে থাকে। শুধুমাত্র সক্রিয় করুন যদি এই আপস্ট্রিম বার্তাগুলির মধ্যে ভূমিকা \"সিস্টেম\" গ্রহণ করে (অন্যথায় এটি 400 উত্তর দেয়)।", + "keepMidConversationSystemLabel": "মধ্য-কথোপকথনের সিস্টেম বার্তাগুলিকে জায়গায় রাখুন", "bulkPasteAdded": "{count, plural, one {১টি কী যোগ করা হয়েছে} other {#টি কী যোগ করা হয়েছে}}", "bulkPasteDuplicatesIgnored": "{count, plural, one {১টি সদৃশ বাদ দেওয়া হয়েছে} other {#টি সদৃশ বাদ দেওয়া হয়েছে}}", "bulkPasteHint": "প্রতি লাইনে একটি করে API কী পেস্ট করুন। ফাঁকা লাইন উপেক্ষা করা হবে এবং সদৃশ কীগুলো বাদ দেওয়া হবে।", diff --git a/src/i18n/messages/bs.json b/src/i18n/messages/bs.json index 25a2f331be60..6bd7d728a129 100644 --- a/src/i18n/messages/bs.json +++ b/src/i18n/messages/bs.json @@ -6322,6 +6322,8 @@ "allowCodexPaidCreditsDescription": "Allow this account to use paid credits after its subscription quota reaches the cutoff. Requires available credits reported by OpenAI; may incur charges. Spark limits and upstream cooldowns still apply. Credit availability is checked even if quota preflight is disabled.", "disableCoolingDescription": "Preskoči privremeni cooldown kako bi ova veza ostala prihvatljiva čak i nakon oporavljivih grešaka (terminalni statusi poput zabrane/isteka i dalje vrijede).", "disableCoolingLabel": "Onemogući cooldown za ovu vezu", + "keepMidConversationSystemDescription": "Ostavite sistemske poruke poslane usred razgovora unutar liste poruka umjesto da ih premještate u sistemski prompt najvišeg nivoa, tako da keš prompta nastavlja da udara u redovima. Omogući samo ako ovaj uzvodno prihvata ulogu \"sistema\" unutar poruka (u suprotnom odgovara 400).", + "keepMidConversationSystemLabel": "Držite sistemske poruke usred razgovora na mjestu", "bulkPasteAdded": "{count, plural, one {1 ključ dodan} other {# ključeva dodano}}", "bulkPasteDuplicatesIgnored": "{count, plural, one {1 duplikat preskočen} other {# duplikata preskočeno}}", "bulkPasteHint": "Zalijepite jedan API ključ po liniji. Prazne linije se ignorišu, a dupli ključevi se preskaču.", diff --git a/src/i18n/messages/cs.json b/src/i18n/messages/cs.json index ec714a5dc0df..9ee7e39615e3 100644 --- a/src/i18n/messages/cs.json +++ b/src/i18n/messages/cs.json @@ -6322,6 +6322,8 @@ "allowCodexPaidCreditsDescription": "Allow this account to use paid credits after its subscription quota reaches the cutoff. Requires available credits reported by OpenAI; may incur charges. Spark limits and upstream cooldowns still apply. Credit availability is checked even if quota preflight is disabled.", "disableCoolingDescription": "Přeskočit přechodný cooldown, aby toto připojení zůstalo způsobilé i po obnovitelných chybách (konečné stavy jako zablokováno/vypršelo stále platí).", "disableCoolingLabel": "Zakázat cooldown pro toto připojení", + "keepMidConversationSystemDescription": "Systémové zprávy odeslané uprostřed konverzace ponechte v seznamu zpráv, místo abyste je přesunuli do systémového příkazového řádku nejvyšší úrovně, takže mezipaměť příkazů bude neustále narážet na různá kola. Povolit pouze v případě, že tento upstream přijímá roli \"systém\" uvnitř zpráv (jinak odpovídá 400).", + "keepMidConversationSystemLabel": "Udržujte systémové zprávy uprostřed konverzace na místě", "bulkPasteAdded": "{count, plural, one {Přidán 1 klíč} few {Přidány # klíče} other {Přidáno # klíčů}}", "bulkPasteDuplicatesIgnored": "{count, plural, one {Přeskočen 1 duplikát} few {Přeskočeny # duplikáty} other {Přeskočeno # duplikátů}}", "bulkPasteHint": "Vložte jeden API klíč na každý řádek. Prázdné řádky budou ignorovány a duplicitní klíče přeskočeny.", diff --git a/src/i18n/messages/da.json b/src/i18n/messages/da.json index a6ef26eb22e4..a8518e83db02 100644 --- a/src/i18n/messages/da.json +++ b/src/i18n/messages/da.json @@ -6322,6 +6322,8 @@ "allowCodexPaidCreditsDescription": "Allow this account to use paid credits after its subscription quota reaches the cutoff. Requires available credits reported by OpenAI; may incur charges. Spark limits and upstream cooldowns still apply. Credit availability is checked even if quota preflight is disabled.", "disableCoolingDescription": "Spring den midlertidige nedkølingsperiode over, så denne forbindelse fortsat kan bruges, selv efter fejl, der kan afhjælpes (sluttilstande som blokeret/udløbet gælder stadig).", "disableCoolingLabel": "Deaktiver afkøling for denne forbindelse", + "keepMidConversationSystemDescription": "Efterlad systemmeddelelser, der er sendt midt i samtalen, inde i meddelelseslisten i stedet for at flytte dem til systemprompten på øverste niveau, så promptcachen bliver ved med at ramme på tværs af sving. Aktiver kun, hvis denne upstream accepterer rollen \"system\" i meddelelser (ellers svarer den 400).", + "keepMidConversationSystemLabel": "Hold beskeder midt i samtalen på plads", "bulkPasteAdded": "{count, plural, one {1 nøgle tilføjet} other {# nøgler tilføjet}}", "bulkPasteDuplicatesIgnored": "{count, plural, one {1 dublet sprunget over} other {# dubletter sprunget over}}", "bulkPasteHint": "Indsæt én API-nøgle pr. linje. Tomme linjer ignoreres, og dublerede nøgler springes over.", diff --git a/src/i18n/messages/de.json b/src/i18n/messages/de.json index bea620af4641..3f14bc94bf3e 100644 --- a/src/i18n/messages/de.json +++ b/src/i18n/messages/de.json @@ -6245,6 +6245,8 @@ "allowCodexPaidCreditsDescription": "Allow this account to use paid credits after its subscription quota reaches the cutoff. Requires available credits reported by OpenAI; may incur charges. Spark limits and upstream cooldowns still apply. Credit availability is checked even if quota preflight is disabled.", "disableCoolingDescription": "Vorübergehende Abklingzeit überspringen, damit diese Verbindung auch nach behebbaren Fehlern verfügbar bleibt (endgültige Zustände wie gesperrt/abgelaufen gelten weiterhin).", "disableCoolingLabel": "Abklingzeit für diese Verbindung deaktivieren", + "keepMidConversationSystemDescription": "Lassen Sie Systemnachrichten, die während der Konversation gesendet werden, in der Nachrichtenliste, anstatt sie in die Systemaufforderung der obersten Ebene zu verschieben, damit der Eingabeaufforderungscache weiterhin über alle Runden hinweg zutrifft. Nur aktivieren, wenn dieser Upstream die Rolle „System“ in Nachrichten akzeptiert (andernfalls antwortet er mit 400).", + "keepMidConversationSystemLabel": "Behalten Sie die Systemnachrichten während des Gesprächs bei", "bulkPasteAdded": "{count, plural, one {1 Schlüssel hinzugefügt} other {# Schlüssel hinzugefügt}}", "bulkPasteDuplicatesIgnored": "{count, plural, one {1 Duplikat übersprungen} other {# Duplikate übersprungen}}", "bulkPasteHint": "Fügen Sie pro Zeile einen API-Schlüssel ein. Leere Zeilen werden ignoriert und doppelte Schlüssel übersprungen.", diff --git a/src/i18n/messages/el.json b/src/i18n/messages/el.json index ae36bd9f0fe8..f0490fbd0d47 100644 --- a/src/i18n/messages/el.json +++ b/src/i18n/messages/el.json @@ -6245,6 +6245,8 @@ "allowCodexPaidCreditsDescription": "Allow this account to use paid credits after its subscription quota reaches the cutoff. Requires available credits reported by OpenAI; may incur charges. Spark limits and upstream cooldowns still apply. Credit availability is checked even if quota preflight is disabled.", "disableCoolingDescription": "Παραλείπει την προσωρινή περίοδο αναμονής, ώστε αυτή η σύνδεση να παραμένει διαθέσιμη προς επιλογή ακόμα και μετά από σφάλματα από τα οποία μπορεί να ανακάμψει (οι τελικές καταστάσεις, όπως η απαγόρευση ή η λήξη, εξακολουθούν να ισχύουν).", "disableCoolingLabel": "Απενεργοποίηση περιόδου αναμονής για αυτή τη σύνδεση", + "keepMidConversationSystemDescription": "Αφήστε τα μηνύματα συστήματος που αποστέλλονται κατά τη διάρκεια της συνομιλίας μέσα στη λίστα μηνυμάτων αντί να τα μετακινήσετε στην προτροπή συστήματος ανώτατου επιπέδου, έτσι ώστε η κρυφή μνήμη προτροπής να συνεχίζει να χτυπά σε όλες τις στροφές. Ενεργοποίηση μόνο εάν αυτό το upstream δέχεται το \"σύστημα\" ρόλων μέσα στα μηνύματα (διαφορετικά απαντά 400).", + "keepMidConversationSystemLabel": "Διατηρήστε τα μηνύματα του συστήματος κατά τη διάρκεια της συνομιλίας στη θέση τους", "bulkPasteAdded": "{count, plural, one {Προστέθηκε 1 κλειδί} other {Προστέθηκαν # κλειδιά}}", "bulkPasteDuplicatesIgnored": "{count, plural, one {Παραλείφθηκε 1 διπλότυπο} other {Παραλείφθηκαν # διπλότυπα}}", "bulkPasteHint": "Επικολλήστε ένα κλειδί API ανά γραμμή. Οι κενές γραμμές αγνοούνται και τα διπλότυπα κλειδιά παραλείπονται.", diff --git a/src/i18n/messages/es.json b/src/i18n/messages/es.json index eff168e21d6b..d8edff650800 100644 --- a/src/i18n/messages/es.json +++ b/src/i18n/messages/es.json @@ -6245,6 +6245,8 @@ "allowCodexPaidCreditsDescription": "Permite que esta cuenta use créditos de pago cuando su cuota de suscripción alcance el umbral. Requiere créditos disponibles informados por OpenAI; puede generar cargos. Los límites de Spark y los enfriamientos del proveedor siguen aplicándose. La disponibilidad de créditos se comprueba aunque la verificación previa de cuota esté desactivada, salvo que el filtrado local de cuota esté totalmente desactivado (verificación previa de cuota y política de límites, ambas desactivadas).", "disableCoolingDescription": "Omite el tiempo de espera transitorio para que esta conexión siga siendo apta incluso después de errores recuperables (los estados terminales, como bloqueado o caducado, siguen siendo aplicables).", "disableCoolingLabel": "Desactivar el tiempo de espera para esta conexión", + "keepMidConversationSystemDescription": "Deje los mensajes del sistema enviados a mitad de la conversación dentro de la lista de mensajes en lugar de moverlos al mensaje del sistema de nivel superior, para que el caché de mensajes siga apareciendo en todos los turnos. Habilítelo solo si este upstream acepta el rol \"sistema\" dentro de los mensajes (de lo contrario, responde 400).", + "keepMidConversationSystemLabel": "Mantenga los mensajes del sistema a mitad de la conversación en su lugar", "bulkPasteAdded": "{count, plural, one {1 clave añadida} other {# claves añadidas}}", "bulkPasteDuplicatesIgnored": "{count, plural, one {1 duplicado omitido} other {# duplicados omitidos}}", "bulkPasteHint": "Pega una clave de API por línea. Las líneas vacías se ignoran y las claves duplicadas se omiten.", diff --git a/src/i18n/messages/et.json b/src/i18n/messages/et.json index bff7517d8af8..2cf4b6d1289d 100644 --- a/src/i18n/messages/et.json +++ b/src/i18n/messages/et.json @@ -6245,6 +6245,8 @@ "allowCodexPaidCreditsDescription": "Allow this account to use paid credits after its subscription quota reaches the cutoff. Requires available credits reported by OpenAI; may incur charges. Spark limits and upstream cooldowns still apply. Credit availability is checked even if quota preflight is disabled.", "disableCoolingDescription": "Jäta ajutine ooteaeg vahele, et see ühendus jääks kasutatavaks ka pärast taastatavaid vigu (lõppolekud, nagu blokeeritud või aegunud, kehtivad endiselt).", "disableCoolingLabel": "Keela selle ühenduse ooteaeg", + "keepMidConversationSystemDescription": "Jätke vestluse keskel saadetud süsteemisõnumid sõnumiloendisse selle asemel, et teisaldada need kõrgeima taseme süsteemiviipale, nii et viipade vahemälu liigub pidevalt üle pöörde. Luba ainult siis, kui see ülesvoolu aktsepteerib sõnumite sees rolli \"süsteem\" (muidu vastab 400).", + "keepMidConversationSystemLabel": "Hoidke vestluse keskel olevad süsteemisõnumid paigas", "bulkPasteAdded": "{count, plural, one {Lisati 1 võti} other {Lisati # võtit}}", "bulkPasteDuplicatesIgnored": "{count, plural, one {1 duplikaat jäeti vahele} other {# duplikaati jäeti vahele}}", "bulkPasteHint": "Kleepige igale reale üks API-võti. Tühje ridu eiratakse ja duplikaatvõtmed jäetakse vahele.", diff --git a/src/i18n/messages/fa.json b/src/i18n/messages/fa.json index 10429847eafe..62a47ced7351 100644 --- a/src/i18n/messages/fa.json +++ b/src/i18n/messages/fa.json @@ -6245,6 +6245,8 @@ "allowCodexPaidCreditsDescription": "Allow this account to use paid credits after its subscription quota reaches the cutoff. Requires available credits reported by OpenAI; may incur charges. Spark limits and upstream cooldowns still apply. Credit availability is checked even if quota preflight is disabled.", "disableCoolingDescription": "نادیده گرفتن دوره خنک‌سازی گذرا تا این اتصال حتی پس از خطاهای قابل بازیابی واجد شرایط باقی بماند (حالت‌های نهایی مانند مسدود شده/منقضی شده همچنان اعمال می‌شوند).", "disableCoolingLabel": "غیرفعال کردن دوره خنک‌سازی برای این اتصال", + "keepMidConversationSystemDescription": "پیامهای سیستمی را که در اواسط مکالمه ارسال میشوند، در فهرست پیامها قرار دهید، بهجای انتقال آنها به اعلان سیستم سطح بالا، بنابراین کش اعلانها همچنان در سراسر نوبتها ضربه میزنند. فقط در صورتی فعال شود که این بالادست «سیستم» نقش را در پیامها بپذیرد (در غیر این صورت به 400 پاسخ میدهد).", + "keepMidConversationSystemLabel": "پیام های سیستم میان مکالمه را در جای خود نگه دارید", "bulkPasteAdded": "{count, plural, one {۱ کلید اضافه شد} other {# کلید اضافه شد}}", "bulkPasteDuplicatesIgnored": "{count, plural, one {۱ مورد تکراری نادیده گرفته شد} other {# مورد تکراری نادیده گرفته شد}}", "bulkPasteHint": "در هر خط یک کلید API جایگذاری کنید. خطوط خالی نادیده گرفته میشوند و کلیدهای تکراری رد میشوند.", diff --git a/src/i18n/messages/fi.json b/src/i18n/messages/fi.json index dc69851debe9..12d7f79ebda0 100644 --- a/src/i18n/messages/fi.json +++ b/src/i18n/messages/fi.json @@ -6245,6 +6245,8 @@ "allowCodexPaidCreditsDescription": "Allow this account to use paid credits after its subscription quota reaches the cutoff. Requires available credits reported by OpenAI; may incur charges. Spark limits and upstream cooldowns still apply. Credit availability is checked even if quota preflight is disabled.", "disableCoolingDescription": "Ohita tilapäinen jäädytysaika, jotta tämä yhteys pysyy käytettävissä myös palautettavien virheiden jälkeen (lopulliset tilat, kuten estetty/vanhentunut, ovat edelleen voimassa).", "disableCoolingLabel": "Poista jäädytysaika käytöstä tälle yhteydelle", + "keepMidConversationSystemDescription": "Jätä keskustelun puolivälissä lähetetyt järjestelmäviestit viestiluetteloon sen sijaan, että siirrät ne ylimmän tason järjestelmäkehotteeseen, jotta kehotteiden välimuisti osuu jatkuvasti käännöksissä. Ota käyttöön vain, jos tämä ylävirta hyväksyy roolin \"järjestelmä\" viestien sisällä (muuten se vastaa 400).", + "keepMidConversationSystemLabel": "Pidä keskustelun puolivälissä olevat järjestelmäviestit paikoillaan", "bulkPasteAdded": "{count, plural, one {1 avain lisätty} other {# avainta lisätty}}", "bulkPasteDuplicatesIgnored": "{count, plural, one {1 päällekkäinen avain ohitettu} other {# päällekkäistä avainta ohitettu}}", "bulkPasteHint": "Liitä yksi API-avain kullekin riville. Tyhjät rivit jätetään huomiotta ja päällekkäiset avaimet ohitetaan.", diff --git a/src/i18n/messages/fr.json b/src/i18n/messages/fr.json index d323168d50af..734b778e5ffb 100644 --- a/src/i18n/messages/fr.json +++ b/src/i18n/messages/fr.json @@ -6245,6 +6245,8 @@ "allowCodexPaidCreditsDescription": "Allow this account to use paid credits after its subscription quota reaches the cutoff. Requires available credits reported by OpenAI; may incur charges. Spark limits and upstream cooldowns still apply. Credit availability is checked even if quota preflight is disabled.", "disableCoolingDescription": "Ignorer le délai de récupération temporaire pour que cette connexion reste éligible même après des erreurs récupérables (les états terminaux comme banni/expiré s'appliquent toujours).", "disableCoolingLabel": "Désactiver le délai de récupération pour cette connexion", + "keepMidConversationSystemDescription": "Laissez les messages système envoyés au milieu d'une conversation dans la liste de messages au lieu de les déplacer vers l'invite système de niveau supérieur, afin que le cache d'invite continue de fonctionner d'un tour à l'autre. Activer uniquement si cet amont accepte le rôle \"système\" dans les messages (sinon il répond 400).", + "keepMidConversationSystemLabel": "Gardez les messages du système en cours de conversation en place", "bulkPasteAdded": "{count, plural, one {1 clé ajoutée} other {# clés ajoutées}}", "bulkPasteDuplicatesIgnored": "{count, plural, one {1 doublon ignoré} other {# doublons ignorés}}", "bulkPasteHint": "Collez une clé API par ligne. Les lignes vides sont ignorées, tout comme les clés en double.", diff --git a/src/i18n/messages/ga.json b/src/i18n/messages/ga.json index 698727bdbb69..40cc5582d016 100644 --- a/src/i18n/messages/ga.json +++ b/src/i18n/messages/ga.json @@ -6245,6 +6245,8 @@ "allowCodexPaidCreditsDescription": "Allow this account to use paid credits after its subscription quota reaches the cutoff. Requires available credits reported by OpenAI; may incur charges. Spark limits and upstream cooldowns still apply. Credit availability is checked even if quota preflight is disabled.", "disableCoolingDescription": "Scipeáil an tréimhse fhuaraithe neamhbhuan ionas go bhfanann an ceangal seo incháilithe fiú tar éis earráidí in-aisghabhála (tá feidhm fós ag stáit chríochacha amhail toirmiscthe/éagtha).", "disableCoolingLabel": "Díchumasaigh an tréimhse fhuaraithe don cheangal seo", + "keepMidConversationSystemDescription": "Fág teachtaireachtaí córais a sheoltar i lár an chomhrá taobh istigh den liosta teachtaireachtaí seachas iad a bhogadh isteach sa chóras barrleibhéil leid, mar sin coinníonn an taisce pras ag bualadh trasna sealaíochta. Ná cumasaigh ach amháin má ghlacann an réamhtheachtacha seo le \"córas\" ról laistigh de theachtaireachtaí (ar shlí eile freagraíonn sé 400).", + "keepMidConversationSystemLabel": "Coinnigh teachtaireachtaí córais lárchomhrá i bhfeidhm", "bulkPasteAdded": "{count, plural, one {1 eochair curtha leis} other {# eochracha curtha leis}}", "bulkPasteDuplicatesIgnored": "{count, plural, one {1 dúblach scipeáilte} other {# dúblacha scipeáilte}}", "bulkPasteHint": "Greamaigh eochair API amháin in aghaidh an líne. Déantar neamhaird ar línte folmha agus scipeáiltear eochracha dúblacha.", diff --git a/src/i18n/messages/gu.json b/src/i18n/messages/gu.json index 46031a3ce8fd..ee08e8db0869 100644 --- a/src/i18n/messages/gu.json +++ b/src/i18n/messages/gu.json @@ -6245,6 +6245,8 @@ "allowCodexPaidCreditsDescription": "Allow this account to use paid credits after its subscription quota reaches the cutoff. Requires available credits reported by OpenAI; may incur charges. Spark limits and upstream cooldowns still apply. Credit availability is checked even if quota preflight is disabled.", "disableCoolingDescription": "અસ્થાયી કૂલડાઉન છોડો જેથી પુનઃપ્રાપ્ત કરી શકાય તેવી ભૂલો પછી પણ આ કનેક્શન પાત્ર રહે (પ્રતિબંધિત/સમાપ્ત જેવી ટર્મિનલ સ્થિતિઓ હજુ પણ લાગુ પડે છે).", "disableCoolingLabel": "આ કનેક્શન માટે કૂલડાઉન અક્ષમ કરો", + "keepMidConversationSystemDescription": "સિસ્ટમ સંદેશાઓને ઉચ્ચ-સ્તરના સિસ્ટમ પ્રોમ્પ્ટમાં ખસેડવાને બદલે સંદેશ સૂચિની અંદર મધ્ય-વાતચીત મોકલવામાં આવે છે, તેથી પ્રોમ્પ્ટ કેશ સમગ્ર વળાંક પર અથડાતું રહે છે. જો આ અપસ્ટ્રીમ સંદેશાઓની અંદર ભૂમિકા \"સિસ્ટમ\" સ્વીકારે તો જ સક્ષમ કરો (અન્યથા તે 400 નો જવાબ આપે છે).", + "keepMidConversationSystemLabel": "મધ્ય-વાતચીત સિસ્ટમ સંદેશાઓને સ્થાને રાખો", "bulkPasteAdded": "{count, plural, one {1 કી ઉમેરાઈ} other {# કીઓ ઉમેરાઈ}}", "bulkPasteDuplicatesIgnored": "{count, plural, one {1 ડુપ્લિકેટ અવગણાયું} other {# ડુપ્લિકેટ્સ અવગણાયા}}", "bulkPasteHint": "દરેક લાઇનમાં એક API કી પેસ્ટ કરો. ખાલી લાઇનો અવગણવામાં આવે છે અને ડુપ્લિકેટ કીઓ છોડવામાં આવે છે.", diff --git a/src/i18n/messages/ha.json b/src/i18n/messages/ha.json index d32f5dbd344e..43c69f605f7a 100644 --- a/src/i18n/messages/ha.json +++ b/src/i18n/messages/ha.json @@ -6245,6 +6245,8 @@ "allowCodexPaidCreditsDescription": "Allow this account to use paid credits after its subscription quota reaches the cutoff. Requires available credits reported by OpenAI; may incur charges. Spark limits and upstream cooldowns still apply. Credit availability is checked even if quota preflight is disabled.", "disableCoolingDescription": "Tsallake lokacin dakatawa na wucin gadi domin wannan haɗin ya ci gaba da cancanta ko bayan kurakuran da za a iya gyarawa (har yanzu yanayin ƙarshe kamar an hana/an ƙare suna aiki).", "disableCoolingLabel": "Kashe lokacin dakatawa na wannan haɗin", + "keepMidConversationSystemDescription": "A bar saƙon tsarin da aka aika tsakiyar tattaunawa a cikin jerin saƙon maimakon matsar da su zuwa babban matakin tsarin faɗakarwa, don haka cache ɗin gaggawa ta ci gaba da bugawa a cikin juyi. Kunna kawai idan wannan na sama ya karɓi rawar \"tsarin\" a cikin saƙonni (in ba haka ba yana amsa 400).", + "keepMidConversationSystemLabel": "Ajiye saƙonnin tsarin tattaunawa a tsakiyar wuri", "bulkPasteAdded": "{count, plural, one {An ƙara maɓalli 1} other {An ƙara maɓallai #}}", "bulkPasteDuplicatesIgnored": "{count, plural, one {An tsallake kwafi 1} other {An tsallake kwafi #}}", "bulkPasteHint": "Liƙa maɓallin API ɗaya a kowane layi. Ana watsi da layukan da babu komai kuma ana tsallake maɓallan da aka maimaita.", diff --git a/src/i18n/messages/he.json b/src/i18n/messages/he.json index 3574365f2080..7a190da47989 100644 --- a/src/i18n/messages/he.json +++ b/src/i18n/messages/he.json @@ -6245,6 +6245,8 @@ "allowCodexPaidCreditsDescription": "Allow this account to use paid credits after its subscription quota reaches the cutoff. Requires available credits reported by OpenAI; may incur charges. Spark limits and upstream cooldowns still apply. Credit availability is checked even if quota preflight is disabled.", "disableCoolingDescription": "דלג על תקופת הצינון הזמנית כדי שחיבור זה יישאר זמין גם לאחר שגיאות הניתנות לשחזור (מצבים סופיים כמו חסום/פג תוקף עדיין חלים).", "disableCoolingLabel": "השבת תקופת צינון עבור חיבור זה", + "keepMidConversationSystemDescription": "השאר הודעות מערכת שנשלחו באמצע השיחה בתוך רשימת ההודעות במקום להעביר אותן להנחיית המערכת ברמה העליונה, כך שמטמון ההנחיות ימשיך להכות על פני פניות. הפעל רק אם זה במעלה הזרם מקבל תפקיד \"מערכת\" בתוך הודעות (אחרת הוא עונה 400).", + "keepMidConversationSystemLabel": "שמור על הודעות מערכת באמצע השיחה במקום", "bulkPasteAdded": "{count, plural, one {נוסף מפתח אחד} other {נוספו # מפתחות}}", "bulkPasteDuplicatesIgnored": "{count, plural, one {דילוג על כפילות אחת} other {דילוג על # כפילויות}}", "bulkPasteHint": "יש להדביק מפתח API אחד בכל שורה. המערכת מתעלמת משורות ריקות ומדלגת על מפתחות כפולים.", diff --git a/src/i18n/messages/hi.json b/src/i18n/messages/hi.json index 1d00213bebaa..3849c58f27fd 100644 --- a/src/i18n/messages/hi.json +++ b/src/i18n/messages/hi.json @@ -6245,6 +6245,8 @@ "allowCodexPaidCreditsDescription": "Allow this account to use paid credits after its subscription quota reaches the cutoff. Requires available credits reported by OpenAI; may incur charges. Spark limits and upstream cooldowns still apply. Credit availability is checked even if quota preflight is disabled.", "disableCoolingDescription": "अस्थायी कूलडाउन को छोड़ें ताकि पुनर्प्राप्ति योग्य त्रुटियों के बाद भी यह कनेक्शन योग्य बना रहे (प्रतिबंधित/समाप्त जैसी अंतिम स्थितियां अभी भी लागू होती हैं)।", "disableCoolingLabel": "इस कनेक्शन के लिए कूलडाउन अक्षम करें", + "keepMidConversationSystemDescription": "भेजे गए सिस्टम संदेशों को शीर्ष-स्तरीय सिस्टम प्रॉम्प्ट में ले जाने के बजाय संदेश सूची के अंदर बातचीत के बीच में छोड़ दें, ताकि प्रॉम्प्ट कैश बारी-बारी से हिट होता रहे। केवल तभी सक्षम करें जब यह अपस्ट्रीम संदेशों के अंदर \"सिस्टम\" भूमिका स्वीकार करता है (अन्यथा यह 400 का उत्तर देता है)।", + "keepMidConversationSystemLabel": "बातचीत के बीच में सिस्टम संदेशों को यथास्थान रखें", "bulkPasteAdded": "{count, plural, one {1 कुंजी जोड़ी गई} other {# कुंजियाँ जोड़ी गईं}}", "bulkPasteDuplicatesIgnored": "{count, plural, one {1 डुप्लिकेट छोड़ा गया} other {# डुप्लिकेट छोड़े गए}}", "bulkPasteHint": "प्रति पंक्ति एक API कुंजी पेस्ट करें। खाली पंक्तियों को अनदेखा किया जाता है और डुप्लिकेट कुंजियाँ छोड़ दी जाती हैं।", diff --git a/src/i18n/messages/hr.json b/src/i18n/messages/hr.json index 64288202e25b..b38197321845 100644 --- a/src/i18n/messages/hr.json +++ b/src/i18n/messages/hr.json @@ -6245,6 +6245,8 @@ "allowCodexPaidCreditsDescription": "Allow this account to use paid credits after its subscription quota reaches the cutoff. Requires available credits reported by OpenAI; may incur charges. Spark limits and upstream cooldowns still apply. Credit availability is checked even if quota preflight is disabled.", "disableCoolingDescription": "Preskoči privremeno razdoblje mirovanja kako bi ova veza ostala dostupna čak i nakon pogrešaka od kojih se moguće oporaviti (terminalna stanja poput zabrane ili isteka i dalje se primjenjuju).", "disableCoolingLabel": "Onemogući razdoblje mirovanja za ovu vezu", + "keepMidConversationSystemDescription": "Ostavite sistemske poruke poslane usred razgovora unutar popisa poruka umjesto da ih premjestite u prompt sustava najviše razine, tako da predmemorija prompta neprestano pogađa. Omogući samo ako ovaj uzvodni sustav prihvaća ulogu \"sustav\" unutar poruka (inače odgovara 400).", + "keepMidConversationSystemLabel": "Držite poruke sustava usred razgovora na mjestu", "bulkPasteAdded": "{count, plural, one {dodan 1 ključ} few {dodana # ključa} other {dodano # ključeva}}", "bulkPasteDuplicatesIgnored": "{count, plural, one {preskočen 1 duplikat} few {preskočena # duplikata} other {preskočeno # duplikata}}", "bulkPasteHint": "Zalijepite jedan API ključ po retku. Prazni reci se ignoriraju, a duplikati se preskaču.", diff --git a/src/i18n/messages/hu.json b/src/i18n/messages/hu.json index 19ad5c41744d..7f9718d89187 100644 --- a/src/i18n/messages/hu.json +++ b/src/i18n/messages/hu.json @@ -6245,6 +6245,8 @@ "allowCodexPaidCreditsDescription": "Allow this account to use paid credits after its subscription quota reaches the cutoff. Requires available credits reported by OpenAI; may incur charges. Spark limits and upstream cooldowns still apply. Credit availability is checked even if quota preflight is disabled.", "disableCoolingDescription": "Az átmeneti várakozási idő kihagyása, hogy ez a kapcsolat helyreállítható hibák után is használható maradjon (a végleges állapotok, például a letiltás vagy lejárat továbbra is érvényesek).", "disableCoolingLabel": "Lehűlési idő (cooldown) letiltása ehhez a kapcsolathoz", + "keepMidConversationSystemDescription": "A beszélgetés közben elküldött rendszerüzeneteket hagyja az üzenetlistán belül ahelyett, hogy a legfelső szintű rendszerpromptba helyezné át őket, így a gyorsítótár folyamatosan üti egymást. Csak akkor engedélyezze, ha ez az upstream elfogadja a \"rendszer\" szerepkört az üzenetekben (egyébként 400-at válaszol).", + "keepMidConversationSystemLabel": "Tartsa a helyén a beszélgetés közbeni rendszerüzeneteket", "bulkPasteAdded": "{count, plural, one {1 kulcs hozzáadva} other {# kulcs hozzáadva}}", "bulkPasteDuplicatesIgnored": "{count, plural, one {1 ismétlődő kulcs kihagyva} other {# ismétlődő kulcs kihagyva}}", "bulkPasteHint": "Illessz be soronként egy API-kulcsot. Az üres sorokat figyelmen kívül hagyjuk, az ismétlődő kulcsokat pedig kihagyjuk.", diff --git a/src/i18n/messages/hy.json b/src/i18n/messages/hy.json index 556682470534..dcfb1d10585f 100644 --- a/src/i18n/messages/hy.json +++ b/src/i18n/messages/hy.json @@ -6245,6 +6245,8 @@ "allowCodexPaidCreditsDescription": "Allow this account to use paid credits after its subscription quota reaches the cutoff. Requires available credits reported by OpenAI; may incur charges. Spark limits and upstream cooldowns still apply. Credit availability is checked even if quota preflight is disabled.", "disableCoolingDescription": "Բաց թողնել ժամանակավոր սպասման ժամանակահատվածը, որպեսզի այս կապը հասանելի մնա նույնիսկ վերականգնելի սխալներից հետո (վերջնական վիճակները, օրինակ՝ արգելափակված/ժամկետանց, շարունակում են կիրառվել)։", "disableCoolingLabel": "Անջատել այս կապի սպասման ժամանակահատվածը", + "keepMidConversationSystemDescription": "Համակարգային հաղորդագրությունները, որոնք ուղարկվել են խոսակցության կեսին, թողեք հաղորդագրությունների ցանկի ներսում՝ դրանք վերին մակարդակի համակարգի հուշում տեղափոխելու փոխարեն, այնպես որ հուշումների քեշը շարունակում է շրջվել: Միացնել միայն այն դեպքում, եթե այս վերին հոսանքն ընդունում է դերերի «համակարգը» հաղորդագրությունների ներսում (հակառակ դեպքում այն պատասխանում է 400-ին):", + "keepMidConversationSystemLabel": "Պահպանեք զրույցի ընթացքում համակարգային հաղորդագրությունները տեղում", "bulkPasteAdded": "{count, plural, one {Ավելացվել է 1 բանալի} other {Ավելացվել է # բանալի}}", "bulkPasteDuplicatesIgnored": "{count, plural, one {Բաց է թողնվել 1 կրկնօրինակ} other {Բաց է թողնվել # կրկնօրինակ}}", "bulkPasteHint": "Տեղադրեք յուրաքանչյուր տողում մեկ API բանալի։ Դատարկ տողերն անտեսվում են, իսկ կրկնվող բանալիները՝ բաց թողնվում։", diff --git a/src/i18n/messages/id.json b/src/i18n/messages/id.json index 2387445ad8ec..02cfeca684ea 100644 --- a/src/i18n/messages/id.json +++ b/src/i18n/messages/id.json @@ -6245,6 +6245,8 @@ "allowCodexPaidCreditsDescription": "Allow this account to use paid credits after its subscription quota reaches the cutoff. Requires available credits reported by OpenAI; may incur charges. Spark limits and upstream cooldowns still apply. Credit availability is checked even if quota preflight is disabled.", "disableCoolingDescription": "Lewati cooldown transien agar koneksi ini tetap memenuhi syarat bahkan setelah kesalahan yang dapat dipulihkan (status terminal seperti diblokir/kedaluwarsa tetap berlaku).", "disableCoolingLabel": "Nonaktifkan cooldown untuk koneksi ini", + "keepMidConversationSystemDescription": "Biarkan pesan sistem terkirim di tengah percakapan di dalam daftar pesan alih-alih memindahkannya ke prompt sistem tingkat atas, sehingga cache prompt terus muncul secara bergantian. Aktifkan hanya jika upstream ini menerima peran \"sistem\" di dalam pesan (jika tidak maka akan menjawab 400).", + "keepMidConversationSystemLabel": "Pertahankan pesan sistem di tengah percakapan", "bulkPasteAdded": "{count, plural, one {1 kunci ditambahkan} other {# kunci ditambahkan}}", "bulkPasteDuplicatesIgnored": "{count, plural, one {1 duplikat dilewati} other {# duplikat dilewati}}", "bulkPasteHint": "Tempelkan satu kunci API per baris. Baris kosong akan diabaikan dan kunci duplikat akan dilewati.", diff --git a/src/i18n/messages/ig.json b/src/i18n/messages/ig.json index 0ef3fb94f3ec..632279fc83e4 100644 --- a/src/i18n/messages/ig.json +++ b/src/i18n/messages/ig.json @@ -6245,6 +6245,8 @@ "allowCodexPaidCreditsDescription": "Allow this account to use paid credits after its subscription quota reaches the cutoff. Requires available credits reported by OpenAI; may incur charges. Spark limits and upstream cooldowns still apply. Credit availability is checked even if quota preflight is disabled.", "disableCoolingDescription": "Mafee oge nkwụsị nwa oge ka njikọ a nọgide bụrụ nke a pụrụ iji ọbụna mgbe njehie ndị a pụrụ idozi mere (ọnọdụ njedebe dịka amachibidoro/agwụla ka na-emetụta ya).", "disableCoolingLabel": "Gbanyụọ oge nkwụsị maka njikọ a", + "keepMidConversationSystemDescription": "Hapụ ozi sistemụ ezigara mkparịta ụka etiti n'ime ndepụta ozi kama ịkwaga ha n'ime sistemụ ọkwa dị elu, yabụ cache ozugbo na-aga n'ihu na-akụgharị n'ofe ntụgharị. Kwado naanị ma ọ bụrụ na mgbago elu a na-anabata ọrụ \"sistemu\" n'ime ozi (ma ọ bụghị ya ọ na-aza 400).", + "keepMidConversationSystemLabel": "Debe ozi sistemụ mkparịta ụka n'etiti ebe", "bulkPasteAdded": "{count, plural, one {Agbakwunyere igodo 1} other {Agbakwunyere igodo #}}", "bulkPasteDuplicatesIgnored": "{count, plural, one {Amafere oyiri 1} other {Amafere oyiri #}}", "bulkPasteHint": "Tapawa otu igodo API n'ahịrị ọ bụla. A na-eleghara ahịrị efu anya ma na-amafe igodo ndị bụ oyiri.", diff --git a/src/i18n/messages/it.json b/src/i18n/messages/it.json index 197bb469cd10..0a9e42a647a5 100644 --- a/src/i18n/messages/it.json +++ b/src/i18n/messages/it.json @@ -6322,6 +6322,8 @@ "allowCodexPaidCreditsDescription": "Allow this account to use paid credits after its subscription quota reaches the cutoff. Requires available credits reported by OpenAI; may incur charges. Spark limits and upstream cooldowns still apply. Credit availability is checked even if quota preflight is disabled.", "disableCoolingDescription": "Salta il cooldown transitorio in modo che questa connessione rimanga idonea anche dopo errori ripristinabili (gli stati terminali come bannato/scaduto si applicano ancora).", "disableCoolingLabel": "Disabilita il cooldown per questa connessione", + "keepMidConversationSystemDescription": "Lascia i messaggi di sistema inviati nel corso di una conversazione all'interno dell'elenco dei messaggi invece di spostarli nel prompt di sistema di livello superiore, in modo che la cache dei prompt continui a funzionare a turno. Abilita solo se questo upstream accetta il ruolo \"sistema\" all'interno dei messaggi (altrimenti risponde 400).", + "keepMidConversationSystemLabel": "Mantieni i messaggi di sistema durante la conversazione", "bulkPasteAdded": "{count, plural, one {1 chiave aggiunta} other {# chiavi aggiunte}}", "bulkPasteDuplicatesIgnored": "{count, plural, one {1 duplicato ignorato} other {# duplicati ignorati}}", "bulkPasteHint": "Incolla una chiave API per riga. Le righe vuote e le chiavi duplicate vengono ignorate.", diff --git a/src/i18n/messages/ja.json b/src/i18n/messages/ja.json index 8f60fa099dd4..a8e9e04ddba8 100644 --- a/src/i18n/messages/ja.json +++ b/src/i18n/messages/ja.json @@ -6245,6 +6245,8 @@ "allowCodexPaidCreditsDescription": "Allow this account to use paid credits after its subscription quota reaches the cutoff. Requires available credits reported by OpenAI; may incur charges. Spark limits and upstream cooldowns still apply. Credit availability is checked even if quota preflight is disabled.", "disableCoolingDescription": "一時的なクールダウンをスキップし、回復可能なエラーの後でもこの接続を対象として維持します (banned/expired などの終端状態は引き続き適用されます)。", "disableCoolingLabel": "この接続のクールダウンを無効化", + "keepMidConversationSystemDescription": "会話中に送信されたシステム メッセージを最上位のシステム プロンプトに移動するのではなく、メッセージ リスト内に残しておきます。これにより、プロンプト キャッシュはターンを超えてヒットし続けます。このアップストリームがメッセージ内で役割「システム」を受け入れる場合にのみ有効にします (そうでない場合は 400 と応答します)。", + "keepMidConversationSystemLabel": "会話中のシステム メッセージを適切な位置に維持する", "bulkPasteAdded": "{count, plural, one {1件のキーを追加しました} other {#件のキーを追加しました}}", "bulkPasteDuplicatesIgnored": "{count, plural, one {重複した1件をスキップしました} other {重複した#件をスキップしました}}", "bulkPasteHint": "1行につき1つのAPIキーを貼り付けてください。空行は無視され、重複するキーはスキップされます。", diff --git a/src/i18n/messages/ka.json b/src/i18n/messages/ka.json index 2a0bc1894e61..1ccd05f0abb9 100644 --- a/src/i18n/messages/ka.json +++ b/src/i18n/messages/ka.json @@ -6322,6 +6322,8 @@ "allowCodexPaidCreditsDescription": "Allow this account to use paid credits after its subscription quota reaches the cutoff. Requires available credits reported by OpenAI; may incur charges. Spark limits and upstream cooldowns still apply. Credit availability is checked even if quota preflight is disabled.", "disableCoolingDescription": "გამოტოვეთ დროებითი დაყოვნების პერიოდი, რათა ეს კავშირი აღდგენადი შეცდომების შემდეგაც ხელმისაწვდომი დარჩეს (ტერმინალური მდგომარეობები, როგორიცაა დაბლოკილი/ვადაგასული, კვლავ მოქმედებს).", "disableCoolingLabel": "ამ კავშირისთვის დაყოვნების პერიოდის გამორთვა", + "keepMidConversationSystemDescription": "საუბრის შუა რიცხვებში გაგზავნილი სისტემის შეტყობინებები დატოვეთ შეტყობინებების სიაში, ნაცვლად იმისა, რომ გადაიტანოთ ისინი ზედა დონის სისტემის მოთხოვნაში, ასე რომ, მოთხოვნის ქეში აგრძელებს მონაცვლეობას. ჩართვა მხოლოდ იმ შემთხვევაში, თუ ეს ზემოთ ნაკადში მიიღებს როლურ \"სისტემას\" შეტყობინებებში (წინააღმდეგ შემთხვევაში ის პასუხობს 400-ს).", + "keepMidConversationSystemLabel": "შეინახეთ შუა საუბრის სისტემის შეტყობინებები ადგილზე", "bulkPasteAdded": "{count, plural, one {დამატებულია 1 გასაღები} other {დამატებულია # გასაღები}}", "bulkPasteDuplicatesIgnored": "{count, plural, one {გამოტოვებულია 1 დუბლიკატი} other {გამოტოვებულია # დუბლიკატი}}", "bulkPasteHint": "თითო ხაზზე ჩასვით თითო API გასაღები. ცარიელი ხაზები უგულებელყოფილია, ხოლო დუბლირებული გასაღებები გამოტოვებულია.", diff --git a/src/i18n/messages/km.json b/src/i18n/messages/km.json index bd0b811193c1..ab5fa20d281d 100644 --- a/src/i18n/messages/km.json +++ b/src/i18n/messages/km.json @@ -6322,6 +6322,8 @@ "allowCodexPaidCreditsDescription": "Allow this account to use paid credits after its subscription quota reaches the cutoff. Requires available credits reported by OpenAI; may incur charges. Spark limits and upstream cooldowns still apply. Credit availability is checked even if quota preflight is disabled.", "disableCoolingDescription": "រំលងរយៈពេលរង់ចាំបណ្តោះអាសន្ន ដើម្បីឱ្យការតភ្ជាប់នេះនៅតែអាចប្រើបាន សូម្បីតែបន្ទាប់ពីមានកំហុសដែលអាចស្ដារឡើងវិញបានក៏ដោយ (ស្ថានភាពបញ្ចប់ដូចជា ត្រូវបានហាមឃាត់/ផុតកំណត់ នៅតែអនុវត្ត)។", "disableCoolingLabel": "បិទរយៈពេលរង់ចាំសម្រាប់ការតភ្ជាប់នេះ", + "keepMidConversationSystemDescription": "ទុកសារប្រព័ន្ធដែលបានផ្ញើការសន្ទនាពាក់កណ្តាលនៅក្នុងបញ្ជីសារ ជំនួសឱ្យការផ្លាស់ទីពួកវាទៅក្នុងប្រអប់បញ្ចូលប្រព័ន្ធកម្រិតកំពូល ដូច្នេះឃ្លាំងសម្ងាត់ភ្លាមៗនៅតែបន្តឆ្លងកាត់។ បើកដំណើរការបានលុះត្រាតែប្រភពខាងលើទទួលយកតួនាទី \"ប្រព័ន្ធ\" នៅក្នុងសារ (បើមិនដូច្នេះទេ វាឆ្លើយ 400)។", + "keepMidConversationSystemLabel": "រក្សាសារប្រព័ន្ធសន្ទនាពាក់កណ្តាលនៅនឹងកន្លែង", "bulkPasteAdded": "{count, plural, one {បានបន្ថែមសោ 1} other {បានបន្ថែមសោ #}}", "bulkPasteDuplicatesIgnored": "{count, plural, one {បានរំលងសោស្ទួន 1} other {បានរំលងសោស្ទួន #}}", "bulkPasteHint": "បិទភ្ជាប់ API key មួយក្នុងមួយបន្ទាត់។ បន្ទាត់ទទេនឹងមិនត្រូវបានអើពើ ហើយសោស្ទួននឹងត្រូវបានរំលង។", diff --git a/src/i18n/messages/kn.json b/src/i18n/messages/kn.json index 5e2df83f0b9d..f08bec08b258 100644 --- a/src/i18n/messages/kn.json +++ b/src/i18n/messages/kn.json @@ -6245,6 +6245,8 @@ "allowCodexPaidCreditsDescription": "Allow this account to use paid credits after its subscription quota reaches the cutoff. Requires available credits reported by OpenAI; may incur charges. Spark limits and upstream cooldowns still apply. Credit availability is checked even if quota preflight is disabled.", "disableCoolingDescription": "ತಾತ್ಕಾಲಿಕ ಕೂಲ್ಡೌನ್ ಅನ್ನು ಬಿಟ್ಟುಬಿಡಿ, ಇದರಿಂದ ಮರುಪಡೆಯಬಹುದಾದ ದೋಷಗಳ ನಂತರವೂ ಈ ಸಂಪರ್ಕವು ಅರ್ಹವಾಗಿರುತ್ತದೆ (ನಿಷೇಧಿತ/ಅವಧಿ ಮುಗಿದಂತಹ ಅಂತಿಮ ಸ್ಥಿತಿಗಳು ಇನ್ನೂ ಅನ್ವಯಿಸುತ್ತವೆ).", "disableCoolingLabel": "ಈ ಸಂಪರ್ಕಕ್ಕಾಗಿ ಕೂಲ್ಡೌನ್ ಅನ್ನು ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಿ", + "keepMidConversationSystemDescription": "ಸಿಸ್ಟಂ ಸಂದೇಶಗಳನ್ನು ಉನ್ನತ ಮಟ್ಟದ ಸಿಸ್ಟಂ ಪ್ರಾಂಪ್ಟ್ಗೆ ಸರಿಸುವ ಬದಲು ಸಂದೇಶ ಪಟ್ಟಿಯೊಳಗೆ ಮಧ್ಯ-ಸಂಭಾಷಣೆಯನ್ನು ಬಿಡಿ, ಆದ್ದರಿಂದ ಪ್ರಾಂಪ್ಟ್ ಸಂಗ್ರಹವು ತಿರುವುಗಳಲ್ಲಿ ಹೊಡೆಯುತ್ತಲೇ ಇರುತ್ತದೆ. ಈ ಅಪ್ಸ್ಟ್ರೀಮ್ ಸಂದೇಶಗಳ ಒಳಗೆ ಪಾತ್ರ \"ಸಿಸ್ಟಮ್\" ಅನ್ನು ಸ್ವೀಕರಿಸಿದರೆ ಮಾತ್ರ ಸಕ್ರಿಯಗೊಳಿಸಿ (ಇಲ್ಲದಿದ್ದರೆ ಅದು 400 ಗೆ ಉತ್ತರಿಸುತ್ತದೆ).", + "keepMidConversationSystemLabel": "ಮಧ್ಯ-ಸಂಭಾಷಣಾ ವ್ಯವಸ್ಥೆಯ ಸಂದೇಶಗಳನ್ನು ಸ್ಥಳದಲ್ಲಿ ಇರಿಸಿ", "bulkPasteAdded": "{count, plural, one {1 ಕೀ ಸೇರಿಸಲಾಗಿದೆ} other {# ಕೀಗಳನ್ನು ಸೇರಿಸಲಾಗಿದೆ}}", "bulkPasteDuplicatesIgnored": "{count, plural, one {1 ನಕಲನ್ನು ಬಿಟ್ಟುಬಿಡಲಾಗಿದೆ} other {# ನಕಲುಗಳನ್ನು ಬಿಟ್ಟುಬಿಡಲಾಗಿದೆ}}", "bulkPasteHint": "ಪ್ರತಿ ಸಾಲಿಗೆ ಒಂದು API ಕೀಯನ್ನು ಅಂಟಿಸಿ. ಖಾಲಿ ಸಾಲುಗಳನ್ನು ನಿರ್ಲಕ್ಷಿಸಲಾಗುತ್ತದೆ ಮತ್ತು ನಕಲಿ ಕೀಗಳನ್ನು ಬಿಟ್ಟುಬಿಡಲಾಗುತ್ತದೆ.", diff --git a/src/i18n/messages/ko.json b/src/i18n/messages/ko.json index 3a0cbd7907a2..4ac892e18bdc 100644 --- a/src/i18n/messages/ko.json +++ b/src/i18n/messages/ko.json @@ -6245,6 +6245,8 @@ "allowCodexPaidCreditsDescription": "Allow this account to use paid credits after its subscription quota reaches the cutoff. Requires available credits reported by OpenAI; may incur charges. Spark limits and upstream cooldowns still apply. Credit availability is checked even if quota preflight is disabled.", "disableCoolingDescription": "일시적인 쿨다운을 건너뛰어 복구 가능한 오류가 발생한 후에도 이 연결이 사용 가능한 상태로 유지되도록 합니다(차단됨/만료됨과 같은 최종 상태는 여전히 적용됨).", "disableCoolingLabel": "이 연결에 대한 쿨다운 비활성화", + "keepMidConversationSystemDescription": "시스템 메시지를 최상위 시스템 프롬프트로 이동하는 대신 메시지 목록 내부의 대화 중간에 전송된 상태로 두십시오. 그러면 프롬프트 캐시가 차례대로 계속 히트합니다. 이 업스트림이 메시지 내에서 \"시스템\" 역할을 수락하는 경우에만 활성화됩니다(그렇지 않으면 400으로 응답).", + "keepMidConversationSystemLabel": "대화 도중 시스템 메시지를 제자리에 유지", "bulkPasteAdded": "{count, plural, one {키 1개 추가됨} other {키 #개 추가됨}}", "bulkPasteDuplicatesIgnored": "{count, plural, one {중복 1개 건너뜀} other {중복 #개 건너뜀}}", "bulkPasteHint": "한 줄에 API 키 하나씩 붙여넣으세요. 빈 줄은 무시되며 중복 키는 건너뜁니다.", diff --git a/src/i18n/messages/lt.json b/src/i18n/messages/lt.json index 84f4bd609db2..68cff4098671 100644 --- a/src/i18n/messages/lt.json +++ b/src/i18n/messages/lt.json @@ -6245,6 +6245,8 @@ "allowCodexPaidCreditsDescription": "Allow this account to use paid credits after its subscription quota reaches the cutoff. Requires available credits reported by OpenAI; may incur charges. Spark limits and upstream cooldowns still apply. Credit availability is checked even if quota preflight is disabled.", "disableCoolingDescription": "Praleisti laikiną atvėsimo laikotarpį, kad šį ryšį būtų galima naudoti net ir po klaidų, po kurių galima atkurti veikimą (galutinės būsenos, pvz., užblokavimas ar galiojimo pabaiga, vis tiek taikomos).", "disableCoolingLabel": "Išjungti šio ryšio atvėsimo laikotarpį", + "keepMidConversationSystemDescription": "Palikite sistemos pranešimus, išsiųstus pokalbio viduryje, pranešimų sąraše, o ne perkelkite juos į aukščiausio lygio sistemos raginimą, kad raginimo talpykla būtų rodoma visuose posūkiuose. Įjungti tik tuo atveju, jei ši prieš srovę priima „sistemos“ vaidmenį pranešimuose (kitaip atsako į 400).", + "keepMidConversationSystemLabel": "Laikykite pokalbio viduryje esančius sistemos pranešimus", "bulkPasteAdded": "{count, plural, one {Pridėtas 1 raktas} other {Pridėta # raktų}}", "bulkPasteDuplicatesIgnored": "{count, plural, one {Praleistas 1 pasikartojantis raktas} other {Praleista # pasikartojančių raktų}}", "bulkPasteHint": "Įklijuokite po vieną API raktą kiekvienoje eilutėje. Tuščios eilutės ignoruojamos, o pasikartojantys raktai praleidžiami.", diff --git a/src/i18n/messages/lv.json b/src/i18n/messages/lv.json index 684b5019e870..ea8af30a0250 100644 --- a/src/i18n/messages/lv.json +++ b/src/i18n/messages/lv.json @@ -6245,6 +6245,8 @@ "allowCodexPaidCreditsDescription": "Allow this account to use paid credits after its subscription quota reaches the cutoff. Requires available credits reported by OpenAI; may incur charges. Spark limits and upstream cooldowns still apply. Credit availability is checked even if quota preflight is disabled.", "disableCoolingDescription": "Izlaist pārejošo nogaidīšanas periodu, lai šo savienojumu varētu izmantot arī pēc novēršamām kļūdām (galīgie stāvokļi, piemēram, bloķēts savienojums vai beidzies derīguma termiņš, joprojām ir spēkā).", "disableCoolingLabel": "Atspējot nogaidīšanas periodu šim savienojumam", + "keepMidConversationSystemDescription": "Sarunas vidū nosūtītos sistēmas ziņojumus atstājiet ziņojumu sarakstā, nevis pārvietojiet tos uz augstākā līmeņa sistēmas uzvedni, lai uzvednes kešatmiņa tiktu rādīta vairākos pagriezienos. Iespējot tikai tad, ja šī augšpuse pieņem lomu \"sistēma\" ziņojumos (pretējā gadījumā tā atbild uz 400).", + "keepMidConversationSystemLabel": "Sarunas vidū saglabājiet sistēmas ziņojumus vietā", "bulkPasteAdded": "{count, plural, one {Pievienota 1 atslēga} other {Pievienotas # atslēgas}}", "bulkPasteDuplicatesIgnored": "{count, plural, one {Izlaists 1 dublikāts} other {Izlaisti # dublikāti}}", "bulkPasteHint": "Ielīmējiet vienu API atslēgu katrā rindā. Tukšās rindas tiek ignorētas, un dublētās atslēgas tiek izlaistas.", diff --git a/src/i18n/messages/ml.json b/src/i18n/messages/ml.json index 1962b5c184fd..8cbf7e03f83e 100644 --- a/src/i18n/messages/ml.json +++ b/src/i18n/messages/ml.json @@ -6322,6 +6322,8 @@ "allowCodexPaidCreditsDescription": "Allow this account to use paid credits after its subscription quota reaches the cutoff. Requires available credits reported by OpenAI; may incur charges. Spark limits and upstream cooldowns still apply. Credit availability is checked even if quota preflight is disabled.", "disableCoolingDescription": "താൽക്കാലിക കൂൾഡൗൺ ഒഴിവാക്കുക, അതുവഴി വീണ്ടെടുക്കാവുന്ന പിശകുകൾക്ക് ശേഷവും ഈ കണക്ഷൻ യോഗ്യമായി തുടരും (നിരോധിച്ചത്/കാലഹരണപ്പെട്ടത് പോലുള്ള അന്തിമ നിലകൾ തുടർന്നും ബാധകമാണ്).", "disableCoolingLabel": "ഈ കണക്ഷന്റെ കൂൾഡൗൺ പ്രവർത്തനരഹിതമാക്കുക", + "keepMidConversationSystemDescription": "സിസ്റ്റം സന്ദേശങ്ങൾ ഉയർന്ന തലത്തിലുള്ള സിസ്റ്റം പ്രോംപ്റ്റിലേക്ക് നീക്കുന്നതിന് പകരം സന്ദേശ ലിസ്റ്റിനുള്ളിൽ സംഭാഷണത്തിൻ്റെ മധ്യത്തിൽ വിടുക, അതിനാൽ പ്രോംപ്റ്റ് കാഷെ തിരിവുകളിലുടനീളം അടിക്കുന്നത് തുടരുന്നു. ഈ അപ്സ്ട്രീം സന്ദേശങ്ങൾക്കുള്ളിൽ റോൾ \"സിസ്റ്റം\" സ്വീകരിച്ചാൽ മാത്രം പ്രവർത്തനക്ഷമമാക്കുക (അല്ലെങ്കിൽ അത് 400-ന് ഉത്തരം നൽകുന്നു).", + "keepMidConversationSystemLabel": "മധ്യ-സംഭാഷണ സിസ്റ്റം സന്ദേശങ്ങൾ സ്ഥലത്ത് സൂക്ഷിക്കുക", "bulkPasteAdded": "{count, plural, one {1 കീ ചേർത്തു} other {# കീകൾ ചേർത്തു}}", "bulkPasteDuplicatesIgnored": "{count, plural, one {1 തനിപ്പകർപ്പ് ഒഴിവാക്കി} other {# തനിപ്പകർപ്പുകൾ ഒഴിവാക്കി}}", "bulkPasteHint": "ഓരോ വരിയിലും ഒരു API കീ വീതം ഒട്ടിക്കുക. ശൂന്യമായ വരികൾ അവഗണിക്കുകയും തനിപ്പകർപ്പ് കീകൾ ഒഴിവാക്കുകയും ചെയ്യും.", diff --git a/src/i18n/messages/mr.json b/src/i18n/messages/mr.json index 902f0c3018c3..82da1078d6bc 100644 --- a/src/i18n/messages/mr.json +++ b/src/i18n/messages/mr.json @@ -6245,6 +6245,8 @@ "allowCodexPaidCreditsDescription": "Allow this account to use paid credits after its subscription quota reaches the cutoff. Requires available credits reported by OpenAI; may incur charges. Spark limits and upstream cooldowns still apply. Credit availability is checked even if quota preflight is disabled.", "disableCoolingDescription": "अस्थायी कूलडाउन वगळा जेणेकरून पुनर्प्राप्त करण्यायोग्य त्रुटींनंतरही हे कनेक्शन पात्र राहील (प्रतिबंधित/कालबाह्य यांसारख्या अंतिम स्थिती अजूनही लागू होतात).", "disableCoolingLabel": "या कनेक्शनसाठी कूलडाउन अक्षम करा", + "keepMidConversationSystemDescription": "मेसेज लिस्टमध्ये मध्य-संभाषणात पाठवलेले सिस्टीम मेसेजेस उच्च-स्तरीय सिस्टीम प्रॉम्प्टमध्ये हलवण्याऐवजी सोडा, त्यामुळे प्रॉम्प्ट कॅशे वळणांवर आदळत राहते. हे अपस्ट्रीम संदेशांच्या आत भूमिका \"सिस्टम\" स्वीकारत असेल तरच सक्षम करा (अन्यथा ते 400 चे उत्तर देते).", + "keepMidConversationSystemLabel": "मध्य-संभाषण प्रणाली संदेश जागी ठेवा", "bulkPasteAdded": "{count, plural, one {1 key जोडली} other {# keys जोडल्या}}", "bulkPasteDuplicatesIgnored": "{count, plural, one {1 डुप्लिकेट key वगळली} other {# डुप्लिकेट keys वगळल्या}}", "bulkPasteHint": "प्रत्येक ओळीत एक API key पेस्ट करा. रिकाम्या ओळींकडे दुर्लक्ष केले जाते आणि डुप्लिकेट keys वगळल्या जातात.", diff --git a/src/i18n/messages/ms.json b/src/i18n/messages/ms.json index c38b0911f069..f35455e150d7 100644 --- a/src/i18n/messages/ms.json +++ b/src/i18n/messages/ms.json @@ -6245,6 +6245,8 @@ "allowCodexPaidCreditsDescription": "Allow this account to use paid credits after its subscription quota reaches the cutoff. Requires available credits reported by OpenAI; may incur charges. Spark limits and upstream cooldowns still apply. Credit availability is checked even if quota preflight is disabled.", "disableCoolingDescription": "Langkau tempoh bertenang sementara supaya sambungan ini kekal layak walaupun selepas ralat yang boleh dipulihkan (keadaan terminal seperti disekat/tamat tempoh masih terpakai).", "disableCoolingLabel": "Nyahdayakan tempoh bertenang untuk sambungan ini", + "keepMidConversationSystemDescription": "Biarkan mesej sistem yang dihantar pertengahan perbualan di dalam senarai mesej dan bukannya mengalihkannya ke gesaan sistem peringkat atas, jadi cache gesaan terus merentasi selekoh. Dayakan hanya jika huluan ini menerima peranan \"sistem\" dalam mesej (jika tidak, ia menjawab 400).", + "keepMidConversationSystemLabel": "Simpan mesej sistem pertengahan perbualan di tempatnya", "bulkPasteAdded": "{count, plural, one {1 kunci ditambah} other {# kunci ditambah}}", "bulkPasteDuplicatesIgnored": "{count, plural, one {1 pendua dilangkau} other {# pendua dilangkau}}", "bulkPasteHint": "Tampal satu kunci API bagi setiap baris. Baris kosong akan diabaikan dan kunci pendua akan dilangkau.", diff --git a/src/i18n/messages/mt.json b/src/i18n/messages/mt.json index 3b9cc5888981..595dd4354b9b 100644 --- a/src/i18n/messages/mt.json +++ b/src/i18n/messages/mt.json @@ -6322,6 +6322,8 @@ "allowCodexPaidCreditsDescription": "Allow this account to use paid credits after its subscription quota reaches the cutoff. Requires available credits reported by OpenAI; may incur charges. Spark limits and upstream cooldowns still apply. Credit availability is checked even if quota preflight is disabled.", "disableCoolingDescription": "Aqbeż il-perjodu ta’ stennija temporanju sabiex din il-konnessjoni tibqa’ eliġibbli anke wara żbalji li jistgħu jiġu rkuprati (l-istati terminali bħal ipprojbiti jew skaduti xorta jibqgħu japplikaw).", "disableCoolingLabel": "Iddiżattiva l-perjodu ta’ stennija għal din il-konnessjoni", + "keepMidConversationSystemDescription": "Ħalli l-messaġġi tas-sistema mibgħuta f'nofs il-konversazzjoni ġewwa l-lista tal-messaġġi minflok iċċaqlaqhom fil-pront tas-sistema tal-ogħla livell, sabiex il-cache fil-pront jibqa' jolqot minn dawriet. Ippermetta biss jekk dan upstream jaċċetta rwol \"sistema\" ġewwa messaġġi (inkella twieġeb 400).", + "keepMidConversationSystemLabel": "Żomm il-messaġġi tas-sistema ta’ nofs il-konversazzjoni f’posthom", "bulkPasteAdded": "{count, plural, one {Żdiedet ċavetta waħda} other {Żdiedu # ċwievet}}", "bulkPasteDuplicatesIgnored": "{count, plural, one {Inqabeż duplikat wieħed} other {Inqabżu # duplikati}}", "bulkPasteHint": "Waħħal ċavetta tal-API waħda f’kull linja. Il-linji vojta jiġu injorati u ċ-ċwievet duplikati jinqabżu.", diff --git a/src/i18n/messages/my.json b/src/i18n/messages/my.json index 0bf3307b5ca7..d742fcec9530 100644 --- a/src/i18n/messages/my.json +++ b/src/i18n/messages/my.json @@ -6322,6 +6322,8 @@ "allowCodexPaidCreditsDescription": "ဤအကောင့်၏ စာရင်းသွင်းမှု ခွဲတမ်းသည် သတ်မှတ်ထားသော ကန့်သတ်ချက်သို့ ရောက်ရှိပြီးနောက် အခပေး ခရက်ဒစ်များကို အသုံးပြုခွင့်ပြုပါ။ OpenAI က ရရှိနိုင်သည်ဟု ဖော်ပြထားသော ခရက်ဒစ်များ ရှိရန် လိုအပ်ပြီး အခကြေးငွေ ကျသင့်နိုင်သည်။ Spark ကန့်သတ်ချက်များနှင့် ဝန်ဆောင်မှုပေးသူဘက်မှ ခေတ္တစောင့်ဆိုင်းကာလများသည် ဆက်လက်သက်ရောက်သည်။ စက်တွင်း ခွဲတမ်းစစ်ထုတ်ခြင်းကို လုံးဝပိတ်ထားခြင်း (ခွဲတမ်းကြိုတင်စစ်ဆေးမှုနှင့် ကန့်သတ်ချက်မူဝါဒ နှစ်ခုစလုံးကို ပိတ်ထားခြင်း) မဟုတ်ပါက ခွဲတမ်းကြိုတင်စစ်ဆေးမှုကို ပိတ်ထားသော်လည်း ခရက်ဒစ် ရရှိနိုင်မှုကို စစ်ဆေးသည်။", "disableCoolingDescription": "ယာယီ cooldown ကို ကျော်သွားစေပြီး ပြန်လည်ပြင်ဆင်နိုင်သော အမှားများ ဖြစ်ပွားပြီးနောက်တွင်ပင် ဤချိတ်ဆက်မှုကို ဆက်လက်အသုံးပြုနိုင်အောင် ထားပါ (ပိတ်ပင်ခံရခြင်း/သက်တမ်းကုန်ခြင်းကဲ့သို့သော terminal အခြေအနေများမှာ ဆက်လက်သက်ရောက်ပါသည်)။", "disableCoolingLabel": "ဤချိတ်ဆက်မှုအတွက် cooldown ကို ပိတ်ရန်", + "keepMidConversationSystemDescription": "စနစ်မက်ဆေ့ချ်များကို ထိပ်တန်းအဆင့်စနစ်အမှာစာသို့ ရွှေ့မည့်အစား မက်ဆေ့ချ်စာရင်းအတွင်း အလယ်အလတ်စကားဝိုင်းကို ပေးပို့ထားလိုက်ပါ၊ ထို့ကြောင့် အချက်ပြ ကက်ရှ်သည် အလှည့်အပြောင်းတွင် ဆက်တိုက်ဖြစ်ပေါ်နေပါသည်။ ဤရေစီးကြောင်းပေါ်ရှိ အခန်းကဏ္ဍ \"စနစ်\" ကို မက်ဆေ့ချ်များအတွင်း လက်ခံမှသာ ဖွင့်ပါ (သို့မဟုတ်ပါက 400 ကို အဖြေပေးသည်)။", + "keepMidConversationSystemLabel": "အလယ်အလတ် စကားဝိုင်းစနစ် မက်ဆေ့ချ်များကို နေရာတွင်ထားပါ။", "bulkPasteAdded": "{count, plural, one {ကီး 1 ခု ထည့်ပြီး} other {ကီး # ခု ထည့်ပြီး}}", "bulkPasteDuplicatesIgnored": "{count, plural, one {ထပ်နေသောကီး 1 ခုကို ကျော်ခဲ့သည်} other {ထပ်နေသောကီး # ခုကို ကျော်ခဲ့သည်}}", "bulkPasteHint": "စာကြောင်းတစ်ကြောင်းလျှင် API ကီးတစ်ခုစီ ကူးထည့်ပါ။ စာသားမရှိသော စာကြောင်းများကို လျစ်လျူရှုပြီး ထပ်နေသောကီးများကို ကျော်ပါမည်။", diff --git a/src/i18n/messages/ne.json b/src/i18n/messages/ne.json index 1f4a3febe45a..280edd626de1 100644 --- a/src/i18n/messages/ne.json +++ b/src/i18n/messages/ne.json @@ -6245,6 +6245,8 @@ "allowCodexPaidCreditsDescription": "Allow this account to use paid credits after its subscription quota reaches the cutoff. Requires available credits reported by OpenAI; may incur charges. Spark limits and upstream cooldowns still apply. Credit availability is checked even if quota preflight is disabled.", "disableCoolingDescription": "क्षणिक कूलडाउन छोड्नुहोस्, ताकि पुनःप्राप्त गर्न सकिने त्रुटिहरूपछि पनि यो जडान योग्य रहोस् (प्रतिबन्धित/म्याद सकिएको जस्ता अन्तिम अवस्थाहरू अझै लागू हुन्छन्)।", "disableCoolingLabel": "यस जडानका लागि कूलडाउन असक्षम गर्नुहोस्", + "keepMidConversationSystemDescription": "प्रणाली सन्देशहरूलाई शीर्ष-स्तर प्रणाली प्रम्प्टमा सार्नुको सट्टा सन्देश सूची भित्र मध्य-वार्तालाप पठाउनुहोस्, त्यसैले प्रम्प्ट क्यासले पालैपालो हिर्काइरहन्छ। यदि यो अपस्ट्रिमले सन्देशहरू भित्र भूमिका \"प्रणाली\" स्वीकार गर्छ भने मात्र सक्षम गर्नुहोस् (अन्यथा यसले 400 जवाफ दिन्छ)।", + "keepMidConversationSystemLabel": "मध्य-वार्तालाप प्रणाली सन्देशहरू ठाउँमा राख्नुहोस्", "bulkPasteAdded": "{count, plural, one {1 कुञ्जी थपियो} other {# कुञ्जीहरू थपिए}}", "bulkPasteDuplicatesIgnored": "{count, plural, one {1 दोहोरिएको कुञ्जी छोडियो} other {# दोहोरिएका कुञ्जीहरू छोडिए}}", "bulkPasteHint": "प्रत्येक लाइनमा एउटा API कुञ्जी टाँस्नुहोस्। खाली लाइनहरू बेवास्ता गरिन्छन् र दोहोरिएका कुञ्जीहरू छोडिन्छन्।", diff --git a/src/i18n/messages/nl.json b/src/i18n/messages/nl.json index 0e8bc3660a6a..4eca4540d7da 100644 --- a/src/i18n/messages/nl.json +++ b/src/i18n/messages/nl.json @@ -6245,6 +6245,8 @@ "allowCodexPaidCreditsDescription": "Allow this account to use paid credits after its subscription quota reaches the cutoff. Requires available credits reported by OpenAI; may incur charges. Spark limits and upstream cooldowns still apply. Credit availability is checked even if quota preflight is disabled.", "disableCoolingDescription": "Sla de tijdelijke afkoelperiode over, zodat deze verbinding in aanmerking blijft komen, zelfs na herstelbare fouten (definitieve statussen zoals verbannen/verlopen zijn nog steeds van toepassing).", "disableCoolingLabel": "Afkoelperiode uitschakelen voor deze verbinding", + "keepMidConversationSystemDescription": "Laat systeemberichten die tijdens een gesprek zijn verzonden in de berichtenlijst staan in plaats van ze naar de systeemprompt op het hoogste niveau te verplaatsen, zodat de promptcache afwisselend blijft. Alleen inschakelen als deze upstream de rol \"systeem\" in berichten accepteert (anders antwoordt hij 400).", + "keepMidConversationSystemLabel": "Houd de berichten in het midden van het gesprekssysteem op hun plaats", "bulkPasteAdded": "{count, plural, one {1 sleutel toegevoegd} other {# sleutels toegevoegd}}", "bulkPasteDuplicatesIgnored": "{count, plural, one {1 duplicaat overgeslagen} other {# duplicaten overgeslagen}}", "bulkPasteHint": "Plak één API-sleutel per regel. Lege regels worden genegeerd en dubbele sleutels worden overgeslagen.", diff --git a/src/i18n/messages/no.json b/src/i18n/messages/no.json index 94d19c22a6cf..26edc5c5bbec 100644 --- a/src/i18n/messages/no.json +++ b/src/i18n/messages/no.json @@ -6245,6 +6245,8 @@ "allowCodexPaidCreditsDescription": "Allow this account to use paid credits after its subscription quota reaches the cutoff. Requires available credits reported by OpenAI; may incur charges. Spark limits and upstream cooldowns still apply. Credit availability is checked even if quota preflight is disabled.", "disableCoolingDescription": "Hopp over den forbigående nedkjølingen slik at denne tilkoblingen forblir kvalifisert selv etter gjenopprettelige feil (sluttilstander som utestengt/utløpt gjelder fortsatt).", "disableCoolingLabel": "Deaktiver nedkjøling for denne tilkoblingen", + "keepMidConversationSystemDescription": "La systemmeldinger som er sendt midt i samtalen inne i meldingslisten i stedet for å flytte dem inn i systemmeldingen på øverste nivå, slik at hurtigbufferen fortsetter å trykke på tvers av svinger. Aktiver bare hvis denne oppstrøms aksepterer rollen \"system\" i meldinger (ellers svarer den 400).", + "keepMidConversationSystemLabel": "Hold systemmeldinger midt i samtalen på plass", "bulkPasteAdded": "{count, plural, one {1 nøkkel lagt til} other {# nøkler lagt til}}", "bulkPasteDuplicatesIgnored": "{count, plural, one {1 duplikat hoppet over} other {# duplikater hoppet over}}", "bulkPasteHint": "Lim inn én API-nøkkel per linje. Tomme linjer ignoreres, og duplikatnøkler hoppes over.", diff --git a/src/i18n/messages/or.json b/src/i18n/messages/or.json index 81c9be4df297..5c53a7c10900 100644 --- a/src/i18n/messages/or.json +++ b/src/i18n/messages/or.json @@ -6245,6 +6245,8 @@ "allowCodexPaidCreditsDescription": "Allow this account to use paid credits after its subscription quota reaches the cutoff. Requires available credits reported by OpenAI; may incur charges. Spark limits and upstream cooldowns still apply. Credit availability is checked even if quota preflight is disabled.", "disableCoolingDescription": "ଅସ୍ଥାୟୀ କୁଲଡାଉନ୍କୁ ଏଡ଼ାଇ ଯାଆନ୍ତୁ, ଯାହାଦ୍ୱାରା ପୁନରୁଦ୍ଧାରଯୋଗ୍ୟ ତ୍ରୁଟି ପରେ ମଧ୍ୟ ଏହି ସଂଯୋଗ ଯୋଗ୍ୟ ରହିବ (ନିଷିଦ୍ଧ/ମିଆଦ ସମାପ୍ତ ପରି ଅନ୍ତିମ ଅବସ୍ଥାଗୁଡ଼ିକ ତଥାପି ଲାଗୁ ହେବ)।", "disableCoolingLabel": "ଏହି ସଂଯୋଗ ପାଇଁ କୁଲଡାଉନ୍ ଅକ୍ଷମ କରନ୍ତୁ", + "keepMidConversationSystemDescription": "ସିଷ୍ଟମ ମେସେଜଗୁଡ଼ିକୁ ଟପ୍-ଲେଭଲ୍ ସିଷ୍ଟମ୍ ପ୍ରମ୍ପ୍ଟକୁ ସ୍ଥାନାନ୍ତର କରିବା ପରିବର୍ତ୍ତେ ବାର୍ତ୍ତା ତାଲିକାରେ ମଧ୍ୟ-ବାର୍ତ୍ତାଳାପ ପଠାନ୍ତୁ, ତେଣୁ ପ୍ରମ୍ପ୍ଟ କ୍ୟାଚ୍ ଟର୍ନ୍ ଉପରେ ଧକ୍କା ଦେଇଥାଏ \\| ଯଦି ଏହି ଅପଷ୍ଟ୍ରିମ୍ ବାର୍ତ୍ତା ଭିତରେ ଭୂମିକା \"ସିଷ୍ଟମ୍\" ଗ୍ରହଣ କରେ ତେବେ ସକ୍ଷମ କର (ଅନ୍ୟଥା ଏହା 400 ର ଉତ୍ତର ଦେଇଥାଏ) \\|", + "keepMidConversationSystemLabel": "ମଧ୍ୟ-ବାର୍ତ୍ତାଳାପ ସିଷ୍ଟମ ସନ୍ଦେଶଗୁଡ଼ିକୁ ସ୍ଥାନରେ ରଖନ୍ତୁ \\|", "bulkPasteAdded": "{count, plural, one {1ଟି କୀ ଯୋଡ଼ାଗଲା} other {#ଟି କୀ ଯୋଡ଼ାଗଲା}}", "bulkPasteDuplicatesIgnored": "{count, plural, one {1ଟି ନକଲ କୀ ଏଡ଼ାଇ ଦିଆଗଲା} other {#ଟି ନକଲ କୀ ଏଡ଼ାଇ ଦିଆଗଲା}}", "bulkPasteHint": "ପ୍ରତ୍ୟେକ ଧାଡ଼ିରେ ଗୋଟିଏ API କୀ ପେଷ୍ଟ କରନ୍ତୁ। ଖାଲି ଧାଡ଼ିଗୁଡ଼ିକୁ ଅଣଦେଖା କରାଯାଏ ଏବଂ ନକଲ କୀଗୁଡ଼ିକୁ ଏଡ଼ାଇ ଦିଆଯାଏ।", diff --git a/src/i18n/messages/pa.json b/src/i18n/messages/pa.json index 23db99b2b88b..42521049a46c 100644 --- a/src/i18n/messages/pa.json +++ b/src/i18n/messages/pa.json @@ -6322,6 +6322,8 @@ "allowCodexPaidCreditsDescription": "Allow this account to use paid credits after its subscription quota reaches the cutoff. Requires available credits reported by OpenAI; may incur charges. Spark limits and upstream cooldowns still apply. Credit availability is checked even if quota preflight is disabled.", "disableCoolingDescription": "ਅਸਥਾਈ ਕੂਲਡਾਊਨ ਨੂੰ ਛੱਡੋ ਤਾਂ ਜੋ ਸੁਧਾਰੀ ਜਾ ਸਕਣ ਵਾਲੀਆਂ ਗਲਤੀਆਂ ਤੋਂ ਬਾਅਦ ਵੀ ਇਹ ਕਨੈਕਸ਼ਨ ਯੋਗ ਰਹੇ (ਪਾਬੰਦੀਸ਼ੁਦਾ/ਮਿਆਦ ਪੁੱਗਣ ਵਰਗੀਆਂ ਅੰਤਿਮ ਸਥਿਤੀਆਂ ਫਿਰ ਵੀ ਲਾਗੂ ਹੁੰਦੀਆਂ ਹਨ)।", "disableCoolingLabel": "ਇਸ ਕਨੈਕਸ਼ਨ ਲਈ ਕੂਲਡਾਊਨ ਅਯੋਗ ਕਰੋ", + "keepMidConversationSystemDescription": "ਸਿਸਟਮ ਸੁਨੇਹਿਆਂ ਨੂੰ ਸਿਖਰ-ਪੱਧਰ ਦੇ ਸਿਸਟਮ ਪ੍ਰੋਂਪਟ ਵਿੱਚ ਲਿਜਾਣ ਦੀ ਬਜਾਏ ਸੁਨੇਹਾ ਸੂਚੀ ਵਿੱਚ ਮੱਧ-ਗੱਲਬਾਤ ਭੇਜੋ, ਇਸਲਈ ਪ੍ਰੋਂਪਟ ਕੈਸ਼ ਵਾਰੀ-ਵਾਰੀ ਹਿੱਟ ਕਰਦਾ ਰਹਿੰਦਾ ਹੈ। ਸਿਰਫ ਤਾਂ ਹੀ ਸਮਰੱਥ ਕਰੋ ਜੇਕਰ ਇਹ ਅੱਪਸਟਰੀਮ ਸੁਨੇਹਿਆਂ ਦੇ ਅੰਦਰ ਭੂਮਿਕਾ \"ਸਿਸਟਮ\" ਨੂੰ ਸਵੀਕਾਰ ਕਰਦਾ ਹੈ (ਨਹੀਂ ਤਾਂ ਇਹ 400 ਦਾ ਜਵਾਬ ਦਿੰਦਾ ਹੈ)।", + "keepMidConversationSystemLabel": "ਮੱਧ-ਗੱਲਬਾਤ ਸਿਸਟਮ ਸੰਦੇਸ਼ਾਂ ਨੂੰ ਥਾਂ 'ਤੇ ਰੱਖੋ", "bulkPasteAdded": "{count, plural, one {1 ਕੁੰਜੀ ਜੋੜੀ ਗਈ} other {# ਕੁੰਜੀਆਂ ਜੋੜੀਆਂ ਗਈਆਂ}}", "bulkPasteDuplicatesIgnored": "{count, plural, one {1 ਡੁਪਲੀਕੇਟ ਛੱਡਿਆ ਗਿਆ} other {# ਡੁਪਲੀਕੇਟ ਛੱਡੇ ਗਏ}}", "bulkPasteHint": "ਹਰ ਲਾਈਨ ਵਿੱਚ ਇੱਕ API ਕੁੰਜੀ ਪੇਸਟ ਕਰੋ। ਖਾਲੀ ਲਾਈਨਾਂ ਨੂੰ ਅਣਡਿੱਠਾ ਕੀਤਾ ਜਾਂਦਾ ਹੈ ਅਤੇ ਡੁਪਲੀਕੇਟ ਕੁੰਜੀਆਂ ਛੱਡ ਦਿੱਤੀਆਂ ਜਾਂਦੀਆਂ ਹਨ।", diff --git a/src/i18n/messages/phi.json b/src/i18n/messages/phi.json index 7c0a660bf961..86fc63233ed1 100644 --- a/src/i18n/messages/phi.json +++ b/src/i18n/messages/phi.json @@ -6245,6 +6245,8 @@ "allowCodexPaidCreditsDescription": "Payagan ang account na ito na gumamit ng mga bayad na kredito kapag umabot na sa itinakdang cutoff ang quota ng subscription nito. Kailangan ng mga magagamit na kredito ayon sa ulat ng OpenAI; maaaring may singil. Nalalapat pa rin ang mga limitasyon ng Spark at ang mga cooldown ng upstream. Sinusuri ang pagkakaroon ng mga kredito kahit hindi pinagana ang paunang pagsusuri ng quota, maliban kung ganap na naka-off ang lokal na pag-filter ng quota (parehong hindi pinagana ang paunang pagsusuri ng quota at ang patakaran sa limitasyon).", "disableCoolingDescription": "Laktawan ang transient cooldown para manatiling kwalipikado ang koneksyong ito kahit pagkatapos ng mga error na maaaring ma-recover (nalalapat pa rin ang mga terminal state tulad ng banned/expired).", "disableCoolingLabel": "I-disable ang cooldown para sa koneksyong ito", + "keepMidConversationSystemDescription": "Iwanan ang mga mensahe ng system na ipinadala sa kalagitnaan ng pag-uusap sa loob ng listahan ng mensahe sa halip na ilipat ang mga ito sa top-level na prompt ng system, kaya ang prompt cache ay patuloy na tumatama sa mga liko. Paganahin lamang kung ang upstream na ito ay tumatanggap ng papel na \"system\" sa loob ng mga mensahe (kung hindi, ito ay sumasagot ng 400).", + "keepMidConversationSystemLabel": "Panatilihing nasa lugar ang mga mensahe ng system sa kalagitnaan ng pag-uusap", "bulkPasteAdded": "{count, plural, one {1 key ang naidagdag} other {# key ang naidagdag}}", "bulkPasteDuplicatesIgnored": "{count, plural, one {1 duplicate ang nilaktawan} other {# duplicate ang nilaktawan}}", "bulkPasteHint": "Mag-paste ng isang API key sa bawat linya. Binabalewala ang mga walang lamang linya at nilalaktawan ang mga duplicate na key.", diff --git a/src/i18n/messages/pl.json b/src/i18n/messages/pl.json index d749e150327d..778d36582dbb 100644 --- a/src/i18n/messages/pl.json +++ b/src/i18n/messages/pl.json @@ -6245,6 +6245,8 @@ "allowCodexPaidCreditsDescription": "Allow this account to use paid credits after its subscription quota reaches the cutoff. Requires available credits reported by OpenAI; may incur charges. Spark limits and upstream cooldowns still apply. Credit availability is checked even if quota preflight is disabled.", "disableCoolingDescription": "Pomiń tymczasowy cooldown, aby to połączenie pozostało kwalifikowalne nawet po błędach podlegających naprawie (stany terminalne, takie jak banned/expired, nadal obowiązują).", "disableCoolingLabel": "Wyłącz cooldown dla tego połączenia", + "keepMidConversationSystemDescription": "Pozostaw wiadomości systemowe wysłane w trakcie rozmowy na liście wiadomości, zamiast przenosić je do monitu systemowego najwyższego poziomu, dzięki czemu pamięć podręczna podpowiedzi będzie trafiać na zmianę. Włącz tylko wtedy, gdy ten upstream akceptuje rolę „systemu” w wiadomościach (w przeciwnym razie odpowiada 400).", + "keepMidConversationSystemLabel": "Przechowuj wiadomości systemowe w trakcie rozmowy na swoim miejscu", "bulkPasteAdded": "{count, plural, one {Dodano 1 klucz} other {Dodano # kluczy}}", "bulkPasteDuplicatesIgnored": "{count, plural, one {Pominięto 1 duplikat} other {Pominięto # duplikatów}}", "bulkPasteHint": "Wklej jeden klucz API w wierszu. Puste linie są ignorowane, a zduplikowane klucze są pomijane.", diff --git a/src/i18n/messages/pt-BR.json b/src/i18n/messages/pt-BR.json index 9587bd983560..8d064ea18541 100644 --- a/src/i18n/messages/pt-BR.json +++ b/src/i18n/messages/pt-BR.json @@ -6245,6 +6245,8 @@ "allowCodexPaidCreditsDescription": "Permite que esta conta use créditos pagos depois que a cota da assinatura atingir o limite de corte. Requer créditos disponíveis informados pela OpenAI; pode gerar cobranças. Os limites do Spark e os cooldowns do upstream continuam se aplicando. A disponibilidade de créditos é verificada mesmo se o preflight de cota estiver desabilitado.", "disableCoolingDescription": "Ignore o tempo de espera transitório para que esta conexão permaneça elegível mesmo após erros recuperáveis (estados terminais como banido/expirado ainda se aplicam).", "disableCoolingLabel": "Desativar o tempo de espera para esta conexão", + "keepMidConversationSystemDescription": "Deixe as mensagens do sistema enviadas no meio da conversa dentro da lista de mensagens em vez de movê-las para o prompt de nível superior do sistema, para que o cache de prompts continue atingindo os turnos. Habilite apenas se este upstream aceitar a função \"sistema\" dentro das mensagens (caso contrário, ele responderá 400).", + "keepMidConversationSystemLabel": "Mantenha as mensagens do sistema no meio da conversa em vigor", "bulkPasteAdded": "{count, plural, one {1 chave adicionada} other {# chaves adicionadas}}", "bulkPasteDuplicatesIgnored": "{count, plural, one {1 duplicata ignorada} other {# duplicatas ignoradas}}", "bulkPasteHint": "Cole uma chave de API por linha. Linhas vazias são ignoradas e chaves duplicadas são puladas.", diff --git a/src/i18n/messages/pt.json b/src/i18n/messages/pt.json index 756c2243d0ff..a772d0c97773 100644 --- a/src/i18n/messages/pt.json +++ b/src/i18n/messages/pt.json @@ -6322,6 +6322,8 @@ "allowCodexPaidCreditsDescription": "Allow this account to use paid credits after its subscription quota reaches the cutoff. Requires available credits reported by OpenAI; may incur charges. Spark limits and upstream cooldowns still apply. Credit availability is checked even if quota preflight is disabled.", "disableCoolingDescription": "Ignorar o cooldown transitório para que esta ligação permaneça elegível mesmo após erros recuperáveis (estados terminais como banido/expirado continuam a aplicar-se).", "disableCoolingLabel": "Desativar cooldown para esta ligação", + "keepMidConversationSystemDescription": "Deixe as mensagens do sistema enviadas no meio da conversa dentro da lista de mensagens em vez de as mover para o prompt de nível superior do sistema, para que a cache de prompts continue a atingir os turnos. Active apenas se este upstream aceitar a função \"system\" dentro das mensagens (caso contrário, responderá 400).", + "keepMidConversationSystemLabel": "Mantenha as mensagens do sistema no meio da conversação em vigor", "bulkPasteAdded": "{count, plural, one {1 chave adicionada} other {# chaves adicionadas}}", "bulkPasteDuplicatesIgnored": "{count, plural, one {1 duplicado ignorado} other {# duplicados ignorados}}", "bulkPasteHint": "Cola uma chave de API por linha. Linhas vazias são ignoradas e chaves duplicadas são ignoradas.", diff --git a/src/i18n/messages/ro.json b/src/i18n/messages/ro.json index 3020c6ade971..7fe9461eee6d 100644 --- a/src/i18n/messages/ro.json +++ b/src/i18n/messages/ro.json @@ -6245,6 +6245,8 @@ "allowCodexPaidCreditsDescription": "Allow this account to use paid credits after its subscription quota reaches the cutoff. Requires available credits reported by OpenAI; may incur charges. Spark limits and upstream cooldowns still apply. Credit availability is checked even if quota preflight is disabled.", "disableCoolingDescription": "Omite perioada de răcire tranzitorie, astfel încât această conexiune să rămână eligibilă chiar și după erori recuperabile (stările terminale precum blocat/expirat se aplică în continuare).", "disableCoolingLabel": "Dezactivează perioada de răcire pentru această conexiune", + "keepMidConversationSystemDescription": "Lăsați mesajele de sistem trimise la mijlocul conversației în lista de mesaje, în loc să le mutați în promptul de sistem de nivel superior, astfel încât memoria cache a prompturilor continuă să lovească în rânduri. Activați numai dacă acest sistem în amonte acceptă rolul „sistem” în interiorul mesajelor (în caz contrar, răspunde la 400).", + "keepMidConversationSystemLabel": "Păstrați mesajele sistemului de la mijlocul conversației în loc", "bulkPasteAdded": "{count, plural, one {1 cheie adăugată} other {# chei adăugate}}", "bulkPasteDuplicatesIgnored": "{count, plural, one {1 duplicat omis} other {# duplicate omise}}", "bulkPasteHint": "Lipește câte o cheie API pe fiecare rând. Rândurile goale sunt ignorate, iar cheile duplicate sunt omise.", diff --git a/src/i18n/messages/si.json b/src/i18n/messages/si.json index bd9095376cc0..ece32d688a4f 100644 --- a/src/i18n/messages/si.json +++ b/src/i18n/messages/si.json @@ -6245,6 +6245,8 @@ "allowCodexPaidCreditsDescription": "Allow this account to use paid credits after its subscription quota reaches the cutoff. Requires available credits reported by OpenAI; may incur charges. Spark limits and upstream cooldowns still apply. Credit availability is checked even if quota preflight is disabled.", "disableCoolingDescription": "තාවකාලික විරාම කාලය මඟ හරින්න, එවිට ප්රතිසාධනය කළ හැකි දෝෂවලින් පසුවත් මෙම සම්බන්ධතාව සුදුසු තත්ත්වයේ පවතී (තහනම් කළ/කල් ඉකුත් වූ වැනි අවසාන තත්ත්වයන් තවමත් අදාළ වේ).", "disableCoolingLabel": "මෙම සම්බන්ධතාව සඳහා විරාම කාලය අක්රිය කරන්න", + "keepMidConversationSystemDescription": "පද්ධති පණිවිඩ ඉහළ මට්ටමේ පද්ධති ප්‍රේරකයට ගෙන යාම වෙනුවට පණිවිඩ ලැයිස්තුව තුළ මැද සංවාදයක් තබන්න, එම නිසා ක්ෂණික හැඹිලිය හැරීම් හරහා පහර දෙයි. මෙම upstream පණිවිඩ තුළ \"පද්ධතිය\" භූමිකාව පිළිගන්නේ නම් පමණක් සබල කරන්න (එසේ නොමැති නම් එය 400 ට පිළිතුරු දෙයි).", + "keepMidConversationSystemLabel": "මධ්‍ය සංවාද පද්ධති පණිවිඩ ස්ථානගතව තබා ගන්න", "bulkPasteAdded": "{count, plural, one {යතුර 1ක් එක් කරන ලදී} other {යතුරු #ක් එක් කරන ලදී}}", "bulkPasteDuplicatesIgnored": "{count, plural, one {අනුපිටපත 1ක් මඟ හරින ලදී} other {අනුපිටපත් #ක් මඟ හරින ලදී}}", "bulkPasteHint": "එක් පේළියකට එක් API යතුරක් අලවන්න. හිස් පේළි නොසලකා හරින අතර අනුපිටපත් යතුරු මඟ හරිනු ලැබේ.", diff --git a/src/i18n/messages/sk.json b/src/i18n/messages/sk.json index 5b77a3735129..a5fe1f5be64f 100644 --- a/src/i18n/messages/sk.json +++ b/src/i18n/messages/sk.json @@ -6245,6 +6245,8 @@ "allowCodexPaidCreditsDescription": "Allow this account to use paid credits after its subscription quota reaches the cutoff. Requires available credits reported by OpenAI; may incur charges. Spark limits and upstream cooldowns still apply. Credit availability is checked even if quota preflight is disabled.", "disableCoolingDescription": "Preskočiť prechodný cooldown (doba chladnutia), aby toto pripojenie zostalo oprávnené aj po odstrániteľných chybách (konečné stavy ako zablokované/vypršané stále platia).", "disableCoolingLabel": "Zakázať cooldown pre toto pripojenie", + "keepMidConversationSystemDescription": "Systémové správy odoslané počas konverzácie ponechajte v zozname správ namiesto toho, aby ste ich presunuli do systémového príkazu najvyššej úrovne, takže vyrovnávacia pamäť výziev neustále naráža na všetky ťahy. Povoliť iba vtedy, ak tento upstream akceptuje rolu \"systém\" vo vnútri správ (inak odpovie 400).", + "keepMidConversationSystemLabel": "Udržujte systémové správy uprostred konverzácie na mieste", "bulkPasteAdded": "{count, plural, one {Pridaný 1 kľúč} few {Pridané # kľúče} other {Pridaných # kľúčov}}", "bulkPasteDuplicatesIgnored": "{count, plural, one {Preskočený 1 duplikát} few {Preskočené # duplikáty} other {Preskočených # duplikátov}}", "bulkPasteHint": "Vložte jeden kľúč API na každý riadok. Prázdne riadky sa ignorujú a duplicitné kľúče sa preskočia.", diff --git a/src/i18n/messages/sl.json b/src/i18n/messages/sl.json index db0d4f2c4e06..dc7b92c6dbed 100644 --- a/src/i18n/messages/sl.json +++ b/src/i18n/messages/sl.json @@ -6245,6 +6245,8 @@ "allowCodexPaidCreditsDescription": "Allow this account to use paid credits after its subscription quota reaches the cutoff. Requires available credits reported by OpenAI; may incur charges. Spark limits and upstream cooldowns still apply. Credit availability is checked even if quota preflight is disabled.", "disableCoolingDescription": "Preskočite prehodno obdobje ohlajanja, da ta povezava ostane na voljo tudi po odpravljivih napakah (končna stanja, kot sta prepovedana ali potekla povezava, še vedno veljajo).", "disableCoolingLabel": "Onemogoči ohlajanje za to povezavo", + "keepMidConversationSystemDescription": "Sistemska sporočila, poslana med pogovorom, pustite znotraj seznama sporočil, namesto da jih premaknete v sistemski poziv na najvišji ravni, tako da se predpomnilnik pozivov vedno znova pojavlja. Omogoči samo, če ta navzgor sprejme vlogo \"sistem\" znotraj sporočil (sicer odgovori 400).", + "keepMidConversationSystemLabel": "Ohranite sistemska sporočila med pogovorom", "bulkPasteAdded": "{count, plural, one {Dodan 1 ključ} two {Dodana # ključa} few {Dodani # ključi} other {Dodanih # ključev}}", "bulkPasteDuplicatesIgnored": "{count, plural, one {Preskočen 1 podvojen ključ} two {Preskočena # podvojena ključa} few {Preskočeni # podvojeni ključi} other {Preskočenih # podvojenih ključev}}", "bulkPasteHint": "Prilepite en ključ API na vrstico. Prazne vrstice bodo prezrte, podvojeni ključi pa preskočeni.", diff --git a/src/i18n/messages/sr.json b/src/i18n/messages/sr.json index 9f833d482ff5..34bb2c3b59fa 100644 --- a/src/i18n/messages/sr.json +++ b/src/i18n/messages/sr.json @@ -6245,6 +6245,8 @@ "allowCodexPaidCreditsDescription": "Allow this account to use paid credits after its subscription quota reaches the cutoff. Requires available credits reported by OpenAI; may incur charges. Spark limits and upstream cooldowns still apply. Credit availability is checked even if quota preflight is disabled.", "disableCoolingDescription": "Прескочите привремени период чекања како би ова веза остала доступна чак и након грешака од којих је могућ опоравак (терминална стања попут забране или истека и даље се примењују).", "disableCoolingLabel": "Онемогући период чекања за ову везу", + "keepMidConversationSystemDescription": "Оставите системске поруке послате усред разговора унутар листе порука уместо да их премештате у системски промпт највишег нивоа, тако да кеш промпта наставља да се креће у низу. Омогући само ако овај упстреам прихвата улогу „система“ унутар порука (у супротном одговара на 400).", + "keepMidConversationSystemLabel": "Држите системске поруке усред разговора на месту", "bulkPasteAdded": "{count, plural, one {Додат је 1 кључ} other {Додато је # кључева}}", "bulkPasteDuplicatesIgnored": "{count, plural, one {1 дупликат прескочен} other {# дупликата прескочено}}", "bulkPasteHint": "Налепите по један API кључ у сваком реду. Празни редови се игноришу, а дупликати кључева се прескачу.", diff --git a/src/i18n/messages/sv.json b/src/i18n/messages/sv.json index d97fda53c5bf..6a311fdfc363 100644 --- a/src/i18n/messages/sv.json +++ b/src/i18n/messages/sv.json @@ -6245,6 +6245,8 @@ "allowCodexPaidCreditsDescription": "Allow this account to use paid credits after its subscription quota reaches the cutoff. Requires available credits reported by OpenAI; may incur charges. Spark limits and upstream cooldowns still apply. Credit availability is checked even if quota preflight is disabled.", "disableCoolingDescription": "Hoppa över den tillfälliga cooldown-perioden så att denna anslutning förblir tillgänglig även efter återställningsbara fel (slutgiltiga tillstånd som banned/expired gäller fortfarande).", "disableCoolingLabel": "Inaktivera cooldown för denna anslutning", + "keepMidConversationSystemDescription": "Lämna systemmeddelanden som skickas mitt i konversationen i meddelandelistan istället för att flytta dem till systemprompten på översta nivån, så promptcachen fortsätter att slå över svängarna. Aktivera endast om denna uppströms accepterar rollen \"system\" i meddelanden (annars svarar den 400).", + "keepMidConversationSystemLabel": "Håll meddelanden i mitten av konversationen på plats", "bulkPasteAdded": "{count, plural, one {1 nyckel tillagd} other {# nycklar tillagda}}", "bulkPasteDuplicatesIgnored": "{count, plural, one {1 dubblett överhoppad} other {# dubbletter överhoppade}}", "bulkPasteHint": "Klistra in en API-nyckel per rad. Tomma rader ignoreras och dubblettnycklar hoppas över.", diff --git a/src/i18n/messages/sw.json b/src/i18n/messages/sw.json index e53c9b986da8..ca6f94a6fef4 100644 --- a/src/i18n/messages/sw.json +++ b/src/i18n/messages/sw.json @@ -6245,6 +6245,8 @@ "allowCodexPaidCreditsDescription": "Allow this account to use paid credits after its subscription quota reaches the cutoff. Requires available credits reported by OpenAI; may incur charges. Spark limits and upstream cooldowns still apply. Credit availability is checked even if quota preflight is disabled.", "disableCoolingDescription": "Ruka muda wa utulivu wa mpito ili muunganisho huu uendelee kustahiki hata baada ya hitilafu zinazoweza kurekebishwa (hali za mwisho kama vile zilizopigwa marufuku/zilizopitwa na wakati bado zinatumika).", "disableCoolingLabel": "Zima muda wa utulivu kwa muunganisho huu", + "keepMidConversationSystemDescription": "Acha ujumbe wa mfumo uliotumwa katikati ya mazungumzo ndani ya orodha ya ujumbe badala ya kuwahamisha hadi kwenye kidokezo cha mfumo wa kiwango cha juu, kwa hivyo akiba ya papo hapo inaendelea kupiga zamu. Washa tu ikiwa mkondo huu wa juu unakubali jukumu la \"mfumo\" ndani ya ujumbe (vinginevyo hujibu 400).", + "keepMidConversationSystemLabel": "Weka ujumbe wa mfumo wa mazungumzo katikati", "bulkPasteAdded": "{count, plural, one {Ufunguo 1 umeongezwa} other {Funguo # zimeongezwa}}", "bulkPasteDuplicatesIgnored": "{count, plural, one {Nakala rudufu 1 imerukwa} other {Nakala rudufu # zimerukwa}}", "bulkPasteHint": "Bandika ufunguo mmoja wa API kwa kila mstari. Mistari tupu hupuuzwa na funguo zilizorudiwa hurukwa.", diff --git a/src/i18n/messages/ta.json b/src/i18n/messages/ta.json index a50a4a982eb1..301a6c02a472 100644 --- a/src/i18n/messages/ta.json +++ b/src/i18n/messages/ta.json @@ -6245,6 +6245,8 @@ "allowCodexPaidCreditsDescription": "Allow this account to use paid credits after its subscription quota reaches the cutoff. Requires available credits reported by OpenAI; may incur charges. Spark limits and upstream cooldowns still apply. Credit availability is checked even if quota preflight is disabled.", "disableCoolingDescription": "தற்காலிக கூல்டவுனைத் தவிர்க்கவும், இதனால் மீட்டெடுக்கக்கூடிய பிழைகளுக்குப் பிறகும் இந்த இணைப்பு தகுதியுடையதாக இருக்கும் (தடைசெய்யப்பட்ட/காலாவதியான போன்ற இறுதி நிலைகள் இன்னும் பொருந்தும்).", "disableCoolingLabel": "இந்த இணைப்பிற்கான கூல்டவுனை முடக்கவும்", + "keepMidConversationSystemDescription": "சிஸ்டம் மெசேஜ்களை மேல்-நிலை சிஸ்டம் ப்ராம்ட்க்கு நகர்த்துவதற்குப் பதிலாக, செய்திப் பட்டியலின் நடுப்பகுதியில் உரையாடலை விட்டு விடுங்கள், எனவே ப்ராம்ட் கேச் திருப்பங்களைத் தாக்கும். இந்த அப்ஸ்ட்ரீம் செய்திகளுக்குள் \"சிஸ்டம்\" பாத்திரத்தை ஏற்றுக்கொண்டால் மட்டுமே இயக்கு (இல்லையெனில் அது 400க்கு பதிலளிக்கும்).", + "keepMidConversationSystemLabel": "இடை-உரையாடல் அமைப்பு செய்திகளை இடத்தில் வைத்திருங்கள்", "bulkPasteAdded": "{count, plural, one {1 விசை சேர்க்கப்பட்டது} other {# விசைகள் சேர்க்கப்பட்டன}}", "bulkPasteDuplicatesIgnored": "{count, plural, one {1 நகல் தவிர்க்கப்பட்டது} other {# நகல்கள் தவிர்க்கப்பட்டன}}", "bulkPasteHint": "ஒவ்வொரு வரியிலும் ஓர் API விசையை ஒட்டவும். காலியான வரிகள் புறக்கணிக்கப்படும்; நகல் விசைகள் தவிர்க்கப்படும்.", diff --git a/src/i18n/messages/te.json b/src/i18n/messages/te.json index dc7a6d8add33..6ce77eb0d50a 100644 --- a/src/i18n/messages/te.json +++ b/src/i18n/messages/te.json @@ -6245,6 +6245,8 @@ "allowCodexPaidCreditsDescription": "Allow this account to use paid credits after its subscription quota reaches the cutoff. Requires available credits reported by OpenAI; may incur charges. Spark limits and upstream cooldowns still apply. Credit availability is checked even if quota preflight is disabled.", "disableCoolingDescription": "తాత్కాలిక కూల్‌డౌన్‌ను దాటవేయండి, తద్వారా రికవర్ చేయగల ఎర్రర్‌ల తర్వాత కూడా ఈ కనెక్షన్ అర్హత కలిగి ఉంటుంది (banned/expired వంటి టెర్మినల్ స్థితులు ఇప్పటికీ వర్తిస్తాయి).", "disableCoolingLabel": "ఈ కనెక్షన్ కోసం కూల్‌డౌన్‌ను నిలిపివేయండి", + "keepMidConversationSystemDescription": "పంపిన సిస్టమ్ సందేశాలను టాప్-లెవల్ సిస్టమ్ ప్రాంప్ట్లోకి తరలించే బదులు మెసేజ్ లిస్ట్లో మధ్య సంభాషణను వదిలివేయండి, కాబట్టి ప్రాంప్ట్ కాష్ మలుపులు తిరుగుతూ ఉంటుంది. ఈ అప్స్ట్రీమ్ సందేశాల లోపల \"సిస్టమ్\" పాత్రను అంగీకరిస్తే మాత్రమే ప్రారంభించండి (లేకపోతే ఇది 400కి సమాధానం ఇస్తుంది).", + "keepMidConversationSystemLabel": "మధ్య-సంభాషణ సిస్టమ్ సందేశాలను స్థానంలో ఉంచండి", "bulkPasteAdded": "{count, plural, one {1 కీ జోడించబడింది} other {# కీలు జోడించబడ్డాయి}}", "bulkPasteDuplicatesIgnored": "{count, plural, one {1 నకిలీ కీ దాటవేయబడింది} other {# నకిలీ కీలు దాటవేయబడ్డాయి}}", "bulkPasteHint": "ఒక్కో పంక్తిలో ఒక API కీని పేస్ట్ చేయండి. ఖాళీ పంక్తులు విస్మరించబడతాయి మరియు నకిలీ కీలు దాటవేయబడతాయి.", diff --git a/src/i18n/messages/th.json b/src/i18n/messages/th.json index 9c13e2346b22..3d1eb5df78fa 100644 --- a/src/i18n/messages/th.json +++ b/src/i18n/messages/th.json @@ -6245,6 +6245,8 @@ "allowCodexPaidCreditsDescription": "Allow this account to use paid credits after its subscription quota reaches the cutoff. Requires available credits reported by OpenAI; may incur charges. Spark limits and upstream cooldowns still apply. Credit availability is checked even if quota preflight is disabled.", "disableCoolingDescription": "ข้ามคูลดาวน์ชั่วคราวเพื่อให้การเชื่อมต่อนี้ยังคงมีสิทธิ์ใช้งานได้แม้หลังจากเกิดข้อผิดพลาดที่กู้คืนได้ (สถานะสิ้นสุด เช่น ถูกแบน/หมดอายุ ยังคงมีผลอยู่)", "disableCoolingLabel": "ปิดใช้งานคูลดาวน์สำหรับการเชื่อมต่อนี้", + "keepMidConversationSystemDescription": "ปล่อยให้ข้อความระบบที่ส่งระหว่างการสนทนาอยู่ในรายการข้อความ แทนที่จะย้ายไปยังพรอมต์ระบบระดับบนสุด ดังนั้นแคชของพรอมต์จะยังคงส่งข้ามผลัดกัน เปิดใช้งานเฉพาะเมื่ออัปสตรีมนี้ยอมรับบทบาท \"ระบบ\" ภายในข้อความ (ไม่เช่นนั้นจะตอบ 400)", + "keepMidConversationSystemLabel": "เก็บข้อความของระบบการสนทนาระหว่างสนทนาไว้", "bulkPasteAdded": "{count, plural, one {เพิ่มคีย์แล้ว 1 คีย์} other {เพิ่มคีย์แล้ว # คีย์}}", "bulkPasteDuplicatesIgnored": "{count, plural, one {ข้ามคีย์ที่ซ้ำ 1 คีย์} other {ข้ามคีย์ที่ซ้ำ # คีย์}}", "bulkPasteHint": "วาง API key หนึ่งคีย์ต่อบรรทัด ระบบจะละเว้นบรรทัดว่างและข้ามคีย์ที่ซ้ำกัน", diff --git a/src/i18n/messages/tr.json b/src/i18n/messages/tr.json index b3abdb78ad68..177ec5c56f43 100644 --- a/src/i18n/messages/tr.json +++ b/src/i18n/messages/tr.json @@ -6245,6 +6245,8 @@ "allowCodexPaidCreditsDescription": "Allow this account to use paid credits after its subscription quota reaches the cutoff. Requires available credits reported by OpenAI; may incur charges. Spark limits and upstream cooldowns still apply. Credit availability is checked even if quota preflight is disabled.", "disableCoolingDescription": "Geçici bekleme süresini atlayın; böylece bu bağlantı kurtarılabilir hatalardan sonra bile uygun kalmaya devam eder (yasaklandı/süresi doldu gibi nihai durumlar yine de geçerlidir).", "disableCoolingLabel": "Bu bağlantı için bekleme süresini devre dışı bırak", + "keepMidConversationSystemDescription": "Konuşmanın ortasında gönderilen sistem mesajlarını üst düzey sistem istemine taşımak yerine mesaj listesinin içinde bırakın, böylece bilgi istemi önbelleği sırayla etkin olmaya devam eder. Yalnızca bu yukarı akış, mesajların içindeki \"sistem\" rolünü kabul ederse etkinleştirin (aksi takdirde 400 yanıtını verir).", + "keepMidConversationSystemLabel": "Konuşma ortasında sistem mesajlarını yerinde tutun", "bulkPasteAdded": "{count, plural, one {1 anahtar eklendi} other {# anahtar eklendi}}", "bulkPasteDuplicatesIgnored": "{count, plural, one {1 yinelenen anahtar atlandı} other {# yinelenen anahtar atlandı}}", "bulkPasteHint": "Her satıra bir API anahtarı yapıştırın. Boş satırlar yok sayılır ve yinelenen anahtarlar atlanır.", diff --git a/src/i18n/messages/uk-UA.json b/src/i18n/messages/uk-UA.json index 9ecffae0c5e7..8f0bf8c84839 100644 --- a/src/i18n/messages/uk-UA.json +++ b/src/i18n/messages/uk-UA.json @@ -6245,6 +6245,8 @@ "allowCodexPaidCreditsDescription": "Allow this account to use paid credits after its subscription quota reaches the cutoff. Requires available credits reported by OpenAI; may incur charges. Spark limits and upstream cooldowns still apply. Credit availability is checked even if quota preflight is disabled.", "disableCoolingDescription": "Пропускати тимчасовий період очікування, щоб це підключення залишалося доступним навіть після відновлюваних помилок (термінальні стани, як-от banned/expired, все одно застосовуються).", "disableCoolingLabel": "Вимкнути період очікування для цього підключення", + "keepMidConversationSystemDescription": "Залиште системні повідомлення, надіслані під час розмови, у списку повідомлень замість того, щоб переміщувати їх у системну підказку верхнього рівня, щоб кеш підказок постійно потрапляв по черзі. Увімкніть, лише якщо цей вихідний поток приймає роль \"система\" всередині повідомлень (інакше він відповідає 400).", + "keepMidConversationSystemLabel": "Зберігайте системні повідомлення в середині розмови", "bulkPasteAdded": "{count, plural, one {Додано 1 ключ} few {Додано # ключі} many {Додано # ключів} other {Додано # ключа}}", "bulkPasteDuplicatesIgnored": "{count, plural, one {Пропущено 1 дублікат} few {Пропущено # дублікати} many {Пропущено # дублікатів} other {Пропущено # дубліката}}", "bulkPasteHint": "Вставте по одному API-ключу в кожному рядку. Порожні рядки ігноруються, а дублікати ключів пропускаються.", diff --git a/src/i18n/messages/ur.json b/src/i18n/messages/ur.json index faaaa0216b11..11fd2c4b6c01 100644 --- a/src/i18n/messages/ur.json +++ b/src/i18n/messages/ur.json @@ -6245,6 +6245,8 @@ "allowCodexPaidCreditsDescription": "Allow this account to use paid credits after its subscription quota reaches the cutoff. Requires available credits reported by OpenAI; may incur charges. Spark limits and upstream cooldowns still apply. Credit availability is checked even if quota preflight is disabled.", "disableCoolingDescription": "عارضی کول ڈاؤن کو چھوڑیں تاکہ یہ کنکشن قابلِ بحالی غلطیوں کے بعد بھی اہل رہے (حتمی حالتیں جیسے banned/expired اب بھی لاگو ہوں گی)۔", "disableCoolingLabel": "اس کنکشن کے لیے کول ڈاؤن غیر فعال کریں", + "keepMidConversationSystemDescription": "چھوڑیں سسٹم کے پیغامات کو میسج لسٹ کے اندر درمیانی گفتگو میں بھیجے جانے کے بجائے انہیں ٹاپ لیول سسٹم پرامپٹ میں لے جایا جاتا ہے، اس لیے پرامپٹ کیش موڑ پر آتا رہتا ہے۔ صرف اس صورت میں فعال کریں جب یہ اپ اسٹریم پیغامات کے اندر کردار \"سسٹم\" کو قبول کرتا ہے (بصورت دیگر یہ 400 کا جواب دیتا ہے)۔", + "keepMidConversationSystemLabel": "درمیانی گفتگو کے نظام کے پیغامات کو جگہ پر رکھیں", "bulkPasteAdded": "{count, plural, one {1 کلید شامل کی گئی} other {# کلیدیں شامل کی گئیں}}", "bulkPasteDuplicatesIgnored": "{count, plural, one {1 تکراری کلید چھوڑ دی گئی} other {# تکراری کلیدیں چھوڑ دی گئیں}}", "bulkPasteHint": "ہر سطر میں ایک API کلید چسپاں کریں۔ خالی سطروں کو نظر انداز کیا جاتا ہے اور تکراری کلیدیں چھوڑ دی جاتی ہیں۔", diff --git a/src/i18n/messages/uz.json b/src/i18n/messages/uz.json index a37cc5050cf9..27a1e938336c 100644 --- a/src/i18n/messages/uz.json +++ b/src/i18n/messages/uz.json @@ -6245,6 +6245,8 @@ "allowCodexPaidCreditsDescription": "Allow this account to use paid credits after its subscription quota reaches the cutoff. Requires available credits reported by OpenAI; may incur charges. Spark limits and upstream cooldowns still apply. Credit availability is checked even if quota preflight is disabled.", "disableCoolingDescription": "Vaqtinchalik kutish davrini chetlab o‘tadi, shunda ushbu ulanish tiklanadigan xatolardan keyin ham foydalanish uchun yaroqli bo‘lib qoladi (bloklangan yoki muddati tugagan kabi yakuniy holatlar baribir amal qiladi).", "disableCoolingLabel": "Ushbu ulanish uchun kutish davrini o‘chirish", + "keepMidConversationSystemDescription": "Suhbat o‘rtasida yuborilgan tizim xabarlarini yuqori darajadagi tizim so‘roviga o‘tkazish o‘rniga xabarlar ro‘yxati ichida qoldiring, shunda so‘rov keshi burilishlar bo‘ylab tegib turadi. Agar bu yuqori oqim xabarlar ichidagi rol \"tizim\" ni qabul qilsa, yoqing (aks holda u 400 ga javob beradi).", + "keepMidConversationSystemLabel": "Suhbat o'rtasida tizim xabarlarini joyida saqlang", "bulkPasteAdded": "{count, plural, one {1 ta kalit qo‘shildi} other {# ta kalit qo‘shildi}}", "bulkPasteDuplicatesIgnored": "{count, plural, one {1 ta takroriy kalit o‘tkazib yuborildi} other {# ta takroriy kalit o‘tkazib yuborildi}}", "bulkPasteHint": "Har bir qatorga bittadan API kalitini joylang. Bo‘sh qatorlar e’tiborga olinmaydi va takroriy kalitlar o‘tkazib yuboriladi.", diff --git a/src/i18n/messages/vi.json b/src/i18n/messages/vi.json index 53e409d4e333..bcb91cb0dc64 100644 --- a/src/i18n/messages/vi.json +++ b/src/i18n/messages/vi.json @@ -6238,6 +6238,8 @@ "claudeAutoLimitResetDescription": "Khi tài khoản này đạt giới hạn sử dụng 5 giờ và có thể đặt lại giới hạn phiên, OmniRoute sẽ tự động sử dụng lượt đặt lại đó rồi thử lại ở tốc độ tối đa. Thao tác này sử dụng lượt đặt lại mỗi tuần một lần của tài khoản và vẫn được tính vào giới hạn hằng tuần.", "disableCoolingDescription": "Bỏ qua thời gian chờ tạm thời để kết nối này vẫn đủ điều kiện hoạt động ngay cả sau các lỗi có thể khôi phục (các trạng thái cuối cùng như bị cấm hoặc hết hạn vẫn áp dụng).", "disableCoolingLabel": "Vô hiệu hóa thời gian chờ cho kết nối này", + "keepMidConversationSystemDescription": "Để các tin nhắn hệ thống được gửi giữa cuộc trò chuyện bên trong danh sách tin nhắn thay vì chuyển chúng vào lời nhắc hệ thống cấp cao nhất, để bộ nhớ đệm lời nhắc tiếp tục chuyển sang các lượt khác. Chỉ bật nếu luồng ngược này chấp nhận vai trò \"hệ thống\" bên trong các tin nhắn (nếu không nó sẽ trả lời 400).", + "keepMidConversationSystemLabel": "Giữ nguyên các thông báo của hệ thống giữa cuộc trò chuyện", "bulkPasteAdded": "{count, plural, one {Đã thêm 1 khóa} other {Đã thêm # khóa}}", "bulkPasteDuplicatesIgnored": "{count, plural, one {Đã bỏ qua 1 khóa trùng lặp} other {Đã bỏ qua # khóa trùng lặp}}", "bulkPasteHint": "Dán một khóa API trên mỗi dòng. Các dòng trống sẽ bị bỏ qua và các khóa trùng lặp sẽ bị bỏ qua.", diff --git a/src/i18n/messages/yo.json b/src/i18n/messages/yo.json index aa91ce78bb3a..04c02e6b565e 100644 --- a/src/i18n/messages/yo.json +++ b/src/i18n/messages/yo.json @@ -6245,6 +6245,8 @@ "allowCodexPaidCreditsDescription": "Allow this account to use paid credits after its subscription quota reaches the cutoff. Requires available credits reported by OpenAI; may incur charges. Spark limits and upstream cooldowns still apply. Credit availability is checked even if quota preflight is disabled.", "disableCoolingDescription": "Fò àkókò ìsinmi fún ìgbà díẹ̀ náà kọjá, kí ìsopọ̀ yìí lè ṣì yẹ fún lílo lẹ́yìn àwọn àṣìṣe tí a lè ṣàtúnṣe (àwọn ipò ìparí bíi banned/expired ṣì kan án).", "disableCoolingLabel": "Pa àkókò ìsinmi fún ìsopọ̀ yìí", + "keepMidConversationSystemDescription": "Fi awọn ifiranṣẹ eto silẹ ti a firanṣẹ ni aarin-ibaraẹnisọrọ inu atokọ ifiranṣẹ dipo gbigbe wọn sinu itọsi eto ipele-oke, nitorinaa kaṣe iyara n tẹsiwaju lilu kọja awọn iyipada. Mu ṣiṣẹ nikan ti oke yii ba gba ipa “eto” inu awọn ifiranṣẹ (bibẹẹkọ o dahun 400).", + "keepMidConversationSystemLabel": "Jeki aarin-ibaraẹnisọrọ eto awọn ifiranṣẹ ni ibi", "bulkPasteAdded": "{count, plural, one {A ṣàfikún kọ́kọ́rọ́ 1} other {A ṣàfikún àwọn kọ́kọ́rọ́ #}}", "bulkPasteDuplicatesIgnored": "{count, plural, one {A fò ó kọjá ẹ̀dà àdáwòkọ 1} other {A fò ó kọjá àwọn ẹ̀dà àdáwòkọ #}}", "bulkPasteHint": "Lẹ kọ́kọ́rọ́ API kan sí ìlà kọ̀ọ̀kan. A ó fo àwọn ìlà òfo kọjá, a ó sì fo àwọn kọ́kọ́rọ́ àdáwòkọ kọjá.", diff --git a/src/i18n/messages/zh-CN.json b/src/i18n/messages/zh-CN.json index 869cc8845cec..709e1d2f74d5 100644 --- a/src/i18n/messages/zh-CN.json +++ b/src/i18n/messages/zh-CN.json @@ -6322,6 +6322,8 @@ "allowCodexPaidCreditsDescription": "允许此账户在订阅配额达到上限后使用付费额度。要求 OpenAI 报告有可用额度;可能产生费用。Spark 限额和上游冷却仍然适用。即使配额预检关闭也会检查额度可用性,除非本地配额过滤完全关闭(配额预检和限额策略都关闭)。", "disableCoolingDescription": "跳过瞬态冷却,以便此连接在发生可恢复错误后仍符合条件(被禁/过期等终态仍适用)。", "disableCoolingLabel": "禁用此连接的冷却", + "keepMidConversationSystemDescription": "将对话中发送的系统消息保留在消息列表中,而不是将其移至顶级系统提示中,以便提示缓存不断命中。仅当该上游接受消息内的角色“system”时才启用(否则它会回答 400)。", + "keepMidConversationSystemLabel": "将对话中的系统消息保留在适当的位置", "bulkPasteAdded": "{count, plural, one {已添加 1 个 key} other {已添加 # 个 key}}", "bulkPasteDuplicatesIgnored": "{count, plural, one {已跳过 1 个重复项} other {已跳过 # 个重复项}}", "bulkPasteHint": "每行粘贴一个 API 密钥。空行会被忽略,重复密钥会被跳过。", diff --git a/src/i18n/messages/zh-TW.json b/src/i18n/messages/zh-TW.json index b2ffe0175452..5bbbc350ea58 100644 --- a/src/i18n/messages/zh-TW.json +++ b/src/i18n/messages/zh-TW.json @@ -6245,6 +6245,8 @@ "allowCodexPaidCreditsDescription": "Allow this account to use paid credits after its subscription quota reaches the cutoff. Requires available credits reported by OpenAI; may incur charges. Spark limits and upstream cooldowns still apply. Credit availability is checked even if quota preflight is disabled.", "disableCoolingDescription": "跳過暫時冷卻,使此連線在可恢復錯誤後仍保持資格(已停用/已過期等終止狀態仍適用)。", "disableCoolingLabel": "對此連線停用冷卻", + "keepMidConversationSystemDescription": "將對話中傳送的系統訊息保留在訊息清單中,而不是將其移至頂級系統提示中,以便提示快取不斷命中。僅當該上游接受訊息內的角色「system」時才啟用(否則它會回答 400)。", + "keepMidConversationSystemLabel": "將對話中的系統訊息保留在適當的位置", "bulkPasteAdded": "{count, plural, one {已新增 1 個 key} other {已新增 # 個 key}}", "bulkPasteDuplicatesIgnored": "{count, plural, one {已跳過 1 個重複項} other {已跳過 # 個重複項}}", "bulkPasteHint": "每行貼上一個 API 金鑰。空行會被忽略,重複金鑰會被跳過。", From 21917b3db57be155a1d3d6dc9b1d0ecdf6c4b913 Mon Sep 17 00:00:00 2001 From: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> Date: Wed, 4 Nov 2026 13:42:42 -0300 Subject: [PATCH 04/20] fix(combo): preserve authorized retries during connection cooldown (#18258) --- .../18258-combo-authorized-cooldown-retry.md | 1 + open-sse/services/combo/attemptLoopTypes.ts | 6 + .../services/combo/authorizedRetryWait.ts | 120 +++ open-sse/services/combo/comboAttemptLoop.ts | 11 +- .../services/combo/executeTargetAttempt.ts | 50 +- .../combo/persistedConnectionCooldown.ts | 71 ++ stryker.conf.json | 2 + ...bo-authorized-retry-cooldown-18257.test.ts | 735 ++++++++++++++++++ 8 files changed, 966 insertions(+), 30 deletions(-) create mode 100644 changelog.d/fixes/18258-combo-authorized-cooldown-retry.md create mode 100644 open-sse/services/combo/authorizedRetryWait.ts create mode 100644 tests/unit/combo-authorized-retry-cooldown-18257.test.ts diff --git a/changelog.d/fixes/18258-combo-authorized-cooldown-retry.md b/changelog.d/fixes/18258-combo-authorized-cooldown-retry.md new file mode 100644 index 000000000000..e9a04fa5803e --- /dev/null +++ b/changelog.d/fixes/18258-combo-authorized-cooldown-retry.md @@ -0,0 +1 @@ +- **fix(combo):** Preserve an authorized same-target retry across a short transient connection cooldown, within existing wait limits and cancellation deadlines (#18257). diff --git a/open-sse/services/combo/attemptLoopTypes.ts b/open-sse/services/combo/attemptLoopTypes.ts index 4723091a8155..0d08044f1e11 100644 --- a/open-sse/services/combo/attemptLoopTypes.ts +++ b/open-sse/services/combo/attemptLoopTypes.ts @@ -102,6 +102,12 @@ export type AttemptLoopDeps = { signal?: AbortSignal | null; body: Record; startTime: number; + /** Shares the existing request wait limits with an inline, already-authorized retry. */ + retryCooldownWait?: { + attempt: { current: number }; + budgetLeftMs: { current: number }; + deadline: number; + }; releaseStickyPinOnFailure: ( messageHash: string | null | undefined, failedConnectionId: string | null | undefined diff --git a/open-sse/services/combo/authorizedRetryWait.ts b/open-sse/services/combo/authorizedRetryWait.ts new file mode 100644 index 000000000000..d0403ce8f7af --- /dev/null +++ b/open-sse/services/combo/authorizedRetryWait.ts @@ -0,0 +1,120 @@ +import { waitForCooldownAwareRetry } from "../../../src/sse/services/cooldownAwareRetry.ts"; +import type { AttemptLoopDeps, AttemptLoopState } from "./attemptLoopTypes.ts"; +import { COMBO_COOLDOWN_WAIT_MARGIN_MS, shouldWaitForComboCooldown } from "./comboCooldownRetry.ts"; +import { COMBO_LOOP_SAFETY_TIMEOUT_MS, resolveDelayMs } from "./comboPredicates.ts"; +import { readConnectionForCooldownGate } from "./executeTargetGates.ts"; +import { + resolvePersistedRetryCooldown, + type PersistedRetryCooldown, +} from "./persistedConnectionCooldown.ts"; + +type RetryCooldownWait = NonNullable; + +/** dispatch = send the retry; client_disconnected = 499; stop = leave this target (null). */ +type AuthorizedRetryOutcome = "dispatch" | "client_disconnected" | "stop"; + +type AuthorizedRetryOptions = { + state: AttemptLoopState; + deps: AttemptLoopDeps; + target: { modelStr: string; connectionId?: string | null }; + targetSignal?: AbortSignal; + retryDelayMs: number; + cooldownWait: RetryCooldownWait; + allowRateLimitedConnection: boolean; +}; + +/** The request-wide wait limits, or a per-target fallback when the loop did not pass them. */ +export function resolveRetryCooldownWait(deps: AttemptLoopDeps): RetryCooldownWait { + if (deps.retryCooldownWait) return deps.retryCooldownWait; + const comboTimeoutMs = resolveDelayMs(deps.config.comboTimeoutMs, 0); + return { + attempt: { current: 0 }, + budgetLeftMs: { current: deps.resilienceSettings.comboCooldownWait?.budgetMs ?? 0 }, + deadline: deps.startTime + (comboTimeoutMs || COMBO_LOOP_SAFETY_TIMEOUT_MS), + }; +} + +function outcomeAfterAbort(deps: AttemptLoopDeps): AuthorizedRetryOutcome { + return deps.signal?.aborted ? "client_disconnected" : "stop"; +} + +function decidePersistedCooldownWait( + deps: AttemptLoopDeps, + persisted: PersistedRetryCooldown, + cooldownWait: RetryCooldownWait +) { + const settings = deps.resilienceSettings.comboCooldownWait; + if (!settings) return null; + const { cooldown } = persisted; + return shouldWaitForComboCooldown({ + reason: cooldown?.reason, + waitMs: cooldown ? cooldown.untilMs - Date.now() + COMBO_COOLDOWN_WAIT_MARGIN_MS : 0, + attempt: cooldownWait.attempt.current, + budgetLeftMs: cooldownWait.budgetLeftMs.current, + settings, + }); +} + +/** A concurrent request can extend or reclassify the row while this retry waits. */ +async function waitOutPersistedCooldown( + opts: AuthorizedRetryOptions, + retrySignal: AbortSignal, + expire: () => void +): Promise { + const { state, deps, target, cooldownWait } = opts; + let persistedCooldownWaited = false; + for (;;) { + const persisted = await resolvePersistedRetryCooldown( + target, + (id) => readConnectionForCooldownGate(id, true), + persistedCooldownWaited, + opts.allowRateLimitedConnection + ); + if (Date.now() >= cooldownWait.deadline) expire(); + if (retrySignal.aborted || state.comboExpired) return outcomeAfterAbort(deps); + if (!persisted.skipReason) return "dispatch"; + const decision = decidePersistedCooldownWait(deps, persisted, cooldownWait); + if (!decision?.wait) { + deps.log.info("COMBO", persisted.skipReason); + state.fallbackCount++; + return "stop"; + } + persistedCooldownWaited = true; + cooldownWait.attempt.current++; + cooldownWait.budgetLeftMs.current -= decision.waitMs; + if (!(await waitForCooldownAwareRetry(decision.waitMs, retrySignal))) { + return outcomeAfterAbort(deps); + } + } +} + +/** + * Sleep out an already-authorized retry delay, then wait out (within the shared cooldown + * budget, combo deadline and abort signals) a transient cooldown persisted meanwhile. + */ +export async function waitForAuthorizedRetry( + opts: AuthorizedRetryOptions +): Promise { + const { state, deps, targetSignal, cooldownWait } = opts; + const deadlineController = new AbortController(); + const retrySignal = AbortSignal.any( + [deps.signal, targetSignal, deadlineController.signal].filter((signal): signal is AbortSignal => + Boolean(signal) + ) + ); + const expire = () => { + state.comboExpired = true; + deadlineController.abort(); + }; + const timeLeftMs = cooldownWait.deadline - Date.now(); + const deadlineTimer = setTimeout(expire, Math.max(0, timeLeftMs)); + if (timeLeftMs <= 0) expire(); + try { + if (!(await waitForCooldownAwareRetry(opts.retryDelayMs, retrySignal))) { + return outcomeAfterAbort(deps); + } + return await waitOutPersistedCooldown(opts, retrySignal, expire); + } finally { + clearTimeout(deadlineTimer); + } +} diff --git a/open-sse/services/combo/comboAttemptLoop.ts b/open-sse/services/combo/comboAttemptLoop.ts index 45e10bed88ee..0137876b6715 100644 --- a/open-sse/services/combo/comboAttemptLoop.ts +++ b/open-sse/services/combo/comboAttemptLoop.ts @@ -376,7 +376,16 @@ export async function dispatchWithCooldownRetry(opts: { return extra.executeAttempt({ index: i, state, - deps, + deps: { + ...deps, + retryCooldownWait: { + attempt: extra.comboCooldownAttempt, + budgetLeftMs: extra.comboCooldownBudgetLeftMs, + deadline: + extra.comboStartTime + + (extra.comboTimeoutMs > 0 ? extra.comboTimeoutMs : COMBO_LOOP_SAFETY_TIMEOUT_MS), + }, + }, targetForAttempt: gate.targetForAttempt, profile: gate.profile, protectedPriorityTarget: gate.protectedPriorityTarget, diff --git a/open-sse/services/combo/executeTargetAttempt.ts b/open-sse/services/combo/executeTargetAttempt.ts index d65c9ae9f48a..1634995eadbb 100644 --- a/open-sse/services/combo/executeTargetAttempt.ts +++ b/open-sse/services/combo/executeTargetAttempt.ts @@ -65,7 +65,6 @@ import { isLocalQueueCapacityErrorBody, toRecordedTarget, resolveDelayMs, - resolvePersistedConnectionCooldownSkipReason, isModelScoped400, isContextOverflowFailure, requestScopedReplayKey, @@ -94,7 +93,7 @@ import { } from "./quotaExhaustion.ts"; import { markAccountExhaustedFromCredits } from "../../../src/domain/quotaCache.ts"; import { classifyComboOutcome, redactConnectionLabel } from "./comboErrorAggregation.ts"; -import { readConnectionForCooldownGate } from "./executeTargetGates.ts"; +import { resolveRetryCooldownWait, waitForAuthorizedRetry } from "./authorizedRetryWait.ts"; import { recordLkgpPin } from "./recordLkgpPin.ts"; import { handlePreContentStreamRetry, @@ -148,6 +147,8 @@ export async function executeTargetAttempt( cause, }); + const targetSignal = state.abortControllers.get(i)?.signal; + const cooldownWait = resolveRetryCooldownWait(deps); const familyTried = new Set(); // #16438 opt-in (EMPTY_STREAM_SIBLING_RECOVERY_ENABLED): an empty_content turn spends // this loop's existing retry on a sibling connection with no lockout. Off = release path. @@ -160,6 +161,7 @@ export async function executeTargetAttempt( deps.log.info("COMBO", `Client disconnected — aborting combo loop before model ${modelStr}`); return { ok: false, response: errorResponse(499, "Client disconnected") }; } + if (state.comboExpired || targetSignal?.aborted) return null; state.globalAttempts++; if (state.globalAttempts > maxGlobalAttempts) { deps.log.warn( @@ -207,36 +209,19 @@ export async function executeTargetAttempt( "COMBO", `Retrying ${modelStr} in ${retryDelayMs}ms (attempt ${retry + 1}/${deps.maxRetries + 1})` ); - await new Promise((resolve) => { - const timer = setTimeout(resolve, retryDelayMs); - deps.signal?.addEventListener( - "abort", - () => { - clearTimeout(timer); - resolve(undefined); - }, - { once: true } - ); + const outcome = await waitForAuthorizedRetry({ + state, + deps, + target, + targetSignal, + retryDelayMs, + cooldownWait, + allowRateLimitedConnection, }); - if (deps.signal?.aborted) { - deps.log.info("COMBO", `Client disconnected during retry delay — aborting`); + if (outcome === "client_disconnected") { return { ok: false, response: errorResponse(499, "Client disconnected") }; } - - // Retry re-check: a sibling attempt (or attempt 1) may have persisted - // a quota cooldown while this attempt was sleeping out its retry delay - // ("Trying model 1/7: zai/glm-5.3 (retry 1)" after "already marked - // unavailable until …"). Reads fresh, not cached: see readConnectionForCooldownGate. - const persistedRetrySkip = await resolvePersistedConnectionCooldownSkipReason( - target, - (id) => readConnectionForCooldownGate(id, true), - allowRateLimitedConnection - ); - if (persistedRetrySkip) { - deps.log.info("COMBO", persistedRetrySkip); - state.fallbackCount++; - return null; - } + if (outcome === "stop") return null; } deps.log.info( @@ -358,6 +343,13 @@ export async function executeTargetAttempt( decision: "dispatched", }); } + if (retry > 0) { + if (Date.now() >= cooldownWait.deadline) state.comboExpired = true; + if (deps.signal?.aborted) { + return { ok: false, response: errorResponse(499, "Client disconnected") }; + } + if (state.comboExpired || targetSignal?.aborted) return null; + } const result = await deps.handleSingleModelWithTimeout(attemptBody, modelStr, { ...targetForAttempt, emptyTurnConnectionId, diff --git a/open-sse/services/combo/persistedConnectionCooldown.ts b/open-sse/services/combo/persistedConnectionCooldown.ts index d182ce8c3622..744937fca4ce 100644 --- a/open-sse/services/combo/persistedConnectionCooldown.ts +++ b/open-sse/services/combo/persistedConnectionCooldown.ts @@ -1,9 +1,11 @@ import { cooldownUntilMs } from "../accountFallback.ts"; import { + getPersistedConnectionCooldownSkipReason, normalizeConnectionStatus, QUOTA_BLOCKING_CONNECTION_STATUSES, resolvePersistedConnectionCooldownSkipReason, } from "./comboPredicates.ts"; +import { isTransientComboCooldownReason } from "./comboCooldownRetry.ts"; import { recordSkippedConnectionCooldown } from "./skippedConnectionCooldown.ts"; import type { AttemptLoopState } from "./attemptLoopTypes.ts"; @@ -45,3 +47,72 @@ export async function resolveAndRecordPersistedCooldown( if (reason) recordRecoverablePersistedCooldown(state, connection); return reason; } + +export type PersistedRetryCooldown = { + skipReason: string | null; + cooldown?: { reason: string; untilMs: number }; +}; + +const RETRY_RECOVERABLE_STATUSES = new Set(["active", "unavailable", "rate_limited"]); +const NON_RETRYABLE_ERROR_CODES = new Set([400, 401, 402, 403, 404, 410]); + +/** The row's transient cooldown reason, or null when a wait cannot recover it. */ +function transientRetryCooldownReason(connection: Record): string | null { + const status = normalizeConnectionStatus(connection.testStatus); + const reason = connection.lastErrorType; + if (!RETRY_RECOVERABLE_STATUSES.has(status)) return null; + if (typeof reason !== "string" || !isTransientComboCooldownReason(reason)) return null; + if (NON_RETRYABLE_ERROR_CODES.has(Number(connection.errorCode))) return null; + return reason; +} + +/** An explicit, representable cooldown deadline in epoch ms, or null. */ +function explicitCooldownUntilMs(value: unknown): number | null { + if (typeof value !== "string" && typeof value !== "number" && !(value instanceof Date)) { + return null; + } + const untilMs = cooldownUntilMs(value); + return Number.isFinite(untilMs) && untilMs > 0 && untilMs <= 8.64e15 ? untilMs : null; +} + +async function fetchRetryConnection( + connectionId: string, + fetchConnection: (id: string) => Promise +): Promise { + try { + return await fetchConnection(connectionId); + } catch { + return null; + } +} + +/** A retry may recover an explicit transient deadline without clearing the persisted label. */ +export async function resolvePersistedRetryCooldown( + target: { modelStr: string; connectionId?: string | null }, + fetchConnection: (id: string) => Promise, + requireFreshConnection = false, + allowRateLimitedConnection = false +): Promise { + if (!target.connectionId) return { skipReason: null }; + const unavailable = `Skipping ${target.modelStr} — connection ${target.connectionId} unavailable for retry`; + const connection = await fetchRetryConnection(target.connectionId, fetchConnection); + // Preserve normal read fallthrough until a known cooldown has actually been waited. + if (!connection) return { skipReason: requireFreshConnection ? unavailable : null }; + if (connection.isActive !== true) return { skipReason: unavailable }; + const skipReason = getPersistedConnectionCooldownSkipReason(target, connection); + const reason = transientRetryCooldownReason(connection); + if (!reason) return { skipReason }; + const untilMs = explicitCooldownUntilMs(connection.rateLimitedUntil); + if (untilMs === null) { + // Preserve an inherited transient-provider allowance, without inventing a wait. + return { + skipReason: getPersistedConnectionCooldownSkipReason( + target, + connection, + allowRateLimitedConnection + ), + }; + } + if (untilMs <= Date.now()) return { skipReason: null }; + return { skipReason, cooldown: { reason, untilMs } }; +} diff --git a/stryker.conf.json b/stryker.conf.json index 5602120a0d8d..9ac5d5c0f8de 100644 --- a/stryker.conf.json +++ b/stryker.conf.json @@ -55,9 +55,11 @@ "tests/unit/cursor-execute-error-stream.test.ts", "tests/unit/anthropic-fallback-policy-16406.test.ts", "tests/unit/combo-cooldown-wait-opt-out.test.ts", + "tests/unit/combo-authorized-retry-cooldown-18257.test.ts", "tests/unit/combo-family-selected-connection-16223.test.ts", "tests/unit/combo-safety-stop.test.ts", "tests/unit/combo-live-connection-id-18374.test.ts", + "tests/unit/combo-authorized-retry-cooldown-18257.test.ts", "tests/unit/model-family-fallback-eligibility.test.ts", "tests/unit/noauth-autocombo-lockout-7623.test.ts", "tests/unit/auth-opencode-zen-noauth-fallback.test.ts", diff --git a/tests/unit/combo-authorized-retry-cooldown-18257.test.ts b/tests/unit/combo-authorized-retry-cooldown-18257.test.ts new file mode 100644 index 000000000000..dcd26230ec7a --- /dev/null +++ b/tests/unit/combo-authorized-retry-cooldown-18257.test.ts @@ -0,0 +1,735 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; + +const dataDir = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-retry-18257-")); +process.env.DATA_DIR = dataDir; +const { handleComboChat } = await import("../../open-sse/services/combo.ts"); +const { invalidateDbCache } = await import("../../src/lib/db/readCache.ts"); +const { clearCooldownState } = await import("../../open-sse/services/providerCooldownTracker.ts"); +const { resetDbInstance } = await import("../../src/lib/db/core.ts"); +const { + createProviderConnection, + updateProviderConnection, + getProviderConnectionById, + deleteProviderConnection, +} = await import("../../src/lib/db/providers.ts"); +const { resolvePersistedRetryCooldown } = + await import("../../open-sse/services/combo/persistedConnectionCooldown.ts"); +const { clearAllModelLockouts } = await import("../../open-sse/services/accountFallback.ts"); +const { resetAllComboMetrics } = await import("../../open-sse/services/comboMetrics.ts"); +const { resetAllCircuitBreakers } = await import("../../src/shared/utils/circuitBreaker.ts"); +const { + CREDENTIAL_GATE_AUTH_DIAGNOSES_ONLY_FLAG: credentialGateFlag, + __test_resetCredentialHealthCache, + getCredentialHealth, + setCredentialHealth, +} = await import("../../src/lib/credentialHealth/cache.ts"); + +const originalCredentialGateFlag = process.env[credentialGateFlag]; +function restoreCredentialGateFlag() { + if (originalCredentialGateFlag === undefined) delete process.env[credentialGateFlag]; + else process.env[credentialGateFlag] = originalCredentialGateFlag; +} + +let fixture = 0; +const primary = "openai/gpt-4o"; +const secondary = "anthropic/claude-sonnet-4"; +const failure = + 'event: response.failed\ndata: {"type":"response.failed","response":{"status":"failed","error":{"code":"no_capacity","type":"server_error","message":"peak capacity"}}}\n\n'; +function stream(body: string) { + return new Response(body, { headers: { "content-type": "text/event-stream" } }); +} +function success() { + return stream('data: {"choices":[{"delta":{"content":"success"}}]}\n\ndata: [DONE]\n\n'); +} + +test.beforeEach(() => { + __test_resetCredentialHealthCache(); + clearAllModelLockouts(); + clearCooldownState(); + invalidateDbCache("connections"); + resetAllComboMetrics(); + resetAllCircuitBreakers(); +}); +test.afterEach(() => { + __test_resetCredentialHealthCache(); + restoreCredentialGateFlag(); +}); +test.after(async () => { + await new Promise((resolve) => setImmediate(resolve)); + clearAllModelLockouts(); + resetDbInstance(); + fs.rmSync(dataDir, { recursive: true, force: true }); +}); + +// Every duration below is written in "base" milliseconds and multiplied by TIME_SCALE inside +// run(), so the ordering margins (retry delay < cooldown end < fresh mutation, etc.) stay +// proportional but are wide enough to survive a starved event loop on a loaded CI runner. +const TIME_SCALE = 4; +const TIGHT_BUDGET_MS = 150; +// Budget scenarios need two waits once scaled (the 50 ms safety margin in production is NOT +// scaled): ~600 ms for the original cooldown (700 ms) and ~450 ms for a fresh extension that +// lands at 260 ms and runs to ~1160 ms. normalizeComboCooldownWait floors budgetMs at maxWaitMs, +// so the tight budget is effectively the 800 ms ceiling: it fits the first wait but not the +// second, while the 1800 ms budget fits both with the same ceiling. +// Guard scenarios (abort, deadline, removal) fire at 65-80 ms (x TIME_SCALE) into a cooldown: +// a long cooldown keeps the trigger well inside the wait even when timers slip under load. +const LONG_COOLDOWN_MS = 250; +const LONG_COOLDOWN_SETTLE_MS = 300; +const LONG_EXTENSION = { cooldownMs: 175, changeAfterMs: 65, extendMs: 225 }; +const LONG_EXTENSION_MAX_WAIT_MS = 200; +const SCALED_CONFIG_KEYS = ["comboTimeoutMs", "hedgeDelayMs", "retryDelayMs"]; +const SCALED_WAIT_KEYS = ["maxWaitMs", "budgetMs"]; +const SCALED_OPTION_KEYS = [ + "cooldownMs", + "abortAfterMs", + "changeAfterMs", + "removeAfterMs", + "extendMs", + "settleMs", +]; + +function scaleKeys | undefined>(source: T, keys: string[]): T { + if (!source) return source; + const scaled: Record = { ...source }; + for (const key of keys) { + if (typeof scaled[key] === "number") scaled[key] = (scaled[key] as number) * TIME_SCALE; + } + return scaled as T; +} + +async function run( + rawOptions: { + cooldownMs?: number; + row?: Record; + config?: Record; + waitSettings?: Record; + fallback?: boolean; + coolingFallback?: boolean; + retryFails?: boolean; + signal?: AbortSignal; + abortAfterMs?: number; + changeAfterMs?: number; + removeAfterMs?: number; + change?: Record; + extendMs?: number; + settleMs?: number; + initialBody?: string; + abortBeforeRetryDispatch?: boolean; + credentialProbe?: "active" | "network_error" | "upstream_auth_error"; + } = {} +) { + const options = { + ...scaleKeys(rawOptions, SCALED_OPTION_KEYS), + config: scaleKeys(rawOptions.config, SCALED_CONFIG_KEYS), + waitSettings: scaleKeys(rawOptions.waitSettings, SCALED_WAIT_KEYS), + }; + const connection = await createProviderConnection({ + provider: "openai", + authType: "apikey", + name: `offline-retry-fixture-${fixture}`, + apiKey: `fixture-not-a-real-key-${++fixture}`, + isActive: true, + testStatus: "active", + }); + const fallbackConnection = options.coolingFallback + ? await createProviderConnection({ + provider: "anthropic", + authType: "apikey", + name: `offline-fallback-${fixture}`, + apiKey: `fixture-fallback-key-${fixture}`, + isActive: true, + testStatus: "active", + }) + : null; + invalidateDbCache("connections"); + if (options.credentialProbe === "active") { + setCredentialHealth(connection.id, "openai", "active"); + } else if (options.credentialProbe) { + setCredentialHealth( + connection.id, + "openai", + "error", + "Synthetic probe failure", + options.credentialProbe, + "upstream" + ); + } + const seededProbe = getCredentialHealth(connection.id); + const credentialSkips: string[] = []; + const calls: Array<{ model: string; at: number; connectionId?: string | null }> = []; + let until = 0; + let extendedUntil = 0; + const controller = new AbortController(); + const updates: Promise[] = []; + const writes: Promise[] = []; + const timers: ReturnType[] = []; + try { + const response = await handleComboChat({ + body: { stream: true, messages: [{ role: "user", content: "hello" }] }, + combo: { + name: `authorized-retry-18257-${connection.id}`, + strategy: "priority", + models: [ + { model: primary, connectionId: connection.id }, + ...(fallbackConnection + ? [{ model: secondary, connectionId: fallbackConnection.id }] + : options.fallback + ? [secondary] + : []), + ], + config: { + maxRetries: 1, + maxSetRetries: 0, + maxGlobalAttempts: 2, + retryDelayMs: 40 * TIME_SCALE, + fallbackDelayMs: 0, + ...options.config, + }, + }, + handleSingleModel: async (_body, model, target) => { + calls.push({ + model, + at: Date.now(), + connectionId: target && "connectionId" in target ? target.connectionId : undefined, + }); + const firstFallbackFailure = + options.coolingFallback && + model === secondary && + calls.filter((call) => call.model === secondary).length === 1; + return (model === primary && (calls.length === 1 || options.retryFails)) || + firstFallbackFailure + ? stream(options.initialBody ?? failure) + : success(); + }, + isModelAvailable: async () => true, + log: { + info: (tag, message) => { + if (tag === "CREDENTIAL_GATE") credentialSkips.push(String(message)); + if (options.abortBeforeRetryDispatch && String(message).includes("(retry 1)")) + controller.abort(); + const cooledConnection = String(message).startsWith(`Retrying ${primary} in `) + ? connection + : String(message).startsWith(`Retrying ${secondary} in `) + ? fallbackConnection + : null; + // A second request writes AFTER the retry delay starts; the initial cached row is healthy. + if (cooledConnection) { + timers.push( + setTimeout(() => { + until = Date.now() + (options.cooldownMs ?? 120 * TIME_SCALE); + writes.push( + updateProviderConnection(cooledConnection.id, { + testStatus: "unavailable", + lastErrorType: "server_error", + errorCode: "503", + lastErrorAt: new Date().toISOString(), + rateLimitedUntil: new Date(until).toISOString(), + ...options.row, + }) + ); + if (options.abortAfterMs !== undefined) { + timers.push(setTimeout(() => controller.abort(), options.abortAfterMs)); + } + if (options.removeAfterMs !== undefined) { + timers.push( + setTimeout(() => { + updates.push(deleteProviderConnection(cooledConnection.id)); + }, options.removeAfterMs) + ); + } + if (options.changeAfterMs !== undefined) { + timers.push( + setTimeout(() => { + extendedUntil = Date.now() + (options.extendMs ?? 0); + updates.push( + updateProviderConnection(cooledConnection.id, { + ...options.change, + ...(options.extendMs + ? { rateLimitedUntil: new Date(extendedUntil).toISOString() } + : {}), + }) + ); + }, options.changeAfterMs) + ); + } + }, 5) + ); + } + }, + warn() {}, + error() {}, + debug() {}, + }, + signal: options.signal ?? controller.signal, + settings: { + modelLockout: { enabled: false }, + resilienceSettings: { + comboCooldownWait: { + enabled: true, + maxWaitMs: 1000 * TIME_SCALE, + maxAttempts: 3, + budgetMs: 1000 * TIME_SCALE, + ...options.waitSettings, + }, + }, + }, + allCombos: null, + }); + await Promise.all(writes); + const responseBody = await response.text(); + if (options.settleMs) await new Promise((resolve) => setTimeout(resolve, options.settleMs)); + await Promise.all(updates); + return { + response, + calls, + until: extendedUntil || until, + connection: await getProviderConnectionById(connection.id), + responseBody, + seededProbe, + cachedProbe: getCredentialHealth(connection.id), + credentialSkips, + }; + } finally { + for (const timer of timers) clearTimeout(timer); + await Promise.allSettled([...writes, ...updates]); + } +} + +test("an authorized retry waits for a concurrent cooldown, retaining the unavailable label", async () => { + const { response, calls, until, connection } = await run(); + assert.equal(response.status, 200); + assert.deepEqual( + calls.map(({ model }) => model), + [primary, primary] + ); + assert.ok(calls[1].at >= until, "the authorized retry must dispatch only after expiry"); + assert.equal(connection?.testStatus, "unavailable", "no background recovery clears the label"); +}); + +for (const flag of ["false", "true"]) { + test(`a cached network_error permits the same cooldown-delayed retry, auth-only flag=${flag}`, async () => { + process.env[credentialGateFlag] = flag; + const { response, responseBody, calls, until, connection, seededProbe, cachedProbe } = + await run({ + credentialProbe: "network_error", + config: { comboTimeoutMs: 1000 }, + waitSettings: { maxAttempts: 1, maxWaitMs: 250, budgetMs: 250 }, + }); + assert.equal(response.status, 200); + assert.match(responseBody, /success/); + assert.deepEqual( + calls.map(({ model, connectionId }) => [model, connectionId]), + [ + [primary, connection?.id], + [primary, connection?.id], + ] + ); + assert.ok(until > calls[0].at, "the other request persisted cooldown after initial dispatch"); + assert.ok(calls[1].at >= until, "the single authorized retry waited for persisted expiry"); + assert.equal(connection?.testStatus, "unavailable"); + assert.equal(connection?.lastErrorType, "server_error"); + assert.equal(connection?.rateLimitedUntil, new Date(until).toISOString()); + assert.equal(cachedProbe?.status, "error"); + assert.equal(cachedProbe?.lastErrorType, "network_error"); + assert.equal(cachedProbe?.consecutiveFailures, 1); + assert.deepEqual(cachedProbe, seededProbe, "successful routing must retain the failed probe"); + }); + + test(`cached confirmed auth failure blocks initial combo dispatch, auth-only flag=${flag}`, async () => { + process.env[credentialGateFlag] = flag; + const { response, calls, until, connection, credentialSkips, cachedProbe } = await run({ + credentialProbe: "upstream_auth_error", + }); + assert.equal(response.status, 503); + assert.equal(calls.length, 0); + assert.equal(until, 0, "no dispatch means no retry or concurrent cooldown write"); + assert.equal(connection?.testStatus, "active"); + assert.equal(credentialSkips.length, 1); + assert.match(credentialSkips[0], /known-bad credentials.*diagnosis: upstream_auth_error/); + assert.equal(cachedProbe?.lastErrorType, "upstream_auth_error"); + }); +} + +test("a positive cached probe remains visible after a persisted-cooldown retry", async () => { + const { response, calls, connection, seededProbe, cachedProbe } = await run({ + credentialProbe: "active", + }); + assert.equal(response.status, 200); + assert.equal(calls.length, 2); + assert.equal(connection?.testStatus, "unavailable"); + assert.equal(cachedProbe?.status, "active"); + assert.equal(cachedProbe?.consecutiveFailures, 0); + assert.deepEqual(cachedProbe, seededProbe); +}); + +test("a cached network_error cannot increase the existing cooldown wait budget", async () => { + const GENEROUS_BUDGET_MS = 450; + for (const budgetMs of [TIGHT_BUDGET_MS, GENEROUS_BUDGET_MS]) { + const generous = budgetMs === GENEROUS_BUDGET_MS; + const { response, calls, until, connection, cachedProbe } = await run({ + credentialProbe: "network_error", + ...LONG_EXTENSION, + config: { comboTimeoutMs: 1000 }, + waitSettings: { maxWaitMs: LONG_EXTENSION_MAX_WAIT_MS, budgetMs }, + }); + assert.equal(response.status, generous ? 200 : 502); + assert.equal(calls.length, generous ? 2 : 1); + assert.ok(until > calls[0].at); + assert.equal(connection?.testStatus, "unavailable"); + assert.equal(cachedProbe?.lastErrorType, "network_error"); + if (generous) { + assert.ok( + calls[1].at >= until, + "the same wait ceiling and deadline permit a larger total budget" + ); + } + } +}); + +for (const guard of [ + { name: "client cancellation", flag: "false", abortAfterMs: 65, status: 499 }, + { name: "the original deadline", flag: "true", comboTimeoutMs: 80, status: 504 }, +]) { + test(`${guard.name} prevents late cooldown retry dispatch with a cached network_error`, async () => { + process.env[credentialGateFlag] = guard.flag; + const { response, calls, until, connection, cachedProbe } = await run({ + credentialProbe: "network_error", + abortAfterMs: guard.abortAfterMs, + config: { comboTimeoutMs: guard.comboTimeoutMs }, + cooldownMs: LONG_COOLDOWN_MS, + settleMs: LONG_COOLDOWN_SETTLE_MS, + }); + assert.equal(response.status, guard.status); + assert.equal(calls.length, 1, "waiting past expiry must not dispatch after cancellation"); + assert.ok(until > calls[0].at); + assert.equal(connection?.testStatus, "unavailable"); + assert.equal(cachedProbe?.lastErrorType, "network_error"); + }); +} + +test("fresh cooldown extensions are reread without spending another logical retry", async () => { + const { response, calls, until } = await run({ changeAfterMs: 65, extendMs: 220 }); + assert.equal(response.status, 200); + assert.deepEqual( + calls.map(({ model }) => model), + [primary, primary] + ); + assert.ok(calls[1].at >= until); +}); + +for (const change of [ + { testStatus: "banned" }, + { testStatus: "credits_exhausted", lastErrorType: "quota_exhausted" }, + { lastErrorType: "auth_error", errorCode: "401" }, + { isActive: false }, +]) { + test(`a fresh prohibition during the wait prevents retry: ${JSON.stringify(change)}`, async () => { + const { response, calls } = await run({ changeAfterMs: 65, change }); + assert.equal(response.status, 502); + assert.equal(calls.length, 1); + }); +} + +for (const waitSettings of [{ enabled: false }, { maxWaitMs: 50 }, { maxAttempts: 0 }]) { + test(`existing wait controls prevent a cooldown wait: ${JSON.stringify(waitSettings)}`, async () => { + const { response, calls } = await run({ waitSettings }); + assert.equal(response.status, 502); + assert.equal(calls.length, 1); + }); +} + +for (const waitSettings of [ + { maxAttempts: 1 }, + { maxWaitMs: LONG_EXTENSION_MAX_WAIT_MS, budgetMs: TIGHT_BUDGET_MS }, +]) { + test(`extensions respect the shared request limits: ${JSON.stringify(waitSettings)}`, async () => { + const { response, calls } = await run({ ...LONG_EXTENSION, waitSettings }); + assert.equal(response.status, 502); + assert.equal(calls.length, 1); + }); +} + +for (const row of [ + { lastErrorType: "unknown" }, + { lastErrorType: null }, + { lastErrorType: "auth_error", errorCode: "401" }, + { lastErrorType: "unauthorized" }, + { lastErrorType: "oauth_invalid_token" }, + { lastErrorType: "quota_exhausted" }, + { errorCode: "401" }, + { testStatus: "expired" }, + { isActive: false }, + { rateLimitedUntil: null }, + { rateLimitedUntil: "invalid" }, +]) { + test(`unconfirmed transient state never authorizes waiting or a bypass: ${JSON.stringify(row)}`, async () => { + const { response, calls } = await run({ row }); + assert.equal(response.status, 502); + assert.equal(calls.length, 1); + }); +} + +for (const abortAfterMs of [10, 65]) { + test(`client abort during retry waiting prevents delayed dispatch (${abortAfterMs}ms)`, async () => { + const { response, calls } = await run({ + abortAfterMs, + cooldownMs: LONG_COOLDOWN_MS, + settleMs: LONG_COOLDOWN_SETTLE_MS, + }); + assert.equal(response.status, 499); + assert.equal(calls.length, 1); + }); +} + +for (const hedgeDelayMs of [10, 70]) { + test(`a winning sibling aborts the waiting target without delayed dispatch (${hedgeDelayMs}ms)`, async () => { + const { response, calls } = await run({ + fallback: true, + settleMs: 220, + config: { + maxGlobalAttempts: 3, + zeroLatencyOptimizationsEnabled: true, + hedging: true, + hedgeDelayMs, + }, + }); + assert.equal(response.status, 200); + assert.deepEqual( + calls.map(({ model }) => model), + [primary, secondary] + ); + }); +} + +for (const comboTimeoutMs of [20, 80]) { + test(`the original combo deadline cancels a retry delay or cooldown wait (${comboTimeoutMs}ms)`, async () => { + const { response, calls } = await run({ + config: { comboTimeoutMs }, + cooldownMs: LONG_COOLDOWN_MS, + settleMs: LONG_COOLDOWN_SETTLE_MS, + }); + assert.equal(response.status, 504); + assert.equal(calls.length, 1); + }); +} + +for (const config of [{ maxRetries: 0 }, { maxGlobalAttempts: 1 }]) { + test(`waiting cannot reset retry or global attempt limits: ${JSON.stringify(config)}`, async () => { + const { response, calls } = await run({ config }); + assert.equal(response.status, config.maxGlobalAttempts === 1 ? 503 : 502); + assert.equal(calls.length, 1); + }); +} + +test("the same target finishes its authorized retry before ordinary fallback", async () => { + const { response, calls, until } = await run({ + fallback: true, + retryFails: true, + config: { maxGlobalAttempts: 3 }, + }); + assert.equal(response.status, 200); + assert.deepEqual( + calls.map(({ model }) => model), + [primary, primary, secondary] + ); + assert.ok(calls[1].at >= until); +}); + +test("useful SSE output followed by an error never grants a replay", async () => { + const { response, calls } = await run({ + initialBody: 'data: {"choices":[{"delta":{"content":"already visible"}}]}\n\n' + failure, + }); + assert.equal(response.status, 200); + assert.equal(calls.length, 1); +}); + +test("an explicit transient deadline expired during the fixed delay recovers unavailable lazily", async () => { + const { response, calls, until, connection } = await run({ cooldownMs: 10 }); + assert.equal(response.status, 200); + assert.deepEqual( + calls.map(({ model }) => model), + [primary, primary] + ); + assert.ok(calls[1].at >= until); + assert.equal(connection?.testStatus, "unavailable"); +}); + +test("a connection removed during the confirmed cooldown wait cannot dispatch", async () => { + const { response, calls, connection } = await run({ + removeAfterMs: 65, + cooldownMs: LONG_COOLDOWN_MS, + }); + assert.equal(response.status, 502); + assert.equal(calls.length, 1); + assert.equal(connection, null); +}); + +test("missing/read-failed rows preserve ordinary fallthrough but cannot reopen a waited cooldown", async () => { + const target = { modelStr: primary, connectionId: "missing-fixture" }; + for (const fetchConnection of [ + async () => null, + async () => { + throw new Error("fixture DB read failure"); + }, + ]) { + assert.equal((await resolvePersistedRetryCooldown(target, fetchConnection)).skipReason, null); + assert.ok((await resolvePersistedRetryCooldown(target, fetchConnection, true)).skipReason); + } +}); + +// normalizeComboCooldownWait floors budgetMs at maxWaitMs, so keep maxWaitMs (x TIME_SCALE = 480) +// just under budgetMs (x TIME_SCALE = 520): one ~375 ms wait fits, a second one cannot. +for (const waitSettings of [{ maxAttempts: 1 }, { maxWaitMs: 120, budgetMs: 130 }]) { + test(`different targets share the existing request wait allowance: ${JSON.stringify(waitSettings)}`, async () => { + const { response, calls } = await run({ + coolingFallback: true, + retryFails: true, + config: { maxGlobalAttempts: 4 }, + waitSettings, + }); + assert.equal(response.status, 502); + assert.deepEqual( + calls.map(({ model }) => model), + [primary, primary, secondary] + ); + }); +} + +test("client cancellation after the cooldown read still prevents retry dispatch", async () => { + const { response, calls } = await run({ abortBeforeRetryDispatch: true }); + assert.equal(response.status, 499); + assert.equal(calls.length, 1); +}); + +test("persisted rate_limited classifier state resumes the authorized retry after expiry", async () => { + const { response, calls, until, connection } = await run({ + row: { testStatus: "rate_limited", lastErrorType: "rate_limited", errorCode: "429" }, + }); + assert.equal(response.status, 200); + assert.deepEqual( + calls.map(({ model }) => model), + [primary, primary] + ); + assert.ok(calls[1].at >= until); + assert.equal(connection?.testStatus, "rate_limited"); +}); + +async function runInheritedTransientRetry( + status: string, + rateLimitedUntil: string | null, + preContent: boolean, + options: { retryRow?: Record; expectRetry?: boolean } = {} +) { + const seed = await createProviderConnection({ + provider: "openai", + authType: "apikey", + name: `transient-seed-${fixture}`, + apiKey: `fixture-transient-seed-${++fixture}`, + isActive: true, + testStatus: "active", + }); + const later = await createProviderConnection({ + provider: "openai", + authType: "apikey", + name: `transient-later-${fixture}`, + apiKey: `fixture-transient-later-${++fixture}`, + isActive: true, + testStatus: "active", + }); + invalidateDbCache("connections"); + const firstModel = "openai/gpt-4o-mini"; + const calls: string[] = []; + const response = await handleComboChat({ + body: { stream: preContent, messages: [{ role: "user", content: "hello" }] }, + combo: { + name: `inherited-transient-retry-${later.id}`, + strategy: "priority", + models: [ + { model: firstModel, connectionId: seed.id }, + { model: primary, connectionId: later.id }, + ], + config: { + maxRetries: 1, + maxSetRetries: 0, + maxGlobalAttempts: 3, + retryDelayMs: 0, + fallbackDelayMs: 0, + failoverBeforeRetry: true, + }, + }, + handleSingleModel: async (_body, model) => { + calls.push(model); + if (model === firstModel) { + await updateProviderConnection(later.id, { + testStatus: status, + lastErrorType: "rate_limited", + errorCode: "429", + lastErrorAt: new Date().toISOString(), + rateLimitedUntil, + }); + return Response.json( + { error: { message: "Too many requests, slow down", type: "rate_limit_error" } }, + { status: 429 } + ); + } + if (calls.length === 2) { + if (options.retryRow) await updateProviderConnection(later.id, options.retryRow); + return preContent + ? stream(failure) + : Response.json({ error: { message: "Temporarily unavailable" } }, { status: 503 }); + } + return preContent + ? success() + : Response.json({ choices: [{ message: { content: "success" } }] }); + }, + isModelAvailable: async () => true, + log: { info() {}, warn() {}, error() {}, debug() {} }, + settings: { + modelLockout: { enabled: false }, + resilienceSettings: { comboCooldownWait: { enabled: false } }, + }, + allCombos: null, + }); + await response.text(); + if (options.expectRetry === false) { + assert.equal(response.ok, false); + assert.deepEqual(calls, [firstModel, primary]); + } else { + assert.equal(response.status, 200); + assert.deepEqual(calls, [firstModel, primary, primary]); + assert.equal((await getProviderConnectionById(later.id))?.testStatus, status); + } +} + +for (const status of ["unavailable", "rate_limited"]) { + for (const rateLimitedUntil of [null, "invalid"]) { + for (const preContent of [false, true]) { + test(`existing transient-provider retry allowance survives status-only ${status}/${rateLimitedUntil}, preContent=${preContent}`, async () => { + await runInheritedTransientRetry(status, rateLimitedUntil, preContent); + }); + } + } +} + +for (const retryRow of [ + { rateLimitedUntil: new Date(Date.now() + 60_000).toISOString() }, + { testStatus: "banned" }, + { isActive: false }, + { lastErrorType: "auth_error", errorCode: "401" }, + { lastErrorType: "quota_exhausted" }, + { lastErrorType: "unknown" }, + { errorCode: "401" }, +]) { + for (const preContent of [false, true]) { + test(`existing transient-provider allowance cannot bypass fresh protection ${JSON.stringify(retryRow)}, preContent=${preContent}`, async () => { + await runInheritedTransientRetry("unavailable", null, preContent, { + retryRow, + expectRetry: false, + }); + }); + } +} From 30d09675d7b7bce77be1f87366e42b3b218b056f Mon Sep 17 00:00:00 2001 From: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> Date: Wed, 4 Nov 2026 13:42:43 -0300 Subject: [PATCH 05/20] fix(combo): cancel fallback waits promptly (#18569) --- .../0-combo-fallback-wait-cancellation.md | 1 + .../services/combo/executeTargetAttempt.ts | 12 +- open-sse/services/combo/roundRobinCombo.ts | 13 +- .../combo-fallback-wait-cancellation.test.ts | 316 ++++++++++++++++++ 4 files changed, 320 insertions(+), 22 deletions(-) create mode 100644 changelog.d/fixes/0-combo-fallback-wait-cancellation.md create mode 100644 tests/unit/combo-fallback-wait-cancellation.test.ts diff --git a/changelog.d/fixes/0-combo-fallback-wait-cancellation.md b/changelog.d/fixes/0-combo-fallback-wait-cancellation.md new file mode 100644 index 000000000000..19b11d06a25b --- /dev/null +++ b/changelog.d/fixes/0-combo-fallback-wait-cancellation.md @@ -0,0 +1 @@ +- **fix(combo):** Priority and round-robin fallback waits now stop immediately when the client has already disconnected and release abort listeners when the wait completes. diff --git a/open-sse/services/combo/executeTargetAttempt.ts b/open-sse/services/combo/executeTargetAttempt.ts index 1634995eadbb..5ac6c20f62aa 100644 --- a/open-sse/services/combo/executeTargetAttempt.ts +++ b/open-sse/services/combo/executeTargetAttempt.ts @@ -1334,17 +1334,7 @@ export async function executeTargetAttempt( : 0; if ([502, 503, 504].includes(result.status) && fallbackWaitMs > 0) { deps.log.debug?.("COMBO", `Waiting ${fallbackWaitMs}ms before fallback to next model`); - await new Promise((resolve) => { - const timer = setTimeout(resolve, fallbackWaitMs); - deps.signal?.addEventListener( - "abort", - () => { - clearTimeout(timer); - resolve(undefined); - }, - { once: true } - ); - }); + await waitForCooldownAwareRetry(fallbackWaitMs, deps.signal); if (deps.signal?.aborted) { deps.log.info("COMBO", `Client disconnected during fallback wait — aborting`); return { ok: false, response: errorResponse(499, "Client disconnected") }; diff --git a/open-sse/services/combo/roundRobinCombo.ts b/open-sse/services/combo/roundRobinCombo.ts index 606e091bdc62..a9d32709d802 100644 --- a/open-sse/services/combo/roundRobinCombo.ts +++ b/open-sse/services/combo/roundRobinCombo.ts @@ -154,6 +154,7 @@ import { resolveComboDailyReset } from "./comboDailyResetClock.ts"; import { resolveTargetTokenLimit } from "./targetTokenLimit.ts"; import { isProviderProbeResponse } from "../../../src/shared/utils/providerProbeResult.ts"; import { recordLocalCircuitRefusal } from "./localCircuitRefusal.ts"; +import { waitForCooldownAwareRetry } from "../../../src/sse/services/cooldownAwareRetry.ts"; /** * Handle round-robin combo: each request goes to the next model in circular order. @@ -1378,17 +1379,7 @@ export async function handleRoundRobinCombo({ : 0; if ([502, 503, 504].includes(result.status) && fallbackWaitMs > 0) { log.debug?.("COMBO-RR", `Waiting ${fallbackWaitMs}ms before fallback to next model`); - await new Promise((resolve) => { - const timer = setTimeout(resolve, fallbackWaitMs); - signal?.addEventListener( - "abort", - () => { - clearTimeout(timer); - resolve(undefined); - }, - { once: true } - ); - }); + await waitForCooldownAwareRetry(fallbackWaitMs, signal); if (signal?.aborted) { log.info("COMBO-RR", `Client disconnected during fallback wait — aborting`); return errorResponse(499, "Client disconnected"); diff --git a/tests/unit/combo-fallback-wait-cancellation.test.ts b/tests/unit/combo-fallback-wait-cancellation.test.ts new file mode 100644 index 000000000000..ea9ddc840e75 --- /dev/null +++ b/tests/unit/combo-fallback-wait-cancellation.test.ts @@ -0,0 +1,316 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { getEventListeners } from "node:events"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; +import { setImmediate } from "node:timers/promises"; + +const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-fallback-wait-")); +const ORIGINAL_DATA_DIR = process.env.DATA_DIR; +const ORIGINAL_AUTO_BACKUP = process.env.DISABLE_SQLITE_AUTO_BACKUP; +process.env.DATA_DIR = TEST_DATA_DIR; +process.env.DISABLE_SQLITE_AUTO_BACKUP = "true"; + +const { handleComboChat } = await import("../../open-sse/services/combo.ts"); +const { resetDbInstance } = await import("../../src/lib/db/core.ts"); + +const WAIT_MS = 137; +type Strategy = "priority" | "round-robin"; +type TimerHandle = ReturnType; + +async function withFallbackClock( + run: (clock: { + fire(): void; + active(): number; + created(): number; + cleared(): number; + }) => Promise +) { + const originalSetTimeout = globalThis.setTimeout; + const originalClearTimeout = globalThis.clearTimeout; + const timers = new Map void>(); + let created = 0; + let cleared = 0; + + globalThis.setTimeout = (( + callback: (...args: unknown[]) => void, + delay?: number, + ...args: unknown[] + ) => { + if (delay !== WAIT_MS) return originalSetTimeout(callback, delay, ...args); + const handle = { unref() {} } as unknown as TimerHandle; + timers.set(handle, () => callback(...args)); + created++; + return handle; + }) as typeof setTimeout; + globalThis.clearTimeout = ((handle: TimerHandle) => { + if (timers.delete(handle)) cleared++; + else originalClearTimeout(handle); + }) as typeof clearTimeout; + + const fire = () => { + const pending = [...timers.values()]; + timers.clear(); + for (const callback of pending) callback(); + }; + try { + await run({ + fire, + active: () => timers.size, + created: () => created, + cleared: () => cleared, + }); + } finally { + fire(); + globalThis.setTimeout = originalSetTimeout; + globalThis.clearTimeout = originalClearTimeout; + } +} + +let invocation = 0; +function startCombo( + strategy: Strategy, + options: { + status?: number; + delayMs?: number; + targets?: number; + maxGlobalAttempts?: number; + onWait?: () => void; + } = {} +) { + const controller = new AbortController(); + const attempted: string[] = []; + const waitLogs: string[] = []; + const infoLogs: string[] = []; + const models = Array.from( + { length: options.targets ?? 2 }, + (_, index) => `synthetic-fallback-${invocation}-${index}/model` + ); + let enterWait!: () => void; + const waitEntered = new Promise((resolve) => { + enterWait = resolve; + }); + let listenersBeforeWait = 0; + let settled = false; + const response = handleComboChat({ + body: { messages: [{ role: "user", content: "fallback wait" }] }, + combo: { + name: `fallback-wait-${strategy}-${invocation++}`, + strategy, + models, + config: { + maxRetries: 0, + maxSetRetries: 0, + maxGlobalAttempts: options.maxGlobalAttempts ?? 30, + fallbackDelayMs: options.delayMs ?? WAIT_MS, + targetTimeoutMs: 0, + }, + }, + handleSingleModel: async (_body, modelStr) => { + attempted.push(modelStr); + return new Response( + JSON.stringify( + attempted.length === 1 + ? { error: { message: "synthetic upstream unavailable" } } + : { choices: [{ message: { content: "fallback succeeded" } }] } + ), + { + status: attempted.length === 1 ? (options.status ?? 502) : 200, + headers: { "content-type": "application/json" }, + } + ); + }, + signal: controller.signal, + log: { + info(_tag: string, message: string) { + infoLogs.push(message); + }, + warn() {}, + error() {}, + debug(_tag: string, message: string) { + if (!message.includes("before fallback to next model")) return; + listenersBeforeWait = getEventListeners(controller.signal, "abort").length; + waitLogs.push(message); + options.onWait?.(); + enterWait(); + }, + }, + settings: {}, + allCombos: [], + hiddenModelsByProvider: [], + }).then((result) => { + settled = true; + return result; + }); + return { + controller, + attempted, + models, + response, + waitEntered, + waitLogs, + infoLogs, + settled: () => settled, + listenersBeforeWait: () => listenersBeforeWait, + listeners: () => getEventListeners(controller.signal, "abort").length, + }; +} + +test.after(() => { + resetDbInstance(); + if (ORIGINAL_DATA_DIR === undefined) delete process.env.DATA_DIR; + else process.env.DATA_DIR = ORIGINAL_DATA_DIR; + if (ORIGINAL_AUTO_BACKUP === undefined) delete process.env.DISABLE_SQLITE_AUTO_BACKUP; + else process.env.DISABLE_SQLITE_AUTO_BACKUP = ORIGINAL_AUTO_BACKUP; + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); +}); + +for (const strategy of ["priority", "round-robin"] as const) { + test( + `${strategy}: abort at fallback-wait entry returns 499 without starting a timer`, + { timeout: 5000 }, + async () => { + await withFallbackClock(async (clock) => { + const run = startCombo(strategy, { onWait: () => run.controller.abort() }); + await run.waitEntered; + await setImmediate(); + const settledWithoutTimer = run.settled(); + const created = clock.created(); + // Release the old implementation's missed-abort timer so RED cannot hang the test process. + clock.fire(); + const response = await run.response; + + assert.equal(response.status, 499); + assert.deepEqual(run.attempted, run.models.slice(0, 1)); + assert.equal(settledWithoutTimer, true, "cancellation must settle before the delay fires"); + assert.equal(created, 0, "an already-aborted wait must not allocate a timer"); + assert.equal(run.listeners(), 0); + assert.equal(clock.active(), 0); + }); + } + ); + + test( + `${strategy}: abort during fallback wait clears the timer and abort listener`, + { timeout: 5000 }, + async () => { + await withFallbackClock(async (clock) => { + const run = startCombo(strategy); + await run.waitEntered; + assert.equal(run.settled(), false); + assert.equal(clock.active(), 1); + assert.equal(run.listeners(), run.listenersBeforeWait() + 1); + run.controller.abort(); + const response = await run.response; + + assert.equal(response.status, 499); + assert.deepEqual(run.attempted, run.models.slice(0, 1)); + assert.ok(run.infoLogs.includes("Client disconnected during fallback wait — aborting")); + assert.equal(clock.created(), 1); + assert.equal(clock.cleared(), 1); + assert.equal(clock.active(), 0); + assert.equal(run.listeners(), 0); + clock.fire(); + await setImmediate(); + assert.deepEqual(run.attempted, run.models.slice(0, 1)); + }); + } + ); + + test( + `${strategy}: abort after the timer fires still returns 499 before fallback dispatch`, + { timeout: 5000 }, + async () => { + await withFallbackClock(async (clock) => { + const run = startCombo(strategy); + await run.waitEntered; + clock.fire(); + run.controller.abort(); + const response = await run.response; + + assert.equal(response.status, 499); + assert.deepEqual(run.attempted, run.models.slice(0, 1)); + assert.equal(clock.created(), 1); + assert.equal(clock.active(), 0); + assert.equal(run.listeners(), 0); + }); + } + ); + + for (const status of [502, 503, 504]) { + test( + `${strategy}: ${status} waits the requested delay, then dispatches the next target`, + { timeout: 5000 }, + async () => { + await withFallbackClock(async (clock) => { + const run = startCombo(strategy, { status }); + await run.waitEntered; + assert.deepEqual(run.attempted, run.models.slice(0, 1)); + assert.equal(run.settled(), false); + assert.deepEqual(run.waitLogs, [`Waiting ${WAIT_MS}ms before fallback to next model`]); + assert.equal(clock.active(), 1); + clock.fire(); + const response = await run.response; + + assert.equal(response.status, 200); + assert.deepEqual(run.attempted, run.models); + assert.equal(clock.created(), 1); + assert.equal(clock.active(), 0); + assert.equal(run.listeners(), 0, "completed waits release listeners"); + run.controller.abort(); + await setImmediate(); + assert.deepEqual(run.attempted, run.models); + assert.equal(clock.cleared(), 0, "a later abort must not clear a completed wait"); + }); + } + ); + } + + for (const scenario of [ + { name: "zero delay", delayMs: 0, status: 502 }, + { name: "ineligible status", delayMs: WAIT_MS, status: 500 }, + ]) { + test( + `${strategy}: ${scenario.name} dispatches fallback without a wait`, + { timeout: 5000 }, + async () => { + await withFallbackClock(async (clock) => { + const run = startCombo(strategy, scenario); + const response = await run.response; + assert.equal(response.status, 200); + assert.deepEqual(run.attempted, run.models); + assert.deepEqual(run.waitLogs, []); + assert.equal(clock.created(), 0); + assert.equal(run.listeners(), 0); + }); + } + ); + } + + for (const scenario of [ + { name: "target exhaustion", targets: 1 }, + { name: "attempt budget exhaustion", maxGlobalAttempts: 1 }, + ]) { + test( + `${strategy}: ${scenario.name} never dispatches an extra target`, + { timeout: 5000 }, + async () => { + await withFallbackClock(async (clock) => { + const run = startCombo(strategy, scenario); + await run.waitEntered; + assert.deepEqual(run.attempted, run.models.slice(0, 1)); + clock.fire(); + const response = await run.response; + + assert.equal(response.ok, false); + if (scenario.targets === 1) assert.equal(response.status, 502); + assert.deepEqual(run.attempted, run.models.slice(0, 1)); + assert.equal(clock.created(), 1); + assert.equal(clock.active(), 0); + assert.equal(run.listeners(), 0); + }); + } + ); + } +} From 3721b273c4ec60421fa20d6f496629d4614c482c Mon Sep 17 00:00:00 2001 From: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> Date: Wed, 4 Nov 2026 13:44:03 -0300 Subject: [PATCH 06/20] fix(combo): honor recovery for pre-content transport failures (#18610) --- ...610-combo-precontent-transport-recovery.md | 1 + open-sse/services/combo.ts | 13 +- open-sse/services/combo/dispatchPrelude.ts | 176 ++- .../services/combo/executeTargetAttempt.ts | 29 +- open-sse/services/combo/firstOutputStream.ts | 101 +- .../combo/preContentTransportFailure.ts | 167 +++ open-sse/services/combo/roundRobinCombo.ts | 38 +- open-sse/services/combo/runtimeUnits.ts | 60 +- open-sse/services/combo/validateQuality.ts | 123 +- stryker.conf.json | 4 +- ...nt-transport-recovery-default-gate.test.ts | 124 ++ ...content-transport-recovery-framing.test.ts | 189 +++ ...content-transport-recovery-routing.test.ts | 538 +++++++ ...content-transport-recovery-scanner.test.ts | 152 ++ ...ombo-precontent-transport-recovery.test.ts | 1329 +++++++++++++++++ 15 files changed, 2919 insertions(+), 125 deletions(-) create mode 100644 changelog.d/fixes/18610-combo-precontent-transport-recovery.md create mode 100644 open-sse/services/combo/preContentTransportFailure.ts create mode 100644 tests/unit/combo-precontent-transport-recovery-default-gate.test.ts create mode 100644 tests/unit/combo-precontent-transport-recovery-framing.test.ts create mode 100644 tests/unit/combo-precontent-transport-recovery-routing.test.ts create mode 100644 tests/unit/combo-precontent-transport-recovery-scanner.test.ts create mode 100644 tests/unit/combo-precontent-transport-recovery.test.ts diff --git a/changelog.d/fixes/18610-combo-precontent-transport-recovery.md b/changelog.d/fixes/18610-combo-precontent-transport-recovery.md new file mode 100644 index 000000000000..88f69638dad5 --- /dev/null +++ b/changelog.d/fixes/18610-combo-precontent-transport-recovery.md @@ -0,0 +1 @@ +- Honor enabled stream recovery for structured transport read failures before usable combo output, with bounded retries and client cancellation cleanup. First-output inspection limits conservatively prevent a new transport retry. diff --git a/open-sse/services/combo.ts b/open-sse/services/combo.ts index 1e5c1a5c29bc..8c184d121386 100644 --- a/open-sse/services/combo.ts +++ b/open-sse/services/combo.ts @@ -63,6 +63,7 @@ import type { SingleModelTarget, ComboLogger, HandleComboChatOptions, + HandleSingleModel, ResolvedComboTarget, AutoProviderCandidate, HistoricalLatencyStatsEntry, @@ -838,8 +839,16 @@ async function handleComboChatInner({ const traceInvocationId = invocationId ?? createInvocationId(); startComboTrace(traceInvocationId, { strategy, comboName: combo.name }); + const preContentTransportRecovery = + clientRequestedStream && resilienceSettings.streamRecovery.enabled === true; + const guardedHandleSingleModel: HandleSingleModel = preContentTransportRecovery + ? async (...dispatchArgs) => + signal?.aborted + ? errorResponse(499, "Client disconnected") + : handleSingleModel(...dispatchArgs) + : handleSingleModel; const handleSingleModelWithTimeout = buildTargetTimeoutRunner({ - handleSingleModel, + handleSingleModel: guardedHandleSingleModel, comboTargetTimeoutMs, resolveTargetTimeoutMs: (target) => resolveTargetTimeoutMsForTarget( @@ -869,6 +878,8 @@ async function handleComboChatInner({ handleSingleModelWithTimeout, log, hiddenModelsByProvider, + preContentTransportRecovery: resilienceSettings.streamRecovery.enabled === true, + signal, }); if (pinnedDispatch) return pinnedDispatch; } diff --git a/open-sse/services/combo/dispatchPrelude.ts b/open-sse/services/combo/dispatchPrelude.ts index ad85a19e3416..43fc3a404e90 100644 --- a/open-sse/services/combo/dispatchPrelude.ts +++ b/open-sse/services/combo/dispatchPrelude.ts @@ -12,13 +12,14 @@ * * Extracted from combo.ts as a pure move (#3501). No behaviour change. */ +import { cloneForQuality } from "./preContentTransportFailure.ts"; import { getCachedProviderPoolConnections } from "../providerConnectionPool.ts"; import { getCircuitBreaker } from "../../../src/shared/utils/circuitBreaker"; import { fisherYatesShuffle, getNextFromDeck } from "../../../src/shared/utils/shuffleDeck"; import { handleFusionChat, type FusionTuning } from "../fusion.ts"; import { handleAgenticPipelineChat, type AgenticOrchestrationConfig } from "../agenticPipeline.ts"; import { getResolvedModelCapabilities } from "../modelCapabilities.ts"; -import { errorResponseWithComboDiagnostics } from "../../utils/error.ts"; +import { errorResponse, errorResponseWithComboDiagnostics } from "../../utils/error.ts"; import { parseModel } from "../model.ts"; import { handlePipelineChat, type PipelineStep } from "../pipeline.ts"; import type { resolveComboSetupConfig } from "../comboConfig.ts"; @@ -216,6 +217,11 @@ export function buildDefaultNesting( ); } +/** True once the client went away while pre-content transport recovery is in charge. */ +function recoveryAborted(recovery: boolean | undefined, signal?: AbortSignal | null): boolean { + return recovery === true && signal?.aborted === true; +} + /** * Decide whether the honored pin's response is good enough to return as-is. * Returns the response to serve it, or null to fall through to the combo @@ -229,25 +235,37 @@ async function evaluatePinnedResponse(args: { clientRequestedStream: boolean; config: ComboSetupConfig; log: ComboLogger; + preContentTransportRecovery?: boolean; + signal?: AbortSignal | null; }): Promise { - const { body, pinnedResult, pinnedModel, hasOtherTargets, clientRequestedStream, config, log } = - args; + const { + body, + pinnedResult, + pinnedModel, + hasOtherTargets, + clientRequestedStream, + config, + log, + preContentTransportRecovery, + signal, + } = args; if (pinnedResult.ok) { - let pinnedClone: Response; - try { - pinnedClone = pinnedResult.clone(); - } catch { - pinnedClone = pinnedResult; - } + const pinnedClone = cloneForQuality(pinnedResult); const pinnedQuality = await validateResponseQuality( pinnedClone, clientRequestedStream, log, config.responseValidation, - null, - await isTrustedEmptyTurn(parseModel(pinnedModel).provider, pinnedResult) + preContentTransportRecovery ? signal : null, + await isTrustedEmptyTurn(parseModel(pinnedModel).provider, pinnedResult), + preContentTransportRecovery ); releaseQualityClone(pinnedClone, pinnedResult, pinnedQuality); + if (recoveryAborted(preContentTransportRecovery, signal)) { + void pinnedQuality.clonedResponse?.body?.cancel().catch(() => {}); + releaseRejectedQualityResponse(pinnedClone, pinnedResult); + return errorResponse(499, "Client disconnected"); + } // #13954 (opt-in): a stalled pin gives the turn back to the combo strategy. const pinnedStalled = pinnedQuality.valid && @@ -277,6 +295,84 @@ async function evaluatePinnedResponse(args: { return null; } +/** + * Dispatch to the honored pin and decide whether its response is served. Returns the response to + * serve (including a 499 when the client went away), or null to fall through to the combo strategy. + */ +async function dispatchHonoredPin(args: { + body: Record; + combo: ComboLike; + pinnedModel: string; + comboTargets: ReturnType; + config: ComboSetupConfig; + clientRequestedStream: boolean; + handleSingleModelWithTimeout: HandleSingleModel; + log: ComboLogger; + preContentTransportRecovery: boolean; + signal?: AbortSignal | null; +}): Promise { + const { + body, + combo, + pinnedModel, + comboTargets, + config, + clientRequestedStream, + handleSingleModelWithTimeout, + log, + preContentTransportRecovery, + signal, + } = args; + let pinnedResult: Response | null = null; + try { + // #5501: the combo system_message also expands on the pinned context path — + // a session pin bypasses the main loop, so without this the template would + // go literal from the second in-session request on. Target context comes + // from the pinned model's resolved combo target when available. + const pinnedTarget = comboTargets.find((t) => t.modelStr === pinnedModel); + const pinnedBody = expandComboSystemPromptIfPresent(body, combo, { + modelId: pinnedModel, + providerId: pinnedTarget && pinnedTarget.provider !== "unknown" ? pinnedTarget.provider : "", + account: + typeof pinnedTarget?.label === "string" && pinnedTarget.label.trim().length > 0 + ? pinnedTarget.label.trim() + : "", + fingerprint: pinnedTarget ? (resolveTargetFingerprint(pinnedTarget) ?? "") : "", + }); + if (recoveryAborted(preContentTransportRecovery, signal)) + return errorResponse(499, "Client disconnected"); + pinnedResult = await handleSingleModelWithTimeout(pinnedBody, pinnedModel, { + modelPinned: true, + } as SingleModelTarget); + } catch (pinErr) { + if (recoveryAborted(preContentTransportRecovery, signal)) + return errorResponse(499, "Client disconnected"); + log.warn( + "COMBO", + `Pinned model ${pinnedModel} threw error: ${pinErr instanceof Error ? pinErr.message : String(pinErr)}, falling through to combo retry/fallback` + ); + } + if (recoveryAborted(preContentTransportRecovery, signal)) { + if (pinnedResult) releaseRejectedQualityResponse(pinnedResult, pinnedResult); + return errorResponse(499, "Client disconnected"); + } + if (pinnedResult) { + const accepted = await evaluatePinnedResponse({ + body, + pinnedResult, + pinnedModel, + hasOtherTargets: comboTargets.length > 1, + clientRequestedStream, + config, + log, + preContentTransportRecovery, + signal, + }); + return accepted; + } + return null; +} + /** * Context-cache pin routing (Fix #679). Returns the pinned model's response when * it is honored AND usable; returns null to fall through to the combo strategy. @@ -292,6 +388,8 @@ export async function tryPinnedModelDispatch(args: { handleSingleModelWithTimeout: HandleSingleModel; log: ComboLogger; hiddenModelsByProvider?: HiddenModelsByProvider; + preContentTransportRecovery?: boolean; + signal?: AbortSignal | null; }): Promise { const { body, @@ -314,6 +412,11 @@ export async function tryPinnedModelDispatch(args: { // combo's resolved targets (refs flattened) before honoring it. Only validate // when allCombos is authoritative (non-empty) so we can resolve combo-refs; // the auto-combo redirect path passes an empty list and keeps prior behavior. + const preContentTransportRecovery = + clientRequestedStream && args.preContentTransportRecovery === true; + const signal = args.signal; + if (recoveryAborted(preContentTransportRecovery, signal)) + return errorResponse(499, "Client disconnected"); const haveFullCombos = Array.isArray(allCombos) ? allCombos.length > 0 : !!allCombos; // Eagerly resolve the combo's targets once (used for the pin-validity check AND // #5501 template expansion). A non-authoritative allCombos (empty/missing) @@ -341,44 +444,19 @@ export async function tryPinnedModelDispatch(args: { "COMBO", `Bypassing strategy — routing directly to pinned context model: ${pinnedModel}` ); - let pinnedResult: Response | null = null; - try { - // #5501: the combo system_message also expands on the pinned context path — - // a session pin bypasses the main loop, so without this the template would - // go literal from the second in-session request on. Target context comes - // from the pinned model's resolved combo target when available. - const pinnedTarget = comboTargets.find((t) => t.modelStr === pinnedModel); - const pinnedBody = expandComboSystemPromptIfPresent(body, combo, { - modelId: pinnedModel, - providerId: - pinnedTarget && pinnedTarget.provider !== "unknown" ? pinnedTarget.provider : "", - account: - typeof pinnedTarget?.label === "string" && pinnedTarget.label.trim().length > 0 - ? pinnedTarget.label.trim() - : "", - fingerprint: pinnedTarget ? (resolveTargetFingerprint(pinnedTarget) ?? "") : "", - }); - pinnedResult = await handleSingleModelWithTimeout(pinnedBody, pinnedModel, { - modelPinned: true, - } as SingleModelTarget); - } catch (pinErr) { - log.warn( - "COMBO", - `Pinned model ${pinnedModel} threw error: ${pinErr instanceof Error ? pinErr.message : String(pinErr)}, falling through to combo retry/fallback` - ); - } - if (pinnedResult) { - const accepted = await evaluatePinnedResponse({ - body, - pinnedResult, - pinnedModel, - hasOtherTargets: comboTargets.length > 1, - clientRequestedStream, - config, - log, - }); - if (accepted) return accepted; - } + const honored = await dispatchHonoredPin({ + body, + combo, + pinnedModel, + comboTargets, + config, + clientRequestedStream, + handleSingleModelWithTimeout, + log, + preContentTransportRecovery, + signal, + }); + if (honored) return honored; // Fall through to the target iteration loop below — retries and sibling // models will be tried via the normal combo machinery. } diff --git a/open-sse/services/combo/executeTargetAttempt.ts b/open-sse/services/combo/executeTargetAttempt.ts index 5ac6c20f62aa..0a691ffe639a 100644 --- a/open-sse/services/combo/executeTargetAttempt.ts +++ b/open-sse/services/combo/executeTargetAttempt.ts @@ -5,6 +5,7 @@ * * @internal — not part of the public combo.ts barrel. */ +import { cloneForQuality } from "./preContentTransportFailure.ts"; import { checkFallbackError, classifyLockoutReason, @@ -120,6 +121,7 @@ import { isReadinessFailure, } from "./emptyContentRetry.ts"; import { isEmptyStreamSiblingRecoveryEnabled } from "../streamRecovery.ts"; +import { waitForCooldownAwareRetry } from "../../../src/sse/services/cooldownAwareRetry.ts"; export async function executeTargetAttempt( opts: ExecuteTargetAttemptOptions @@ -154,6 +156,8 @@ export async function executeTargetAttempt( // this loop's existing retry on a sibling connection with no lockout. Off = release path. const emptyStreamSiblingRecovery = isEmptyStreamSiblingRecoveryEnabled(); let emptyTurnConnectionId: string | null = null; + const preContentTransportRecovery = + deps.clientRequestedStream && deps.resilienceSettings.streamRecovery.enabled === true; // Retry loop for transient errors for (let retry = 0; retry <= deps.maxRetries; retry++) { // Fix #1681: Bail out immediately if the client has disconnected @@ -334,6 +338,9 @@ export async function executeTargetAttempt( : "", fingerprint: resolveTargetFingerprint(target) ?? "", }); + if (preContentTransportRecovery && deps.signal?.aborted) { + return { ok: false, response: errorResponse(499, "Client disconnected") }; + } // #10681: record dispatch once per target (retries keep the first decision). if (!state.dispatchedTargets.has(target.executionKey)) { state.dispatchedTargets.add(target.executionKey); @@ -357,6 +364,11 @@ export async function executeTargetAttempt( failoverBeforeRetry: deps.config.failoverBeforeRetry, }); + if (preContentTransportRecovery && deps.signal?.aborted) { + releaseRejectedQualityResponse(result, result); + return { ok: false, response: errorResponse(499, "Client disconnected") }; + } + // Success — validate response quality before returning if (result.ok) { // #18460 follow-up — a cut buffered stream is a retryable attempt failure. @@ -381,21 +393,22 @@ export async function executeTargetAttempt( // Clone BEFORE quality check — validateResponseQuality reads the body // via getReader() which locks the stream. The clone's body is consumed // by the quality check; the original stays unlocked for piping. - let qualityClone: Response; - try { - qualityClone = result.clone(); - } catch { - qualityClone = result; - } + const qualityClone = cloneForQuality(result); const quality = await validateResponseQuality( qualityClone, deps.clientRequestedStream, deps.log, deps.config.responseValidation as ResponseValidationConfig | null | undefined, - null, - await isTrustedEmptyTurn(provider, result, target.connectionId) + preContentTransportRecovery ? deps.signal : null, + await isTrustedEmptyTurn(provider, result, target.connectionId), + preContentTransportRecovery ); releaseQualityClone(qualityClone, result, quality); + if (preContentTransportRecovery && deps.signal?.aborted) { + void quality.clonedResponse?.body?.cancel().catch(() => {}); + releaseRejectedQualityResponse(qualityClone, result); + return { ok: false, response: errorResponse(499, "Client disconnected") }; + } if (!quality.valid) { releaseRejectedQualityResponse(qualityClone, result); deps.log.warn( diff --git a/open-sse/services/combo/firstOutputStream.ts b/open-sse/services/combo/firstOutputStream.ts index e8c4e1c39dbb..84e60e7ac4bd 100644 --- a/open-sse/services/combo/firstOutputStream.ts +++ b/open-sse/services/combo/firstOutputStream.ts @@ -19,12 +19,18 @@ */ import { COMBO_PER_MODEL_TIMEOUT_REASON } from "./comboAbortReasons.ts"; import { sseChunkCarriesOutput } from "../../utils/sseOutputSignal.ts"; +import { createSSEDataLineNormalizer } from "../../utils/streamHelpers.ts"; +import { + createTransportReplayObserver, + inspectTransportReplayPayload, +} from "./preContentTransportFailure.ts"; /** Floor for the gate window, so a dispatch that ate the whole budget cannot abandon a live target. */ const MIN_FIRST_OUTPUT_BUDGET_MS = 250; /** Give up accumulating at this size: a streaming body this far into silence is not answering. */ const MAX_BUFFERED_CHARS = 64 * 1024; +const MAX_PENDING_REPLAY_LINES = 256; /** * Why the gate stopped waiting. @@ -71,9 +77,20 @@ export class SseOutputScanner { private pendingLine = ""; private dataLines: string[] = []; private bufferedChars = 0; + private previousChunkEndedWithCr = false; + private replayNormalizer = createSSEDataLineNormalizer(); + private replayBufferedChars = 0; + private replayBufferedLines = 0; + private replayInspectionDisabled = false; + private replayOverflowed = false; + private replayEventType = ""; + + constructor(private readonly observeReplay?: () => void) {} /** Feed decoded text; true once an event has shown output. */ push(text: string): boolean { + const previousCr = this.previousChunkEndedWithCr; + if (text.length > 0) this.previousChunkEndedWithCr = text.endsWith("\r"); this.pendingLine += text; let start = 0; let index = 0; @@ -95,7 +112,7 @@ export class SseOutputScanner { index += 1; } start = index; - if (this.consumeLine(line)) { + if (this.consumeLine(line, previousCr && index === 1 && code === 10)) { sawOutput = true; break; } @@ -113,28 +130,98 @@ export class SseOutputScanner { * line is treated as "talking in a framing this reader does not know" rather than failed. */ overflowedWithoutOutput(): boolean { - return this.bufferedChars > MAX_BUFFERED_CHARS; + const overflowed = this.bufferedChars > MAX_BUFFERED_CHARS; + if (overflowed) this.observeReplay?.(); + return overflowed; } /** Evaluate whatever is left after the body closed without a trailing blank line. */ flush(): boolean { + this.resetReplayFrame(); if (this.dataLines.length === 0) return false; const payload = this.dataLines.join("\n"); this.dataLines = []; - return payload.length > 0 && carriesOutput(payload); + const ready = payload.length > 0 && carriesOutput(payload); + if (ready) this.observeReplay?.(); + return ready; } - private consumeLine(line: string): boolean { + private consumeLine(line: string, splitCrLf = false): boolean { + if (!splitCrLf && !this.replayInspectionDisabled) { + try { + this.observeReplayLine(line); + } catch { + this.observeReplay?.(); + this.resetReplayFrame(); + this.replayInspectionDisabled = true; + } + } if (line === "") { const payload = this.dataLines.join("\n"); this.dataLines = []; - return payload.length > 0 && carriesOutput(payload); + const ready = payload.length > 0 && carriesOutput(payload); + if (ready) this.observeReplay?.(); + return ready; } if (!line.startsWith("data:")) return false; const value = line.slice(5); - this.dataLines.push(value.startsWith(" ") ? value.slice(1) : value); + const data = value.startsWith(" ") ? value.slice(1) : value; + this.dataLines.push(data); return false; } + + private resetReplayFrame(): void { + this.replayNormalizer = createSSEDataLineNormalizer(); + this.replayBufferedChars = 0; + this.replayBufferedLines = 0; + this.replayOverflowed = false; + this.replayEventType = ""; + } + + private replayInspectionExceeded(): boolean { + return ( + this.replayOverflowed || + this.replayBufferedChars > MAX_BUFFERED_CHARS || + this.replayBufferedLines > MAX_PENDING_REPLAY_LINES + ); + } + + private observeCompletedReplayLines(completed: string[]): void { + for (const normalized of completed) { + const trimmed = normalized.trim(); + if (trimmed.startsWith("event:")) { + const type = trimmed.slice(6).trim(); + this.replayEventType = type.length <= 64 ? type : ""; + } else if (trimmed.startsWith("data:")) { + const result = inspectTransportReplayPayload(trimmed.slice(5).trim(), this.replayEventType); + if (result.veto) this.observeReplay?.(); + if (result.consumed) this.replayEventType = ""; + } else if (!trimmed) { + this.replayEventType = ""; + } + } + } + + private observeReplayLine(line: string): void { + if (!this.observeReplay) return; + this.replayBufferedChars += line.length + 1; + this.replayBufferedLines++; + if (this.replayInspectionExceeded()) { + // Incomplete inspection cannot authorize a new transport retry. + this.observeReplay(); + this.resetReplayFrame(); + this.replayOverflowed = line.trim().length > 0; + return; + } + // Use quality framing, including implicit field/comment flushes and invalid-JSON fallback. + const completed = this.replayNormalizer.normalize([line]); + this.observeCompletedReplayLines(completed); + if (completed.length > 0) { + const pending = this.replayNormalizer.hasPending(); + this.replayBufferedChars = pending ? line.length + 1 : 0; + this.replayBufferedLines = pending ? 1 : 0; + } + } } /** @@ -158,7 +245,7 @@ export async function awaitFirstOutput( const reader = peekBody.getReader(); const decoder = new TextDecoder(); - const scanner = new SseOutputScanner(); + const scanner = new SseOutputScanner(createTransportReplayObserver(response)); let timer: ReturnType | undefined; const outcome = await new Promise<"output" | FirstOutputStallReason>((resolve) => { diff --git a/open-sse/services/combo/preContentTransportFailure.ts b/open-sse/services/combo/preContentTransportFailure.ts new file mode 100644 index 000000000000..3664d317b483 --- /dev/null +++ b/open-sse/services/combo/preContentTransportFailure.ts @@ -0,0 +1,167 @@ +import { hasOpenAIFinishReason, isKnownNonClaudeStreamPayload } from "../../utils/streamHelpers.ts"; + +const TRANSPORT_CODES = new Set(["ECONNRESET", "EPIPE", "UND_ERR_SOCKET", "UND_ERR_BODY_TIMEOUT"]); +const ABORT_CODES = new Set(["ABORT_ERR", "UND_ERR_ABORTED"]); + +type ReadFailureDisposition = { kind: "abort" } | { kind: "transport"; code: string } | null; + +function isRecord(value: unknown): value is Record { + return value !== null && typeof value === "object" && !Array.isArray(value); +} + +type ReadFailureHop = "abort" | "invalid" | { transportCode: string | null; cause: unknown }; + +/** One link of the cause chain. Reads happen in the same order as the original single loop. */ +function inspectReadFailureHop(current: unknown, seen: Set): ReadFailureHop { + if (!isRecord(current) || seen.has(current)) return "invalid"; + seen.add(current); + const name = current.name; + if (name === "AbortError") return "abort"; + const code = current.code; + if (ABORT_CODES.has(code as string)) return "abort"; + const causeCode = current.causeCode; + if (ABORT_CODES.has(causeCode as string)) return "abort"; + if ([name, code, causeCode].some((value) => value != null && typeof value !== "string")) + return "invalid"; + const transportCode = [code, causeCode].find((value) => TRANSPORT_CODES.has(value as string)); + return { transportCode: (transportCode as string | undefined) ?? null, cause: current.cause }; +} + +/** Inspect the whole bounded chain before accepting a transport code. */ +export function classifyPreContentReadFailure(error: unknown): ReadFailureDisposition { + const seen = new Set(); + let current = error; + let transportCode: string | null = null; + for (let depth = 0; depth < 8; depth++) { + try { + const hop = inspectReadFailureHop(current, seen); + if (hop === "abort") return { kind: "abort" }; + if (hop === "invalid") return null; + transportCode ??= hop.transportCode; + if (hop.cause == null) + return transportCode ? { kind: "transport", code: transportCode } : null; + current = hop.cause; + } catch { + return null; + } + } + return null; +} + +export function safeStreamReadMessage(error: unknown): string { + try { + return String(error instanceof Error ? error.message : error); + } catch { + return "Unreadable stream error"; + } +} + +/** Additional retry vetoes only: do not broaden the validator's EOF rules. */ +export function hasTransportReplayBoundary(parsed: unknown, eventType: string): boolean { + if (eventType.startsWith("content_block_") || eventType === "message_stop") return true; + if ( + [ + "response.completed", + "response.done", + "response.incomplete", + "response.cancelled", + "response.canceled", + ].includes(eventType) + ) + return true; + if (!isRecord(parsed)) return false; + if (isKnownNonClaudeStreamPayload(parsed, eventType) || hasOpenAIFinishReason(parsed)) + return true; + if (eventType === "message_delta" && isRecord(parsed.delta) && parsed.delta.stop_reason != null) + return true; + if ( + parsed.usage && + typeof parsed.usage === "object" && + (!Array.isArray(parsed.choices) || parsed.choices.length === 0) && + !eventType.startsWith("response.") + ) + return true; + return [parsed, parsed.response].some( + (payload) => + isRecord(payload) && + Array.isArray(payload.candidates) && + payload.candidates.some( + (candidate: unknown) => + isRecord(candidate) && + typeof candidate.finishReason === "string" && + candidate.finishReason.length > 0 && + candidate.finishReason !== "FINISH_REASON_UNSPECIFIED" + ) + ); +} + +/** + * Whether an `error` field carries a real failure signal. A key-presence check + * (`!= null`) false-positives on benign values some backends emit on every + * chunk (`{}`, `""`, `false`, `0`) — e.g. tool-call turns where a chunk with + * real tool_calls content also carries `"error": {}`. Only substantive values + * are treated as upstream failures. + */ +function isSubstantiveError(value: unknown): boolean { + if (value === null || value === undefined) return false; + if (typeof value === "string") return value.trim().length > 0; + if (typeof value === "object" && !Array.isArray(value)) { + return Object.keys(value as Record).length > 0; + } + return value === true; +} + +export function isStreamingUpstreamError(parsed: unknown, eventType: string): boolean { + if (eventType === "response.failed" || eventType === "error") return true; + if (!isRecord(parsed)) return false; + if (isSubstantiveError(parsed.error)) return true; + + const nestedResponse = isRecord(parsed.response) ? parsed.response : null; + return nestedResponse?.status === "failed" && nestedResponse.error != null; +} + +// Response identities carry only the shared veto; headers cannot establish it. +const replayVetoes = new WeakMap(); + +export function hasObservedTransportReplayVeto(response: Response): boolean { + return replayVetoes.get(response)?.observed === true; +} + +export function cloneForQuality(response: Response): Response { + try { + const clone = response.clone(); + const state = replayVetoes.get(response); + if (state) replayVetoes.set(clone, state); + return clone; + } catch { + return response; + } +} + +/** Inspect only completed normalized data; keep the original payload type for content checks. */ +export function inspectTransportReplayPayload( + payload: string, + eventType: string +): { consumed: boolean; veto: boolean } { + if (payload.trim() === "[DONE]") return { consumed: false, veto: true }; + try { + const parsed: unknown = JSON.parse(payload); + if (!parsed) return { consumed: false, veto: false }; + const type = + (isRecord(parsed) && typeof parsed.type === "string" ? parsed.type : "") || eventType; + return { + consumed: true, + veto: isStreamingUpstreamError(parsed, type) || hasTransportReplayBoundary(parsed, type), + }; + } catch { + return { consumed: false, veto: false }; + } +} + +export function createTransportReplayObserver(response: Response): () => void { + const state = replayVetoes.get(response) ?? { observed: false }; + replayVetoes.set(response, state); + return () => { + state.observed = true; + }; +} diff --git a/open-sse/services/combo/roundRobinCombo.ts b/open-sse/services/combo/roundRobinCombo.ts index a9d32709d802..063b4c055732 100644 --- a/open-sse/services/combo/roundRobinCombo.ts +++ b/open-sse/services/combo/roundRobinCombo.ts @@ -1,4 +1,5 @@ /** Round-robin combo dispatcher. Lift-as-is from combo.ts. @internal */ +import { cloneForQuality } from "./preContentTransportFailure.ts"; import { checkFallbackError, decayModelFailureCount, @@ -128,6 +129,7 @@ import { isEarlyEofResponse, } from "./emptyContentRetry.ts"; import { isEmptyStreamSiblingRecoveryEnabled } from "../streamRecovery.ts"; +import { waitForCooldownAwareRetry } from "../../../src/sse/services/cooldownAwareRetry.ts"; import { getCachedProviderConnectionById } from "../../../src/lib/db/readCache.ts"; import { applyComboTargetExhaustion } from "./targetExhaustion.ts"; import { isRetryAfterEligibleStatus } from "./unavailableRetryGate.ts"; @@ -154,7 +156,6 @@ import { resolveComboDailyReset } from "./comboDailyResetClock.ts"; import { resolveTargetTokenLimit } from "./targetTokenLimit.ts"; import { isProviderProbeResponse } from "../../../src/shared/utils/providerProbeResult.ts"; import { recordLocalCircuitRefusal } from "./localCircuitRefusal.ts"; -import { waitForCooldownAwareRetry } from "../../../src/sse/services/cooldownAwareRetry.ts"; /** * Handle round-robin combo: each request goes to the next model in circular order. @@ -504,7 +505,11 @@ export async function handleRoundRobinCombo({ // #16438 opt-in (EMPTY_STREAM_SIBLING_RECOVERY_ENABLED): empty_content sibling retry, // abort-aware retry delay and the early-EOF provider-cooldown skip. Off = release path. const emptyStreamSiblingRecovery = isEmptyStreamSiblingRecoveryEnabled(); - const clientGone = () => emptyStreamSiblingRecovery && signal?.aborted === true; + const preContentTransportRecovery = + clientRequestedStream && resilienceSettings.streamRecovery.enabled === true; + let encounteredTransportReadFailure = false; + const clientGone = () => + (emptyStreamSiblingRecovery || preContentTransportRecovery) && signal?.aborted === true; const retryEmptyContent = ({ response, @@ -772,7 +777,9 @@ export async function handleRoundRobinCombo({ "COMBO-RR", `Retrying ${modelStr} in ${retryDelayMs}ms (attempt ${retry + 1}/${maxRetries + 1})` ); - if (emptyStreamSiblingRecovery) { + if (encounteredTransportReadFailure) { + await waitForCooldownAwareRetry(retryDelayMs, signal); + } else if (emptyStreamSiblingRecovery) { await new Promise((resolve) => { const finish = () => { clearTimeout(timer); @@ -834,6 +841,8 @@ export async function handleRoundRobinCombo({ fingerprint: resolveTargetFingerprint(target) ?? "", }); + if (preContentTransportRecovery && signal?.aborted) + return errorResponse(499, "Client disconnected"); rrEvents.attempt(); const result = await Promise.race([ handleSingleModel(attemptBody, modelStr, { @@ -844,6 +853,10 @@ export async function handleRoundRobinCombo({ }), rrSafetyPromise, ]); + if (preContentTransportRecovery && signal?.aborted) { + releaseRejectedQualityResponse(result, result); + return errorResponse(499, "Client disconnected"); + } if (rrExpired) return result; // G4: safety timer won — stop everything // Quota-aware scheduling: reserve the estimated budget for this @@ -867,21 +880,24 @@ export async function handleRoundRobinCombo({ // Success — validate response quality before returning if (result.ok) { - let rrClone: Response; - try { - rrClone = result.clone(); - } catch { - rrClone = result; - } + const rrClone = cloneForQuality(result); const quality = await validateResponseQuality( rrClone, clientRequestedStream, log, config.responseValidation, - null, - await isTrustedEmptyTurn(provider, result, targetForAttempt.connectionId) + preContentTransportRecovery ? signal : null, + await isTrustedEmptyTurn(provider, result, targetForAttempt.connectionId), + preContentTransportRecovery ); releaseQualityClone(rrClone, result, quality); + if (preContentTransportRecovery && signal?.aborted) { + void quality.clonedResponse?.body?.cancel().catch(() => {}); + releaseRejectedQualityResponse(rrClone, result); + return errorResponse(499, "Client disconnected"); + } + encounteredTransportReadFailure ||= + quality.upstreamFailure?.transportReadFailure === true; if (!quality.valid) { releaseRejectedQualityResponse(rrClone, result); log.warn( diff --git a/open-sse/services/combo/runtimeUnits.ts b/open-sse/services/combo/runtimeUnits.ts index cf45761946bb..304ba99124b8 100644 --- a/open-sse/services/combo/runtimeUnits.ts +++ b/open-sse/services/combo/runtimeUnits.ts @@ -5,7 +5,10 @@ * @changes * - [2026-07-24] [Composer] - Skip execute-mode units at concurrency cap before dispatch */ +import { cloneForQuality } from "./preContentTransportFailure.ts"; import { errorResponse } from "../../utils/error.ts"; +import { resolveResilienceSettings } from "../../../src/lib/resilience/settings"; +import { waitForCooldownAwareRetry } from "../../../src/sse/services/cooldownAwareRetry.ts"; import { buildContextBudgetResponse, readBudgetFailure } from "./budgetExhaustion.ts"; import type { ComboDiagnostics } from "../../utils/error.ts"; import { recordComboRequest } from "../comboMetrics.ts"; @@ -215,6 +218,16 @@ export async function executeRuntimeUnitCombo(args: { // model, so later units of that model are skipped rather than replayed. const rejectedModelKeys = new Set(); const clientRequestedStream = args.body?.stream === true; + const preContentTransportRecovery = + clientRequestedStream && + resolveResilienceSettings(args.settings).streamRecovery.enabled === true; + const guardedHandleSingleModel: HandleSingleModel = preContentTransportRecovery + ? async (...dispatchArgs) => + args.signal?.aborted + ? errorResponse(499, "Client disconnected") + : args.handleSingleModel(...dispatchArgs) + : args.handleSingleModel; + let encounteredTransportReadFailure = false; const startTime = Date.now(); const effectiveStrategy = args.effectiveComboStrategy ?? args.strategy; const { honorsFallbackOnlyTargets } = getStrategyTraits(effectiveStrategy); @@ -263,14 +276,15 @@ export async function executeRuntimeUnitCombo(args: { fallbackCount += 1; continue; } - if ( - await isRuntimeUnitAtConcurrencyCap( - unit, - args.allCombos, - undefined, - args.hiddenModelsByProvider - ) - ) { + const atCapacity = await isRuntimeUnitAtConcurrencyCap( + unit, + args.allCombos, + undefined, + args.hiddenModelsByProvider + ); + if (preContentTransportRecovery && args.signal?.aborted) + return { response: errorResponse(499, "Client disconnected"), unit }; + if (atCapacity) { args.log.info( "COMBO", `Skipping ${unit.kind} ${unitDisplayName(unit)} — concurrency cap reached` @@ -284,6 +298,8 @@ export async function executeRuntimeUnitCombo(args: { for (let retry = 0; retry <= maxRetries; retry += 1) { if (args.signal?.aborted) { + if (preContentTransportRecovery) + return { response: errorResponse(499, "Client disconnected"), unit }; lastResponse = errorResponse(499, "Client disconnected"); await observeFailure(lastResponse, unit); return { response: finalFailure(lastResponse), unit }; @@ -299,7 +315,11 @@ export async function executeRuntimeUnitCombo(args: { return { response: finalFailure(lastResponse), unit }; } if (retry > 0) { - await new Promise((resolve) => setTimeout(resolve, retryDelayMs)); + if (encounteredTransportReadFailure) { + await waitForCooldownAwareRetry(retryDelayMs, args.signal); + if (args.signal?.aborted) + return { response: errorResponse(499, "Client disconnected"), unit }; + } else await new Promise((resolve) => setTimeout(resolve, retryDelayMs)); } attemptedUnits.push({ provider: unit.kind === "model" ? unit.provider : "combo-ref", @@ -314,7 +334,7 @@ export async function executeRuntimeUnitCombo(args: { body: args.body, unit, allCombos: args.allCombos, - handleSingleModel: args.handleSingleModel, + handleSingleModel: guardedHandleSingleModel, isModelAvailable: args.isModelAvailable, runCombo: args.runCombo, baseOptions: args.baseOptions, @@ -323,6 +343,10 @@ export async function executeRuntimeUnitCombo(args: { effectiveComboStrategy: effectiveStrategy, fallbackAttempts: fallbackCount, }); + if (preContentTransportRecovery && args.signal?.aborted) { + releaseRejectedQualityResponse(response, response); + return { response: errorResponse(499, "Client disconnected"), unit }; + } // #17049: a nested child combo already ended on a provider safety stop (HTTP-level or // classified in-stream); the parent must not replace it with a sibling unit. if (unit.kind === "combo-ref" && (await isSafetyRejection(response))) { @@ -347,12 +371,7 @@ export async function executeRuntimeUnitCombo(args: { }); return { response, unit }; } - let unitClone: Response; - try { - unitClone = response.clone(); - } catch { - unitClone = response; - } + const unitClone = cloneForQuality(response); const quality = await validateResponseQuality( unitClone, clientRequestedStream, @@ -360,9 +379,16 @@ export async function executeRuntimeUnitCombo(args: { args.config.responseValidation as ResponseValidationConfig | undefined, args.signal, unit.kind === "model" && - (await isTrustedEmptyTurn(unit.provider, response, unit.connectionId)) + (await isTrustedEmptyTurn(unit.provider, response, unit.connectionId)), + preContentTransportRecovery ); releaseQualityClone(unitClone, response, quality); + if (preContentTransportRecovery && args.signal?.aborted) { + void quality.clonedResponse?.body?.cancel().catch(() => {}); + releaseRejectedQualityResponse(unitClone, response); + return { response: errorResponse(499, "Client disconnected"), unit }; + } + encounteredTransportReadFailure ||= quality.upstreamFailure?.transportReadFailure === true; if (quality.valid) { recordComboRequest(args.combo.name, unit.modelStr, { success: true, diff --git a/open-sse/services/combo/validateQuality.ts b/open-sse/services/combo/validateQuality.ts index e96e1f9878bc..8a9a043e04c4 100644 --- a/open-sse/services/combo/validateQuality.ts +++ b/open-sse/services/combo/validateQuality.ts @@ -19,6 +19,13 @@ import { evaluateResponseValidation, type ResponseValidationConfig } from "./res import { getReasoningTokens } from "../../../src/lib/usage/tokenAccounting.ts"; import { REASONING_BUFFER_MIN_TRIGGER } from "../reasoningTokenBuffer.ts"; import type { ComboRetryAfter } from "./types.ts"; +import { + classifyPreContentReadFailure, + safeStreamReadMessage, + hasTransportReplayBoundary, + hasObservedTransportReplayVeto, + isStreamingUpstreamError, +} from "./preContentTransportFailure.ts"; /** * #12659: below this actual `completion_tokens` count, a reasoning-truncated @@ -254,31 +261,6 @@ function isRecord(value: unknown): value is Record { return !!value && typeof value === "object" && !Array.isArray(value); } -/** - * Whether an `error` field carries a real failure signal. A key-presence check - * (`!= null`) false-positives on benign values some backends emit on every - * chunk (`{}`, `""`, `false`, `0`) — e.g. tool-call turns where a chunk with - * real tool_calls content also carries `"error": {}`. Only substantive values - * are treated as upstream failures. - */ -function isSubstantiveError(value: unknown): boolean { - if (value === null || value === undefined) return false; - if (typeof value === "string") return value.trim().length > 0; - if (typeof value === "object" && !Array.isArray(value)) { - return Object.keys(value as Record).length > 0; - } - return value === true; -} - -function isStreamingUpstreamError(parsed: unknown, eventType: string): boolean { - if (eventType === "response.failed" || eventType === "error") return true; - if (!isRecord(parsed)) return false; - if (isSubstantiveError(parsed.error)) return true; - - const nestedResponse = isRecord(parsed.response) ? parsed.response : null; - return nestedResponse?.status === "failed" && nestedResponse.error != null; -} - export interface StreamingUpstreamFailure { status: number; type?: string; @@ -292,6 +274,8 @@ export interface StreamingUpstreamFailure { * only when EMPTY_STREAM_SIBLING_RECOVERY_ENABLED is on; `code` keeps the wire value. */ emptyContent?: boolean; + /** In-process transport read failure, never inferred from an upstream SSE payload. */ + transportReadFailure?: boolean; } export interface ResponseQualityResult { @@ -365,7 +349,8 @@ export async function validateResponseQuality( log: { warn?: (...args: unknown[]) => void }, responseValidation?: ResponseValidationConfig | null, signal?: AbortSignal | null, - trustedEmptyTurn: boolean | (() => boolean) = false + trustedEmptyTurn: boolean | (() => boolean) = false, + preContentTransportRecovery = false ): Promise { const permitsEmptyTurn = () => typeof trustedEmptyTurn === "function" ? trustedEmptyTurn() : trustedEmptyTurn; @@ -391,6 +376,18 @@ export async function validateResponseQuality( const reader = response.body.getReader(); const decoder = new TextDecoder("utf-8"); + const cancelPeekReader = (reason?: unknown) => { + try { + void reader.cancel(reason).catch(() => {}); + } catch { + /* best effort */ + } + try { + reader.releaseLock(); + } catch { + /* already released */ + } + }; // Raw Uint8Array chunks accumulated so far — used to replay the prefix // in the returned clonedResponse. @@ -444,6 +441,7 @@ export async function validateResponseQuality( let sawStructuredSSE = false; let upstreamFailure: StreamingUpstreamFailure | null = null; let sawTerminator = false; + let transportReplayBoundary = false; // Set when the streaming peek loop hits an "outcome === content" verdict // (a content_block_* event observed). Guards the catch-block failover // check below so a stream that already produced content before an error @@ -526,6 +524,8 @@ export async function validateResponseQuality( const eventType = (typeof parsed.type === "string" ? parsed.type : null) || pendingEventType || ""; pendingEventType = ""; + if (preContentTransportRecovery && hasTransportReplayBoundary(parsed, eventType)) + transportReplayBoundary = true; if (isStreamingUpstreamError(parsed, eventType)) { upstreamFailure = classifyStreamingUpstreamFailure(parsed) ?? upstreamFailure; @@ -557,6 +557,7 @@ export async function validateResponseQuality( // Snapshot the prefix so mutations after this point don't affect it. const prefix = bufferedChunks.slice(); let prefixIdx = 0; + let replayCancelled = false; const stream = new ReadableStream({ async pull(controller) { // 1. Drain the buffered prefix one chunk at a time. @@ -567,15 +568,26 @@ export async function validateResponseQuality( // 2. Forward the remainder from the original reader. try { const { done, value } = await readerToForward.read(); + if (replayCancelled) return; if (done) { + if (preContentTransportRecovery) cancelPeekReader(); controller.close(); } else { controller.enqueue(value); } } catch { - controller.close(); + if (preContentTransportRecovery) cancelPeekReader(); + if (!replayCancelled) controller.close(); } }, + ...(preContentTransportRecovery + ? { + cancel(reason: unknown) { + replayCancelled = true; + cancelPeekReader(reason); + }, + } + : {}), }); return new Response(stream, { status: response.status, @@ -607,7 +619,8 @@ export async function validateResponseQuality( const next = await readOrAbort(); if (next === null) { // Same as the error path: never await cancel() on a tee branch. - reader.cancel(signal?.reason).catch(() => {}); + if (preContentTransportRecovery) cancelPeekReader(signal?.reason); + else reader.cancel(signal?.reason).catch(() => {}); return { valid: true }; } const { done, value } = next; @@ -737,7 +750,8 @@ export async function validateResponseQuality( if (outcome === "error") { // Do not await cancellation of a Response.clone() tee branch: the // promise may remain pending until the client-facing branch drains. - reader.cancel().catch(() => {}); + if (preContentTransportRecovery) cancelPeekReader(); + else reader.cancel().catch(() => {}); const reason = describeStreamingFailure(upstreamFailure); log.warn?.( "COMBO", @@ -776,15 +790,62 @@ export async function validateResponseQuality( // or corrupted (e.g. "Invalid state: The ReadableStream is locked"). // Broad match: Chrome/V8 throws "body used already", Firefox throws // "ReadableStream is locked", etc. - const errMsg = streamErr instanceof Error ? streamErr.message : String(streamErr); + const errMsg = preContentTransportRecovery + ? safeStreamReadMessage(streamErr) + : streamErr instanceof Error + ? streamErr.message + : String(streamErr); + if (preContentTransportRecovery) cancelPeekReader(); + let lockedTypeError = false; + if (preContentTransportRecovery) { + try { + lockedTypeError = streamErr instanceof TypeError; + } catch { + /* malformed error */ + } + } else lockedTypeError = streamErr instanceof TypeError; if ( - streamErr instanceof TypeError && + lockedTypeError && (errMsg.includes("locked") || errMsg.includes("disturbed") || errMsg.includes("used already")) ) { return { valid: false, reason: "stream locked or disturbed" }; } + if (preContentTransportRecovery) { + const disposition = classifyPreContentReadFailure(streamErr); + if (signal?.aborted || disposition?.kind === "abort") return { valid: true }; + if (disposition?.kind === "transport") { + if ( + anyContentFound || + sse.hasContentBlock || + sawTerminator || + transportReplayBoundary || + hasObservedTransportReplayVeto(response) + ) + return { valid: true }; + const message = "Upstream stream transport failed before usable output"; + const reason = `${message} (${disposition.code})`; + try { + log.warn?.("COMBO", reason); + } catch { + /* diagnostics cannot replace the failure */ + } + return { + valid: false, + reason, + upstreamFailure: { + status: 502, + type: "stream_error", + code: "stream_terminated", + message, + requestScoped: false, + retryable: true, + transportReadFailure: true, + }, + }; + } + } // Cursor empty-turn and stream-timeout read errors are hop failures. // Any other pre-content read error still passes through; broadening this // to every combo made a network reset fail over the whole chain. diff --git a/stryker.conf.json b/stryker.conf.json index 9ac5d5c0f8de..30136c76c83b 100644 --- a/stryker.conf.json +++ b/stryker.conf.json @@ -717,7 +717,9 @@ "tests/unit/antigravity-argon-404.test.ts", "tests/unit/combo/combo-resilience-no-provider-state.test.ts", "tests/unit/combo/hedge-cancel-streaming-leg.test.ts", - "tests/unit/singbox-routes-local-only.test.ts" + "tests/unit/singbox-routes-local-only.test.ts", + "tests/unit/combo-precontent-transport-recovery-routing.test.ts", + "tests/unit/combo-precontent-transport-recovery.test.ts" ], "nodeArgs": [ "--import", diff --git a/tests/unit/combo-precontent-transport-recovery-default-gate.test.ts b/tests/unit/combo-precontent-transport-recovery-default-gate.test.ts new file mode 100644 index 000000000000..46170024733c --- /dev/null +++ b/tests/unit/combo-precontent-transport-recovery-default-gate.test.ts @@ -0,0 +1,124 @@ +import assert from "node:assert/strict"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; +import test from "node:test"; + +const data = fs.mkdtempSync(path.join(os.tmpdir(), "combo-transport-default-gate-")); +process.env.DATA_DIR = data; +process.env.APP_LOG_TO_FILE = "false"; +process.env.DISABLE_SQLITE_AUTO_BACKUP = "true"; +process.env.EMPTY_STREAM_SIBLING_RECOVERY_ENABLED = "false"; +process.env.FETCH_TIMEOUT_MS = "600000"; +const { handleComboChat } = await import("../../open-sse/services/combo.ts"); +const { getComboMetrics } = await import("../../open-sse/services/comboMetrics.ts"); +const { FETCH_TIMEOUT_MS } = await import("../../open-sse/config/constants.ts"); +const { resetDbInstance } = await import("../../src/lib/db/core.ts"); +const { closeCallLogSaves } = await import("../../src/lib/usage/callLogs.ts"); +const { dispatchEvent } = await import("../../src/lib/webhookDispatcher.ts"); +const noop = () => {}; +const log = { info: noop, warn: noop, error: noop, debug: noop }; +const answer = + 'data: {"choices":[{"delta":{"content":"default gate recovered"}}]}\n\ndata: [DONE]\n\n'; + +test.after(async () => { + // Drain the same webhook import chain used by fire-and-forget combo notifications. + await dispatchEvent("request.completed", {}); + await closeCallLogSaves(2000); + resetDbInstance(); + fs.rmSync(data, { recursive: true, force: true }); +}); + +for (const known of [true, false]) { + test(`ordinary first-output gate preserves quality handling of a coded read error, known=${known}`, async () => { + assert.equal(FETCH_TIMEOUT_MS, 600000); + const calls: string[] = []; + const model = "transport-default/one"; + const response = await handleComboChat({ + body: { stream: true, messages: [{ role: "user", content: "hello" }] }, + combo: { + name: `transport-default-${known}`, + strategy: "priority", + models: [{ model }], + config: { maxRetries: 1, retryDelayMs: 0 }, + }, + settings: { resilienceSettings: { streamRecovery: { enabled: true } } }, + hiddenModelsByProvider: new Map(), + isModelAvailable: async () => true, + log, + handleSingleModel: async (_body, selected) => { + calls.push(selected); + if (calls.length > 1) + return new Response(answer, { headers: { "Content-Type": "text/event-stream" } }); + return new Response( + new ReadableStream({ + pull(controller) { + controller.error( + Object.assign(new Error("connection interrupted"), { + code: known ? "ECONNRESET" : "EAI_AGAIN", + }) + ); + }, + }), + { headers: { "Content-Type": "text/event-stream" } } + ); + }, + }); + assert.deepEqual(calls, known ? [model, model] : [model]); + if (known) assert.equal(await response.text(), answer); + else await assert.rejects(response.text()); + }); +} + +for (const transport of [true, false]) { + test(`review14: priority late transport cancellation preserves ordinary non-aborted HTTP retry, transport=${transport}`, async () => { + const ac = new AbortController(); + const model = "transport-late/one"; + const name = `transport-late-${transport}`; + const calls: string[] = []; + const response = await handleComboChat({ + body: { stream: true, messages: [{ role: "user", content: "hello" }] }, + combo: { + name, + strategy: "priority", + models: [{ model }], + config: { maxRetries: 1, retryDelayMs: 0 }, + }, + settings: { + resilienceSettings: { streamRecovery: { enabled: true }, modelLockout: { enabled: false } }, + }, + hiddenModelsByProvider: new Map(), + isModelAvailable: async () => true, + signal: ac.signal, + log: { + ...log, + info: (_tag: string, message: string) => { + if (transport && message.includes("(retry 1)")) ac.abort(); + }, + }, + handleSingleModel: async (_body, selected) => { + calls.push(selected); + if (calls.length > 1) + return new Response(answer, { headers: { "Content-Type": "text/event-stream" } }); + if (!transport) + return Response.json({ error: { message: "service busy" } }, { status: 503 }); + return new Response( + new ReadableStream({ + pull(controller) { + controller.error( + Object.assign(new Error("connection interrupted"), { code: "ECONNRESET" }) + ); + }, + }), + { headers: { "Content-Type": "text/event-stream" } } + ); + }, + }); + assert.equal(response.status, transport ? 499 : 200); + assert.deepEqual(calls, transport ? [model] : [model, model]); + if (transport) assert.equal(getComboMetrics(name)?.totalFailures, 1); + assert.equal(ac.signal.aborted, transport); + const text = await response.text(); + if (!transport) assert.equal(text, answer); + }); +} diff --git a/tests/unit/combo-precontent-transport-recovery-framing.test.ts b/tests/unit/combo-precontent-transport-recovery-framing.test.ts new file mode 100644 index 000000000000..5d93ce73cad6 --- /dev/null +++ b/tests/unit/combo-precontent-transport-recovery-framing.test.ts @@ -0,0 +1,189 @@ +import assert from "node:assert/strict"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; +import test from "node:test"; + +const data = fs.mkdtempSync(path.join(os.tmpdir(), "combo-replay-framing-")); +process.env.DATA_DIR = data; +process.env.APP_LOG_TO_FILE = "false"; +process.env.DISABLE_SQLITE_AUTO_BACKUP = "true"; +const { SseOutputScanner, awaitFirstOutput } = + await import("../../open-sse/services/combo/firstOutputStream.ts"); +const { resetDbInstance } = await import("../../src/lib/db/core.ts"); +test.after(() => { + resetDbInstance(); + fs.rmSync(data, { recursive: true, force: true }); +}); + +for (const split of [false, true]) { + test(`review12: observer assembles multiline output while readiness keeps its prior result, split=${split}`, () => { + const event = + 'event: response.output_text.delta\r\ndata: {\r\ndata: "type":"response.output_text.delta","delta":"hello"}\r\n\r\n'; + const chunks = split ? event.split(/(?<=\r)/) : [event]; + let observed = false; + const scanner = new SseOutputScanner(() => { + observed = true; + }); + const results = chunks.map((chunk) => scanner.push(chunk)); + assert.deepEqual(results, split ? chunks.map(() => false) : [true]); + assert.equal(observed, true); + }); +} +test("review12: split CRLF and error flush cannot commit an incomplete terminal", () => { + const chunks = [ + "event: response.done\r", + "\ndata: {\r", + '\ndata: "response":{"output":[]}}\r', + "\n", + ]; + let observed = false; + const scanner = new SseOutputScanner(() => { + observed = true; + }); + assert.deepEqual( + chunks.map((chunk) => scanner.push(chunk)), + chunks.map(() => false) + ); + assert.equal(observed, false, "split CRLF is not an event boundary"); + assert.equal(scanner.flush(), false, "preserve readiness EOF/error result"); + assert.equal(observed, false, "discard uncommitted terminal candidates on error/EOF flush"); + scanner.push("event: response.done\ndata: {}\n\n"); + assert.equal(observed, true); +}); +test("review13: oversized metadata conservatively vetoes retry without changing readiness", () => { + let observed = false; + const scanner = new SseOutputScanner(() => { + observed = true; + }); + const frame = + "event: progress\n" + + Array.from({ length: 80 }, () => "data: " + "x".repeat(1024)).join("\n") + + "\n\n"; + assert.equal(scanner.push(frame), false); + assert.equal(scanner.overflowedWithoutOutput(), false, "complete frame cleared readiness bytes"); + assert.equal(scanner.flush(), false); + assert.equal(observed, true, "discarded inspection cannot establish pre-content eligibility"); + scanner.push("event: ping\ndata: {}\n\n"); + assert.equal(observed, true, "later frames cannot clear the sticky retry veto"); +}); + +for (const terminated of [false, true]) { + test(`review13: inspection cap preserves readiness/flush/overflow, terminated=${terminated}`, () => { + let observed = false; + const scanner = new SseOutputScanner(() => { + observed = true; + }); + const prefix = ":" + "x".repeat(80 * 1024) + (terminated ? "\n\n" : ""); + assert.equal(scanner.push(prefix), false); + assert.equal(observed, terminated, "unterminated line is inspected by the overflow guard"); + assert.equal(scanner.overflowedWithoutOutput(), !terminated); + assert.equal(observed, true, "ending bounded inspection conservatively vetoes retry"); + assert.equal(scanner.flush(), false); + scanner.push("event: ping\ndata: {}\n\n"); + assert.equal(observed, true); + }); +} + +const deepMetadata = "data: " + '{"a":'.repeat(10000) + "0" + "}".repeat(10000) + "\n\n"; +async function withObserverSerializationFailure(run: (hits: () => number) => void | Promise) { + const stringify = JSON.stringify; + let hits = 0; + JSON.stringify = ((value: unknown, ...args: unknown[]) => { + if ( + value !== null && + typeof value === "object" && + Object.keys(value).length === 1 && + Object.hasOwn(value, "a") + ) { + hits++; + throw new RangeError("Controlled observer serialization failure"); + } + return Reflect.apply(stringify, JSON, [value, ...args]); + }) as typeof JSON.stringify; + try { + await run(() => hits); + } finally { + JSON.stringify = stringify; + } +} +test("review14: native deep metadata preserves readiness without requiring an exception", () => { + const scanner = new SseOutputScanner(() => {}); + assert.doesNotThrow(() => assert.equal(scanner.push(deepMetadata), false)); + assert.equal(scanner.overflowedWithoutOutput(), false); + assert.equal(scanner.flush(), false); +}); +test("review14: controlled observer exception cannot change readiness", async () => { + await withObserverSerializationFailure((hits) => { + let observed = false; + const scanner = new SseOutputScanner(() => { + observed = true; + }); + assert.doesNotThrow(() => assert.equal(scanner.push(deepMetadata), false)); + assert.equal(observed, true, "incomplete observer inspection conservatively vetoes retry"); + assert.equal(scanner.overflowedWithoutOutput(), false); + assert.equal(scanner.flush(), false); + assert.doesNotThrow(() => assert.equal(scanner.push(deepMetadata), false)); + assert.equal(hits(), 1, "failed observer inspection stays disabled after reset/flush"); + assert.equal(scanner.push('data: {"choices":[{"delta":{"content":"answer"}}]}\n\n'), true); + }); +}); +for (const lines of [200, 256, 257]) { + test(`review14: pending-line work budget preserves readiness, lines=${lines}`, () => { + let observed = false; + const scanner = new SseOutputScanner(() => { + observed = true; + }); + assert.equal(scanner.push(":\n".repeat(lines)), false); + assert.equal(scanner.overflowedWithoutOutput(), false); + assert.equal(observed, lines > 256); + assert.equal(scanner.flush(), false); + }); +} +for (const injected of [false, true]) { + for (const ending of ["cancelled", "budget"] as const) { + test(`review14: real first-output wait ends at ${ending}, observer fault injected=${injected}`, async () => { + const run = async () => { + const ac = new AbortController(); + const response = new Response( + new ReadableStream({ + start(controller) { + controller.enqueue(new TextEncoder().encode(deepMetadata)); + }, + }), + { headers: { "Content-Type": "text/event-stream" } } + ); + let settled = false; + const waiting = awaitFirstOutput(response, 250, ac.signal).then((result) => { + settled = true; + return result; + }); + try { + await new Promise((resolve) => setTimeout(resolve, 50)); + assert.equal(settled, false, "observer work must not end the historical wait"); + if (ending === "cancelled") ac.abort(); + assert.deepEqual(await waiting, { kind: "stalled", reason: ending }); + } finally { + ac.abort(); + void response.body?.cancel().catch(() => {}); + await waiting; + } + }; + if (injected) await withObserverSerializationFailure(run); + else await run(); + }); + } +} +for (const implicit of [false, true]) { + test(`review14: work budget resets after completed normalized events, implicit=${implicit}`, () => { + let observed = false; + const scanner = new SseOutputScanner(() => { + observed = true; + }); + const frame = 'data: {"type":"progress"}\n' + (implicit ? ": ping\n" : "\n"); + assert.equal(scanner.push(frame.repeat(300)), false); + assert.equal(scanner.overflowedWithoutOutput(), false); + assert.equal(scanner.flush(), false); + assert.equal(observed, false, "completed events release the pending-line budget"); + }); +} diff --git a/tests/unit/combo-precontent-transport-recovery-routing.test.ts b/tests/unit/combo-precontent-transport-recovery-routing.test.ts new file mode 100644 index 000000000000..651ebb015aa7 --- /dev/null +++ b/tests/unit/combo-precontent-transport-recovery-routing.test.ts @@ -0,0 +1,538 @@ +import assert from "node:assert/strict"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; +import test from "node:test"; +type Options = import("../../open-sse/services/combo/types.ts").HandleComboChatOptions; +const data = fs.mkdtempSync(path.join(os.tmpdir(), "combo-transport-routing-")); +process.env.DATA_DIR = data; +process.env.APP_LOG_TO_FILE = "false"; +process.env.DISABLE_SQLITE_AUTO_BACKUP = "true"; +process.env.EMPTY_STREAM_SIBLING_RECOVERY_ENABLED = "false"; +process.env.STREAM_RECOVERY_ENABLED = "false"; +process.env.FETCH_TIMEOUT_MS = "0"; +const { handleComboChat, validateResponseQuality } = + await import("../../open-sse/services/combo.ts"); +const { recordSessionModelUsage, getLastSessionModel } = + await import("../../src/lib/db/contextHandoffs.ts"); +const { createProviderConnection } = await import("../../src/lib/db/providers.ts"); +const { getCachedProviderConnectionById } = await import("../../src/lib/db/readCache.ts"); +const { getComboMetrics } = await import("../../open-sse/services/comboMetrics.ts"); +const { clearAllModelLockouts } = await import("../../open-sse/services/accountFallback.ts"); +const { clearNativeCodexTurnPinsForTests } = + await import("../../open-sse/services/combo/nativeCodexTurnPin.ts"); +const { acquire, buildAccountSemaphoreKey, resetAll } = + await import("../../open-sse/services/accountSemaphore.ts"); +const { closeCallLogSaves } = await import("../../src/lib/usage/callLogs.ts"); +const { resetDbInstance } = await import("../../src/lib/db/core.ts"); +const { dispatchEvent } = await import("../../src/lib/webhookDispatcher.ts"); +const pinConnection = await createProviderConnection({ + provider: "transport-a", + authType: "apikey", + name: "routing-pin", + isActive: true, + apiKey: "fixture-only", +}); +assert.ok(pinConnection?.id); +const noop = () => {}; +const quiet = { info: noop, warn: noop, debug: noop, error: noop }; +const encoder = new TextEncoder(); +const first = "transport-a/first", + second = "transport-b/second", + third = "transport-c/third"; +const answer = 'data: {"choices":[{"delta":{"content":"recovered"}}]}\n\ndata: [DONE]\n\n'; +const settings = (enabled = true) => ({ + resilienceSettings: { streamRecovery: { enabled }, modelLockout: { enabled: false } }, +}); +const coded = () => Object.assign(new Error("connection interrupted"), { code: "ECONNRESET" }); +const frame = (payload: unknown) => `data: ${JSON.stringify(payload)}\n\n`; +let sequence = 0; +function cut(prefix: string | string[] = "", error: unknown = coded(), beforeError?: () => void) { + const chunks = typeof prefix === "string" ? (prefix ? [prefix] : []) : prefix; + let release: () => void = noop; + const gate = new Promise((resolve) => { + release = resolve; + }); + let pulls = 0; + const body = new ReadableStream( + { + async pull(controller) { + if (pulls < chunks.length) { + controller.enqueue(encoder.encode(chunks[pulls++])); + return; + } + await gate; + beforeError?.(); + controller.error(error); + }, + }, + { highWaterMark: 0 } + ); + const instrumentReader = (response: Response): void => { + const getReader = response.body!.getReader.bind(response.body!); + response.body!.getReader = (() => { + const reader = getReader(); + const read = reader.read.bind(reader); + let reads = 0; + reader.read = () => { + if (++reads > chunks.length) release(); + return read(); + }; + return reader; + }) as typeof response.body.getReader; + }; + const instrument = (response: Response): Response => { + instrumentReader(response); + const clone = response.clone.bind(response); + response.clone = () => { + const result = clone(); + instrumentReader(response); + return instrument(result); + }; + return response; + }; + return instrument(new Response(body, { headers: { "Content-Type": "text/event-stream" } })); +} + +function setup( + route: "priority" | "round-robin" | "runtime" | "pin", + config: Record = {} +) { + const name = `transport-${route}-${++sequence}`; + const child = { + name: `${name}-child`, + strategy: "priority", + models: [{ model: third }], + config: { maxRetries: 0 }, + }; + const combo = { + name, + strategy: route === "round-robin" ? "round-robin" : "priority", + context_cache_protection: route === "pin", + models: [ + { model: first }, + { model: second }, + ...(route === "runtime" ? [{ kind: "combo-ref", comboName: child.name }] : []), + ], + config: { + maxRetries: 1, + retryDelayMs: 0, + fallbackDelayMs: 0, + targetTimeoutMs: 1000, + stickyRoundRobinLimit: 1, + ...(route === "runtime" ? { nestedComboMode: "execute" } : {}), + ...config, + }, + }; + const relayOptions = route === "pin" ? { sessionId: `${name}-session` } : undefined; + if (relayOptions) { + recordSessionModelUsage(relayOptions.sessionId, name, first, "transport-a"); + assert.equal(getLastSessionModel(relayOptions.sessionId, name), first); + } + return { combo, allCombos: [combo, child], relayOptions }; +} +function success() { + return new Response(answer, { headers: { "Content-Type": "text/event-stream" } }); +} +async function invoke( + fixture: ReturnType, + callback: Options["handleSingleModel"], + extra: Partial = {} +) { + return handleComboChat({ + ...fixture, + body: { stream: true, messages: [{ role: "user", content: "hello" }] }, + settings: settings(), + hiddenModelsByProvider: new Map(), + isModelAvailable: async () => true, + handleSingleModel: callback, + log: quiet, + ...extra, + }); +} +async function drain(response: Response) { + try { + return await response.text(); + } catch { + return "read rejected"; + } +} +const failures = (fixture: ReturnType) => + getComboMetrics(fixture.combo.name)?.totalFailures ?? 0; +function probe(fixture: ReturnType) { + const ac = new AbortController(); + const calls: string[] = [], + warnings: string[] = []; + let errors = 0, + failuresAtAbort: number | null = null; + const stop = () => { + if (ac.signal.aborted) return; + failuresAtAbort = failures(fixture); + ac.abort(); + }; + const callback: Options["handleSingleModel"] = async (_body, model) => { + calls.push(model); + return calls.length === 1 + ? cut("", coded(), () => { + errors++; + }) + : Response.json( + { error: { message: "must not dispatch after cancellation" } }, + { status: 503 } + ); + }; + const log = { + ...quiet, + warn: (_tag: string, message: string) => { + warnings.push(message); + }, + }; + const assertStopped = async (response: Response) => { + try { + assert.equal(ac.signal.aborted, true); + assert.equal(errors, 1, "the original provider delivered the genuine coded failure"); + assert.deepEqual(calls, [first]); + assert.equal(response.status, 499); + assert.equal( + warnings.filter((x) => + x.startsWith("Upstream stream transport failed before usable output") + ).length, + 1 + ); + assert.notEqual(failuresAtAbort, null); + assert.equal( + failures(fixture), + failuresAtAbort, + "cancellation adds no failure and retains previously recorded failures" + ); + } finally { + await drain(response); + } + }; + return { ac, calls, warnings, stop, callback, log, assertStopped }; +} +test.beforeEach(() => { + clearAllModelLockouts(); + clearNativeCodexTurnPinsForTests(); + resetAll(); +}); +test.after(async () => { + await dispatchEvent("request.completed", {}); + await closeCallLogSaves(2000); + resetAll(); + resetDbInstance(); + fs.rmSync(data, { recursive: true, force: true }); +}); + +for (const route of ["priority", "round-robin", "runtime"] as const) { + for (const queued of [false, true]) { + test(`review15: ${route} stops same-target late retry cancellation, queued=${queued}`, async () => { + const f = setup(route), + p = probe(f); + const log = { + ...p.log, + info: (_tag: string, message: string) => { + if (message.includes("(retry 1)")) { + if (queued) queueMicrotask(p.stop); + else p.stop(); + } + }, + }; + const response = await invoke(f, p.callback, { signal: p.ac.signal, log }); + await p.assertStopped(response); + }); + } +} +for (const pinned of [false, true]) { + test(`review15: priority stops late sibling dispatch after ${pinned ? "failed pinned prelude" : "target failure"}`, async () => { + const f = setup(pinned ? "pin" : "priority", { maxRetries: 0 }); + if (pinned) f.combo.models = [{ model: second }, { model: first }]; + const p = probe(f); + let pinSeen = false; + const response = await invoke( + f, + async (body, model, target) => { + if (p.calls.length === 0 && pinned) { + assert.equal(target?.modelPinned, true); + pinSeen = true; + } + return p.callback(body, model, target); + }, + { + signal: p.ac.signal, + log: { + ...p.log, + info: (_tag: string, message: string) => { + if (message.startsWith("Trying model ") && message.includes(second)) p.stop(); + }, + }, + } + ); + if (pinned) assert.equal(pinSeen, true); + await p.assertStopped(response); + }); +} +for (const fallback of [false, true]) { + for (const available of [false, true]) { + test(`review15: runtime stops after availability cancels, fallback=${fallback}, available=${available}`, async () => { + const f = setup("runtime", { maxRetries: fallback ? 0 : 1 }); + const guarded = fallback ? 1 : 0; + Object.assign(f.combo.models[guarded], { fallbackOnlyOnQuotaExhaustion: true }); + const p = probe(f); + let entered = false; + const response = await invoke(f, p.callback, { + signal: p.ac.signal, + log: p.log, + isModelAvailable: async (model) => { + if (p.calls.length === 1) { + assert.equal(model, fallback ? second : first); + entered = true; + await Promise.resolve(); + p.stop(); + return available; + } + return true; + }, + }); + assert.equal(entered, true); + await p.assertStopped(response); + }); + } +} +for (const full of [false, true]) { + test(`review15: runtime capacity cancellation wins before skip accounting, full=${full}`, async () => { + const f = setup("runtime", { maxRetries: 0 }); + const connection = await createProviderConnection({ + provider: "transport-b", + authType: "apikey", + name: f.combo.name, + isActive: true, + apiKey: "fixture-only", + }); + assert.ok(connection?.id); + Object.assign(f.combo.models[1], { + connectionId: connection.id, + fallbackOnlyOnQuotaExhaustion: true, + }); + const cached = await getCachedProviderConnectionById(connection.id); + assert.ok(cached); + const p = probe(f); + const original = Object.getOwnPropertyDescriptor(cached, "maxConcurrent"); + let stack = ""; + Object.defineProperty(cached, "maxConcurrent", { + configurable: true, + get() { + if (p.calls.length === 1 && !p.ac.signal.aborted) { + stack = new Error().stack ?? ""; + p.stop(); + } + return 1; + }, + }); + const release = full + ? await acquire( + buildAccountSemaphoreKey({ provider: "transport-b", accountKey: connection.id }), + { maxConcurrency: 1 } + ) + : noop; + try { + const response = await invoke(f, p.callback, { signal: p.ac.signal, log: p.log }); + assert.match(stack, /lookupPositiveCap/); + await p.assertStopped(response); + } finally { + release(); + if (original) Object.defineProperty(cached, "maxConcurrent", original); + else delete cached.maxConcurrent; + } + }); +} + +for (const route of ["priority", "round-robin", "runtime", "pin"] as const) { + for (const enabled of [true, false, undefined]) { + test(`review15: ${route} ordinary recovery policy control, enabled=${enabled}`, async () => { + const f = setup(route), + calls: string[] = []; + const response = await invoke( + f, + async (_body, model, target) => { + if (route === "pin" && calls.length === 0) assert.equal(target?.modelPinned, true); + calls.push(model); + return calls.length === 1 ? cut() : success(); + }, + { settings: enabled === undefined ? {} : settings(enabled) } + ); + try { + assert.deepEqual(calls, enabled === true ? [first, first] : [first]); + assert.equal(response.status, 200); + } finally { + await drain(response); + } + }); + } +} +const stop = [{}, { finishReason: "STOP" }], + unspecified = [{ finishReason: "FINISH_REASON_UNSPECIFIED" }]; +const geminiCases: Array<[string, unknown, boolean]> = [ + ["root terminal plus unrelated wrapper", { candidates: stop, response: { output: [] } }, true], + [ + "root terminal plus empty wrapped candidates", + { candidates: stop, response: { candidates: [] } }, + true, + ], + [ + "root terminal plus wrapped unspecified", + { candidates: stop, response: { candidates: unspecified } }, + true, + ], + [ + "root unspecified plus wrapped terminal", + { candidates: unspecified, response: { candidates: stop } }, + true, + ], + ["root empty plus wrapped terminal", { candidates: [], response: { candidates: stop } }, true], + ["both unspecified", { candidates: unspecified, response: { candidates: unspecified } }, false], +]; +for (const [label, payload, terminal] of geminiCases) { + test(`review15: direct quality preserves mixed Gemini ${label}`, async () => { + const source = cut(frame(payload)); + const result = await validateResponseQuality(source, true, quiet, null, null, false, true); + try { + assert.equal(result.valid, terminal); + if (!terminal) assert.equal(result.upstreamFailure?.transportReadFailure, true); + } finally { + void result.clonedResponse?.body?.cancel().catch(noop); + void source.body?.cancel().catch(noop); + } + }); + for (const route of ["priority", "round-robin", "runtime", "pin"] as const) { + test(`review15: positive gate ${route} preserves mixed Gemini ${label}`, async () => { + const f = setup(route), + calls: string[] = []; + const response = await invoke(f, async (_body, model, target) => { + if (route === "pin" && calls.length === 0) assert.equal(target?.modelPinned, true); + calls.push(model); + return !terminal && calls.length > 1 ? success() : cut(frame(payload)); + }); + try { + assert.deepEqual(calls, terminal ? [first] : [first, first]); + assert.equal(response.status, 200); + } finally { + await drain(response); + } + }); + } +} +test("review15: mixed Gemini EOF keeps existing enabled/off behavior", async () => { + const payload = frame(geminiCases[0][1]), + outcomes: Array<{ valid: boolean; reason?: string }> = []; + for (const enabled of [false, true]) { + const response = new Response(payload, { headers: { "Content-Type": "text/event-stream" } }); + const result = await validateResponseQuality(response, true, quiet, null, null, false, enabled); + outcomes.push({ valid: result.valid, reason: result.reason }); + if (result.clonedResponse) await drain(result.clonedResponse); + } + assert.deepEqual(outcomes[1], outcomes[0]); +}); + +test("review15: queued pin cancellation crosses timeout resolution without invoking provider", async () => { + const f = setup("pin"), + ac = new AbortController(), + calls: string[] = []; + let armed = false; + const response = await invoke( + f, + async (_body, model) => { + calls.push(model); + return success(); + }, + { + signal: ac.signal, + log: { + ...quiet, + info: (_tag: string, message: string) => { + if (message.startsWith("Bypassing strategy")) { + armed = true; + queueMicrotask(() => ac.abort()); + } + }, + }, + } + ); + try { + assert.equal(armed, true); + assert.equal(ac.signal.aborted, true); + assert.deepEqual(calls, []); + assert.equal(response.status, 499); + assert.equal(failures(f), 0); + } finally { + await drain(response); + } +}); +for (const route of ["priority", "round-robin", "runtime", "pin"] as const) { + test(`review15: ${route} cancels a returned non-OK body before cancellation accounting`, async () => { + const f = setup(route, { maxRetries: 0 }), + ac = new AbortController(), + calls: string[] = []; + f.combo.models = [ + Object.assign({ model: first }, { fallbackOnlyOnQuotaExhaustion: true }), + ...(route === "runtime" ? [{ kind: "combo-ref", comboName: f.allCombos[1].name }] : []), + ]; + let cancelled = false; + const response = await invoke( + f, + async (_body, model, target) => { + if (route === "pin" && calls.length === 0) assert.equal(target?.modelPinned, true); + calls.push(model); + ac.abort(); + let sent = false; + return new Response( + new ReadableStream( + { + pull(controller) { + if (!sent) { + sent = true; + controller.enqueue(encoder.encode('{"error":{"message":"cancelled result"}}')); + } else controller.close(); + }, + cancel() { + cancelled = true; + }, + }, + { highWaterMark: 0 } + ), + { status: 503, headers: { "Content-Type": "application/json" } } + ); + }, + { signal: ac.signal } + ); + try { + await new Promise((resolve) => setImmediate(resolve)); + assert.deepEqual(calls, [first]); + assert.equal(response.status, 499); + assert.equal(failures(f), 0); + assert.equal(cancelled, true); + } finally { + await drain(response); + } + }); + test(`review15: ${route} non-streaming ordinary retry remains unchanged`, async () => { + const f = setup(route), + calls: string[] = []; + const response = await invoke( + f, + async (_body, model) => { + calls.push(model); + return calls.length === 1 + ? Response.json({ error: { message: "service busy" } }, { status: 503 }) + : Response.json({ choices: [{ message: { content: "ok" } }] }); + }, + { body: { stream: false, messages: [{ role: "user", content: "hello" }] } } + ); + try { + assert.deepEqual(calls, [first, first]); + assert.equal(response.status, 200); + } finally { + await drain(response); + } + }); +} diff --git a/tests/unit/combo-precontent-transport-recovery-scanner.test.ts b/tests/unit/combo-precontent-transport-recovery-scanner.test.ts new file mode 100644 index 000000000000..e63240297576 --- /dev/null +++ b/tests/unit/combo-precontent-transport-recovery-scanner.test.ts @@ -0,0 +1,152 @@ +import assert from "node:assert/strict"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; +import test from "node:test"; + +// Split out of combo-precontent-transport-recovery.test.ts to keep that file under the +// test-file line cap: pure scanner-level frame table, no database or routing involved. +const data = fs.mkdtempSync(path.join(os.tmpdir(), "combo-transport-scanner-")); +process.env.DATA_DIR = data; +process.env.APP_LOG_TO_FILE = "false"; +process.env.DISABLE_SQLITE_AUTO_BACKUP = "true"; +const { SseOutputScanner } = await import("../../open-sse/services/combo/firstOutputStream.ts"); +const ping = "event: ping\ndata: {}\n\n"; +const frame = (payload: unknown, event?: string) => + `${event ? `event: ${event}\n` : ""}data: ${JSON.stringify(payload)}\n\n`; +function assertScannerVeto(prefix: string | string[], expected: boolean) { + let observed = false; + const scanner = new SseOutputScanner(() => { + observed = true; + }); + const readiness = new SseOutputScanner(); + for (const chunk of typeof prefix === "string" ? [prefix] : prefix) { + const ready = scanner.push(chunk); + assert.equal(ready, readiness.push(chunk), "observer preserves push readiness"); + const overflowed = scanner.overflowedWithoutOutput(); + assert.equal(overflowed, readiness.overflowedWithoutOutput()); + if (ready || overflowed) break; + } + assert.equal(scanner.flush(), readiness.flush(), "observer preserves error/EOF flush"); + assert.equal(observed, expected, "scanner's completed frames/output/caps decide replay veto"); +} +test.after(() => { + fs.rmSync(data, { recursive: true, force: true }); +}); +const splitReviewFrame = (event: string, data: string[]) => + [`event: ${event}`, ...data.map((line) => `data: ${line}`), "", ""].join("\r\n").split(/(?<=\r)/); +type ReviewFrame = [string, string | string[], boolean]; +const reviewFrames: ReviewFrame[] = [ + [ + "implicit terminal flush on comment", + 'event: response.done\ndata: {"type":"response.done"}\n: ping\n', + false, + ], + ["comment after non-self-describing data", "event: response.done\ndata: {}\n: ping\n", true], + ["array-valued usage terminal", frame({ choices: null, usage: [] }), false], + [ + "incomplete terminal frame", + frame({ type: "response.done" }, "response.done").slice(0, -1), + true, + ], + [ + "incomplete error frame", + frame({ type: "error", error: { code: "invalid_request_error" } }, "error").slice(0, -1), + true, + ], + [ + "incomplete useful output", + frame( + { type: "response.output_text.delta", delta: "hello" }, + "response.output_text.delta" + ).slice(0, -1), + false, + ], + ["usage terminal with choices null", frame({ choices: null, usage: { total_tokens: 1 } }), false], + ["usage terminal with non-array choices", frame({ choices: {}, usage: {} }), false], + ["response.created usage metadata", frame({ type: "response.created", usage: {} }), true], + ["response.in_progress usage metadata", frame({ type: "response.in_progress", usage: {} }), true], + [ + "individual JSON data lines", + splitReviewFrame("response.output_item.added", [ + '{"item":{"type":"message","content":[]}}', + '{"metadata":true}', + ]), + false, + ], + ["empty-type terminal", frame({ type: "", response: { output: [] } }, "response.done"), false], + [ + "empty-type request failure", + frame({ type: "", response: { error: { code: "invalid_request_error" } } }, "response.failed"), + false, + ], + [ + "empty-type error", + frame({ type: "", code: "invalid_request_error", message: "invalid request" }, "error"), + false, + ], + [ + "split multiline terminal", + splitReviewFrame("response.done", ["{", '"response":{"output":[]}}']), + false, + ], + [ + "split multiline request failure", + splitReviewFrame("response.failed", [ + "{", + '"response":{"status":"failed","error":{"code":"invalid_request_error"}}}', + ]), + false, + ], + [ + "split multiline error", + splitReviewFrame("error", ["{", '"error":{"code":"invalid_request_error"}}']), + false, + ], + [ + "split multiline output item", + splitReviewFrame("response.output_item.added", [ + "{", + '"item":{"type":"message","content":[]}}', + ]), + false, + ], + ["split multiline metadata", splitReviewFrame("ping", ["{", '"metadata":true}']), true], +]; +for (const event of ["response.done", "error", "progress"]) { + reviewFrames.push([ + `review13 oversized complete ${event} then later metadata`, + [frame({ type: event, padding: "x".repeat(80 * 1024) }, event), ping], + false, + ]); +} +reviewFrames.push(["review13 pending line overflow", ":" + "x".repeat(80 * 1024), false]); +reviewFrames.push(["review13 below cap", frame({ padding: "x".repeat(60 * 1024) }), true]); +for (const payload of [true, 1, "signal", []]) { + for (const event of [ + "response.done", + "content_block_start", + "message_stop", + "message_start", + "message_delta", + "progress", + ]) { + reviewFrames.push([ + `review13 ${event} ${JSON.stringify(payload)}`, + frame(payload, event), + ["message_start", "message_delta", "progress"].includes(event), + ]); + } +} +for (const payload of [false, 0, "", null]) { + reviewFrames.push([ + `review13 falsey terminal ${JSON.stringify(payload)}`, + frame(payload, "response.done"), + true, + ]); +} +reviewFrames.push(["review14 line budget", ":\n".repeat(257), false]); +reviewFrames.push(["review14 below line budget", ":\n".repeat(200), true]); +for (const [label, prefix, recover] of reviewFrames) { + test(`scanner preserves ${label}`, () => assertScannerVeto(prefix, !recover)); +} diff --git a/tests/unit/combo-precontent-transport-recovery.test.ts b/tests/unit/combo-precontent-transport-recovery.test.ts new file mode 100644 index 000000000000..d662a7690937 --- /dev/null +++ b/tests/unit/combo-precontent-transport-recovery.test.ts @@ -0,0 +1,1329 @@ +import assert from "node:assert/strict"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; +import test from "node:test"; +import { setImmediate as nextTurn } from "node:timers/promises"; + +type Options = import("../../open-sse/services/combo/types.ts").HandleComboChatOptions; +const data = fs.mkdtempSync(path.join(os.tmpdir(), "combo-precontent-transport-")); +process.env.DATA_DIR = data; +process.env.APP_LOG_TO_FILE = "false"; +process.env.DISABLE_SQLITE_AUTO_BACKUP = "true"; +process.env.EMPTY_STREAM_SIBLING_RECOVERY_ENABLED = "false"; +process.env.STREAM_RECOVERY_ENABLED = "false"; +// Route these fixtures to the quality peek, bypassing the separate first-output gate. +process.env.FETCH_TIMEOUT_MS = "0"; +const { handleComboChat, validateResponseQuality } = + await import("../../open-sse/services/combo.ts"); +const { classifyPreContentReadFailure } = + await import("../../open-sse/services/combo/preContentTransportFailure.ts"); +const { SseOutputScanner } = await import("../../open-sse/services/combo/firstOutputStream.ts"); +const { createRecoverableStream } = await import("../../open-sse/services/streamRecovery.ts"); +const { recordSessionModelUsage, getLastSessionModel } = + await import("../../src/lib/db/contextHandoffs.ts"); +const { createProviderConnection } = await import("../../src/lib/db/providers.ts"); +const pinConnection = await createProviderConnection({ + provider: "transport-a", + authType: "apikey", + name: "precontent-pin-fixture", + isActive: true, + apiKey: "fixture-only", +}); +assert.ok(pinConnection?.id); +const { getDbInstance, resetDbInstance } = await import("../../src/lib/db/core.ts"); +const { clearAllModelLockouts } = await import("../../open-sse/services/accountFallback.ts"); +const { getComboMetrics } = await import("../../open-sse/services/comboMetrics.ts"); +const { clearNativeCodexTurnPinsForTests, pinNativeCodexTurn } = + await import("../../open-sse/services/combo/nativeCodexTurnPin.ts"); +const { closeCallLogSaves } = await import("../../src/lib/usage/callLogs.ts"); +const { dispatchEvent } = await import("../../src/lib/webhookDispatcher.ts"); +const noop = () => {}; +const quiet = { info: noop, warn: noop, error: noop, debug: noop }; +const encoder = new TextEncoder(); +const first = "transport-a/first"; +const second = "transport-b/second"; +const third = "transport-c/third"; +const role = 'data: {"choices":[{"delta":{"role":"assistant"}}]}\n\n'; +const ping = "event: ping\ndata: {}\n\n"; +const answer = 'data: {"choices":[{"delta":{"content":"recovered"}}]}\n\ndata: [DONE]\n\n'; +let sequence = 0; +const settings = (enabled = true) => ({ + resilienceSettings: { streamRecovery: { enabled }, modelLockout: { enabled: false } }, +}); +const frame = (payload: unknown, event?: string) => + `${event ? `event: ${event}\n` : ""}data: ${JSON.stringify(payload)}\n\n`; +const coded = (code = "ECONNRESET", message = "connection interrupted") => + Object.assign(new Error(message), { code }); + +// A second consumer read releases the rejection only after the prefix was observed. +function cut(prefix: string | string[] = "", error: unknown = coded(), beforeError?: () => void) { + const chunks = typeof prefix === "string" ? (prefix ? [prefix] : []) : prefix; + let release: () => void = noop; + const gate = new Promise((resolve) => { + release = resolve; + }); + let pulls = 0; + const body = new ReadableStream( + { + async pull(controller) { + if (pulls < chunks.length) { + controller.enqueue(encoder.encode(chunks[pulls++])); + return; + } + await gate; + beforeError?.(); + controller.error(error); + }, + }, + { highWaterMark: 0 } + ); + const instrumentReader = (response: Response): void => { + const getReader = response.body!.getReader.bind(response.body!); + response.body!.getReader = (() => { + const reader = getReader(); + const read = reader.read.bind(reader); + let reads = 0; + reader.read = () => { + if (++reads > chunks.length) release(); + return read(); + }; + return reader; + }) as typeof response.body.getReader; + }; + const instrument = (response: Response): Response => { + instrumentReader(response); + const clone = response.clone.bind(response); + response.clone = () => { + const result = clone(); + instrumentReader(response); + return instrument(result); + }; + return response; + }; + return instrument(new Response(body, { headers: { "Content-Type": "text/event-stream" } })); +} + +function success() { + return new Response(answer, { headers: { "Content-Type": "text/event-stream" } }); +} +function setup( + route: "priority" | "round-robin" | "runtime" | "pin", + config: Record = {} +) { + const name = `transport-${route}-${++sequence}`; + const child = { + name: `${name}-child`, + strategy: "priority", + models: [{ model: third }], + config: { maxRetries: 0 }, + }; + const combo = { + name, + strategy: route === "round-robin" ? "round-robin" : "priority", + context_cache_protection: route === "pin", + models: [ + { model: first }, + { model: second }, + ...(route === "runtime" ? [{ kind: "combo-ref", comboName: child.name }] : []), + ], + config: { + maxRetries: 1, + retryDelayMs: 0, + fallbackDelayMs: 0, + targetTimeoutMs: 0, + stickyRoundRobinLimit: 1, + ...(route === "runtime" ? { nestedComboMode: "execute" } : {}), + ...config, + }, + }; + const relayOptions = route === "pin" ? { sessionId: `${name}-session` } : undefined; + if (relayOptions) { + recordSessionModelUsage(relayOptions.sessionId, name, first, "transport-a"); + assert.equal(getLastSessionModel(relayOptions.sessionId, name), first); + } + return { combo, allCombos: [combo, child], relayOptions }; +} +async function run( + route: Parameters[0], + callback: Options["handleSingleModel"], + extra: Partial = {}, + config: Record = {} +) { + const fixture = setup(route, config); + const response = await handleComboChat({ + ...fixture, + body: { stream: true, messages: [{ role: "user", content: "hello" }] }, + settings: settings(), + hiddenModelsByProvider: new Map(), + isModelAvailable: async () => true, + handleSingleModel: callback, + log: quiet, + ...extra, + }); + return { response, name: fixture.combo.name }; +} +async function drain(response: Response) { + try { + return await response.text(); + } catch { + return "read rejected"; + } +} +async function quality(prefix: string, error: unknown, enabled = true, signal?: AbortSignal) { + const response = cut(prefix, error); + const result = await validateResponseQuality(response, true, quiet, null, signal, false, enabled); + if (result.clonedResponse) await drain(result.clonedResponse); + else void response.body?.cancel().catch(noop); + return result; +} + +test.beforeEach(() => { + clearAllModelLockouts(); + clearNativeCodexTurnPinsForTests(); +}); +test.after(async () => { + // Drain the same webhook import chain used by fire-and-forget combo notifications. + await dispatchEvent("request.completed", {}); + await closeCallLogSaves(2000); + resetDbInstance(); + fs.rmSync(data, { recursive: true, force: true }); +}); + +test("test database is inside the temporary DATA_DIR", () => { + const rows = getDbInstance().pragma("database_list") as Array<{ name: string; file: string }>; + assert.equal(path.dirname(rows.find((row) => row.name === "main")!.file), data); +}); + +for (const code of ["ECONNRESET", "EPIPE", "UND_ERR_SOCKET", "UND_ERR_BODY_TIMEOUT"]) { + for (const form of ["root", "cause", "sanitized"] as const) { + test(`${code} in ${form} classifies and rejects pre-content quality`, async () => { + const error = + form === "root" + ? coded(code) + : form === "cause" + ? new Error("fetch failed", { cause: coded(code) }) + : Object.assign(new Error("fetch failed"), { causeCode: code }); + assert.deepEqual(classifyPreContentReadFailure(error), { kind: "transport", code }); + const result = await quality(form === "root" ? "" : form === "cause" ? ping : role, error); + assert.equal(result.valid, false); + assert.equal(result.upstreamFailure?.retryable, true); + assert.equal(result.upstreamFailure?.transportReadFailure, true); + }); + } +} +for (const route of ["priority", "round-robin", "runtime", "pin"] as const) { + test(`${route}: coded pre-content failure retries the same target`, async () => { + const calls: string[] = []; + const { response } = await run(route, async (_body, model, target) => { + if (route === "pin" && calls.length === 0) assert.equal(target?.modelPinned, true); + calls.push(model); + return calls.length === 1 ? cut() : success(); + }); + assert.deepEqual(calls, [first, first]); + assert.equal(await drain(response), answer); + }); +} + +for (const route of ["priority", "round-robin", "runtime", "pin"] as const) { + for (const enabled of [false, undefined, true]) { + test(`${route}: setting ${enabled} preserves unknown errors`, async () => { + const calls: string[] = []; + const { response } = await run( + route, + async (_body, model) => { + calls.push(model); + return cut(ping, new Error("network reset")); + }, + { settings: enabled === undefined ? {} : settings(enabled) } + ); + assert.equal(response.status, 200); + assert.deepEqual(calls, [first]); + assert.equal(await drain(response), "read rejected"); + }); + } + test(`${route}: explicit false overrides environment true for coded errors`, async () => { + process.env.STREAM_RECOVERY_ENABLED = "true"; + try { + const calls: string[] = []; + const { response } = await run( + route, + async (_body, model) => { + calls.push(model); + return cut(ping); + }, + { settings: settings(false) } + ); + assert.deepEqual(calls, [first]); + assert.equal(response.status, 200); + await drain(response); + } finally { + process.env.STREAM_RECOVERY_ENABLED = "false"; + } + }); + test(`${route}: unset recovery preserves generic coded errors`, async () => { + const calls: string[] = []; + const { response } = await run( + route, + async (_body, model) => { + calls.push(model); + return cut(ping); + }, + { settings: {} } + ); + assert.deepEqual(calls, [first]); + assert.equal(response.status, 200); + await drain(response); + }); +} + +for (const route of ["priority", "round-robin", "runtime"] as const) { + test(`${route}: retries disabled falls back in order`, async () => { + const calls: string[] = []; + const { response } = await run( + route, + async (_body, model) => { + calls.push(model); + return model === first ? cut(role) : success(); + }, + {}, + { maxRetries: 0 } + ); + assert.deepEqual(calls, [first, second]); + assert.equal(await drain(response), answer); + }); + test(`${route}: exhausted retries use the existing fallback`, async () => { + const calls: string[] = []; + const { response } = await run(route, async (_body, model) => { + calls.push(model); + return model === first ? cut(ping) : success(); + }); + assert.deepEqual(calls, [first, first, second]); + assert.equal(await drain(response), answer); + }); + test(`${route}: global attempt budget bounds repeated transport failures`, async () => { + const calls: string[] = []; + const { response } = await run( + route, + async (_body, model) => { + calls.push(model); + return cut(role); + }, + {}, + { maxRetries: 3, maxGlobalAttempts: 2 } + ); + assert.deepEqual(calls, [first, first]); + assert.equal(response.status, 503); + await drain(response); + }); + test(`${route}: priority-only protection policy is preserved on transport failure`, async () => { + const fixture = setup(route, { maxRetries: 0 }); + Object.assign(fixture.combo.models[0], { fallbackOnlyOnQuotaExhaustion: true }); + const calls: string[] = []; + const { response } = await run( + route, + async (_body, model) => { + calls.push(model); + return cut(ping); + }, + fixture + ); + // Round-robin does not honor priority-only protected-target policy. + assert.deepEqual(calls, route === "round-robin" ? [first, second] : [first]); + assert.equal(response.status, 502); + await drain(response); + }); +} + +for (const route of ["priority", "round-robin", "runtime"] as const) { + test(`${route}: abort while peeking cancels both branches without provider failure`, async () => { + const ac = new AbortController(); + let cancelled = false; + let closeTimer: ReturnType | undefined; + const calls: string[] = []; + const { response, name } = await run( + route, + async (_body, model) => { + calls.push(model); + return new Response( + new ReadableStream( + { + start(controller) { + controller.enqueue(encoder.encode(ping)); + closeTimer = setTimeout(() => controller.close(), 100); + }, + async pull() { + await new Promise((resolve) => setTimeout(resolve, 20)); + ac.abort(); + }, + cancel() { + cancelled = true; + clearTimeout(closeTimer); + }, + }, + { highWaterMark: 0 } + ), + { headers: { "Content-Type": "text/event-stream" } } + ); + }, + { signal: ac.signal } + ); + await nextTurn(); + assert.equal(response.status, 499); + assert.deepEqual(calls, [first]); + assert.equal(cancelled, true); + assert.equal(getComboMetrics(name)?.totalFailures ?? 0, 0); + await drain(response); + }); + test(`${route}: abort before classifying a coded error adds no failure`, async () => { + const ac = new AbortController(); + const calls: string[] = []; + const { response, name } = await run( + route, + async (_body, model) => { + calls.push(model); + return cut(ping, coded(), () => ac.abort()); + }, + { signal: ac.signal } + ); + assert.equal(response.status, 499); + assert.deepEqual(calls, [first]); + assert.equal(getComboMetrics(name)?.totalFailures ?? 0, 0); + await drain(response); + }); + test(`${route}: abort during transport retry delay preserves only the genuine failure`, async () => { + const ac = new AbortController(); + const calls: string[] = []; + const retryLog = { + ...quiet, + info: (_tag: string, message: string) => { + if (message.startsWith("Retrying ") || message.includes("returned 200 but failed quality")) + setImmediate(() => ac.abort()); + }, + warn: (_tag: string, message: string) => { + if (route === "runtime" && message.includes("quality")) setImmediate(() => ac.abort()); + }, + }; + // Runtime units have no retry-delay log; abort after the failed peek's read. + let timer: ReturnType | undefined; + const { response, name } = await run( + route, + async (_body, model) => { + calls.push(model); + if (route === "runtime") timer = setTimeout(() => ac.abort(), 30); + return cut(role); + }, + { signal: ac.signal, log: retryLog }, + { retryDelayMs: 200 } + ); + clearTimeout(timer); + assert.equal(response.status, 499); + assert.deepEqual(calls, [first]); + if (route !== "runtime") assert.equal(getComboMetrics(name)?.totalFailures, 1); + await drain(response); + }); + test(`${route}: abort before fallback with maxRetries zero prevents sibling dispatch`, async () => { + const ac = new AbortController(); + const calls: string[] = []; + const { response } = await run( + route, + async (_body, model) => { + calls.push(model); + return cut(ping, coded(), () => ac.abort()); + }, + { signal: ac.signal }, + { maxRetries: 0 } + ); + assert.equal(response.status, 499); + assert.deepEqual(calls, [first]); + await drain(response); + }); +} +for (const route of ["round-robin", "runtime"] as const) { + test(`${route}: transport cancellation state survives fallback to an ordinary 503 retry`, async () => { + const ac = new AbortController(); + const calls: string[] = []; + let timer: ReturnType | undefined; + const { response } = await run( + route, + async (_body, model) => { + calls.push(model); + if (model === first) return cut(ping); + timer = setTimeout(() => ac.abort(), 20); + return Response.json( + { error: { message: "service busy", code: "upstream_busy", type: "server_error" } }, + { status: 503 } + ); + }, + { signal: ac.signal }, + { retryDelayMs: 80 } + ); + clearTimeout(timer); + assert.deepEqual(calls, [first, first, second]); + assert.equal(response.status, 499); + await drain(response); + }); +} +for (const phase of [ + "before-dispatch", + "callback", + "callback-rejection", + "peek", + "content", +] as const) { + test(`pin: cancellation at ${phase} stops fallthrough and releases the source`, async () => { + const ac = new AbortController(); + if (phase === "before-dispatch") ac.abort(); + let cancelled = false; + let closeTimer: ReturnType | undefined; + const calls: string[] = []; + const { response, name } = await run( + "pin", + async (_body, model) => { + calls.push(model); + if (phase === "callback-rejection") { + ac.abort(); + throw new Error("callback rejected"); + } + if (phase === "callback") ac.abort(); + let sent = false; + return new Response( + new ReadableStream( + { + start(controller) { + closeTimer = setTimeout(() => controller.close(), 100); + }, + async pull(controller) { + if (!sent) { + sent = true; + controller.enqueue(encoder.encode(phase === "content" ? answer : ping)); + if (phase === "content") queueMicrotask(() => ac.abort()); + return; + } + if (phase === "peek") { + await nextTurn(); + ac.abort(); + } + }, + cancel() { + cancelled = true; + clearTimeout(closeTimer); + }, + }, + { highWaterMark: 0 } + ), + { headers: { "Content-Type": "text/event-stream" } } + ); + }, + { signal: ac.signal } + ); + await nextTurn(); + assert.equal(response.status, 499); + assert.deepEqual(calls, phase === "before-dispatch" ? [] : [first]); + if (phase !== "before-dispatch" && phase !== "callback-rejection") + assert.equal(cancelled, true); + assert.equal(getComboMetrics(name)?.totalFailures ?? 0, 0); + await drain(response); + }); +} + +const boundaries: Array<[string, string]> = [ + ["text", frame({ choices: [{ delta: { content: "hello" } }] })], + ["reasoning", frame({ choices: [{ delta: { reasoning_content: "thinking" } }] })], + [ + "encrypted reasoning item", + frame({ + type: "response.output_item.added", + item: { type: "reasoning", encrypted_content: "opaque" }, + }), + ], + [ + "tool item", + frame({ + type: "response.output_item.added", + item: { type: "function_call", name: "tool", arguments: "" }, + }), + ], + [ + "opened Claude block", + frame({ type: "content_block_start", index: 0, content_block: { type: "text", text: "" } }), + ], + ["DONE", "data: [DONE]\n\n"], + ["finish reason", frame({ choices: [{ delta: {}, finish_reason: "stop" }] })], + ["usage without choices", frame({ usage: { total_tokens: 1 } })], + ["usage with empty choices", frame({ choices: [], usage: { total_tokens: 1 } })], + ["Claude stop", frame({ type: "message_stop" })], + ["Claude delta stop", frame({ type: "message_delta", delta: { stop_reason: "end_turn" } })], + ...["completed", "done", "incomplete", "cancelled", "canceled"].map((kind): [string, string] => [ + `Responses ${kind}`, + frame({ type: `response.${kind}`, response: { output: [] } }), + ]), + ["Gemini finish", frame({ candidates: [{ finishReason: "STOP" }] })], + ["wrapped Gemini finish", frame({ response: { candidates: [{ finishReason: "MAX_TOKENS" }] } })], +]; +for (const [name, prefix] of boundaries) { + test(`quality does not replay after ${name}, including Cursor words in a coded error`, async () => { + const warnings: unknown[][] = []; + const logger = { + ...quiet, + warn: (...args: unknown[]) => { + warnings.push(args); + }, + }; + assertScannerVeto(prefix, true); + const source = cut(prefix, coded("ECONNRESET", "no usable content secret=must-stay-private")); + const result = await validateResponseQuality( + source.clone(), + true, + logger, + null, + null, + false, + true + ); + assert.equal(result.valid, true); + assert.equal(result.upstreamFailure, undefined); + try { + await rejectNextRead( + source, + ["text", "reasoning", "encrypted reasoning item", "tool item"].includes(name) + ? prefix + : undefined + ); + } finally { + void result.clonedResponse?.body?.cancel().catch(noop); + } + assert.doesNotMatch(JSON.stringify(result), /must-stay-private/); + assert.doesNotMatch(JSON.stringify(warnings), /must-stay-private/); + }); +} +for (const route of ["priority", "round-robin", "runtime", "pin"] as const) { + test(`${route}: useful output preserves its payload and vetoes coded Cursor errors`, () => + assertRouteVeto(route, boundaries[0][1], 0)); +} + +test("unspecified Gemini finish remains eligible for transport recovery", async () => { + const result = await quality( + frame({ candidates: [{ finishReason: "FINISH_REASON_UNSPECIFIED" }] }), + coded() + ); + assert.equal(result.valid, false); + assert.equal(result.upstreamFailure?.retryable, true); +}); + +test("recognized transport diagnostic never includes Cursor words or raw secrets", async () => { + const result = await quality( + ping, + coded("EPIPE", "cursor-agent stream timed out token=synthetic-secret /srv/private") + ); + assert.equal(result.valid, false); + assert.deepEqual(result.upstreamFailure, { + status: 502, + type: "stream_error", + code: "stream_terminated", + message: "Upstream stream transport failed before usable output", + requestScoped: false, + retryable: true, + transportReadFailure: true, + }); + assert.doesNotMatch(JSON.stringify(result), /synthetic-secret|\/srv\/private|cursor-agent/); +}); + +for (const code of ["ABORT_ERR", "UND_ERR_ABORTED"]) { + for (const outer of [true, false]) { + test(`explicit ${code} dominates transport code, outer=${outer}`, async () => { + const abort = Object.assign(new Error("no usable content secret=abort-private"), { code }); + const error = outer + ? Object.assign(abort, { cause: coded() }) + : Object.assign(coded("ECONNRESET", "no usable content"), { cause: abort }); + assert.equal((await quality(ping, error)).valid, true); + }); + } +} +test("AbortError name in a cause dominates transport code", async () => { + const error = Object.assign(coded(), { + cause: Object.assign(new Error("cancelled"), { name: "AbortError" }), + }); + assert.equal((await quality(ping, error)).valid, true); +}); +for (const malformed of [ + "cycle", + "depth", + "cause getter", + "code getter", + "primitive cause", +] as const) { + test(`malformed ${malformed} is not a structured transport match`, async () => { + const error: Record = { + code: "ECONNRESET", + message: "connection interrupted", + }; + if (malformed === "cycle") error.cause = error; + if (malformed === "depth") { + let node = error; + for (let i = 0; i < 8; i++) { + const child = {}; + node.cause = child; + node = child; + } + } + if (malformed === "cause getter") + Object.defineProperty(error, "cause", { + get() { + throw new Error("hostile cause"); + }, + }); + if (malformed === "code getter") + Object.defineProperty(error, "code", { + get() { + throw new Error("hostile code"); + }, + }); + if (malformed === "primitive cause") error.cause = "ECONNRESET"; + assert.equal((await quality(ping, error)).valid, true); + }); +} +test("eight-object cause chain is accepted when fully inspected", async () => { + const root: Record = {}; + let node = root; + for (let i = 1; i < 8; i++) { + const child = {}; + node.cause = child; + node = child; + } + node.code = "UND_ERR_SOCKET"; + assert.equal((await quality(ping, root)).valid, false); +}); +test("throwing message getter does not defeat structured recovery", async () => { + const error = coded(); + Object.defineProperty(error, "message", { + get() { + throw new Error("hostile message"); + }, + }); + assert.equal((await quality(ping, error)).valid, false); +}); +test("throwing conversion on an unknown error preserves passthrough", async () => { + const error = { + toString() { + throw new Error("hostile string conversion"); + }, + }; + assert.equal((await quality(ping, error)).valid, true); +}); +test("locked TypeError keeps its legacy verdict even with a recognized code", async () => { + const result = await quality( + ping, + Object.assign(new TypeError("ReadableStream is locked"), { code: "ECONNRESET" }) + ); + assert.equal(result.reason, "stream locked or disturbed"); + assert.equal(result.upstreamFailure, undefined); +}); +test("message-only Cursor error retains its legacy verdict", async () => { + const result = await quality(ping, new Error("cursor-agent stream timed out")); + assert.equal(result.valid, false); + assert.match(result.reason ?? "", /streaming aborted before content/); +}); +test("normal EOF and byte-cap policies stay unchanged", async () => { + for (const prefix of [ping, role]) { + const result = await validateResponseQuality( + new Response(prefix, { headers: { "Content-Type": "text/event-stream" } }), + true, + quiet, + null, + null, + false, + true + ); + assert.equal(result.valid, prefix === ping); + if (result.clonedResponse) await drain(result.clonedResponse); + } + const huge = `:${"x".repeat(1_048_576)}\n\n`; + const result = await quality(huge, coded()); + assert.equal(result.valid, true); +}); + +test("real recoverable stream commits metadata, then combo retries its coded read failure", async () => { + const calls: string[] = []; + let now = 0; + let finalized = 0; + let reopens = 0; + const { response } = await run("priority", async (_body, model) => { + calls.push(model); + if (calls.length > 1) return success(); + let pull = 0; + const upstream = new ReadableStream( + { + async pull(controller) { + if (pull++ === 0) { + controller.enqueue(encoder.encode(ping)); + return; + } + if (pull === 2) { + now = 60_000; + controller.enqueue(encoder.encode(role)); + return; + } + await nextTurn(); + controller.error(coded("UND_ERR_SOCKET")); + }, + }, + { highWaterMark: 0 } + ); + return new Response( + createRecoverableStream( + upstream, + async () => { + reopens++; + return null; + }, + { + now: () => now, + maxEarlyRetries: 1, + finalize: () => { + finalized++; + }, + } + ), + { headers: { "Content-Type": "text/event-stream" } } + ); + }); + assert.deepEqual(calls, [first, first]); + assert.equal(await drain(response), answer); + assert.equal(reopens, 0); + assert.equal(finalized, 1); +}); +test("real recoverable stream precommit ping-only exhaustion stays clean EOF", async () => { + const calls: string[] = []; + let reopens = 0; + let finalized = 0; + const { response } = await run("priority", async (_body, model) => { + calls.push(model); + const initial = cut(ping).body!; + const body = createRecoverableStream( + initial, + async () => { + reopens++; + return cut(ping).body!; + }, + { + now: () => 0, + maxEarlyRetries: 1, + finalize: () => { + finalized++; + }, + } + ); + return new Response(body, { headers: { "Content-Type": "text/event-stream" } }); + }); + assert.deepEqual(calls, [first]); + assert.equal(await drain(response), ping); + assert.equal(reopens, 1); + assert.equal(finalized, 1); +}); + +test("enabled transport retry respects explicit connection pins and API-key ACLs", async () => { + const fixture = setup("priority", { maxRetries: 1 }); + Object.assign(fixture.combo.models[0], { + connectionId: "allowed", + allowedConnectionIds: ["allowed"], + }); + Object.assign(fixture.combo.models[1], { connectionId: "forbidden" }); + const targets: Array<{ model: string; connection: unknown; allowed: unknown }> = []; + const { response } = await run( + "priority", + async (_body, model, target) => { + targets.push({ + model, + connection: target?.connectionId, + allowed: target?.allowedConnectionIds, + }); + return targets.length === 1 ? cut(ping) : success(); + }, + { ...fixture, apiKeyAllowedConnections: ["allowed"] } + ); + assert.deepEqual(targets, [ + { model: first, connection: "allowed", allowed: ["allowed"] }, + { model: first, connection: "allowed", allowed: ["allowed"] }, + ]); + assert.equal(await drain(response), answer); +}); + +test("transport recovery cannot leave a native turn's model pin", async () => { + const fixture = setup("priority", { maxRetries: 0 }); + const nativeBody = { + stream: true, + messages: [{ role: "user", content: "hello" }], + client_metadata: { + "x-codex-turn-metadata": JSON.stringify({ thread_id: fixture.combo.name, turn_id: "turn" }), + }, + }; + const model = "codex/gpt-5.6-sol"; + fixture.combo.models[0] = { model }; + pinNativeCodexTurn({ + body: nativeBody, + comboName: fixture.combo.name, + target: { + kind: "model", + modelStr: model, + provider: "codex", + providerId: "codex", + connectionId: "native-account", + stepId: "native", + executionKey: "native", + label: null, + weight: 1, + }, + connectionId: "native-account", + }); + const calls: string[] = []; + const { response } = await run( + "priority", + async (_body, selected) => { + calls.push(selected); + return cut(ping); + }, + { ...fixture, body: nativeBody, clientManagedResponsesContext: true } + ); + assert.deepEqual(calls, [model]); + assert.equal(response.status, 502); + await drain(response); +}); + +for (const route of ["priority", "round-robin", "runtime"] as const) { + test(`${route}: abort after useful output releases the quality replay reader`, async () => { + const ac = new AbortController(); + let cancelled = false; + let closeTimer: ReturnType | undefined; + const calls: string[] = []; + const { response, name } = await run( + route, + async (_body, model) => { + calls.push(model); + let sent = false; + return new Response( + new ReadableStream( + { + start(controller) { + closeTimer = setTimeout(() => controller.close(), 100); + }, + pull(controller) { + if (!sent) { + sent = true; + controller.enqueue(encoder.encode(answer)); + queueMicrotask(() => ac.abort()); + } + }, + cancel() { + cancelled = true; + clearTimeout(closeTimer); + }, + }, + { highWaterMark: 0 } + ), + { headers: { "Content-Type": "text/event-stream" } } + ); + }, + { signal: ac.signal } + ); + await nextTurn(); + assert.equal(response.status, 499); + assert.deepEqual(calls, [first]); + assert.equal(cancelled, true, "canceling both tee branches must reach their source"); + assert.equal(getComboMetrics(name)?.totalFailures ?? 0, 0); + await drain(response); + }); +} + +test("priority transport retry removes the abort listener after normal delay expiry", async () => { + const ac = new AbortController(); + const active = new Set(); + const add = ac.signal.addEventListener.bind(ac.signal); + const remove = ac.signal.removeEventListener.bind(ac.signal); + ac.signal.addEventListener = (type, callback, options) => { + if (type === "abort" && callback) active.add(callback); + add(type, callback, options); + }; + ac.signal.removeEventListener = (type, callback, options) => { + if (type === "abort" && callback) active.delete(callback); + remove(type, callback, options); + }; + let calls = 0; + const { response } = await run( + "priority", + async () => (++calls === 1 ? cut(ping) : success()), + { signal: ac.signal }, + { retryDelayMs: 5 } + ); + assert.equal(await drain(response), answer); + await nextTurn(); + assert.equal(calls, 2); + assert.equal(active.size, 0); +}); + +test("an unknown structured code remains passthrough when enabled", async () => { + assert.equal((await quality(ping, coded("EAI_AGAIN"))).valid, true); +}); +test("aborted signal vetoes known transport and Cursor classification", async () => { + const ac = new AbortController(); + ac.abort(); + assert.equal( + (await quality(ping, coded("EPIPE", "no usable content secret=private"), true, ac.signal)) + .valid, + true + ); +}); +for (const enabled of [false, true]) { + test(`legacy locked and Cursor verdicts remain when enabled=${enabled}`, async () => { + assert.equal( + (await quality(ping, new TypeError("body used already"), enabled)).reason, + "stream locked or disturbed" + ); + assert.equal((await quality(ping, new Error("no usable content"), enabled)).valid, false); + }); +} + +test("canceling an enabled quality replay releases the reader it owns", async () => { + let cancelled = false; + let sent = false; + const original = new Response( + new ReadableStream( + { + pull(controller) { + if (!sent) { + sent = true; + controller.enqueue(encoder.encode(answer)); + } + }, + cancel() { + cancelled = true; + }, + }, + { highWaterMark: 0 } + ), + { headers: { "Content-Type": "text/event-stream" } } + ); + const clone = original.clone(); + const result = await validateResponseQuality(clone, true, quiet, null, null, false, true); + assert.equal(result.valid, true); + assert.ok(result.clonedResponse); + void result.clonedResponse.body!.cancel().catch(noop); + void original.body!.cancel().catch(noop); + await nextTurn(); + assert.equal(cancelled, true); + assert.equal(clone.body!.locked, false); +}); + +test("one configured target recovers through its existing same-target retry", async () => { + const fixture = setup("priority", { maxRetries: 1 }); + fixture.combo.models = [{ model: first }]; + const calls: string[] = []; + const { response } = await run( + "priority", + async (_body, model) => { + calls.push(model); + return calls.length === 1 ? cut(role) : success(); + }, + fixture + ); + assert.deepEqual(calls, [first, first]); + assert.equal(await drain(response), answer); +}); + +test("non-streaming client request keeps coded read-error passthrough", async () => { + const calls: string[] = []; + const { response } = await run( + "priority", + async (_body, model) => { + calls.push(model); + return cut("", coded()); + }, + { body: { stream: false, messages: [{ role: "user", content: "hello" }] } } + ); + assert.deepEqual(calls, [first]); + assert.equal(response.status, 200); + await drain(response); +}); + +async function rejectNextRead(response: Response, prefix?: string) { + const reader = response.body!.getReader(); + try { + if (prefix !== undefined) { + const initial = await reader.read(); + assert.equal(new TextDecoder().decode(initial.value), prefix); + } + await assert.rejects(reader.read(), { code: "ECONNRESET" }); + } finally { + reader.releaseLock(); + } +} +async function rejectReturnedStream(response: Response) { + const reader = response.body!.getReader(); + try { + await assert.rejects( + async () => { + for (;;) { + const chunk = await reader.read(); + if (chunk.done) return; + } + }, + { code: "ECONNRESET" } + ); + } finally { + reader.releaseLock(); + } +} +async function assertRouteVeto( + route: Parameters[0], + prefix: string, + targetTimeoutMs = 1000 +) { + const calls: string[] = []; + const warnings: unknown[][] = []; + const { response } = await run( + route, + async (_body, model, target) => { + if (route === "pin" && calls.length === 0) assert.equal(target?.modelPinned, true); + calls.push(model); + return cut(prefix, coded("ECONNRESET", "no usable content secret=must-stay-private")); + }, + { + log: { + ...quiet, + warn: (...args: unknown[]) => { + warnings.push(args); + }, + }, + }, + { targetTimeoutMs } + ); + assert.deepEqual(calls, [first]); + assert.equal(response.status, 200); + if (targetTimeoutMs === 0) await rejectNextRead(response, prefix); + else await rejectReturnedStream(response); + assert.doesNotMatch(JSON.stringify(warnings), /must-stay-private/); +} +const eventOnlyItems: Array<[string, string]> = ["added", "done"].map((kind) => [ + `event-only empty message item ${kind}`, + frame({ item: { type: "message", content: [] } }, `response.output_item.${kind}`), +]); +function assertScannerVeto(prefix: string | string[], expected: boolean) { + let observed = false; + const scanner = new SseOutputScanner(() => { + observed = true; + }); + const readiness = new SseOutputScanner(); + for (const chunk of typeof prefix === "string" ? [prefix] : prefix) { + const ready = scanner.push(chunk); + assert.equal(ready, readiness.push(chunk), "observer preserves push readiness"); + const overflowed = scanner.overflowedWithoutOutput(); + assert.equal(overflowed, readiness.overflowedWithoutOutput()); + if (ready || overflowed) break; + } + assert.equal(scanner.flush(), readiness.flush(), "observer preserves error/EOF flush"); + assert.equal(observed, expected, "scanner's completed frames/output/caps decide replay veto"); +} +for (const [boundary, prefix] of eventOnlyItems) { + test(`scanner observes ${boundary} veto`, () => assertScannerVeto(prefix, true)); +} +for (const route of ["priority", "round-robin", "runtime", "pin"] as const) { + for (const [boundary, prefix] of [boundaries[0], boundaries[12], eventOnlyItems[1]]) { + test(`${route}: positive first-output gate retains ${boundary} veto`, () => + assertRouteVeto(route, prefix)); + } + test(`${route}: inherited terminal veto precedes Cursor diagnostics`, async () => { + const calls: string[] = []; + const warnings: unknown[][] = []; + const { response } = await run( + route, + async (_body, model) => { + calls.push(model); + return cut( + frame({}, "response.done"), + coded("ECONNRESET", "no usable content synthetic-secret-veto") + ); + }, + { + log: { + ...quiet, + warn: (...args: unknown[]) => { + warnings.push(args); + }, + }, + }, + { targetTimeoutMs: 1000 } + ); + assert.deepEqual(calls, [first]); + assert.doesNotMatch(JSON.stringify(warnings), /synthetic-secret-veto/); + assert.equal(response.status, 200); + await rejectReturnedStream(response); + }); + for (const prefix of [ping, role]) { + test(`${route}: positive gate metadata still permits transport retry (${prefix === ping ? "ping" : "role"})`, async () => { + const calls: string[] = []; + const { response } = await run( + route, + async (_body, model) => { + calls.push(model); + return calls.length === 1 ? cut(prefix) : success(); + }, + {}, + { targetTimeoutMs: 1000 } + ); + assert.deepEqual(calls, [first, first]); + assert.equal(await drain(response), answer); + }); + } +} +const failureFrames = [ + frame( + { + response: { + status: "failed", + error: { code: "invalid_request_error", message: "invalid request" }, + }, + }, + "response.failed" + ), + frame({ error: { code: "invalid_request_error", message: "invalid request" } }, "error"), + frame({ error: { code: "context_length_exceeded", message: "context too long" } }), + frame({ response: { status: "failed", error: { message: "failed" } } }), + frame("invalid request", "error"), +]; +for (const [index, prefix] of failureFrames.entries()) { + test(`scanner observes failure ${index} veto`, () => assertScannerVeto(prefix, true)); +} +for (const route of ["priority", "round-robin", "runtime", "pin"] as const) { + test(`${route}: first-gate observed failure preserves lost-frame passthrough`, () => + assertRouteVeto(route, failureFrames[0])); +} +for (const benign of [{}, "", false, 0]) { + test(`benign error ${JSON.stringify(benign)} cannot create a first-output veto`, async () => { + let calls = 0; + const { response } = await run( + "priority", + async () => (++calls === 1 ? cut(frame({ error: benign })) : success()), + {}, + { targetTimeoutMs: 1000 } + ); + assert.equal(calls, 2); + assert.equal(await drain(response), answer); + }); +} +test("an error still visible to quality retains its request-scoped classification", async () => { + const fixture = setup("priority", { maxRetries: 1 }); + fixture.combo.models = [{ model: first }]; + let calls = 0; + const { response } = await run( + "priority", + async () => { + calls++; + return new Response(failureFrames[0], { headers: { "Content-Type": "text/event-stream" } }); + }, + fixture + ); + assert.equal(calls, 1); + assert.equal(response.status, 400); + await drain(response); +}); +for (const ending of ["\n", "\r\n", "\r"]) { + for (const kind of ["added", "done"]) { + test(`event-only item ${kind} survives split ${JSON.stringify(ending)} without changing readiness`, async () => { + const prefix = frame( + { item: { type: "message", content: [] } }, + `response.output_item.${kind}` + ).replaceAll("\n", ending); + const chunks = [...prefix]; + const calls: string[] = []; + const { response } = await run( + "priority", + async (_body, model) => { + calls.push(model); + return cut(chunks); + }, + {}, + { targetTimeoutMs: 1000 } + ); + assert.deepEqual(calls, [first]); + assert.equal(response.status, 200); + await rejectReturnedStream(response); + }); + } +} +test("response-bound vetoes remain isolated across interleaved invocations", async () => { + const callsA: string[] = []; + const callsB: string[] = []; + const [a, b] = await Promise.all([ + run( + "priority", + async (_body, model) => { + callsA.push(model); + return cut(frame({}, "response.done")); + }, + {}, + { targetTimeoutMs: 1000 } + ), + run( + "priority", + async (_body, model) => { + callsB.push(model); + return callsB.length === 1 ? cut(ping) : success(); + }, + {}, + { targetTimeoutMs: 1000 } + ), + ]); + assert.deepEqual(callsA, [first]); + assert.deepEqual(callsB, [first, first]); + await rejectReturnedStream(a.response); + assert.equal(await drain(b.response), answer); +}); +test("forged headers cannot create the private veto and no policy header is emitted", async () => { + let calls = 0; + const { response } = await run( + "priority", + async () => { + calls++; + const result = calls === 1 ? cut(ping) : success(); + result.headers.set("X-Forged-Replay-Veto", "true"); + return result; + }, + { requestHeaders: { "X-Forged-Replay-Veto": "true" } }, + { targetTimeoutMs: 1000 } + ); + assert.equal(calls, 2); + assert.equal(await drain(response), answer); + assert.deepEqual( + [...response.headers.keys()].filter((name) => /veto|transport/i.test(name)), + ["x-forged-replay-veto"] + ); +}); +for (const root of [true, false]) { + test(`revoked proxy ${root ? "root" : "cause"} remains safe unknown-error passthrough`, async () => { + const proxy = Proxy.revocable({}, {}); + proxy.revoke(); + const error = root ? proxy.proxy : Object.assign(coded(), { cause: proxy.proxy }); + assert.equal((await quality(ping, error)).valid, true); + }); +} +const largeMetadata = frame({ type: "progress", padding: "x".repeat(80 * 1024) }, "progress"); +for (const route of ["priority", "round-robin", "runtime", "pin"] as const) { + for (const enabled of [false, undefined]) { + test(`${route}: oversized metadata passthrough with recovery ${enabled}`, async () => { + const calls: string[] = []; + const { response } = await run( + route, + async (_body, model, target) => { + if (route === "pin" && calls.length === 0) assert.equal(target?.modelPinned, true); + calls.push(model); + return cut(largeMetadata); + }, + { settings: enabled === undefined ? {} : settings(enabled) }, + { targetTimeoutMs: 1000 } + ); + assert.deepEqual(calls, [first]); + assert.equal(response.status, 200); + await rejectReturnedStream(response); + }); + } +} From 73ee6e900c050a8ff5cf294f09824b9300ba2f6b Mon Sep 17 00:00:00 2001 From: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> Date: Wed, 4 Nov 2026 13:44:04 -0300 Subject: [PATCH 07/20] fix(translator): hoist displaced tool results into their assistant turn (#18709) --- open-sse/translator/helpers/toolCallHelper.ts | 90 +++++++++++ open-sse/translator/index.ts | 3 + .../translator-displaced-tool-results.test.ts | 147 ++++++++++++++++++ 3 files changed, 240 insertions(+) create mode 100644 tests/unit/translator-displaced-tool-results.test.ts diff --git a/open-sse/translator/helpers/toolCallHelper.ts b/open-sse/translator/helpers/toolCallHelper.ts index c935e6370d5b..7b06336279b1 100644 --- a/open-sse/translator/helpers/toolCallHelper.ts +++ b/open-sse/translator/helpers/toolCallHelper.ts @@ -391,6 +391,96 @@ export function fixMissingToolResponses(body: T): T { return body; } +/** + * Scan forward from `from` for response messages that resolve the turn's remaining calls, + * stopping at the next assistant message. Returns their indexes and removes the resolved ids + * from `openIds` (mutated in place — callers rely on that to know what is still open). + */ +function collectDisplacedResultIndexes( + messages: ToolMessage[], + from: number, + openIds: Set +): number[] { + const ahead: number[] = []; + for (let j = from; j < messages.length; j++) { + const later = messages[j]; + if (later.role === "assistant") break; + const resolved = respondedToolIds(later).filter((id) => openIds.has(id)); + if (resolved.length === 0) continue; + ahead.push(j); + for (const id of resolved) openIds.delete(id); + if (openIds.size === 0) break; + } + return ahead; +} + +/** Move the messages at `indexes` (ascending, all after `target`) directly above `target`, in place. */ +function hoistMessagesAbove(messages: ToolMessage[], indexes: number[], target: number): void { + const hoisted = indexes.map((idx) => messages[idx]); + const hoistedSet = new Set(indexes); + const rebuilt: ToolMessage[] = []; + for (let j = 0; j < messages.length; j++) { + if (j === target) rebuilt.push(...hoisted); + if (!hoistedSet.has(j)) rebuilt.push(messages[j]); + } + messages.length = 0; + messages.push(...rebuilt); +} + +/** + * Hoist displaced tool results back into their assistant turn's result run. + * + * Clients that steer mid-turn (queued user messages, task notifications) can + * interleave a non-tool message between an assistant tool_calls message and + * part of its results: [assistant(tc:c1,c2), tool(c1), user, tool(c2)]. Strict + * upstream validators (DeepSeek V4 thinking confirmed live) reject that with + * "insufficient tool messages following tool_calls message". The result + * belongs to the turn, so move it up to the end of the contiguous run. + * Stops at the next assistant message: a later assistant turn's own pairing + * must not be scrambled. + */ +export function repairDisplacedToolResults(body: T): T { + if (!body.messages || !Array.isArray(body.messages)) return body; + const messages = body.messages; + + let turnIds: Set | null = null; + + for (let i = 0; i < messages.length; i++) { + const msg = messages[i]; + const ids = getToolCallIds(msg); + if (ids.length > 0) { + turnIds = new Set(ids); + continue; + } + if (!turnIds || turnIds.size === 0) continue; + // Stable non-null alias: `turnIds` is reassigned below, which defeats narrowing in callbacks. + const openIds: Set = turnIds; + + if (msg.role === "assistant") { + // A new assistant turn (without tool calls) ends the previous turn's window. + turnIds = null; + continue; + } + if (respondedToolIds(msg).some((id) => openIds.has(id))) { + // Contiguous member of the run — still fine where it is. + continue; + } + // Non-tool message inside the turn's window: hoist the later response messages that + // resolve the turn's remaining calls above this message. + const ahead = collectDisplacedResultIndexes(messages, i + 1, openIds); + if (ahead.length === 0) { + // Nothing ahead belongs to this turn; close the window at this boundary. + turnIds = null; + continue; + } + hoistMessagesAbove(messages, ahead, i); + // Re-scan from the same index: the hoisted results now precede msg. + i -= 1; + } + + return body; +} + // Strip tool-result carriers whose id has no matching tool call anywhere in the // conversation. Mirrors fixMissingToolResponses on the result side: that helper // ensures every call has a result; this one ensures every result has a call. diff --git a/open-sse/translator/index.ts b/open-sse/translator/index.ts index 88a3a181d3e9..d905b851aa6f 100644 --- a/open-sse/translator/index.ts +++ b/open-sse/translator/index.ts @@ -2,6 +2,7 @@ import { FORMATS } from "./formats.ts"; import { ensureToolCallIds, fixMissingToolResponses, + repairDisplacedToolResults, stripOrphanedToolResults, } from "./helpers/toolCallHelper.ts"; import { @@ -435,6 +436,7 @@ export function translateRequest( ensureToolCallIds(result, { use9CharId }); // Fix missing tool responses (insert empty tool_result if needed) + repairDisplacedToolResults(result); fixMissingToolResponses(result); // Claude reconciliation preserves orphaned tool output as labelled user text. @@ -773,6 +775,7 @@ export function translateRequest( // Ensure unique tool_call ids on final payload (translators may have introduced duplicates) ensureToolCallIds(result, { use9CharId }); + repairDisplacedToolResults(result); fixMissingToolResponses(result); stripOrphanedToolResults(result); diff --git a/tests/unit/translator-displaced-tool-results.test.ts b/tests/unit/translator-displaced-tool-results.test.ts new file mode 100644 index 000000000000..aeff6d31f04f --- /dev/null +++ b/tests/unit/translator-displaced-tool-results.test.ts @@ -0,0 +1,147 @@ +import test from "node:test"; +import assert from "node:assert/strict"; + +// Displaced tool results: clients that steer mid-turn (queued user messages, +// task notifications) can interleave a non-tool message between an assistant +// tool_calls message and part of its results: +// [assistant(tc:c1,c2), tool(c1), user, tool(c2)] +// Strict upstream validators (DeepSeek V4 thinking confirmed live 2026-11-01, +// "insufficient tool messages following tool_calls message") reject the whole +// request. repairDisplacedToolResults hoists the result back into the turn's +// contiguous run; fixMissingToolResponses (per-id coverage) inserts empty +// results for calls nothing resolves. +const { repairDisplacedToolResults, fixMissingToolResponses } = await import( + "../../open-sse/translator/helpers/toolCallHelper.ts" +); + +const asst = (ids, text = null) => ({ + role: "assistant", + content: text, + tool_calls: ids.map((id) => ({ + id, + type: "function", + function: { name: "shell", arguments: "{}" }, + })), +}); +const tool = (id, content = "result") => ({ role: "tool", tool_call_id: id, content }); +const user = (text) => ({ role: "user", content: text }); + +function runBoth(body) { + return fixMissingToolResponses(repairDisplacedToolResults(body)); +} + +test("displaced result is hoisted back into the assistant turn's result run", () => { + const body = { + messages: [user("go"), asst(["c1", "c2"]), tool("c1"), user("task notification"), tool("c2"), user("next")], + }; + const out = runBoth(body).messages; + // The displaced c2 result moves above the steering user message; the run is + // contiguous, so no synthetic results are injected. + assert.deepEqual( + out.map((m) => m.role), + ["user", "assistant", "tool", "tool", "user", "user"] + ); + assert.deepEqual(out.slice(2, 4).map((m) => m.tool_call_id), ["c1", "c2"]); + assert.equal(out[2].content, "result"); + assert.equal(out[3].content, "result"); +}); + +test("partial coverage: missing id gets an empty result, existing result untouched", () => { + const body = { messages: [user("go"), asst(["c1", "c2"]), tool("c1"), user("next")] }; + const out = runBoth(body).messages; + assert.equal(out.length, 5); + assert.equal(out[2].tool_call_id, "c1"); + assert.equal(out[2].content, "result"); + assert.equal(out[3].tool_call_id, "c2"); + assert.equal(out[3].content, ""); + assert.equal(out[4].role, "user"); +}); + +test("dangling call mid-conversation still gets an empty result", () => { + const body = { messages: [user("go"), asst(["c1"]), user("next")] }; + const out = runBoth(body).messages; + assert.equal(out.length, 4); + assert.equal(out[2].role, "tool"); + assert.equal(out[2].tool_call_id, "c1"); + assert.equal(out[3].role, "user"); +}); + +test("trailing assistant turn gets its pending call closed with an empty result", () => { + // release/v3.8.53 normalizeTrailingCalls: a trailing pending call gets an + // empty response so the conversation is complete for the next turn. + const body = { messages: [user("go"), asst(["c1"])] }; + const out = runBoth(body).messages; + assert.equal(out.length, 3); + assert.equal(out[2].role, "tool"); + assert.equal(out[2].tool_call_id, "c1"); + assert.equal(out[2].content, ""); +}); + +test("hoisting never crosses the next assistant message", () => { + const body = { + messages: [user("go"), asst(["c1"]), user("steer"), user("later"), asst([], "hi"), tool("c1"), user("next")], + }; + const out = runBoth(body).messages; + // The dangling call gets its empty result right after the assistant; the + // stale result beyond the later assistant stays in place (hoisting never + // crosses an assistant boundary). + assert.equal(out[2].role, "tool"); + assert.equal(out[2].tool_call_id, "c1"); + assert.equal(out[2].content, ""); + assert.equal(out[5].role, "assistant"); + assert.equal(out[6].role, "tool"); + assert.equal(out[6].tool_call_id, "c1"); +}); + +test("multiple displaced results hoist in stable relative order", () => { + const body = { + messages: [ + user("go"), + asst(["c1", "c2", "c3"]), + tool("c1"), + user("notif one"), + tool("c3"), + user("notif two"), + tool("c2"), + user("next"), + ], + }; + const out = runBoth(body).messages; + assert.deepEqual( + out.slice(2, 5).map((m) => (m.role === "tool" ? m.tool_call_id : m.role)), + ["c1", "c3", "c2"] + ); + assert.equal(out[5].role, "user"); +}); + +test("Claude-format displaced tool_result user message hoists the same way", () => { + const claudeAsst = { + role: "assistant", + content: [ + { type: "tool_use", id: "c1", name: "shell", input: {} }, + { type: "tool_use", id: "c2", name: "shell", input: {} }, + ], + }; + const resultCarrier = (id) => ({ + role: "user", + content: [{ type: "tool_result", tool_use_id: id, content: "r" }], + }); + const body = { + messages: [user("go"), claudeAsst, resultCarrier("c1"), user("notif"), resultCarrier("c2"), user("next")], + }; + const out = runBoth(body).messages; + // The c2 carrier hoists above the notification; no synthetic result needed. + assert.deepEqual( + out.slice(2, 5).map((m) => m.role), + ["user", "user", "user"] + ); + const ids = out.slice(2, 5).map((m) => m.content?.[0]?.tool_use_id); + assert.deepEqual(ids, ["c1", "c2", undefined]); +}); + +test("fully-paired conversation is a no-op", () => { + const body = { messages: [user("go"), asst(["c1"]), tool("c1"), user("next")] }; + const before = JSON.stringify(body.messages); + const out = runBoth(body).messages; + assert.equal(JSON.stringify(out), before); +}); From ef59155f206df05079aecebe3018ceb63894f9ee Mon Sep 17 00:00:00 2001 From: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> Date: Wed, 4 Nov 2026 13:44:06 -0300 Subject: [PATCH 08/20] fix(sse): read a JSON response document served as text/event-stream (#18377) --- .../18377-json-body-behind-event-stream.md | 1 + .../chatCore/nonStreamingJsonDocument.ts | 76 ++++++++ .../chatCore/nonStreamingResponseParse.ts | 137 ++++++++----- .../json-body-behind-event-stream.test.ts | 182 ++++++++++++++++++ 4 files changed, 351 insertions(+), 45 deletions(-) create mode 100644 changelog.d/fixes/18377-json-body-behind-event-stream.md create mode 100644 open-sse/handlers/chatCore/nonStreamingJsonDocument.ts create mode 100644 tests/unit/json-body-behind-event-stream.test.ts diff --git a/changelog.d/fixes/18377-json-body-behind-event-stream.md b/changelog.d/fixes/18377-json-body-behind-event-stream.md new file mode 100644 index 000000000000..95820b415fc9 --- /dev/null +++ b/changelog.d/fixes/18377-json-body-behind-event-stream.md @@ -0,0 +1 @@ +- **fix(sse):** a non-streaming request no longer fails with a 502 `invalid_sse` when a forced-streaming upstream answers with one complete JSON response document (chat completion, Responses object, Claude message or structured error) labelled `text/event-stream`, even with `data: [DONE]` glued after it; the document is used as the response, so a combo no longer skips that target. Corrupt event streams still return the 502 ([#18377](https://github.com/cheaper-inference/OmniRoute/pull/18377)) — thanks @cristianob diff --git a/open-sse/handlers/chatCore/nonStreamingJsonDocument.ts b/open-sse/handlers/chatCore/nonStreamingJsonDocument.ts new file mode 100644 index 000000000000..d985497831b7 --- /dev/null +++ b/open-sse/handlers/chatCore/nonStreamingJsonDocument.ts @@ -0,0 +1,76 @@ +/** + * Complete JSON response document served as `text/event-stream`. + * + * A forced-streaming upstream answering a non-streaming client can return one whole JSON + * document (an OpenAI `chat.completion`, a Responses `response`, a Claude `message`, or a + * structured error envelope) under an event-stream content type, sometimes with a + * `data: [DONE]` sentinel glued after the closing brace. None of its lines is a `data:` frame, + * so the buffered-SSE parsers find nothing and the request used to fail as `invalid_sse`. + * This reads such a body as the non-stream response it is. Anything that is not exactly one + * recognised JSON document (truncated JSON, trailing garbage, unknown objects, bare stream + * chunks) returns null so the caller keeps its invalid-SSE failure. + */ + +import { FORMATS } from "../../translator/formats.ts"; + +type JsonRecord = Record; + +const TRAILING_DONE_SENTINEL = /data:[ \t]*\[DONE\]\s*$/; + +function isRecord(value: unknown): value is JsonRecord { + return !!value && typeof value === "object" && !Array.isArray(value); +} + +function detectResponseDocumentFormat(doc: JsonRecord): string | null { + if (Array.isArray(doc.choices) && doc.choices.some((c) => isRecord(c) && isRecord(c.message))) { + return FORMATS.OPENAI; + } + if (doc.object === "response" && Array.isArray(doc.output)) return FORMATS.OPENAI_RESPONSES; + if (doc.type === "message" && Array.isArray(doc.content)) return FORMATS.CLAUDE; + return null; +} + +function hasStructuredError(doc: JsonRecord): boolean { + const err = doc.error; + return ( + (typeof err === "string" && err.length > 0) || (isRecord(err) && Object.keys(err).length > 0) + ); +} + +// Mirrors parseNonStreamingSSEPayload: the preferred (target) format keeps its own label when +// its parser family matches the document; otherwise the document's own format is used. +function parserFamily(format: string): string { + if (format === FORMATS.OPENAI_RESPONSES || format === FORMATS.CLAUDE) return format; + if (format === FORMATS.GEMINI || format === FORMATS.ANTIGRAVITY) return FORMATS.GEMINI; + return FORMATS.OPENAI; +} + +export function parseJsonDocumentBehindEventStream( + rawBody: string, + targetFormat: string +): { body: JsonRecord; format: string } | null { + const text = String(rawBody || "") + .trim() + .replace(TRAILING_DONE_SENTINEL, "") + .trim(); + if (!text.startsWith("{") || !text.endsWith("}")) return null; + + let doc: unknown; + try { + doc = JSON.parse(text); + } catch { + return null; + } + if (!isRecord(doc)) return null; + + const detected = detectResponseDocumentFormat(doc); + if (detected) { + return { + body: doc, + format: parserFamily(targetFormat) === detected ? targetFormat : detected, + }; + } + // An error envelope is handled exactly like the same body served as application/json. + if (hasStructuredError(doc)) return { body: doc, format: targetFormat }; + return null; +} diff --git a/open-sse/handlers/chatCore/nonStreamingResponseParse.ts b/open-sse/handlers/chatCore/nonStreamingResponseParse.ts index 1d68987e32ec..4011bb0beacc 100644 --- a/open-sse/handlers/chatCore/nonStreamingResponseParse.ts +++ b/open-sse/handlers/chatCore/nonStreamingResponseParse.ts @@ -14,6 +14,7 @@ import { normalizePayloadForLog } from "@/lib/logPayloads"; import { extractSSEErrorMessage } from "../sseParser.ts"; import { readNonStreamingResponseBody } from "./nonStreamingResponseBody.ts"; +import { parseJsonDocumentBehindEventStream } from "./nonStreamingJsonDocument.ts"; import { isTruncatedStreamBody } from "../sseParser/truncation.ts"; import { markTruncatedStreamResponse } from "../../utils/truncatedStreamPolicy.ts"; import { @@ -58,7 +59,7 @@ export type NonStreamingParseResult = normalizedProviderPayload: unknown; }; -export async function parseNonStreamingResponseBody(opts: { +type ParseOptions = { providerResponse: Response; upstreamStream: boolean; providerHeaders: Record | Headers | null | undefined; @@ -66,64 +67,91 @@ export async function parseNonStreamingResponseBody(opts: { targetFormat: string; model: string; log?: LoggerLike; -}): Promise { - const { providerResponse, upstreamStream, providerHeaders, finalBody, targetFormat, model, log } = - opts; +}; - const contentType = (providerResponse.headers.get("content-type") || "").toLowerCase(); - const rawBody = await readNonStreamingResponseBody(providerResponse, contentType, upstreamStream); - const normalizedProviderPayload = normalizePayloadForLog(rawBody); - const looksLikeSSE = - contentType.includes("text/event-stream") || - contentType.includes("application/x-ndjson") || - /(^|\n)\s*(event|data):/m.test(rawBody); +/** Debug/warn log for an event stream buffered into a non-streaming client response. */ +function logBufferedEventStream( + opts: Pick, + streamKind: string +): void { + const { upstreamStream, providerHeaders, finalBody, log } = opts; + if (shouldTreatBufferedEventResponseAsExpected(upstreamStream, providerHeaders, finalBody)) { + log?.debug?.( + "STREAM", + `Buffering upstream ${streamKind} response for non-streaming client request` + ); + } else { + log?.warn?.( + "STREAM", + `Unexpected ${streamKind} response for non-streaming request — buffering` + ); + } +} - if (looksLikeSSE) { - const streamPayload = normalizeNonStreamingEventPayload(rawBody, contentType); - const streamKind = contentType.includes("application/x-ndjson") ? "NDJSON" : "SSE"; - if (shouldTreatBufferedEventResponseAsExpected(upstreamStream, providerHeaders, finalBody)) { - log?.debug?.( - "STREAM", - `Buffering upstream ${streamKind} response for non-streaming client request` - ); - } else { - log?.warn?.( - "STREAM", - `Unexpected ${streamKind} response for non-streaming request — buffering` - ); - } - // Upstream returned an event stream for a non-streaming client; convert best-effort to JSON. - const parsedFromSSE = parseNonStreamingSSEPayload(streamPayload, targetFormat, model); +/** + * Upstream returned an event stream for a non-streaming client; convert best-effort to JSON. + * Falls back to a complete JSON document behind the event-stream content type, then to the + * `invalid_sse` outcome. + */ +function parseBufferedEventStream( + opts: ParseOptions, + rawBody: string, + contentType: string, + normalizedProviderPayload: unknown +): NonStreamingParseResult { + const { providerResponse, targetFormat, model, log } = opts; + const streamPayload = normalizeNonStreamingEventPayload(rawBody, contentType); + const streamKind = contentType.includes("application/x-ndjson") ? "NDJSON" : "SSE"; + logBufferedEventStream(opts, streamKind); + const parsedFromSSE = parseNonStreamingSSEPayload(streamPayload, targetFormat, model); - if (!parsedFromSSE) { - // Some executors (e.g. the Devin/Windsurf CLI) always emit text/event-stream, signalling - // failure with an error-only chunk (`data: {"error":{"message":"Devin CLI not found..."}}`) - // that carries no `choices`. Surface that real, sanitized message instead of the generic 502 - // so the actionable error is not swallowed (#3324). - const surfacedSseError = extractSSEErrorMessage(streamPayload); - const invalidSseMessage = - surfacedSseError || "Invalid SSE response for non-streaming request"; + if (!parsedFromSSE) { + // A forced-streaming upstream may answer with one complete JSON response document under + // an event-stream content type: use it as the non-stream response instead of a 502. + const jsonDocument = parseJsonDocumentBehindEventStream(rawBody, targetFormat); + if (jsonDocument) { + log?.debug?.("STREAM", `Upstream ${streamKind} body is a complete JSON response document`); return { - kind: "invalid_sse", - message: invalidSseMessage, + kind: "ok", + responseBody: jsonDocument.body, + responsePayloadFormat: jsonDocument.format, looksLikeSSE: true, normalizedProviderPayload, }; } - - // #18460 follow-up — carry the parser's cut-stream verdict onto the upstream Response - // (internal marker, never serialized) so a combo can fail over to its next target. - if (isTruncatedStreamBody(parsedFromSSE.body)) markTruncatedStreamResponse(providerResponse); - + // Some executors (e.g. the Devin/Windsurf CLI) always emit text/event-stream, signalling + // failure with an error-only chunk (`data: {"error":{"message":"Devin CLI not found..."}}`) + // that carries no `choices`. Surface that real, sanitized message instead of the generic 502 + // so the actionable error is not swallowed (#3324). + const surfacedSseError = extractSSEErrorMessage(streamPayload); + const invalidSseMessage = surfacedSseError || "Invalid SSE response for non-streaming request"; return { - kind: "ok", - responseBody: parsedFromSSE.body, - responsePayloadFormat: parsedFromSSE.format, + kind: "invalid_sse", + message: invalidSseMessage, looksLikeSSE: true, normalizedProviderPayload, }; } + // #18460 follow-up — carry the parser's cut-stream verdict onto the upstream Response + // (internal marker, never serialized) so a combo can fail over to its next target. + if (isTruncatedStreamBody(parsedFromSSE.body)) markTruncatedStreamResponse(providerResponse); + + return { + kind: "ok", + responseBody: parsedFromSSE.body, + responsePayloadFormat: parsedFromSSE.format, + looksLikeSSE: true, + normalizedProviderPayload, + }; +} + +/** Plain (non event-stream) body: parse as a JSON object payload. */ +function parseJsonObjectBody( + rawBody: string, + targetFormat: string, + normalizedProviderPayload: unknown +): NonStreamingParseResult { try { const responseBody: unknown = rawBody ? JSON.parse(rawBody) : {}; if (!isJsonRecord(responseBody)) { @@ -154,3 +182,22 @@ export async function parseNonStreamingResponseBody(opts: { }; } } + +export async function parseNonStreamingResponseBody( + opts: ParseOptions +): Promise { + const { providerResponse, upstreamStream, targetFormat } = opts; + + const contentType = (providerResponse.headers.get("content-type") || "").toLowerCase(); + const rawBody = await readNonStreamingResponseBody(providerResponse, contentType, upstreamStream); + const normalizedProviderPayload = normalizePayloadForLog(rawBody); + const looksLikeSSE = + contentType.includes("text/event-stream") || + contentType.includes("application/x-ndjson") || + /(^|\n)\s*(event|data):/m.test(rawBody); + + if (looksLikeSSE) { + return parseBufferedEventStream(opts, rawBody, contentType, normalizedProviderPayload); + } + return parseJsonObjectBody(rawBody, targetFormat, normalizedProviderPayload); +} diff --git a/tests/unit/json-body-behind-event-stream.test.ts b/tests/unit/json-body-behind-event-stream.test.ts new file mode 100644 index 000000000000..f453e2cf733d --- /dev/null +++ b/tests/unit/json-body-behind-event-stream.test.ts @@ -0,0 +1,182 @@ +// A forced-streaming upstream answering a non-streaming client can return one complete JSON +// response document (chat.completion, Responses `response`, Claude `message`, or a structured +// error) labelled `Content-Type: text/event-stream`, sometimes with a `data: [DONE]` glued to +// the closing brace. No line starts with `data:`, so the buffered-SSE parsers found nothing and +// the client got a 502 `invalid_sse` (and a combo skipped a target that had answered). The body +// must be read as the non-stream response it is; genuinely corrupt SSE keeps the 502. +import { test } from "node:test"; +import assert from "node:assert/strict"; + +import { parseNonStreamingResponseBody } from "../../open-sse/handlers/chatCore/nonStreamingResponseParse.ts"; +import { runNonStreamingProviderLeg } from "../../open-sse/handlers/chatCore/nonStreamingProviderLeg.ts"; +import { baseInput } from "./_helpers/nonStreamingProviderLegFixtures.ts"; + +const GENERIC_INVALID_SSE = "Invalid SSE response for non-streaming request"; + +const CHAT_DOC = { + id: "chatcmpl-doc", + object: "chat.completion", + created: 1700000000, + model: "deepseek-v4.1-flash", + choices: [ + { + index: 0, + finish_reason: "stop", + message: { role: "assistant", content: "ok", reasoning_content: "" }, + }, + ], + usage: { prompt_tokens: 404, completion_tokens: 2, total_tokens: 406 }, +}; + +function eventStream(body: string): Response { + return new Response(body, { headers: { "content-type": "text/event-stream" } }); +} + +function chunkedEventStream(parts: string[]): Response { + const encoder = new TextEncoder(); + const stream = new ReadableStream({ + start(controller) { + for (const part of parts) controller.enqueue(encoder.encode(part)); + controller.close(); + }, + }); + return new Response(stream, { headers: { "content-type": "text/event-stream" } }); +} + +function parse(providerResponse: Response, targetFormat = "openai", upstreamStream = false) { + return parseNonStreamingResponseBody({ + providerResponse, + upstreamStream, + providerHeaders: null, + finalBody: null, + targetFormat, + model: "fallback-model", + }); +} + +for (const [label, body] of [ + ["bare document", JSON.stringify(CHAT_DOC)], + ["document with a glued data: [DONE]", `${JSON.stringify(CHAT_DOC)}data: [DONE]\n\n`], + ["document followed by a data: [DONE] frame", `${JSON.stringify(CHAT_DOC)}\n\ndata: [DONE]\n\n`], + ["pretty-printed document", `\n${JSON.stringify(CHAT_DOC, null, 2)}\n`], +] as const) { + test(`chat.completion ${label} behind text/event-stream -> ok non-stream body`, async () => { + const res = await parse(eventStream(body)); + assert.equal(res.kind, "ok"); + if (res.kind !== "ok") return; + assert.equal(res.responsePayloadFormat, "openai"); + assert.equal(res.looksLikeSSE, true); + assert.deepEqual(res.responseBody, CHAT_DOC); + }); +} + +test("chat.completion drained from a chunked forced-stream upstream -> ok", async () => { + const body = `${JSON.stringify(CHAT_DOC)}data: [DONE]\n\n`; + const res = await parse( + chunkedEventStream([body.slice(0, 40), body.slice(40, 120), body.slice(120)]), + "openai", + true + ); + assert.equal(res.kind, "ok"); + if (res.kind !== "ok") return; + assert.equal( + (res.responseBody.choices as Array<{ message: { content: string } }>)[0].message.content, + "ok" + ); +}); + +test("Responses document behind text/event-stream -> ok, labelled openai-responses", async () => { + const doc = { + id: "resp_doc", + object: "response", + status: "completed", + output: [ + { type: "message", role: "assistant", content: [{ type: "output_text", text: "ok" }] }, + ], + }; + const res = await parse(eventStream(JSON.stringify(doc)), "openai"); + assert.equal(res.kind, "ok"); + if (res.kind !== "ok") return; + assert.equal(res.responsePayloadFormat, "openai-responses"); + assert.deepEqual(res.responseBody, doc); +}); + +test("Claude message document behind text/event-stream -> ok, labelled claude", async () => { + const doc = { + id: "msg_doc", + type: "message", + role: "assistant", + model: "claude-x", + content: [{ type: "text", text: "ok" }], + stop_reason: "end_turn", + usage: { input_tokens: 3, output_tokens: 1 }, + }; + const res = await parse(eventStream(JSON.stringify(doc)), "claude"); + assert.equal(res.kind, "ok"); + if (res.kind !== "ok") return; + assert.equal(res.responsePayloadFormat, "claude"); + assert.deepEqual(res.responseBody, doc); +}); + +test("structured error document behind text/event-stream -> handled as a non-stream body", async () => { + const doc = { error: { message: "Rate limit reached", type: "rate_limit_error" } }; + const res = await parse(eventStream(JSON.stringify(doc)), "openai"); + // Same outcome as the identical body served as application/json: the error envelope reaches + // the caller intact instead of being replaced by the generic invalid-SSE 502. + assert.equal(res.kind, "ok"); + if (res.kind !== "ok") return; + assert.equal(res.responsePayloadFormat, "openai"); + assert.deepEqual(res.responseBody, doc); +}); + +for (const [label, body] of [ + ["truncated JSON document", JSON.stringify(CHAT_DOC).slice(0, 60)], + ["JSON object that is not a response", '{"foo":"bar"}'], + ["bare chunk without data: framing", '{"choices":[{"index":0,"delta":{"content":"hi"}}]}'], + ["JSON document followed by garbage", `${JSON.stringify(CHAT_DOC)}\ngarbage`], + ["unparseable data: frame", "data: not-json-at-all\n\n"], +] as const) { + test(`corrupt SSE (${label}) still fails as invalid_sse`, async () => { + const res = await parse(eventStream(body)); + assert.equal(res.kind, "invalid_sse"); + if (res.kind !== "invalid_sse") return; + assert.equal(res.message, GENERIC_INVALID_SSE); + }); +} + +function legWith(body: string) { + return baseInput({ + executeProviderRequest: async () => ({ + response: { + ok: true, + status: 200, + statusText: "OK", + headers: new Headers({ "content-type": "text/event-stream" }), + text: async () => body, + body: null, + } as unknown as Response, + url: "https://api.example.com/v1/chat/completions", + headers: {}, + transformedBody: null, + }), + }); +} + +test("provider leg: chat.completion behind text/event-stream returns ok, not 502", async () => { + const result = await runNonStreamingProviderLeg( + legWith(`${JSON.stringify(CHAT_DOC)}data: [DONE]\n\n`) + ); + assert.equal(result.kind, "ok"); + if (result.kind !== "ok") return; + assert.equal(result.response.choices[0].message.content, "ok"); + assert.equal(result.receipt.httpStatus, 200); + assert.equal(result.usage?.prompt_tokens, 404); +}); + +test("provider leg: corrupt event-stream body keeps the 502 invalid_sse_payload", async () => { + const result = await runNonStreamingProviderLeg(legWith(JSON.stringify(CHAT_DOC).slice(0, 60))); + assert.equal(result.kind, "error"); + if (result.kind !== "error") return; + assert.equal(result.result.status, 502); + assert.equal(result.result.errorCode, "invalid_sse_payload"); +}); From 2074436d124d4d48929e2e1a56df57b2add30df8 Mon Sep 17 00:00:00 2001 From: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> Date: Wed, 4 Nov 2026 13:44:06 -0300 Subject: [PATCH 09/20] fix(antigravity): route requests to an account whose plan lists the model (#18665) --- ...18665-antigravity-route-by-account-plan.md | 1 + src/sse/handlers/chat.ts | 1 + src/sse/services/auth.ts | 10 ++ src/sse/services/connectionModelInventory.ts | 129 ++++++++++---- .../services/credentialSelectionOptions.ts | 2 + src/sse/services/perAccountCatalogFilter.ts | 31 ++++ stryker.conf.json | 1 + ...tigravity-plan-aware-model-routing.test.ts | 164 ++++++++++++++++++ 8 files changed, 308 insertions(+), 31 deletions(-) create mode 100644 changelog.d/fixes/18665-antigravity-route-by-account-plan.md create mode 100644 src/sse/services/perAccountCatalogFilter.ts create mode 100644 tests/unit/antigravity-plan-aware-model-routing.test.ts diff --git a/changelog.d/fixes/18665-antigravity-route-by-account-plan.md b/changelog.d/fixes/18665-antigravity-route-by-account-plan.md new file mode 100644 index 000000000000..64e613fba8d4 --- /dev/null +++ b/changelog.d/fixes/18665-antigravity-route-by-account-plan.md @@ -0,0 +1 @@ +- **fix(antigravity):** Route each request to an Antigravity/agy account whose synced model list includes the requested model, so Claude 5.5 and Gemini 4 Argon requests no longer start on or fail over to accounts whose plan lacks them; fails open when no account lists the model, keeps never-synced accounts eligible, and lets an operator-pinned account through (a combo step pinned to an account whose plan lacks the model is skipped while another account lists it) ([#18665](https://github.com/cheaper-inference/OmniRoute/pull/18665)) — thanks @bufftop25 diff --git a/src/sse/handlers/chat.ts b/src/sse/handlers/chat.ts index d803b3769ed9..bcea3f95a40d 100644 --- a/src/sse/handlers/chat.ts +++ b/src/sse/handlers/chat.ts @@ -1812,6 +1812,7 @@ async function handleSingleModelChat( preferredConnectionIds: apiKeyInfo?.preferredConnections ?? null, reserveOAuthSession: true, excludeConnectionIds: Array.from(excluded), + ...(hasForcedConnection && !isCombo ? { explicitConnectionPin: true } : {}), ...(agy.on ? { reserveAntigravityLease: true, routingRequestId: agy.requestId } : {}), ...(runtimeOptions.allowRateLimitedConnection ? { allowRateLimitedConnections: true } diff --git a/src/sse/services/auth.ts b/src/sse/services/auth.ts index 3890d7ca2deb..2d4fb21ea17a 100644 --- a/src/sse/services/auth.ts +++ b/src/sse/services/auth.ts @@ -6,6 +6,7 @@ import { getProviderSearchPool } from "@omniroute/open-sse/services/providerConn import { hydrateConnectionProviderSpecificData } from "./compatibleNodeBaseUrl.ts"; // #13452 import { extractGoogApiKeyHeader } from "./googApiKeyAuth.ts"; import { isCooldownRetrySkipRefusalsEnabled } from "./cooldownAwareRetry.ts"; +import { isPerAccountCatalogProvider } from "./perAccountCatalogFilter.ts"; import { describeUpstreamFailure } from "@/shared/utils/upstreamError"; import { isRequestScopedMisalignmentRefusal } from "@omniroute/open-sse/utils/providerSafety.ts"; import { buildAllExpiredCredentials } from "./authExpiredCredentials.ts"; @@ -1268,6 +1269,15 @@ export async function getProviderCredentials( connections, requestedModel ); + // An account the operator pinned for this request (dashboard test) is their call: the inferred + // per-account inventory must not veto it, or an entitlement Google under-reports is untestable. + if ( + forcedConnectionId && + options.explicitConnectionPin && + isPerAccountCatalogProvider(provider) + ) { + advertisedModelsByConnection.delete(forcedConnectionId); + } // Filter out unavailable accounts and excluded connection let availableConnections = connections.filter((c) => { if (excludedConnectionIds.has(c.id)) { diff --git a/src/sse/services/connectionModelInventory.ts b/src/sse/services/connectionModelInventory.ts index fc3126a5e6fe..68167ef476bd 100644 --- a/src/sse/services/connectionModelInventory.ts +++ b/src/sse/services/connectionModelInventory.ts @@ -2,6 +2,7 @@ import { splitCodexReasoningSuffix } from "@omniroute/open-sse/executors/codex/r import { isRegisteredImageModel } from "@omniroute/open-sse/config/imageRegistry"; import { isModelAdvertisedByConnection } from "@/domain/connectionModelRules"; import { isSelfHostedChatProvider } from "@/shared/constants/providers"; +import { inventoryOrFailOpen, isPerAccountCatalogProvider } from "./perAccountCatalogFilter.ts"; import { type ProviderConnectionView } from "@/lib/db/providers/lazyConnectionView"; import { getSyncedAvailableModelsByConnection, @@ -43,9 +44,77 @@ export function isRequestedModelAdvertised( return baseModel !== rawModel && isModelAdvertisedByConnection(baseModel, advertised); } +type InventoryAccumulator = { + advertised: Map>; + // Per-account catalogs (Antigravity plans): `antigravity` and `agy` connections share one pool, + // so their inventories are merged and judged together before the fail-open rule applies. + perAccountInventory: Map>; + perAccountMalformed: boolean; +}; + +/** Unreadable or malformed persisted rows: a per-account catalog can no longer be trusted. */ +function markPerAccountMalformed(providerId: string, acc: InventoryAccumulator): void { + if (isPerAccountCatalogProvider(providerId)) acc.perAccountMalformed = true; +} + +/** Does any Codex account's non-empty synced inventory advertise the requested model? */ +function isAdvertisedByAnyCodexAccount( + byConnection: SyncedAvailableModelsByConnection, + requestedModel: string +): boolean { + return Object.values(byConnection).some( + (models) => + Array.isArray(models) && + // An empty set means "advertises everything" to the matcher, so an + // empty row must not count as evidence for the requested model. + models.length > 0 && + isRequestedModelAdvertised("codex", requestedModel, new Set(models.map((m) => m.id))) + ); +} + +function recordConnectionInventories( + providerId: string, + byConnection: SyncedAvailableModelsByConnection, + acc: InventoryAccumulator +): void { + for (const [connectionId, models] of Object.entries(byConnection)) { + if (!Array.isArray(models) || models.length === 0) continue; + const ids = new Set(models.map((m) => m.id)); + if (isPerAccountCatalogProvider(providerId)) acc.perAccountInventory.set(connectionId, ids); + else acc.advertised.set(connectionId, ids); + } +} + +async function collectProviderInventory( + providerId: string, + connections: ProviderConnectionView[], + requestedModel: string, + acc: InventoryAccumulator +): Promise { + let byConnection: SyncedAvailableModelsByConnection; + try { + byConnection = await getSyncedAvailableModelsByConnection(providerId); + } catch { + markPerAccountMalformed(providerId, acc); + return; + } + // Malformed persisted rows: fail open for the whole provider. + if (byConnection[SYNCED_AVAILABLE_MODELS_MALFORMED]) { + markPerAccountMalformed(providerId, acc); + return; + } + if (providerId === "codex") { + if (!isAdvertisedByAnyCodexAccount(byConnection, requestedModel)) return; + for (const connection of connections) { + if (connection.provider === providerId) acc.advertised.set(connection.id, new Set()); + } + } + recordConnectionInventories(providerId, byConnection, acc); +} + /** - * Self-hosted hosts and Codex subscription accounts can advertise different - * inventories. For Codex, once an account inventory advertises the requested + * Self-hosted hosts, Codex subscription accounts and Antigravity/agy accounts (plans list + * different models) can advertise different inventories. For Codex, once an account inventory advertises the requested * model, unsynchronized accounts are not eligible guesses for it. With no * inventories at all, retain the existing bootstrap/offline behavior. * @@ -54,6 +123,10 @@ export function isRequestedModelAdvertised( * account can serve it, so Codex accounts are not filtered for it and the * upstream decides. Filtering every account answered a misleading * provider_not_configured despite active accounts. + * + * Antigravity and agy share one pool and one per-account catalog: accounts whose synced + * inventory does not list the requested model are skipped, failing open when no account + * lists it (see perAccountCatalogFilter.ts). */ export async function loadAdvertisedModelsForConnections( connections: ProviderConnectionView[], @@ -68,41 +141,35 @@ export async function loadAdvertisedModelsForConnections( .filter( (p): p is string => typeof p === "string" && - (isSelfHostedChatProvider(p) || (p === "codex" && !isCodexImageModel(requestedModel))) + (isSelfHostedChatProvider(p) || + (p === "codex" && !isCodexImageModel(requestedModel)) || + isPerAccountCatalogProvider(p)) ) ); if (inventoryProviders.size === 0) return advertised; + const acc: InventoryAccumulator = { + advertised, + perAccountInventory: new Map>(), + perAccountMalformed: false, + }; + await Promise.all( - [...inventoryProviders].map(async (providerId) => { - let byConnection: SyncedAvailableModelsByConnection; - try { - byConnection = await getSyncedAvailableModelsByConnection(providerId); - } catch { - return; - } - // Malformed persisted rows: fail open for the whole provider. - if (byConnection[SYNCED_AVAILABLE_MODELS_MALFORMED]) return; - if (providerId === "codex") { - const advertisedByAnyAccount = Object.values(byConnection).some( - (models) => - Array.isArray(models) && - // An empty set means "advertises everything" to the matcher, so an - // empty row must not count as evidence for the requested model. - models.length > 0 && - isRequestedModelAdvertised(providerId, requestedModel, new Set(models.map((m) => m.id))) - ); - if (!advertisedByAnyAccount) return; - for (const connection of connections) { - if (connection.provider === providerId) advertised.set(connection.id, new Set()); - } - } - for (const [connectionId, models] of Object.entries(byConnection)) { - if (!Array.isArray(models) || models.length === 0) continue; - advertised.set(connectionId, new Set(models.map((m) => m.id))); - } - }) + [...inventoryProviders].map((providerId) => + collectProviderInventory(providerId, connections, requestedModel, acc) + ) ); + if (!acc.perAccountMalformed) { + // When no account advertises the model the lists are stale/incomplete: fail open instead of + // emptying the pool. + for (const [connectionId, ids] of inventoryOrFailOpen( + requestedModel, + acc.perAccountInventory + )) { + advertised.set(connectionId, ids); + } + } + return advertised; } diff --git a/src/sse/services/credentialSelectionOptions.ts b/src/sse/services/credentialSelectionOptions.ts index 7ec6aba78099..344c5ceab55c 100644 --- a/src/sse/services/credentialSelectionOptions.ts +++ b/src/sse/services/credentialSelectionOptions.ts @@ -12,6 +12,8 @@ export interface CredentialSelectionOptions { forcedConnectionId?: string | null; /** API-key-scoped ordered preference. Only ranks connections that survived eligibility gates. */ preferredConnectionIds?: string[] | null; + /** The pin was chosen by the operator for this request (x-omniroute-connection), not derived by a combo. */ + explicitConnectionPin?: boolean; excludeConnectionIds?: string[] | null; sessionKey?: string | null; sessionAffinityTtlMs?: number | null; diff --git a/src/sse/services/perAccountCatalogFilter.ts b/src/sse/services/perAccountCatalogFilter.ts new file mode 100644 index 000000000000..271c93c27ba7 --- /dev/null +++ b/src/sse/services/perAccountCatalogFilter.ts @@ -0,0 +1,31 @@ +import { isModelAdvertisedByConnection } from "@/domain/connectionModelRules"; + +/** + * Hosted providers whose model entitlement differs PER ACCOUNT (plan/tier), so the + * per-connection synced inventory is authoritative for routing. Antigravity: only + * Ultra accounts are offered the 5.5 Claude models; the rest list 4.6 only and answer + * 403/404/429 to a 5.5 request, burning a retry (and a cooldown) per account. + * `agy` connections are served from the same account pool as `antigravity`, and a model can + * be advertised only by them (Gemini 4 Argon), so both inventories are judged together. + */ +const PER_ACCOUNT_CATALOG_PROVIDERS = new Set(["antigravity", "agy"]); + +export function isPerAccountCatalogProvider(providerId: unknown): boolean { + return typeof providerId === "string" && PER_ACCOUNT_CATALOG_PROVIDERS.has(providerId); +} + +/** + * Keep a provider's per-connection inventory only when at least one account really + * advertises the requested model. If none does, the synced lists are stale or + * incomplete (a hidden model, a sync that predates a plan upgrade): fail OPEN and + * return an empty map so selection is unchanged instead of emptying the whole pool. + */ +export function inventoryOrFailOpen( + requestedModel: string, + inventory: Map> +): Map> { + for (const models of inventory.values()) { + if (models.size > 0 && isModelAdvertisedByConnection(requestedModel, models)) return inventory; + } + return new Map(); +} diff --git a/stryker.conf.json b/stryker.conf.json index 30136c76c83b..9cd417477d12 100644 --- a/stryker.conf.json +++ b/stryker.conf.json @@ -64,6 +64,7 @@ "tests/unit/noauth-autocombo-lockout-7623.test.ts", "tests/unit/auth-opencode-zen-noauth-fallback.test.ts", "tests/unit/codex-account-catalog-routing.test.ts", + "tests/unit/antigravity-plan-aware-model-routing.test.ts", "tests/unit/provider-transfer.test.ts", "tests/unit/credential-scope-diagnostics-15889.test.ts", "tests/unit/account-weighted-runtime-16459.test.ts", diff --git a/tests/unit/antigravity-plan-aware-model-routing.test.ts b/tests/unit/antigravity-plan-aware-model-routing.test.ts new file mode 100644 index 000000000000..33659e54d905 --- /dev/null +++ b/tests/unit/antigravity-plan-aware-model-routing.test.ts @@ -0,0 +1,164 @@ +/** + * Antigravity entitlement is per ACCOUNT (plan): only Ultra accounts are offered the 5.5 Claude + * models. Routing must (1) skip accounts whose live inventory lacks the requested model, + * (2) fail open when no account advertises it, and (3) let an operator-pinned account through. + */ +import test from "node:test"; +import assert from "node:assert/strict"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; + +const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-agy-plan-routing-")); +process.env.DATA_DIR = TEST_DATA_DIR; + +const core = await import("../../src/lib/db/core.ts"); +const providersDb = await import("../../src/lib/db/providers.ts"); +const modelsDb = await import("../../src/lib/db/models.ts"); +const auth = await import("../../src/sse/services/auth.ts"); + +const PROVIDER = "antigravity"; +const OPUS55 = "claude-opus-5-5-low"; + +async function resetStorage() { + core.resetDbInstance(); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); + fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); +} + +test.after(() => { + core.resetDbInstance(); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); +}); + +async function account(name: string, priority: number, models: string[]): Promise { + const created = (await providersDb.createProviderConnection({ + provider: PROVIDER, + authType: "oauth", + name, + accessToken: `token-${name}`, + priority, + isActive: true, + })) as { id: string }; + if (models.length > 0) { + await modelsDb.replaceSyncedAvailableModelsForConnection( + PROVIDER, + created.id, + models.map((id) => ({ id, name: id })) + ); + } + return created.id; +} + +const idOf = (selected: unknown): string | null => { + if (!selected || typeof selected !== "object") return null; + const id = (selected as { connectionId?: unknown }).connectionId; + return typeof id === "string" ? id : null; +}; + +const OLD = ["claude-opus-4-6-thinking", "claude-sonnet-4-6"]; +const ULTRA = [...OLD, OPUS55, "claude-opus-5-5-high"]; + +test("a 5.5 request skips the accounts whose plan does not list it", async () => { + await resetStorage(); + const pro1 = await account("pro1", 1, OLD); + const pro2 = await account("pro2", 2, OLD); + const ultra = await account("ultra", 3, ULTRA); + + const first = await auth.getProviderCredentials(PROVIDER, null, null, OPUS55); + assert.equal(idOf(first), ultra, "only the Ultra account advertises the 5.5 model"); + assert.notEqual(idOf(first), pro1); + assert.notEqual(idOf(first), pro2); + + const next = await auth.getProviderCredentials(PROVIDER, ultra, null, OPUS55); + assert.notEqual(idOf(next), pro1, "failover must not wander onto an account without the model"); + assert.notEqual(idOf(next), pro2); +}); + +test("a model every account lists keeps all accounts eligible (4.6 failover intact)", async () => { + await resetStorage(); + const a = await account("a", 1, ULTRA); + const b = await account("b", 2, OLD); + + assert.equal( + idOf(await auth.getProviderCredentials(PROVIDER, null, null, "claude-sonnet-4-6")), + a + ); + assert.equal(idOf(await auth.getProviderCredentials(PROVIDER, a, null, "claude-sonnet-4-6")), b); +}); + +test("fails open when no account advertises the model (stale or incomplete lists)", async () => { + await resetStorage(); + const a = await account("a", 1, OLD); + await account("b", 2, OLD); + + const selected = await auth.getProviderCredentials(PROVIDER, null, null, "claude-opus-9-9-low"); + assert.equal(idOf(selected), a, "an unlisted model must not empty the pool"); +}); + +test("accounts never synced stay eligible (unknown is not 'does not have it')", async () => { + await resetStorage(); + const unsynced = await account("unsynced", 1, []); + await account("ultra", 2, ULTRA); + + const selected = await auth.getProviderCredentials(PROVIDER, null, null, OPUS55); + assert.equal(idOf(selected), unsynced); +}); + +test("inventoryOrFailOpen: keeps the inventory only when some account advertises the model", async () => { + const filter = await import("../../src/sse/services/perAccountCatalogFilter.ts"); + const inv = new Map([ + ["x", new Set(["m1"])], + ["y", new Set(["m2"])], + ]); + assert.equal(filter.inventoryOrFailOpen("m1", inv), inv); + assert.equal(filter.inventoryOrFailOpen("missing", inv).size, 0); + assert.equal(filter.isPerAccountCatalogProvider("antigravity"), true); + assert.equal(filter.isPerAccountCatalogProvider("openai"), false); +}); + +test("an account the operator pinned is not vetoed, but a combo-derived pin still is", async () => { + await resetStorage(); + const pro = await account("pro", 1, OLD); + const ultra = await account("ultra", 2, ULTRA); + + const pinned = await auth.getProviderCredentials(PROVIDER, null, null, OPUS55, { + forcedConnectionId: pro, + explicitConnectionPin: true, + }); + assert.equal(idOf(pinned), pro, "operator pinned this account; it must be tried as asked"); + + const comboTarget = await auth.getProviderCredentials(PROVIDER, null, null, OPUS55, { + forcedConnectionId: pro, + }); + assert.equal( + idOf(comboTarget), + null, + "a combo expansion must not spend calls on a non-covering account" + ); + + const automatic = await auth.getProviderCredentials(PROVIDER, null, null, OPUS55); + assert.equal(idOf(automatic), ultra, "automatic selection still follows the live inventory"); +}); + +test("a model only the agy accounts list (Gemini 4 Argon) skips the antigravity-provider accounts", async () => { + await resetStorage(); + const ARGON = "gemini-4-argon-low"; + const proAccount = await account("pro", 1, OLD); + const agy = (await providersDb.createProviderConnection({ + provider: "agy", + authType: "oauth", + name: "ultra-agy", + accessToken: "token-ultra-agy", + priority: 2, + isActive: true, + })) as { id: string }; + await modelsDb.replaceSyncedAvailableModelsForConnection("agy", agy.id, [ + { id: ARGON, name: ARGON }, + ...OLD.map((id) => ({ id, name: id })), + ]); + + const first = await auth.getProviderCredentials(PROVIDER, null, null, ARGON); + assert.equal(idOf(first), agy.id, "only the agy account advertises Argon"); + assert.notEqual(idOf(first), proAccount); +}); From b88fc83c465d8e76957a9ca753003f1598a2b08d Mon Sep 17 00:00:00 2001 From: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> Date: Wed, 4 Nov 2026 13:44:07 -0300 Subject: [PATCH 10/20] fix(sse): replay restored Claude thinking at its original content index (#18616) --- .../18607-exact-thinking-replay-layout.md | 1 + open-sse/services/claudeThinkingStore.ts | 113 +++++---- .../translator/request/openai-to-claude.ts | 17 +- .../translator/response/claude-to-openai.ts | 16 +- ...thropic-thinking-modified-recovery.test.ts | 6 +- ...claude-thinking-exact-replay-18607.test.ts | 219 ++++++++++++++++++ .../claude-thinking-replay-layout.test.ts | 2 +- ...nking-modified-recovery-recurrence.test.ts | 4 +- 8 files changed, 323 insertions(+), 55 deletions(-) create mode 100644 changelog.d/fixes/18607-exact-thinking-replay-layout.md create mode 100644 tests/unit/claude-thinking-exact-replay-18607.test.ts diff --git a/changelog.d/fixes/18607-exact-thinking-replay-layout.md b/changelog.d/fixes/18607-exact-thinking-replay-layout.md new file mode 100644 index 000000000000..6e82ff49c5a4 --- /dev/null +++ b/changelog.d/fixes/18607-exact-thinking-replay-layout.md @@ -0,0 +1 @@ +- **fix(sse):** replay server-restored Claude signed thinking at its original content index — the continuity store kept a flat list and `openai-to-claude` pushed every block to the front of each assistant fragment that owned any of the turn's `tool_use` ids, so `[thinking, text, tool_use, thinking, tool_use]` was reordered and a turn the client split into several messages replayed the blocks twice; the store now records which `tool_use` each run preceded and the translator puts each run back directly before that `tool_use`, once (Refs [#18607](https://github.com/cheaper-inference/OmniRoute/issues/18607)) diff --git a/open-sse/services/claudeThinkingStore.ts b/open-sse/services/claudeThinkingStore.ts index c882537069d7..b7b1a37cc91e 100644 --- a/open-sse/services/claudeThinkingStore.ts +++ b/open-sse/services/claudeThinkingStore.ts @@ -10,8 +10,8 @@ * * This store keeps the exact signed blocks server-side, keyed by the `tool_use` * ids of the assistant turn that produced them (same pattern as - * geminiThoughtSignatureStore), so the request translator can restore them in - * front of the owning `tool_use` on the next turn. + * geminiThoughtSignatureStore), grouped by the `tool_use` each run preceded, so + * the request translator can put every block back where Claude emitted it (#18607). * * Invariants: * - Memory only, TTL + size bounded. Thinking text and signatures never reach the @@ -20,20 +20,24 @@ * model, provider, or OAuth org/connection is a hard 400 (#6953, #17324), so a * binding mismatch restores nothing. * - All-or-nothing: a turn is stored only when every thinking-ish block is - * replayable (non-empty signature / data); a partial set would reorder the turn. + * replayable (non-empty signature / data) and precedes a tool_use; a partial + * set would reorder the turn. */ export type ClaudeSignedThinkingBlock = | { type: "thinking"; thinking: string; signature: string } | { type: "redacted_thinking"; data: string }; +/** The signed blocks Claude emitted between the turn's previous tool_use and `toolUseId`. */ +export type ClaudeThinkingSegment = { toolUseId: string; blocks: ClaudeSignedThinkingBlock[] }; + type Entry = { provider: string; model: string; /** Serving connection id; thinking signatures are org-scoped, so a block * produced by one OAuth org must never be replayed on another (#17324). */ connectionId: string; - blocks: ClaudeSignedThinkingBlock[]; + segments: ClaudeThinkingSegment[]; size: number; expiresAt: number; }; @@ -92,8 +96,15 @@ function blockSize(block: ClaudeSignedThinkingBlock): number { : block.data.length; } -function cloneBlocks(blocks: ClaudeSignedThinkingBlock[]): ClaudeSignedThinkingBlock[] { - return blocks.map((block) => ({ ...block })); +function cloneSegments(segments: ClaudeThinkingSegment[]): ClaudeThinkingSegment[] { + return segments.map((segment) => ({ + toolUseId: segment.toolUseId, + blocks: segment.blocks.map((block) => ({ ...block })), + })); +} + +function blocksOf(entry: Entry): ClaudeSignedThinkingBlock[] { + return entry.segments.flatMap((segment) => segment.blocks); } function deleteKey(key: string) { @@ -117,32 +128,53 @@ function pruneExpired(now: number) { } } -function collectToolUseIds(toolUseIds: unknown): string[] { - if (!Array.isArray(toolUseIds)) return []; - return toolUseIds.filter((id): id is string => typeof id === "string" && id.length > 0); +/** + * Group an ordered assistant content array into one segment per tool_use, each + * holding the signed blocks that preceded it. Null when any thinking-ish block is + * not replayable, when thinking trails the last tool_use (no anchor to replay + * against), when nothing is stored, or when the turn exceeds the entry size cap. + */ +function collectThinkingSegments( + content: unknown[] +): { segments: ClaudeThinkingSegment[]; size: number } | null { + const segments: ClaudeThinkingSegment[] = []; + let pending: ClaudeSignedThinkingBlock[] = []; + let size = 0; + for (const block of content) { + const record = (block ?? {}) as Record; + if (record.type === "thinking" || record.type === "redacted_thinking") { + const normalized = normalizeBlock(block); + if (!normalized) return null; + pending.push(normalized); + size += blockSize(normalized); + } else if (record.type === "tool_use" && typeof record.id === "string" && record.id) { + segments.push({ toolUseId: record.id, blocks: pending }); + pending = []; + } + } + if (size === 0 || size > MAX_CLAUDE_THINKING_ENTRY_CHARS || pending.length > 0) return null; + if (segments.length === 0) return null; + return { segments, size }; } /** - * Remember the signed thinking blocks of one assistant turn under each of its - * tool_use ids. No-op unless the turn owns tool calls and every block is replayable. + * Remember the signed thinking blocks of one assistant turn, in their original + * layout, under each of its tool_use ids. `content` is the ordered content array + * (thinking / redacted_thinking / tool_use; other block types are ignored). No-op + * unless the turn owns tool calls and every block is replayable. */ export function storeClaudeThinkingBlocks(args: { - toolUseIds: unknown; - blocks: unknown; + content: unknown; provider: unknown; model: unknown; connectionId?: unknown; }): boolean { - const toolUseIds = collectToolUseIds(args.toolUseIds); const model = normalizeClaudeThinkingModel(args.model); - if (toolUseIds.length === 0 || !model || !Array.isArray(args.blocks)) return false; - if (args.blocks.length === 0) return false; + if (!model || !Array.isArray(args.content)) return false; - const blocks = args.blocks.map(normalizeBlock); - if (!blocks.every(Boolean)) return false; - const normalizedBlocks = blocks as ClaudeSignedThinkingBlock[]; - const size = normalizedBlocks.reduce((sum, block) => sum + blockSize(block), 0); - if (size > MAX_CLAUDE_THINKING_ENTRY_CHARS) return false; + const collected = collectThinkingSegments(args.content); + if (!collected) return false; + const { segments, size } = collected; const now = Date.now(); const provider = normalizeProvider(args.provider); @@ -152,13 +184,13 @@ export function storeClaudeThinkingBlocks(args: { // the next request that is equally unbound (""==="" would match). Refuse. const connectionId = typeof args.connectionId === "string" ? args.connectionId : ""; if (connectionId.length === 0) return false; - for (const toolUseId of toolUseIds) { + for (const { toolUseId } of segments) { deleteKey(toolUseId); entries.set(toolUseId, { provider, model, connectionId, - blocks: cloneBlocks(normalizedBlocks), + segments: cloneSegments(segments), size, expiresAt: now + CLAUDE_THINKING_MEMORY_TTL_MS, }); @@ -181,17 +213,8 @@ export function storeClaudeThinkingFromResponse( return false; } const root = responseBody as Record; - if (!Array.isArray(root.content)) return false; - const blocks: unknown[] = []; - const toolUseIds: unknown[] = []; - for (const block of root.content) { - const type = (block as Record | null)?.type; - if (type === "thinking" || type === "redacted_thinking") blocks.push(block); - else if (type === "tool_use") toolUseIds.push((block as Record).id); - } return storeClaudeThinkingBlocks({ - toolUseIds, - blocks, + content: root.content, provider: binding.provider, model: typeof root.model === "string" && root.model ? root.model : binding.model, connectionId: binding.connectionId, @@ -199,7 +222,7 @@ export function storeClaudeThinkingFromResponse( } /** - * Return fresh copies of the signed blocks stored for the first tool_use id that + * Return fresh copies of the turn layout stored for the first tool_use id that * hits with the same provider + model + connection binding, or null. */ export function restoreClaudeThinkingBlocks(args: { @@ -207,8 +230,8 @@ export function restoreClaudeThinkingBlocks(args: { provider: unknown; model: unknown; connectionId?: unknown; -}): ClaudeSignedThinkingBlock[] | null { - const toolUseIds = collectToolUseIds(args.toolUseIds); +}): ClaudeThinkingSegment[] | null { + const toolUseIds = Array.isArray(args.toolUseIds) ? args.toolUseIds : []; const model = normalizeClaudeThinkingModel(args.model); if (toolUseIds.length === 0 || !model) return null; @@ -221,17 +244,23 @@ export function restoreClaudeThinkingBlocks(args: { // which adaptive thinking accepts). const connectionId = typeof args.connectionId === "string" ? args.connectionId : ""; for (const toolUseId of toolUseIds) { - const entry = entries.get(toolUseId); + const entry = typeof toolUseId === "string" ? entries.get(toolUseId) : undefined; if (!entry) continue; if (entry.provider !== provider || entry.model !== model) return null; if (entry.connectionId !== connectionId) return null; - const blocks = cloneBlocks(entry.blocks); - for (const block of blocks) restoredBlocks.add(block); - return blocks; + return markRestored(cloneSegments(entry.segments)); } return null; } +/** Tag every block of freshly cloned segments as restored-by-binding provenance. */ +function markRestored(segments: ClaudeThinkingSegment[]): ClaudeThinkingSegment[] { + for (const segment of segments) { + for (const block of segment.blocks) restoredBlocks.add(block); + } + return segments; +} + function sameBlock(stored: ClaudeSignedThinkingBlock, block: unknown): boolean { const normalized = normalizeBlock(block); if (!normalized) return false; @@ -243,7 +272,7 @@ function sameBlock(stored: ClaudeSignedThinkingBlock, block: unknown): boolean { function isStoredBlock(block: unknown): boolean { for (const entry of entries.values()) { - if (entry.blocks.some((stored) => sameBlock(stored, block))) return true; + if (blocksOf(entry).some((stored) => sameBlock(stored, block))) return true; } return false; } @@ -251,7 +280,7 @@ function isStoredBlock(block: unknown): boolean { function forgetEntriesHolding(blocks: unknown[]) { const keys: string[] = []; for (const [key, entry] of entries) { - if (entry.blocks.some((stored) => blocks.some((block) => sameBlock(stored, block)))) { + if (blocksOf(entry).some((stored) => blocks.some((block) => sameBlock(stored, block)))) { keys.push(key); } } diff --git a/open-sse/translator/request/openai-to-claude.ts b/open-sse/translator/request/openai-to-claude.ts index e1c6872d91fe..b887579f2905 100644 --- a/open-sse/translator/request/openai-to-claude.ts +++ b/open-sse/translator/request/openai-to-claude.ts @@ -834,10 +834,13 @@ function getContentBlocksFromMessage( const hasToolUseBlock = blocks.some((b) => b.type === "tool_use"); // Signed-thinking continuity: the client (OpenAI Chat / Responses) never held the // signed blocks Claude produced for this tool turn, but claude-to-openai stored them - // keyed by the tool_use ids. Restore them verbatim before the owning tool_use — + // keyed by the tool_use ids. Put each run back where Claude emitted it: after the + // turn's previous tool_use (or at the front), before the tool_use it preceded — // only for MANUAL extended thinking (`type:"enabled"`), for the same provider + // model + connection, only when the client sent no thinking block of its own, - // never for a video-derived request. A miss injects nothing. + // never for a video-derived request. A fragment that does not carry a run's + // tool_use skips that run, so a turn the client split into several messages + // replays every block once (#18607). A miss injects nothing. // #17324 recurrence (2026-10-27): adaptive-only models (Opus 4.7+/5.x, Fable 5) // accept a tool_use turn with NO thinking block at all, but replaying stored // signed blocks there breaks Anthropic's warm-cache block-equality whenever the @@ -858,8 +861,14 @@ function getContentBlocksFromMessage( model: thinkingBinding.model, connectionId: thinkingBinding.connectionId, }); - if (restored) { - blocks.unshift(...restored); + let cursor = 0; + for (const { toolUseId, blocks: signed } of restored ?? []) { + const owner = blocks.findIndex( + (b, i) => i >= cursor && b.type === "tool_use" && b.id === toolUseId + ); + if (owner < 0) continue; + blocks.splice(cursor, 0, ...signed); + cursor = owner + signed.length + 1; hasThinkingBlock = true; } } diff --git a/open-sse/translator/response/claude-to-openai.ts b/open-sse/translator/response/claude-to-openai.ts index b706a28a1cd9..72ebfb5d1258 100644 --- a/open-sse/translator/response/claude-to-openai.ts +++ b/open-sse/translator/response/claude-to-openai.ts @@ -38,8 +38,8 @@ function createChunk(state, delta, finishReason = null) { // Signed-thinking continuity: the OpenAI client only sees reasoning text, so keep the // exact signed thinking / redacted_thinking blocks (per content-block index) and hand -// them to claudeThinkingStore, keyed by this turn's tool_use ids, when the message ends. -// openai-to-claude restores them before the owning tool_use on the next tool turn. +// them to claudeThinkingStore in their original layout relative to this turn's tool_use +// blocks when the message ends. openai-to-claude puts them back on the next tool turn. function thinkingBlocksOf(state): Map> { if (!(state.claudeThinkingBlocks instanceof Map)) state.claudeThinkingBlocks = new Map(); return state.claudeThinkingBlocks; @@ -50,9 +50,17 @@ function flushThinkingContinuity(state) { state.claudeThinkingFlushed = true; const blocks = thinkingBlocksOf(state); if (blocks.size === 0 || !(state.toolCalls instanceof Map) || state.toolCalls.size === 0) return; + const indexed: Array<[number, Record]> = [ + ...blocks.entries(), + ...[...state.toolCalls.entries()].map( + ([index, toolCall]): [number, Record] => [ + index, + { type: "tool_use", id: toolCall?.id }, + ] + ), + ]; storeClaudeThinkingBlocks({ - toolUseIds: [...state.toolCalls.values()].map((toolCall) => toolCall?.id), - blocks: [...blocks.entries()].sort(([left], [right]) => left - right).map(([, b]) => b), + content: indexed.sort(([left], [right]) => left - right).map(([, block]) => block), provider: state.provider, model: state.model, connectionId: state.connectionId, diff --git a/tests/unit/anthropic-thinking-modified-recovery.test.ts b/tests/unit/anthropic-thinking-modified-recovery.test.ts index 431352d37011..989375256520 100644 --- a/tests/unit/anthropic-thinking-modified-recovery.test.ts +++ b/tests/unit/anthropic-thinking-modified-recovery.test.ts @@ -132,8 +132,10 @@ test("Anthropic thinking-modified recovery contract", async (t) => { await t.test("a store-restored block in the latest message is dropped and forgotten", () => { clearClaudeThinkingStore(); const stored = storeClaudeThinkingBlocks({ - toolUseIds: ["toolu_restored"], - blocks: [{ type: "thinking", thinking: "server-restored", signature: "RESTORED_SIG" }], + content: [ + { type: "thinking", thinking: "server-restored", signature: "RESTORED_SIG" }, + { type: "tool_use", id: "toolu_restored" }, + ], provider: "claude", model: "claude-opus-5-5", connectionId: "conn-recovery", diff --git a/tests/unit/claude-thinking-exact-replay-18607.test.ts b/tests/unit/claude-thinking-exact-replay-18607.test.ts new file mode 100644 index 000000000000..55778ace8a11 --- /dev/null +++ b/tests/unit/claude-thinking-exact-replay-18607.test.ts @@ -0,0 +1,219 @@ +/** + * #18607 — restored signed thinking must land where Claude emitted it, once. + * + * The store only kept a flat list of thinking blocks; openai-to-claude unshifted + * them to the front of every assistant fragment that carried any owning tool_use. + * Interleaved layouts were reordered and fragmented turns replayed the blocks twice. + */ +import test from "node:test"; +import assert from "node:assert/strict"; +import { createHash } from "node:crypto"; +import { clearClaudeThinkingStore } from "../../open-sse/services/claudeThinkingStore.ts"; +import { claudeToOpenAIResponse } from "../../open-sse/translator/response/claude-to-openai.ts"; +import { translateRequest } from "../../open-sse/translator/index.ts"; +import { FORMATS } from "../../open-sse/translator/formats.ts"; + +// Manual extended thinking (type:"enabled"): adaptive-only models never restore (#17324). +const MODEL = "claude-sonnet-4-5"; +const CONN = "conn-18607"; +const T1 = { type: "thinking", thinking: "SYNTHETIC_1", signature: "SYNTHETIC_SIG_1" }; +const T2 = { type: "redacted_thinking", data: "SYNTHETIC_OPAQUE_2" }; +const digest = (value: unknown) => createHash("sha256").update(JSON.stringify(value)).digest("hex"); + +/** Replay a Claude stream whose content is [T1, text, tool_use A, T2, tool_use B]. */ +function captureInterleavedTurn() { + clearClaudeThinkingStore(); + const state: Record = { + provider: "claude", + connectionId: CONN, + toolCalls: new Map(), + }; + const events = [ + { type: "message_start", message: { id: "msg_1", model: MODEL } }, + { type: "content_block_start", index: 0, content_block: { type: "thinking", thinking: "" } }, + { + type: "content_block_delta", + index: 0, + delta: { type: "thinking_delta", thinking: T1.thinking }, + }, + { + type: "content_block_delta", + index: 0, + delta: { type: "signature_delta", signature: T1.signature }, + }, + { type: "content_block_stop", index: 0 }, + { type: "content_block_start", index: 1, content_block: { type: "text", text: "" } }, + { type: "content_block_delta", index: 1, delta: { type: "text_delta", text: "Reading A" } }, + { type: "content_block_stop", index: 1 }, + { + type: "content_block_start", + index: 2, + content_block: { type: "tool_use", id: "toolu_A", name: "read", input: {} }, + }, + { + type: "content_block_delta", + index: 2, + delta: { type: "input_json_delta", partial_json: "{}" }, + }, + { type: "content_block_stop", index: 2 }, + { + type: "content_block_start", + index: 3, + content_block: { type: "redacted_thinking", data: T2.data }, + }, + { type: "content_block_stop", index: 3 }, + { + type: "content_block_start", + index: 4, + content_block: { type: "tool_use", id: "toolu_B", name: "read", input: {} }, + }, + { + type: "content_block_delta", + index: 4, + delta: { type: "input_json_delta", partial_json: "{}" }, + }, + { type: "content_block_stop", index: 4 }, + { type: "message_delta", delta: { stop_reason: "tool_use" }, usage: { output_tokens: 5 } }, + { type: "message_stop" }, + ]; + for (const event of events) claudeToOpenAIResponse(event, state); +} + +const toolCall = (id: string) => ({ + id, + type: "function", + function: { name: "read", arguments: "{}" }, +}); + +function translate(assistantMessages: Array>) { + const result = translateRequest( + FORMATS.OPENAI, + FORMATS.CLAUDE, + MODEL, + { + reasoning_effort: "high", + messages: [ + { role: "user", content: "Synthetic task" }, + ...assistantMessages, + { role: "tool", tool_call_id: "toolu_A", content: "A" }, + { role: "tool", tool_call_id: "toolu_B", content: "B" }, + ], + tools: [{ type: "function", function: { name: "read", parameters: { type: "object" } } }], + }, + false, + { _connectionId: CONN }, + "claude" + ) as { messages: Array<{ role: string; content: Array> }> }; + return result.messages + .filter((m) => m.role === "assistant") + .map((m) => m.content.map(({ cache_control: _c, ...b }) => b)); +} + +const stripToolUse = (b: Record) => + b.type === "tool_use" ? { type: "tool_use", id: b.id } : b; + +test("#18607(a): restored blocks keep their original content index", () => { + captureInterleavedTurn(); + const [turn] = translate([ + { + role: "assistant", + content: "Reading A", + tool_calls: [toolCall("toolu_A"), toolCall("toolu_B")], + }, + ]); + assert.deepEqual( + turn.map(stripToolUse).map(digest), + [ + T1, + { type: "text", text: "Reading A" }, + { type: "tool_use", id: "toolu_A" }, + T2, + { type: "tool_use", id: "toolu_B" }, + ].map(digest), + "exact original layout: [thinking, text, tool_use, redacted_thinking, tool_use]" + ); + clearClaudeThinkingStore(); +}); + +test("#18607(b): a turn the client split into [text] + [tool_calls] messages replays each block once, before its own tool_use", () => { + captureInterleavedTurn(); + const [turn] = translate([ + { role: "assistant", content: "Reading A" }, + { role: "assistant", content: null, tool_calls: [toolCall("toolu_A"), toolCall("toolu_B")] }, + ]); + assert.deepEqual( + turn.map(stripToolUse).map(digest), + // shortcut: text that lives in another fragment stays where the client put it, + // upgrade when a client is seen splitting text away from its tool_calls. + [ + { type: "text", text: "Reading A" }, + T1, + { type: "tool_use", id: "toolu_A" }, + T2, + { type: "tool_use", id: "toolu_B" }, + ].map(digest), + "merged fragments: every signed block once, directly before the tool_use it preceded" + ); + clearClaudeThinkingStore(); +}); + +test("#18607(b) negative: a history that only carries toolu_B gets T2, never toolu_A's T1", () => { + captureInterleavedTurn(); + const result = translateRequest( + FORMATS.OPENAI, + FORMATS.CLAUDE, + MODEL, + { + reasoning_effort: "high", + messages: [ + { role: "user", content: "Synthetic task" }, + { role: "assistant", content: null, tool_calls: [toolCall("toolu_B")] }, + { role: "tool", tool_call_id: "toolu_B", content: "B" }, + ], + tools: [{ type: "function", function: { name: "read", parameters: { type: "object" } } }], + }, + false, + { _connectionId: CONN }, + "claude" + ) as { messages: Array<{ role: string; content: Array> }> }; + const [turn] = result.messages + .filter((m) => m.role === "assistant") + .map((m) => m.content.map(({ cache_control: _c, ...b }) => b)); + assert.deepEqual( + turn.map(stripToolUse).map(digest), + [T2, { type: "tool_use", id: "toolu_B" }].map(digest) + ); + clearClaudeThinkingStore(); +}); + +test("#18607 negative: a different model restores nothing from the layout store", () => { + captureInterleavedTurn(); + const result = translateRequest( + FORMATS.OPENAI, + FORMATS.CLAUDE, + "claude-opus-4-1", + { + reasoning_effort: "high", + messages: [ + { role: "user", content: "Synthetic task" }, + { + role: "assistant", + content: "Reading A", + tool_calls: [toolCall("toolu_A"), toolCall("toolu_B")], + }, + { role: "tool", tool_call_id: "toolu_A", content: "A" }, + { role: "tool", tool_call_id: "toolu_B", content: "B" }, + ], + tools: [{ type: "function", function: { name: "read", parameters: { type: "object" } } }], + }, + false, + { _connectionId: CONN }, + "claude" + ) as { messages: Array<{ role: string; content: Array> }> }; + const signed = result.messages + .filter((m) => m.role === "assistant") + .flatMap((m) => m.content) + .filter((b) => b.type === "thinking" || (b.type === "redacted_thinking" && b.data === T2.data)); + assert.equal(signed.length, 0, "model binding still fails closed"); + clearClaudeThinkingStore(); +}); diff --git a/tests/unit/claude-thinking-replay-layout.test.ts b/tests/unit/claude-thinking-replay-layout.test.ts index 436ad94156a6..93029eae7e16 100644 --- a/tests/unit/claude-thinking-replay-layout.test.ts +++ b/tests/unit/claude-thinking-replay-layout.test.ts @@ -115,7 +115,7 @@ for (const completed of [false, true]) { provider: "claude", model, connectionId: "conn-scope", - })!; + })!.flatMap((segment) => segment.blocks); assert.equal(isRestoredClaudeThinkingBlock(restored[0]), true); assert.equal( isRestoredClaudeThinkingBlock({ ...restored[0] }), diff --git a/tests/unit/thinking-modified-recovery-recurrence.test.ts b/tests/unit/thinking-modified-recovery-recurrence.test.ts index 82e20eade721..9561ff52fec6 100644 --- a/tests/unit/thinking-modified-recovery-recurrence.test.ts +++ b/tests/unit/thinking-modified-recovery-recurrence.test.ts @@ -137,7 +137,7 @@ test("modified-recovery: injected blocks in an EARLIER turn are stripped (recove provider: "claude", model: ADAPTIVE_MODEL, connectionId: "conn-org-a", - })!; + })!.flatMap((segment) => segment.blocks); assert.ok( restored, "manual-thinking restore still works — used here to obtain provenance-marked blocks" @@ -207,7 +207,7 @@ test("modified-recovery under manual thinking: an EARLIER tool_use turn injects provider: "claude", model: "claude-sonnet-4-5", connectionId: "conn-org-a", - })!; + })!.flatMap((segment) => segment.blocks); assert.ok(restored, "restore yields provenance-marked blocks"); const body = { From 6cbf05195ec9545f2a20ab76a2e53f71dfa73072 Mon Sep 17 00:00:00 2001 From: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> Date: Wed, 4 Nov 2026 13:44:09 -0300 Subject: [PATCH 11/20] fix(claude-thinking): skip signed-thinking restore for duplicated tool_call ids (#18688) --- ...8688-claude-thinking-duplicate-tool-ids.md | 1 + .../translator/request/openai-to-claude.ts | 33 ++++- ...hinking-restore-duplicate-tool-ids.test.ts | 115 ++++++++++++++++++ 3 files changed, 147 insertions(+), 2 deletions(-) create mode 100644 changelog.d/fixes/18688-claude-thinking-duplicate-tool-ids.md create mode 100644 tests/unit/claude-thinking-restore-duplicate-tool-ids.test.ts diff --git a/changelog.d/fixes/18688-claude-thinking-duplicate-tool-ids.md b/changelog.d/fixes/18688-claude-thinking-duplicate-tool-ids.md new file mode 100644 index 000000000000..bdfec850b827 --- /dev/null +++ b/changelog.d/fixes/18688-claude-thinking-duplicate-tool-ids.md @@ -0,0 +1 @@ +- **fix(claude-thinking):** skip the signed-thinking restore for assistant turns whose tool_call id appears more than once in the client history (a retried turn re-sends the same id for a different generation), which made Anthropic answer 400 "thinking or redacted_thinking blocks in the latest assistant message cannot be modified" (#18688). diff --git a/open-sse/translator/request/openai-to-claude.ts b/open-sse/translator/request/openai-to-claude.ts index b887579f2905..787070c615cc 100644 --- a/open-sse/translator/request/openai-to-claude.ts +++ b/open-sse/translator/request/openai-to-claude.ts @@ -382,6 +382,24 @@ export function openaiToClaudeRequest( // tool result is paired with the oldest of them instead of being dropped. let pendingToolUseIds: string[] = []; + // Tool_call ids that appear on more than one assistant turn in the client history + // (a retried turn re-sends the same id for a different generation of the answer). + // The signed-thinking store is keyed by tool_use id and holds only the newest + // generation, so restoring into an older turn replays a block Anthropic never saw + // in that position and it answers 400 "…blocks in the latest assistant message + // cannot be modified". Skip the restore entirely for such turns: a request without + // thinking blocks is valid and is what older clients sent. + const toolUseIdCounts = new Map(); + for (const m of nonSystemMessages) { + for (const tc of Array.isArray(m?.tool_calls) ? m.tool_calls : []) { + const id = typeof tc?.id === "string" ? tc.id : ""; + if (id) toolUseIdCounts.set(id, (toolUseIdCounts.get(id) ?? 0) + 1); + } + } + const ambiguousToolUseIds = new Set( + [...toolUseIdCounts.entries()].filter(([, n]) => n > 1).map(([id]) => id) + ); + for (const msg of nonSystemMessages) { const newRole = msg.role === "user" || msg.role === "tool" ? "user" : "assistant"; const blocks = getContentBlocksFromMessage( @@ -394,6 +412,7 @@ export function openaiToClaudeRequest( provider: routedProvider, model, disabled: credentials?._videoTranscriptSensitive === true, + ambiguousToolUseIds, connectionId: typeof credentials?._connectionId === "string" ? credentials._connectionId : undefined, }, @@ -639,6 +658,7 @@ function getContentBlocksFromMessage( model?: unknown; disabled?: boolean; connectionId?: unknown; + ambiguousToolUseIds?: Set; } = {}, pendingToolUseIds: string[] = [], thinkingTypeForRequest: string | undefined = undefined @@ -847,16 +867,25 @@ function getContentBlocksFromMessage( // restore hits/misses differently than the turn that warmed the cache (TTL, // eviction, completed-turn rewrite) → 400 "latest assistant message cannot be // modified". + const ownToolUseIds = blocks.filter((b) => b.type === "tool_use").map((b) => String(b.id)); + // A duplicated tool_call id makes the store's entry ambiguous (it may belong to a + // different generation of this turn); restoring it replays a block the upstream + // did not see here and it answers 400 "cannot be modified". Skip instead: an + // assistant turn without thinking blocks is accepted. + const hasAmbiguousToolUseId = ownToolUseIds.some((id) => + thinkingBinding.ambiguousToolUseIds?.has(id) + ); if ( !isKimiCoding && !thinkingBinding.disabled && thinkingEnabledForRequest && thinkingTypeForRequest === "enabled" && hasToolUseBlock && - !hasThinkingBlock + !hasThinkingBlock && + !hasAmbiguousToolUseId ) { const restored = restoreClaudeThinkingBlocks({ - toolUseIds: blocks.filter((b) => b.type === "tool_use").map((b) => b.id), + toolUseIds: ownToolUseIds, provider: thinkingBinding.provider, model: thinkingBinding.model, connectionId: thinkingBinding.connectionId, diff --git a/tests/unit/claude-thinking-restore-duplicate-tool-ids.test.ts b/tests/unit/claude-thinking-restore-duplicate-tool-ids.test.ts new file mode 100644 index 000000000000..69f3d0b8e69f --- /dev/null +++ b/tests/unit/claude-thinking-restore-duplicate-tool-ids.test.ts @@ -0,0 +1,115 @@ +// The signed-thinking store (claudeThinkingStore) is keyed by tool_use id and keeps +// only the newest generation of a turn. A client that retries a turn re-sends the same +// tool_call id for a different generation, so restoring the store's block into the older +// turn replays a block the upstream never saw in that position and it answers +// 400 "…blocks in the latest assistant message cannot be modified". +// +// Fix: openaiToClaudeRequest counts tool_call ids across the whole client history and +// skips the restore for any turn whose id is ambiguous (appears more than once). +// A request without thinking blocks is valid and is what clients sent before the store. + +import { test } from "node:test"; +import assert from "node:assert/strict"; +import { openaiToClaudeRequest } from "../../open-sse/translator/request/openai-to-claude.ts"; +import { storeClaudeThinkingFromResponse } from "../../open-sse/services/claudeThinkingStore.ts"; + +const PROVIDER = "claude"; +const MODEL = "claude-opus-5-5"; +// Signed blocks are bound to the serving connection (#17324), so store and request share one. +const CONNECTION = "conn-dup-ids"; + +type Block = { type?: string; id?: string }; +type Msg = { role?: string; content?: unknown }; + +function body(assistantTurns: Array<{ id: string }>) { + return { + model: MODEL, + max_tokens: 4096, + stream: true, + // The restore only runs for MANUAL extended thinking on the tip (adaptive-only models + // accept tool turns without a thinking block, #17324). + thinking: { type: "enabled", budget_tokens: 2048 }, + messages: [ + { role: "user", content: "do something" }, + ...assistantTurns.map((t) => ({ + role: "assistant", + content: "", + tool_calls: [ + { id: t.id, type: "function", function: { name: "get_time", arguments: "{}" } }, + ], + })), + ...assistantTurns.map((t) => ({ role: "tool", tool_call_id: t.id, content: "12:00" })), + { role: "user", content: "continue" }, + ], + }; +} + +// Warm the store the way production does: from a complete Claude Messages response +// (signed thinking block followed by the tool_use it precedes). This entry point is +// stable across the store's internal layout, so the test also holds on top of the +// exact-replay layout change (#18607). +function warmStore(id: string, thinking: string, signature: string): boolean { + return storeClaudeThinkingFromResponse( + { + content: [ + { type: "thinking", thinking, signature }, + { type: "tool_use", id, name: "get_time", input: {} }, + ], + }, + { provider: PROVIDER, model: MODEL, connectionId: CONNECTION } + ); +} + +function translate(turns: Array<{ id: string }>): { messages: Msg[] } { + return openaiToClaudeRequest( + MODEL, + body(turns) as unknown as Parameters[1], + true, + { _provider: PROVIDER, _connectionId: CONNECTION } + ) as unknown as { messages: Msg[] }; +} + +function lastAssistant(messages: Msg[]) { + return [...messages].reverse().find((m) => m.role === "assistant"); +} + +function thinkingBlocks(msg: Msg | undefined): Block[] { + return Array.isArray(msg?.content) + ? (msg.content as Block[]).filter( + (b) => b?.type === "thinking" || b?.type === "redacted_thinking" + ) + : []; +} + +test("duplicated tool_call id: no restore from the store (prevents the 400 'cannot be modified')", () => { + const id = "toolu_dup_1"; + assert.ok(warmStore(id, "old generation", "IMZA_A")); + const out = translate([{ id }, { id }]); + const son = lastAssistant(out.messages); + assert.equal( + thinkingBlocks(son).length, + 0, + "the last assistant message carrying a duplicated id must not get a thinking block" + ); +}); + +test("unique tool_call id: the store restore still runs (feature preserved)", () => { + const id = "toolu_tekil_1"; + assert.ok(warmStore(id, "right generation", "IMZA_B")); + const out = translate([{ id }]); + const son = lastAssistant(out.messages); + assert.equal(thinkingBlocks(son).length, 1, "a unique id must get its thinking block restored"); +}); + +test("distinct ids but one duplicated: only the duplicated turn is skipped", () => { + const dup = "toolu_mix_dup"; + const tek = "toolu_mix_tek"; + assert.ok(warmStore(dup, "x", "IMZA_C")); + assert.ok(warmStore(tek, "y", "IMZA_D")); + const out = translate([{ id: dup }, { id: dup }, { id: tek }]); + const asistans = out.messages.filter((m) => m.role === "assistant"); + const duz = asistans.filter((m) => thinkingBlocks(m).length > 0); + assert.equal(duz.length, 1, "only the turn with the unique id gets a thinking block"); + const aldiId = (duz[0].content as Block[]).find((b) => b?.type === "tool_use")?.id; + assert.equal(aldiId, tek, "the turn that got the block must be the unique-id one"); +}); From 124ed0ae3d572b2cd52fe92333ba3f00658c458b Mon Sep 17 00:00:00 2001 From: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> Date: Wed, 4 Nov 2026 13:44:11 -0300 Subject: [PATCH 12/20] fix(codex): normalize quota evidence without fabricating healthy windows (#18334) --- .../fixes/18330-codex-dual-window-policy.md | 1 + .../18330-codex-weekly-window-identity.md | 1 + .../18334-codex-quota-signal-normalization.md | 1 + open-sse/executors/codex/quota.ts | 66 ++-- open-sse/services/codexAccount/quota.ts | 5 +- open-sse/services/codexQuotaFetcher.ts | 24 +- open-sse/services/codexQuotaParser.ts | 75 ++-- open-sse/services/codexQuotaSignals.ts | 255 +++++++++++++ open-sse/services/codexQuotaWindowPolicy.ts | 56 +++ open-sse/services/codexUsageQuotas.ts | 168 +++------ open-sse/services/quotaPreflight.ts | 9 +- open-sse/services/usage/codex.ts | 3 +- src/domain/quotaCache.ts | 17 +- src/lib/usage/cachedQuotaConversion.ts | 27 +- src/sse/services/quotaLimitPolicy.ts | 42 +-- stryker.conf.json | 10 + tests/unit/chat-context-relay.test.ts | 2 + .../codex-banked-reset-credits-5199.test.ts | 36 +- .../codex-dual-toggle-policy-18330.test.ts | 231 ++++++++++++ .../codex-dual-toggle-routing-18330.test.ts | 206 +++++++++++ tests/unit/codex-paid-credits.test.ts | 5 +- .../codex-pro-weekly-window-18326.test.ts | 180 ++++++++++ .../unit/codex-quota-extracted-parser.test.ts | 65 ++++ tests/unit/codex-quota-fetcher.test.ts | 39 +- .../codex-quota-signal-normalization.test.ts | 335 ++++++++++++++++++ .../codex-quota-window-identity-18330.test.ts | 227 ++++++++++++ .../unit/codex-quota-wire-normalizer.test.ts | 203 +++++++++++ tests/unit/codex-reset-credits.test.ts | 18 +- .../codex-usage-quotas-latent-window.test.ts | 45 ++- .../codex-usage-quotas-review-window.test.ts | 6 +- tests/unit/codex-usage-windows.test.ts | 2 +- tests/unit/codex-weekly-cutoff-18330.test.ts | 187 ++++++++++ tests/unit/codex-weekly-routing-18330.test.ts | 132 +++++++ tests/unit/combo-strategies.test.ts | 4 + .../combo/quota-weighted-pace-plan.test.ts | 6 +- ...headroom-codex-quota-snapshot-6379.test.ts | 4 +- .../quota-availability-codex-cache.test.ts | 62 +++- .../quota-availability-spark-limit.test.ts | 8 +- tests/unit/quota-window-label.test.ts | 6 +- tests/unit/usage-service-hardening.test.ts | 4 + 40 files changed, 2476 insertions(+), 297 deletions(-) create mode 100644 changelog.d/fixes/18330-codex-dual-window-policy.md create mode 100644 changelog.d/fixes/18330-codex-weekly-window-identity.md create mode 100644 changelog.d/fixes/18334-codex-quota-signal-normalization.md create mode 100644 open-sse/services/codexQuotaSignals.ts create mode 100644 open-sse/services/codexQuotaWindowPolicy.ts create mode 100644 tests/unit/codex-dual-toggle-policy-18330.test.ts create mode 100644 tests/unit/codex-dual-toggle-routing-18330.test.ts create mode 100644 tests/unit/codex-pro-weekly-window-18326.test.ts create mode 100644 tests/unit/codex-quota-extracted-parser.test.ts create mode 100644 tests/unit/codex-quota-signal-normalization.test.ts create mode 100644 tests/unit/codex-quota-window-identity-18330.test.ts create mode 100644 tests/unit/codex-quota-wire-normalizer.test.ts create mode 100644 tests/unit/codex-weekly-cutoff-18330.test.ts create mode 100644 tests/unit/codex-weekly-routing-18330.test.ts diff --git a/changelog.d/fixes/18330-codex-dual-window-policy.md b/changelog.d/fixes/18330-codex-dual-window-policy.md new file mode 100644 index 000000000000..e2f40fa8f2e2 --- /dev/null +++ b/changelog.d/fixes/18330-codex-dual-window-policy.md @@ -0,0 +1 @@ +- Honor Codex session and weekly tracking toggles consistently in cached routing and live quota preflight, including explicit cutoffs, while retaining unknown-window exhaustion and paid-credit controls (#18330). diff --git a/changelog.d/fixes/18330-codex-weekly-window-identity.md b/changelog.d/fixes/18330-codex-weekly-window-identity.md new file mode 100644 index 000000000000..7c5a61eebd84 --- /dev/null +++ b/changelog.d/fixes/18330-codex-weekly-window-identity.md @@ -0,0 +1 @@ +- fix(providers): classify Codex quota windows by their reported duration so weekly-only Pro quota enforces weekly cutoffs and agrees across usage badges and account pools; preserve unknown durations without inventing 5h availability (#18326, #18330). diff --git a/changelog.d/fixes/18334-codex-quota-signal-normalization.md b/changelog.d/fixes/18334-codex-quota-signal-normalization.md new file mode 100644 index 000000000000..f7ba272708cd --- /dev/null +++ b/changelog.d/fixes/18334-codex-quota-signal-normalization.md @@ -0,0 +1 @@ +- **fix(codex):** Parse official primary/secondary quota headers, keep malformed usage unknown instead of inventing healthy zero-percent windows, validate reset units, and preserve independent Codex/Spark evidence and explicit quota denial. ([#18334](https://github.com/cheaper-inference/OmniRoute/pull/18334)) — thanks @shevar0 diff --git a/open-sse/executors/codex/quota.ts b/open-sse/executors/codex/quota.ts index 977f794ddee9..bb93ac85de0a 100644 --- a/open-sse/executors/codex/quota.ts +++ b/open-sse/executors/codex/quota.ts @@ -1,4 +1,10 @@ -// Codex quota-snapshot parsing + reset/cooldown scheduling (pure). Verbatim from codex.ts. +// Codex quota-snapshot parsing + reset/cooldown scheduling (pure). +import { + classifyCodexQuotaWindow, + parseCodexFiniteNumber, + parseCodexLegacyReset, + parseCodexOfficialQuotaHeaders, +} from "../../services/codexQuotaSignals.ts"; /** * T03: Parsed quota snapshot from Codex response headers. @@ -22,27 +28,45 @@ export interface CodexQuotaSnapshot { * x-codex-5h-usage / x-codex-5h-limit / x-codex-5h-reset-at * x-codex-7d-usage / x-codex-7d-limit / x-codex-7d-reset-at */ -export function parseCodexQuotaHeaders(headers: Record): CodexQuotaSnapshot | null { - const usage5h = headers["x-codex-5h-usage"] ?? null; - const limit5h = headers["x-codex-5h-limit"] ?? null; - const resetAt5h = headers["x-codex-5h-reset-at"] ?? null; - const usage7d = headers["x-codex-7d-usage"] ?? null; - const limit7d = headers["x-codex-7d-limit"] ?? null; - const resetAt7d = headers["x-codex-7d-reset-at"] ?? null; - - // Return null if none of the quota headers are present (not a quota-aware response) - if (!usage5h && !limit5h && !resetAt5h && !usage7d && !limit7d && !resetAt7d) { - return null; - } - - return { - usage5h: usage5h ? parseFloat(usage5h) : 0, - limit5h: limit5h ? parseFloat(limit5h) : Infinity, - resetAt5h: resetAt5h ?? null, - usage7d: usage7d ? parseFloat(usage7d) : 0, - limit7d: limit7d ? parseFloat(limit7d) : Infinity, - resetAt7d: resetAt7d ?? null, +export function parseCodexQuotaHeaders( + headers: Record, + model?: string | null +): CodexQuotaSnapshot | null { + const lower = Object.fromEntries( + Object.entries(headers).map(([key, value]) => [key.toLowerCase(), value]) + ); + const quota: CodexQuotaSnapshot = { + usage5h: 0, + limit5h: Infinity, + resetAt5h: null, + usage7d: 0, + limit7d: Infinity, + resetAt7d: null, }; + const observed = new Set<"5h" | "7d">(); + for (const window of ["5h", "7d"] as const) { + const prefix = `x-codex-${window}-`; + const usage = parseCodexFiniteNumber(lower[`${prefix}usage`]); + const limit = parseCodexFiniteNumber(lower[`${prefix}limit`]); + if (usage === null || usage < 0 || limit === null || limit <= 0) continue; + quota[`usage${window}`] = usage; + quota[`limit${window}`] = limit; + quota[`resetAt${window}`] = parseCodexLegacyReset(lower[`${prefix}reset-at`]); + observed.add(window); + } + const official = parseCodexOfficialQuotaHeaders(lower, model); + for (const window of official?.windows ?? []) { + const family = classifyCodexQuotaWindow(window); + const slot = family === "session" ? "5h" : family === "weekly" ? "7d" : null; + // This legacy DTO cannot represent arbitrary durations. Keep them in the + // normalized signal rather than inventing a 5h/7d identity for persistence. + if (!slot || observed.has(slot)) continue; + quota[`usage${slot}`] = window.usedPercent; + quota[`limit${slot}`] = 100; + quota[`resetAt${slot}`] = window.resetAt; + observed.add(slot); + } + return observed.size > 0 ? quota : null; } /** diff --git a/open-sse/services/codexAccount/quota.ts b/open-sse/services/codexAccount/quota.ts index 80b15c8f0fb5..cd75032a4b85 100644 --- a/open-sse/services/codexAccount/quota.ts +++ b/open-sse/services/codexAccount/quota.ts @@ -21,10 +21,9 @@ export async function persistCodexChildQuotaResponse(params: { fallbackRateLimitedUntil?: string | null; }): Promise { if (params.model.trim().length === 0) return null; - const quota = parseCodexQuotaHeaders(params.headers); - if (!quota) return null; - const scope = getCodexModelScope(params.model); + const quota = parseCodexQuotaHeaders(params.headers, params.model); + if (!quota) return null; const quotaState = { usage5h: quota.usage5h, limit5h: quota.limit5h, diff --git a/open-sse/services/codexQuotaFetcher.ts b/open-sse/services/codexQuotaFetcher.ts index a209794ac624..3aedd8d6d07d 100644 --- a/open-sse/services/codexQuotaFetcher.ts +++ b/open-sse/services/codexQuotaFetcher.ts @@ -3,9 +3,8 @@ * * Implements QuotaFetcher for the Codex provider (quotaPreflight.ts + quotaMonitor.ts). * - * Codex has TWO independent quota windows: - * - Primary (5h): short-term rate limit, resets every 5 hours - * - Secondary (7d): weekly limit, resets every 7 days + * Codex may report independent 5h and 7d windows in either upstream position. + * Their explicit duration determines identity; primary may be weekly-only. * * We return percentUsed = max(5h%, 7d%) so the system switches accounts when * EITHER window approaches exhaustion (95% threshold). @@ -42,25 +41,16 @@ export { CODEX_WINDOW_WEEKLY, } from "./codexQuotaParser.ts"; -/** - * Stable identifiers for Codex's quota windows. These match the quota keys - * surfaced by `getCodexUsage` (in usage.ts) and rendered by the dashboard, - * so per-window thresholds set in the UI line up with the keys persisted - * in `provider_connections.quota_window_thresholds_json`. The dedicated - * Codex fetcher exposes only session + weekly today; the plan-dependent - * code_review window is surfaced by the generic path when present. - */ - // Codex usage endpoint (same as usage.ts CODEX_CONFIG) const CODEX_USAGE_URL = "https://chatgpt.com/backend-api/wham/usage"; // Cache TTL — short enough to be reactive, long enough to avoid rate limits const CACHE_TTL_MS = 60_000; // 60 seconds -// Per-account quota window info (richer than QuotaInfo — includes both windows) +// Per-account quota window info; structural slots exist only for known durations. export interface CodexDualWindowQuota extends QuotaInfo { - window5h: { percentUsed: number; resetAt: string | null }; - window7d: { percentUsed: number; resetAt: string | null }; + window5h?: { percentUsed: number; resetAt: string | null }; + window7d?: { percentUsed: number; resetAt: string | null }; limitReached: boolean; /** All known Codex quota windows, including Spark when the upstream exposes it. */ allWindows?: Record; @@ -293,13 +283,13 @@ export function getCodexQuotaCooldownMs(quota: CodexDualWindowQuota, threshold = const now = Date.now(); // 7d window takes priority (if exhausted, must wait longer) - if (quota.window7d.percentUsed >= threshold && quota.window7d.resetAt) { + if (quota.window7d && quota.window7d.percentUsed >= threshold && quota.window7d.resetAt) { const resetTime = new Date(quota.window7d.resetAt).getTime(); if (resetTime > now) return resetTime - now; } // 5h window - if (quota.window5h.percentUsed >= threshold && quota.window5h.resetAt) { + if (quota.window5h && quota.window5h.percentUsed >= threshold && quota.window5h.resetAt) { const resetTime = new Date(quota.window5h.resetAt).getTime(); if (resetTime > now) return resetTime - now; } diff --git a/open-sse/services/codexQuotaParser.ts b/open-sse/services/codexQuotaParser.ts index 7a8f3c02e0a9..edebe9e95421 100644 --- a/open-sse/services/codexQuotaParser.ts +++ b/open-sse/services/codexQuotaParser.ts @@ -6,6 +6,12 @@ import { } from "../config/codexQuotaScopes.ts"; import { parseCodexPaidCredits } from "@/lib/providers/codexPaidCredits"; import type { CodexDualWindowQuota } from "./codexQuotaFetcher.ts"; +import { + codexQuotaWindowKey, + isCodexLimitReached, + parseCodexQuotaWindow, + type CodexQuotaWindow, +} from "./codexQuotaSignals.ts"; export const CODEX_WINDOW_SESSION = "session"; export const CODEX_WINDOW_WEEKLY = "weekly"; @@ -22,21 +28,6 @@ function toRecord(value: unknown): Record { ? (value as Record) : {}; } -function parseWindowReset(window: Record): string | null { - const resetAt = parseCodexNumber(window["reset_at"] ?? window["resetAt"], 0); - if (resetAt > 0) return new Date(resetAt * 1000).toISOString(); - const resetAfterSeconds = parseCodexNumber( - window["reset_after_seconds"] ?? window["resetAfterSeconds"], - 0 - ); - if (resetAfterSeconds > 0) return new Date(Date.now() + resetAfterSeconds * 1000).toISOString(); - return null; -} -function parseCodexWindow(window: Record | null | undefined) { - if (!window || Object.keys(window).length === 0) return null; - const percentUsed = parseCodexNumber(window["used_percent"] ?? window["usedPercent"], 0) / 100; - return { percentUsed, resetAt: parseWindowReset(window) }; -} function parseBankedResetCredits(data: Record): number | undefined { const resetCredits = toRecord(data["rate_limit_reset_credits"] ?? data["rateLimitResetCredits"]); const availableCount = resetCredits["available_count"] ?? resetCredits["availableCount"]; @@ -76,11 +67,18 @@ function findSparkRateLimit(data: Record): Record) { +function getCodexRateLimitWindows(rateLimit: Record): { + primary: CodexQuotaWindow | null; + secondary: CodexQuotaWindow | null; +} { return { - primary: parseCodexWindow(toRecord(rateLimit["primary_window"] ?? rateLimit["primaryWindow"])), - secondary: parseCodexWindow( - toRecord(rateLimit["secondary_window"] ?? rateLimit["secondaryWindow"]) + primary: parseCodexQuotaWindow( + rateLimit["primary_window"] ?? rateLimit["primaryWindow"], + "primary" + ), + secondary: parseCodexQuotaWindow( + rateLimit["secondary_window"] ?? rateLimit["secondaryWindow"], + "secondary" ), }; } @@ -90,8 +88,14 @@ function assignCodexWindows( names: { primary: string; secondary: string } ): void { const { primary, secondary } = getCodexRateLimitWindows(rateLimit); - if (primary) target[names.primary] = primary; - if (secondary) target[names.secondary] = secondary; + // Publish only the declared {percentUsed, resetAt} shape; identity/duration stay internal. + for (const window of [primary, secondary]) { + if (!window) continue; + target[codexQuotaWindowKey(window, names)] = { + percentUsed: window.percentUsed, + resetAt: window.resetAt, + }; + } } function getSelectedCodexRateLimit( normalRateLimit: Record, @@ -105,14 +109,13 @@ function parseCodexPlan(data: Record): string | undefined { const rawPlan = data["plan_type"] ?? data["planType"]; return typeof rawPlan === "string" && rawPlan.trim() ? rawPlan.trim() : undefined; } -function getDominantResetAt( - quota: Pick -): string | null { - if (quota.window7d.percentUsed > quota.window5h.percentUsed) - return quota.window7d.resetAt || quota.window5h.resetAt; - if (quota.window5h.percentUsed > quota.window7d.percentUsed) - return quota.window5h.resetAt || quota.window7d.resetAt; - return quota.window7d.resetAt || quota.window5h.resetAt; +function getDominantResetAt(windows: CodexQuotaWindow[]): string | null { + const dominant = [...windows].sort( + (a, b) => + b.percentUsed - a.percentUsed || + Number(b.windowSeconds === 604800) - Number(a.windowSeconds === 604800) + ); + return dominant.find((window) => window.resetAt)?.resetAt ?? null; } /** The dispatch fetcher's parser, also usable without importing its network/identity graph. */ export function parseCodexUsageResponse( @@ -132,12 +135,11 @@ export function parseCodexUsageResponse( const { primary: parsedPrimary, secondary: parsedSecondary } = getCodexRateLimitWindows(selectedRateLimit); if (!parsedPrimary && !parsedSecondary) return null; - const window5h = parsedPrimary ?? { percentUsed: 0, resetAt: null }; - const window7d = parsedSecondary ?? { percentUsed: 0, resetAt: null }; - const worstPercentUsed = Math.max(window5h.percentUsed, window7d.percentUsed); - const limitReached = Boolean( - selectedRateLimit["limit_reached"] ?? selectedRateLimit["limitReached"] + const measured = [parsedPrimary, parsedSecondary].filter( + (window): window is CodexQuotaWindow => window !== null ); + const worstPercentUsed = Math.max(...measured.map((window) => window.percentUsed)); + const limitReached = isCodexLimitReached(selectedRateLimit); const windows: Record = {}; assignCodexWindows(windows, selectedRateLimit, { primary: useSparkWindows ? CODEX_SPARK_QUOTA_SESSION : CODEX_WINDOW_SESSION, @@ -153,6 +155,9 @@ export function parseCodexUsageResponse( primary: CODEX_WINDOW_SESSION, secondary: CODEX_WINDOW_WEEKLY, }); + // Fixed compatibility slots follow measured durations, never primary/secondary position. + const window5h = windows[useSparkWindows ? CODEX_SPARK_QUOTA_SESSION : CODEX_WINDOW_SESSION]; + const window7d = windows[useSparkWindows ? CODEX_SPARK_QUOTA_WEEKLY : CODEX_WINDOW_WEEKLY]; const bankedResetCredits = parseBankedResetCredits(obj); const paidCredits = parseCodexPaidCredits(obj.credits); const rateLimitReachedType = parseRateLimitReachedType(obj); @@ -162,7 +167,7 @@ export function parseCodexUsageResponse( used: Math.round(worstPercentUsed * 100), total: 100, percentUsed: worstPercentUsed, - resetAt: getDominantResetAt({ window5h, window7d }), + resetAt: getDominantResetAt(measured), windows, allWindows, window5h, diff --git a/open-sse/services/codexQuotaSignals.ts b/open-sse/services/codexQuotaSignals.ts new file mode 100644 index 000000000000..e5d2baeeb15e --- /dev/null +++ b/open-sse/services/codexQuotaSignals.ts @@ -0,0 +1,255 @@ +import { z } from "zod"; +import { + getCodexModelScope, + isCodexSparkLimitDescriptor, + type CodexQuotaScope, +} from "../config/codexQuotaScopes.ts"; + +type JsonRecord = Record; +export interface CodexQuotaWindow { + id: "primary" | "secondary"; + /** Fraction, converted exactly once from the wire's 0..100 percentage. */ + percentUsed: number; + /** Validated wire percentage, retained for 0..100 UI/legacy DTOs without float round trips. */ + usedPercent: number; + resetAt: string | null; + windowSeconds: number | null; + durationProvided: boolean; +} +export interface CodexQuotaSignal { + scope: CodexQuotaScope; + limitId: string; + source: "usage" | "headers" | "websocket"; + allowed: boolean | null; + limitReached: boolean | null; + windows: CodexQuotaWindow[]; +} + +// Defensive proxy bounds, not constraints claimed for Codex's signed Rust DTOs. +const MAX_WINDOW_SECONDS = 31 * 86400; +const decimal = /^[+-]?(?:\d{1,16}(?:\.\d{1,8})?|\.\d{1,8})(?:[eE][+-]?\d{1,3})?$/; +const finiteNumber = z.preprocess( + (value) => + typeof value === "string" && value.length <= 64 && decimal.test(value.trim()) + ? Number(value) + : value, + z.number().finite() +); +const percentage = finiteNumber.pipe(z.number().min(0).max(100)); +const duration = finiteNumber.pipe(z.number().int().positive().max(MAX_WINDOW_SECONDS)); + +function record(value: unknown): JsonRecord { + return value && typeof value === "object" && !Array.isArray(value) ? (value as JsonRecord) : {}; +} +function read(value: JsonRecord, snake: string, camel: string): unknown { + return value[snake] ?? value[camel]; +} +export function parseCodexFiniteNumber(value: unknown): number | null { + const parsed = finiteNumber.safeParse(value); + return parsed.success ? parsed.data : null; +} +function resetFromMs(ms: number, now: number): string | null { + return Number.isFinite(ms) && ms > 0 && ms <= now + MAX_WINDOW_SECONDS * 1000 + ? new Date(ms).toISOString() + : null; +} +/** Official reset_at is epoch SECONDS; do not guess milliseconds from its magnitude. */ +export function parseCodexResetSeconds(value: unknown, now = Date.now()): string | null { + const seconds = parseCodexFiniteNumber(value); + return seconds === null ? null : resetFromMs(seconds * 1000, now); +} +/** The older token-count headers carry ISO timestamps, not official epoch seconds. */ +export function parseCodexLegacyReset(value: unknown, now = Date.now()): string | null { + if (typeof value !== "string" || value.length > 64) return null; + return resetFromMs(Date.parse(value), now); +} + +/** Invalid optional timing fields do not discard otherwise valid exhaustion evidence. */ +export function parseCodexQuotaWindow( + value: unknown, + id: CodexQuotaWindow["id"] = "primary", + now = Date.now(), + event = false +): CodexQuotaWindow | null { + const raw = record(value); + const parsed = percentage.safeParse(read(raw, "used_percent", "usedPercent")); + if (!parsed.success) return null; + const secondsValue = + read(raw, "limit_window_seconds", "limitWindowSeconds") ?? + read(raw, "window_seconds", "windowSeconds"); + const minutesValue = read(raw, "window_minutes", "windowMinutes"); + const rawDuration = event + ? (() => { + const minutes = parseCodexFiniteNumber(minutesValue); + return minutes === null ? null : minutes * 60; + })() + : secondsValue; + const parsedDuration = duration.safeParse(rawDuration); + const after = parseCodexFiniteNumber(read(raw, "reset_after_seconds", "resetAfterSeconds")); + const relativeReset = + after !== null && after >= 0 && after <= MAX_WINDOW_SECONDS + ? resetFromMs(now + after * 1000, now) + : null; + return { + id, + percentUsed: parsed.data / 100, + usedPercent: parsed.data, + resetAt: parseCodexResetSeconds(read(raw, "reset_at", "resetAt"), now) ?? relativeReset, + windowSeconds: parsedDuration.success ? parsedDuration.data : null, + durationProvided: event ? minutesValue != null : secondsValue != null, + }; +} + +function boolean(value: unknown): boolean | null { + const parsed = z.boolean().safeParse(value); + return parsed.success ? parsed.data : null; +} +/** Also used by the usage API: a string 'false' is not the boolean true. */ +export function isCodexLimitReached(value: unknown): boolean { + const raw = record(value); + return raw.allowed === false || read(raw, "limit_reached", "limitReached") === true; +} +function signal( + value: unknown, + scope: CodexQuotaScope, + limitId: string, + source: CodexQuotaSignal["source"], + now: number, + event = false +): CodexQuotaSignal | null { + const raw = record(value); + const windows = (["primary", "secondary"] as const).flatMap((id) => { + const window = parseCodexQuotaWindow( + event ? raw[id] : read(raw, `${id}_window`, `${id}Window`), + id, + now, + event + ); + return window ? [window] : []; + }); + const allowed = boolean(raw.allowed); + const reached = boolean(read(raw, "limit_reached", "limitReached")); + if (windows.length === 0 && allowed === null && reached === null) return null; + return { + scope, + limitId, + source, + allowed, + limitReached: allowed === false ? true : reached, + windows, + }; +} +function scopeForLimit(id: string): CodexQuotaScope | null { + if (id === "codex") return "codex"; + return isCodexSparkLimitDescriptor(id) ? "spark" : null; +} + +/** Normalize only the requested known meter. An unknown future meter is not normal Codex. */ +export function parseCodexOfficialQuotaHeaders( + headers: Record, + model?: string | null, + now = Date.now() +): CodexQuotaSignal | null { + const lower = Object.fromEntries( + Object.entries(headers).map(([key, value]) => [key.toLowerCase(), value]) + ); + const wanted = getCodexModelScope(model); + const families = new Set( + Object.keys(lower).flatMap((key) => { + const match = /^x-([a-z0-9_-]{1,128})-(?:primary|secondary)-used-percent$/.exec(key); + return match ? [match[1]] : []; + }) + ); + for (const family of families) { + if (scopeForLimit(family) !== wanted) continue; + const raw: JsonRecord = {}; + for (const id of ["primary", "secondary"] as const) { + const prefix = `x-${family}-${id}-`; + raw[id] = { + used_percent: lower[`${prefix}used-percent`], + window_minutes: lower[`${prefix}window-minutes`], + reset_at: lower[`${prefix}reset-at`], + }; + } + const result = signal(raw, wanted, family.replaceAll("-", "_"), "headers", now, true); + if (result) return result; + } + return null; +} + +/** Pure normalization; credits/reset coupons remain separate display/billing evidence. */ +export function parseCodexUsageSignal( + payload: unknown, + model?: string | null, + now = Date.now() +): CodexQuotaSignal | null { + const data = record(payload); + const scope = getCodexModelScope(model); + if (scope === "codex") + return signal(read(data, "rate_limit", "rateLimit"), scope, "codex", "usage", now); + const additional = read(data, "additional_rate_limits", "additionalRateLimits"); + if (!Array.isArray(additional)) return null; + for (const value of additional) { + const entry = record(value); + const descriptors = [ + "limit_id", + "limitId", + "limit_name", + "limitName", + "metered_feature", + "meteredFeature", + "id", + "name", + "model", + ].map((key) => entry[key]); + if (!isCodexSparkLimitDescriptor(...descriptors)) continue; + return signal( + read(entry, "rate_limit", "rateLimit"), + scope, + String(entry.limit_id ?? entry.limitId ?? "spark"), + "usage", + now + ); + } + return null; +} + +const eventSchema = z.object({ + type: z.literal("codex.rate_limits"), + metered_limit_name: z.string().max(128).nullish(), + limit_name: z.string().max(128).nullish(), + rate_limits: z.unknown().optional(), +}); +/** Parser only: transport-side persistence must additionally be owner/revision fenced. */ +export function parseCodexRateLimitEvent( + payload: unknown, + now = Date.now() +): CodexQuotaSignal | null { + const parsed = eventSchema.safeParse(payload); + if (!parsed.success) return null; + const id = (parsed.data.metered_limit_name ?? parsed.data.limit_name ?? "codex") + .trim() + .toLowerCase(); + const scope = scopeForLimit(id); + return scope ? signal(parsed.data.rate_limits, scope, id, "websocket", now, true) : null; +} + +/** Fixed routing slots require an explicit duration, never the upstream position. */ +export function classifyCodexQuotaWindow( + window: Pick +): "session" | "weekly" | null { + if (window.windowSeconds === 18000) return "session"; + if (window.windowSeconds === 604800) return "weekly"; + return null; +} + +/** Preserve unclassified evidence under a neutral key; do not fabricate known quota. */ +export function codexQuotaWindowKey( + window: CodexQuotaWindow, + names: { primary: string; secondary: string } +): string { + const family = classifyCodexQuotaWindow(window); + if (family === "session") return names.primary; + if (family === "weekly") return names.secondary; + return `${names.primary.replace(/session$/, "")}unknown_${window.id}`; +} diff --git a/open-sse/services/codexQuotaWindowPolicy.ts b/open-sse/services/codexQuotaWindowPolicy.ts new file mode 100644 index 000000000000..2c8c4689cdb0 --- /dev/null +++ b/open-sse/services/codexQuotaWindowPolicy.ts @@ -0,0 +1,56 @@ +import { toCodexBaseQuotaWindowName } from "../config/codexQuotaScopes.ts"; + +function asRecord(value: unknown): Record { + return value && typeof value === "object" && !Array.isArray(value) + ? (value as Record) + : {}; +} + +function getCodexLimitPolicy(providerSpecificData: unknown) { + const policy = asRecord(asRecord(providerSpecificData).codexLimitPolicy); + return { use5h: policy.use5h !== false, useWeekly: policy.useWeekly !== false }; +} + +function normalizeCodexWindowName(windowName: string): string { + const normalized = windowName.trim().toLowerCase(); + if (["session (5h)", "5h", "five_hour"].includes(normalized)) return "session"; + if (["weekly (7d)", "7d", "seven_day"].includes(normalized)) return "weekly"; + return toCodexBaseQuotaWindowName(normalized) ?? normalized; +} + +function isWindowEnabled(windowName: string, policy: ReturnType) { + const normalized = normalizeCodexWindowName(windowName); + if (normalized === "session") return policy.use5h; + if (normalized === "weekly") return policy.useWeekly; + return true; +} + +/** Shared tracking policy: disabled known windows stay disabled even with explicit cutoffs. */ +export function applyCodexWindowPolicy( + rawWindows: string[], + providerSpecificData: unknown +): string[] { + const policy = getCodexLimitPolicy(providerSpecificData); + const windows = rawWindows + .map(normalizeCodexWindowName) + .filter(Boolean) + .filter((name) => isWindowEnabled(name, policy)); + if (policy.use5h) windows.push("session"); + if (policy.useWeekly) windows.push("weekly"); + return [...new Set(windows)]; +} + +/** + * Filter measured windows without renaming Spark keys or guessing unknown durations. + * Preserve the input reference when nothing was excluded: the preflight uses that + * distinction to retain unattributed upstream exhaustion unless a known window was disabled. + */ +export function filterCodexQuotaWindows( + windows: Record, + providerSpecificData: unknown +): Record { + const policy = getCodexLimitPolicy(providerSpecificData); + const entries = Object.entries(windows); + const selected = entries.filter(([name]) => isWindowEnabled(name, policy)); + return selected.length === entries.length ? windows : Object.fromEntries(selected); +} diff --git a/open-sse/services/codexUsageQuotas.ts b/open-sse/services/codexUsageQuotas.ts index 8ddd5fde4e5e..6358978b16cf 100644 --- a/open-sse/services/codexUsageQuotas.ts +++ b/open-sse/services/codexUsageQuotas.ts @@ -1,3 +1,4 @@ +import { parseCodexPaidCredits, type CodexPaidCredits } from "@/lib/providers/codexPaidCredits"; import { CODEX_RESERVE_DISPLAY_NAME, CODEX_RESERVE_QUOTA_SESSION, @@ -9,7 +10,7 @@ import { isCodexSparkLimitDescriptor, } from "../config/codexQuotaScopes.ts"; import { inferWindowFamilyLabel } from "./quotaWindowLabel.ts"; -import { parseCodexPaidCredits, type CodexPaidCredits } from "@/lib/providers/codexPaidCredits"; +import { codexQuotaWindowKey, parseCodexQuotaWindow } from "./codexQuotaSignals.ts"; type JsonRecord = Record; @@ -45,71 +46,50 @@ function toNumber(value: unknown, fallback = 0): number { return fallback; } -function toNullableNumber(value: unknown): number | null { - if (typeof value === "number" && Number.isFinite(value)) return value; - if (typeof value === "string" && value.trim().length > 0) { - const parsed = Number(value); - return Number.isFinite(parsed) ? parsed : null; - } - return null; -} - -function parseResetTime(resetValue: unknown): string | null { - if (!resetValue) return null; - try { - let date: Date | null = null; - if (resetValue instanceof Date) { - date = resetValue; - } else if (typeof resetValue === "number") { - date = new Date(resetValue < 1e12 ? resetValue * 1000 : resetValue); - } else if (typeof resetValue === "string") { - // Numeric strings are Unix timestamps too (seconds or milliseconds). - if (/^\d+$/.test(resetValue)) { - const ts = Number(resetValue); - date = new Date(ts < 1e12 ? ts * 1000 : ts); - } else { - date = new Date(resetValue); - } - } - if (!date || date.getTime() <= 0) return null; - return date.toISOString(); - } catch { - return null; - } -} - -function parseWindowReset(window: unknown): string | null { - const resetAt = toNumber(getFieldValue(window, "reset_at", "resetAt"), 0); - const resetAfterSeconds = toNumber( - getFieldValue(window, "reset_after_seconds", "resetAfterSeconds"), - 0 - ); - if (resetAt > 0) return parseResetTime(resetAt * 1000); - if (resetAfterSeconds > 0) return parseResetTime(Date.now() + resetAfterSeconds * 1000); - return null; -} - -function buildPercentageQuota(window: JsonRecord, displayName?: string): CodexUsageQuota { - const usedPercent = toNumber(getFieldValue(window, "used_percent", "usedPercent"), 0); - return { - used: usedPercent, +function assignPercentageQuota( + quotas: Record, + key: string, + rawWindow: JsonRecord, + displayName?: string +): void { + const window = parseCodexQuotaWindow(rawWindow); + if (!window) return; + quotas[key] = { + used: window.usedPercent, total: 100, - remaining: 100 - usedPercent, - resetAt: parseWindowReset(window), + remaining: 100 - window.usedPercent, + resetAt: window.resetAt, unlimited: false, - windowSeconds: toNullableNumber( - getFieldValue( - window, - "limit_window_seconds", - "limitWindowSeconds", - "window_seconds", - "windowSeconds" - ) - ), + windowSeconds: window.windowSeconds, ...(displayName ? { displayName } : {}), }; } +function assignDurationQuotas( + quotas: Record, + rateLimit: JsonRecord, + names: { primary: string; secondary: string }, + displayName?: string, + skipLatent = false +): void { + for (const id of ["primary", "secondary"] as const) { + const raw = toRecord(getFieldValue(rateLimit, `${id}_window`, `${id}Window`)); + if (skipLatent && isLatentWindow(raw)) continue; + const window = parseCodexQuotaWindow(raw, id); + if (!window) continue; + const key = codexQuotaWindowKey(window, names); + const family = windowDurationLabel(window.windowSeconds); + const label = displayName + ? family === "Weekly" + ? `${displayName} Weekly` + : displayName + : family === "Monthly" + ? family + : undefined; + assignPercentageQuota(quotas, key, raw, label || undefined); + } +} + // A window whose duration is >= this is a weekly (or longer) window; <= the // session threshold is a short rolling ("session") window. ChatGPT reports the // window length in `limit_window_seconds`, so labels can follow the real @@ -140,12 +120,10 @@ function isLatentWindow(window: JsonRecord): boolean { * e.g. a 7-day `primary_window` is labeled "Weekly" rather than "Session". * Returns undefined for durations that don't clearly map to either bucket. */ -function windowDurationLabel(window: JsonRecord): "Session" | "Weekly" | "Monthly" | undefined { - const limitWindow = toNumber( - getFieldValue(window, "limit_window_seconds", "limitWindowSeconds"), - 0 - ); - if (limitWindow <= 0) return undefined; +function windowDurationLabel( + limitWindow: number | null +): "Session" | "Weekly" | "Monthly" | undefined { + if (limitWindow === null || limitWindow <= 0) return undefined; const inferred = inferWindowFamilyLabel(limitWindow); if (inferred === "Monthly" || inferred === "Weekly" || inferred === "Session") return inferred; if (limitWindow >= MONTHLY_MIN_WINDOW_SECONDS) return "Monthly"; @@ -273,8 +251,9 @@ function appendCodexReserveQuotas(data: JsonRecord, quotas: Record 0) { - const primaryLabel = windowDurationLabel(primaryWindow); - quotas.session = buildPercentageQuota( - primaryWindow, - primaryLabel === "Weekly" || primaryLabel === "Monthly" ? primaryLabel : undefined - ); - } - - const secondaryWindow = toRecord(getFieldValue(rateLimit, "secondary_window", "secondaryWindow")); - if (Object.keys(secondaryWindow).length > 0) { - const secondaryLabel = windowDurationLabel(secondaryWindow); - quotas.weekly = buildPercentageQuota( - secondaryWindow, - secondaryLabel === "Session" ? secondaryLabel : undefined - ); - } + assignDurationQuotas(quotas, rateLimit, { primary: "session", secondary: "weekly" }); // Resolve the code-review rate limit block. ChatGPT Codex exposes the same // information under two different shapes depending on the plan tier @@ -411,7 +370,7 @@ export function buildCodexUsageQuotas(dataValue: unknown): { getFieldValue(codeReviewWindow, "used_percent", "usedPercent") !== null || getFieldValue(codeReviewWindow, "remaining_count", "remainingCount") !== null ) { - quotas.code_review = buildPercentageQuota(codeReviewWindow); + assignPercentageQuota(quotas, "code_review", codeReviewWindow); } const codeReviewSecondaryWindow = toRecord( @@ -421,7 +380,7 @@ export function buildCodexUsageQuotas(dataValue: unknown): { getFieldValue(codeReviewSecondaryWindow, "used_percent", "usedPercent") !== null || getFieldValue(codeReviewSecondaryWindow, "remaining_count", "remainingCount") !== null ) { - quotas.code_review_weekly = buildPercentageQuota(codeReviewSecondaryWindow); + assignPercentageQuota(quotas, "code_review_weekly", codeReviewSecondaryWindow); } const spark = findCodexSparkRateLimit(data); @@ -430,25 +389,16 @@ export function buildCodexUsageQuotas(dataValue: unknown): { // reports (e.g. as the spark model version bumps) rather than a hardcoded // constant; fall back to the constant when the field is absent. const sparkDisplayName = spark.limitName || CODEX_SPARK_DISPLAY_NAME; - const sparkPrimaryWindow = toRecord( - getFieldValue(sparkRateLimit, "primary_window", "primaryWindow") - ); - // Skip latent (never-used, full-window) spark ceilings so they don't render as - // a permanent 100% row with a meaningless always-full reset. Once the account - // actually uses the spark model the window is no longer latent and appears. - if (Object.keys(sparkPrimaryWindow).length > 0 && !isLatentWindow(sparkPrimaryWindow)) { - quotas[CODEX_SPARK_QUOTA_SESSION] = buildPercentageQuota(sparkPrimaryWindow, sparkDisplayName); - } - - const sparkSecondaryWindow = toRecord( - getFieldValue(sparkRateLimit, "secondary_window", "secondaryWindow") + assignDurationQuotas( + quotas, + sparkRateLimit, + { + primary: CODEX_SPARK_QUOTA_SESSION, + secondary: CODEX_SPARK_QUOTA_WEEKLY, + }, + sparkDisplayName, + true ); - if (Object.keys(sparkSecondaryWindow).length > 0 && !isLatentWindow(sparkSecondaryWindow)) { - quotas[CODEX_SPARK_QUOTA_WEEKLY] = buildPercentageQuota( - sparkSecondaryWindow, - `${sparkDisplayName} Weekly` - ); - } appendCodexReserveQuotas(data, quotas); diff --git a/open-sse/services/quotaPreflight.ts b/open-sse/services/quotaPreflight.ts index f21cc8e002b6..3f7712997073 100644 --- a/open-sse/services/quotaPreflight.ts +++ b/open-sse/services/quotaPreflight.ts @@ -31,6 +31,7 @@ import { isCodexPaidCreditsEnabled, type CodexPaidCredits, } from "@/lib/providers/codexPaidCredits"; +import { filterCodexQuotaWindows } from "./codexQuotaWindowPolicy.ts"; import { fetchNewApiAggregatorQuota, invalidateNewApiAggregatorQuotaCache, @@ -254,6 +255,9 @@ export function windowsForScope( windows: NonNullable, scope?: QuotaCutoffScope ): NonNullable { + if (scope?.provider === "codex") { + return filterCodexQuotaWindows(windows, scope.providerSpecificData); + } if (!scope?.requestedModel || !isAntigravityQuotaProvider(scope.provider ?? null)) { return windows; } @@ -417,7 +421,10 @@ function windowedQuotaCutoffResult( if (isAntigravityQuotaProvider(scope?.provider ?? null) && scope?.requestedModel) { return { proceed: true, quotaPercent: quota.percentUsed }; } - if (quota.limitReached === true) return limitReachedResult(quota); + // An aggregate signal cannot re-enable a Codex window explicitly removed by its toggle. + if (quota.limitReached === true && !(scope?.provider === "codex" && scopedWindows !== windows)) { + return limitReachedResult(quota); + } return { proceed: true, quotaPercent: quota.percentUsed }; } diff --git a/open-sse/services/usage/codex.ts b/open-sse/services/usage/codex.ts index 7cd4a76db12c..119e89495ceb 100644 --- a/open-sse/services/usage/codex.ts +++ b/open-sse/services/usage/codex.ts @@ -9,6 +9,7 @@ */ import { buildCodexUsageQuotas, findCodexSparkRateLimit } from "../codexUsageQuotas.ts"; +import { isCodexLimitReached } from "../codexQuotaSignals.ts"; import { fetchWithConnectionProxy } from "../connectionProxyFetch.ts"; import { getCodexBackendIdentityHeaders } from "../../config/codexClient.ts"; import { getFieldValue } from "./scalars.ts"; @@ -96,7 +97,7 @@ export async function getCodexUsage( return { ...(typeof sparkLimitReached === "boolean" ? { sparkLimitReached } : {}), plan: String(getFieldValue(data, "plan_type", "planType") || "unknown"), - limitReached: Boolean(getFieldValue(rateLimit, "limit_reached", "limitReached")), + limitReached: isCodexLimitReached(rateLimit), quotas, // Banked reset credits (display-only, eligibility-gated — issue #5199). // Absent for most accounts; never throws when the upstream omits it. diff --git a/src/domain/quotaCache.ts b/src/domain/quotaCache.ts index 4b524f1e904c..ecd9199886b4 100644 --- a/src/domain/quotaCache.ts +++ b/src/domain/quotaCache.ts @@ -39,6 +39,7 @@ import { } from "@omniroute/open-sse/services/codexAccount/index.ts"; import { selectAntigravityQuotaWindowNames } from "@omniroute/open-sse/services/antigravityQuotaFamily.ts"; import { isClaudeExtraUsageAllowed } from "@/lib/providers/claudeExtraUsage"; +import { filterCodexQuotaWindows } from "@omniroute/open-sse/services/codexQuotaWindowPolicy.ts"; import { isCodexQuotaFilteringDisabled } from "@/lib/providers/codexQuotaFiltering"; import { resolveProviderId } from "@/shared/constants/providers"; import { @@ -441,13 +442,17 @@ export function hydrateCodexQuotaCacheForRequest( function isCodexQuotaExhausted( connectionId: string, entry: QuotaCacheEntry, - requestedModel: string | null + requestedModel: string | null, + // Optional: callers without the connection's settings keep the default (all windows tracked). + providerSpecificData?: unknown ): boolean { - if (!requestedModel) return entry.exhausted; - const quotaNames = Object.keys(entry.quotas || {}); - if (quotaNames.length === 0) return entry.exhausted; + if (Object.keys(entry.quotas).length === 0) return entry.exhausted; + const windows = filterCodexQuotaWindows(entry.quotas, providerSpecificData); + if (!requestedModel && windows === entry.quotas) return entry.exhausted; const filterWindow = getCodexQuotaWindowFilterForModel(requestedModel); - const scopedWindowNames = quotaNames.filter((windowName) => filterWindow?.(windowName)); + const scopedWindowNames = Object.keys(windows).filter( + (name) => !filterWindow || filterWindow(name) + ); return ( scopedWindowNames.length > 0 && // Exhaustion is distinct from the operator-configured early quota cutoff. @@ -754,7 +759,7 @@ export function isQuotaExhaustedForRequest( } if (provider === "codex") { - return isCodexQuotaExhausted(connectionId, entry, requestedModel); + return isCodexQuotaExhausted(connectionId, entry, requestedModel, providerSpecificData); } if (resolveProviderId(provider) === "claude") { diff --git a/src/lib/usage/cachedQuotaConversion.ts b/src/lib/usage/cachedQuotaConversion.ts index 8e3b6f8c4b66..bf0842e323db 100644 --- a/src/lib/usage/cachedQuotaConversion.ts +++ b/src/lib/usage/cachedQuotaConversion.ts @@ -24,13 +24,32 @@ import { import type { ProviderLimitsCacheEntry } from "@/lib/db/providerLimits"; /** Convert the cached percentage/count fields with routing's existing converter. */ -function codexWindow(value: unknown) { +function codexWindow(value: unknown, canonicalDuration?: number) { const window = convertUsageToQuotaInfo({ quotas: { window: value } }, { provider: "codex" }) ?.windows?.window; if (!window) return undefined; + const measured = value && typeof value === "object" ? (value as Record) : {}; return { used_percent: window.percentUsed * 100, reset_at: window.resetAt ? Date.parse(window.resetAt) / 1000 : undefined, + // Canonical cache keys carry a known duration; neutral keys retain measured evidence only. + limit_window_seconds: canonicalDuration ?? measured.windowSeconds, + }; +} + +function codexCachedWindows( + quotas: Record, + sessionKey: string, + weeklyKey: string +) { + const neutralPrefix = sessionKey.replace(/session$/, "unknown_"); + const known = [ + codexWindow(quotas[sessionKey], 18000), + codexWindow(quotas[weeklyKey], 604800), + ].filter((window) => window !== undefined); + return { + primary_window: codexWindow(quotas[`${neutralPrefix}primary`]) ?? known.shift(), + secondary_window: codexWindow(quotas[`${neutralPrefix}secondary`]) ?? known.shift(), }; } export function convertCachedUsageToQuotaInfo( @@ -59,16 +78,14 @@ export function convertCachedUsageToQuotaInfo( rate_limit_reached_type: usage.rateLimitReachedType, rate_limit: { limit_reached: usage.limitReached ?? false, - primary_window: codexWindow(quotas.session), - secondary_window: codexWindow(quotas.weekly), + ...codexCachedWindows(quotas, "session", "weekly"), }, additional_rate_limits: [ { limit_name: CODEX_SPARK_DISPLAY_NAME, rate_limit: { limit_reached: usage.sparkLimitReached ?? false, - primary_window: codexWindow(quotas[CODEX_SPARK_QUOTA_SESSION]), - secondary_window: codexWindow(quotas[CODEX_SPARK_QUOTA_WEEKLY]), + ...codexCachedWindows(quotas, CODEX_SPARK_QUOTA_SESSION, CODEX_SPARK_QUOTA_WEEKLY), }, }, ], diff --git a/src/sse/services/quotaLimitPolicy.ts b/src/sse/services/quotaLimitPolicy.ts index 3be6dbf056c2..7738e0467fbc 100644 --- a/src/sse/services/quotaLimitPolicy.ts +++ b/src/sse/services/quotaLimitPolicy.ts @@ -1,10 +1,8 @@ import { DEFAULT_QUOTA_THRESHOLD_PERCENT, getQuotaWindowStatus } from "@/domain/quotaCache"; import { defersQuotaCutoff } from "@/lib/providers/quotaCutoffOptIns"; +import { applyCodexWindowPolicy } from "@omniroute/open-sse/services/codexQuotaWindowPolicy.ts"; import { toNumber } from "@/shared/utils/numeric"; -import { - toCodexBaseQuotaWindowName, - toCodexScopedQuotaWindowName, -} from "@omniroute/open-sse/config/codexQuotaScopes.ts"; +import { toCodexScopedQuotaWindowName } from "@omniroute/open-sse/config/codexQuotaScopes.ts"; import { cooldownUntilMs } from "@omniroute/open-sse/services/accountFallback.ts"; import { formatQuotaUsageReason } from "@omniroute/open-sse/services/quotaWindowLabel.ts"; import { seedGitHubCreditLimitReasons } from "./githubCreditLimitPolicy.ts"; @@ -23,16 +21,6 @@ function asRecord(value: unknown): JsonRecord { function toBooleanOrDefault(value: unknown, fallback: boolean): boolean { return typeof value === "boolean" ? value : fallback; } -function getCodexLimitPolicy(providerSpecificData: JsonRecord): { - use5h: boolean; - useWeekly: boolean; -} { - const policy = asRecord(providerSpecificData.codexLimitPolicy); - return { - use5h: toBooleanOrDefault(policy.use5h, true), - useWeekly: toBooleanOrDefault(policy.useWeekly, true), - }; -} function normalizeQuotaThreshold( value: unknown, fallback = DEFAULT_QUOTA_THRESHOLD_PERCENT @@ -45,32 +33,6 @@ export function normalizeWindowName(windowName: unknown): string | null { const normalized = windowName.trim().toLowerCase(); return normalized.length > 0 ? normalized : null; } -function uniqueWindows(windows: string[]): string[] { - return [...new Set(windows)]; -} -function normalizeCodexWindowName(windowName: unknown): string | null { - if (typeof windowName !== "string") return null; - const normalized = windowName.trim().toLowerCase(); - if (normalized === "session (5h)" || normalized === "5h" || normalized === "five_hour") - return "session"; - if (normalized === "weekly (7d)" || normalized === "7d" || normalized === "seven_day") - return "weekly"; - return toCodexBaseQuotaWindowName(normalized); -} -function applyCodexWindowPolicy(rawWindows: string[], providerSpecificData: JsonRecord): string[] { - const codexPolicy = getCodexLimitPolicy(providerSpecificData); - const normalizedRaw = rawWindows.map(normalizeCodexWindowName).filter(Boolean) as string[]; - // Preserve custom windows, while canonical toggles always contribute their windows. - let windows = [...normalizedRaw]; - windows = windows.filter((windowName) => { - if (windowName === "session") return codexPolicy.use5h; - if (windowName === "weekly") return codexPolicy.useWeekly; - return true; - }); - if (codexPolicy.use5h) windows.push("session"); - if (codexPolicy.useWeekly) windows.push("weekly"); - return uniqueWindows(windows); -} export function resolveQuotaLimitPolicy( provider: string, providerSpecificData: JsonRecord diff --git a/stryker.conf.json b/stryker.conf.json index 9cd417477d12..eb1c17d5001b 100644 --- a/stryker.conf.json +++ b/stryker.conf.json @@ -44,6 +44,11 @@ "tests/unit/antigravity-headers-budget.test.ts", "tests/unit/log-truncation-image-ref.test.ts", "tests/unit/provider-safety-refusal-not-a-ban.test.ts", + "tests/unit/codex-dual-toggle-policy-18330.test.ts", + "tests/unit/codex-dual-toggle-routing-18330.test.ts", + "tests/unit/codex-weekly-cutoff-18330.test.ts", + "tests/unit/codex-weekly-routing-18330.test.ts", + "tests/unit/codex-quota-window-identity-18330.test.ts", "tests/unit/muse-session-ownership.test.ts", "tests/unit/muse-empty-response-retry.test.ts", "tests/unit/deepseek-api-flash-vision.test.ts", @@ -101,6 +106,11 @@ "tests/unit/check-error-helper-call-scope.test.ts", "tests/unit/check-error-helper-false-positives-15159.test.ts", "tests/unit/codex-quota-optout.test.ts", + "tests/unit/codex-dual-toggle-policy-18330.test.ts", + "tests/unit/codex-dual-toggle-routing-18330.test.ts", + "tests/unit/codex-weekly-cutoff-18330.test.ts", + "tests/unit/codex-weekly-routing-18330.test.ts", + "tests/unit/codex-quota-window-identity-18330.test.ts", "tests/unit/codex-passive-refresh-gate.test.ts", "tests/unit/compression/default-combo-lossy-policy.test.ts", "tests/unit/compression/output-styles-anthropic-placement.test.ts", diff --git a/tests/unit/chat-context-relay.test.ts b/tests/unit/chat-context-relay.test.ts index e9c766a6996f..93daf66e561b 100644 --- a/tests/unit/chat-context-relay.test.ts +++ b/tests/unit/chat-context-relay.test.ts @@ -62,10 +62,12 @@ function buildQuotaResponse(usedPercent, resetAfterSeconds = 3600) { rate_limit: { primary_window: { used_percent: usedPercent, + limit_window_seconds: 18000, reset_after_seconds: resetAfterSeconds, }, secondary_window: { used_percent: 0, + limit_window_seconds: 604800, reset_after_seconds: 86400, }, }, diff --git a/tests/unit/codex-banked-reset-credits-5199.test.ts b/tests/unit/codex-banked-reset-credits-5199.test.ts index b831956bfda5..5bd15089d398 100644 --- a/tests/unit/codex-banked-reset-credits-5199.test.ts +++ b/tests/unit/codex-banked-reset-credits-5199.test.ts @@ -41,8 +41,8 @@ test.afterEach(() => { test("buildCodexUsageQuotas surfaces bankedResetCredits when present", () => { const { quotas, bankedResetCredits, rateLimitReachedType } = buildCodexUsageQuotas({ rate_limit: { - primary_window: { used_percent: 10 }, - secondary_window: { used_percent: 20 }, + primary_window: { limit_window_seconds: 18000, used_percent: 10 }, + secondary_window: { limit_window_seconds: 604800, used_percent: 20 }, }, rate_limit_reset_credits: { available_count: 3 }, rate_limit_reached_type: { type: "secondary_window" }, @@ -57,7 +57,7 @@ test("buildCodexUsageQuotas surfaces bankedResetCredits when present", () => { test("buildCodexUsageQuotas tolerates camelCase field shape", () => { const { bankedResetCredits, rateLimitReachedType } = buildCodexUsageQuotas({ - rateLimit: { primaryWindow: { usedPercent: 5 } }, + rateLimit: { primaryWindow: { limitWindowSeconds: 18000, usedPercent: 5 } }, rateLimitResetCredits: { availableCount: 7 }, rateLimitReachedType: "primary_window", }); @@ -69,8 +69,8 @@ test("buildCodexUsageQuotas tolerates camelCase field shape", () => { test("buildCodexUsageQuotas leaves bankedResetCredits undefined when absent (fail-open)", () => { const result = buildCodexUsageQuotas({ rate_limit: { - primary_window: { used_percent: 10 }, - secondary_window: { used_percent: 20 }, + primary_window: { limit_window_seconds: 18000, used_percent: 10 }, + secondary_window: { limit_window_seconds: 604800, used_percent: 20 }, }, }); @@ -84,7 +84,7 @@ test("buildCodexUsageQuotas leaves bankedResetCredits undefined when absent (fai test("buildCodexUsageQuotas never throws on a garbage rate_limit_reset_credits shape", () => { assert.doesNotThrow(() => { const result = buildCodexUsageQuotas({ - rate_limit: { primary_window: { used_percent: 1 } }, + rate_limit: { primary_window: { limit_window_seconds: 18000, used_percent: 1 } }, rate_limit_reset_credits: "not-an-object", rate_limit_reached_type: 12345, }); @@ -101,8 +101,8 @@ test("getCodexUsage threads bankedResetCredits through additively", async () => JSON.stringify({ plan_type: "plus", rate_limit: { - primary_window: { used_percent: 15 }, - secondary_window: { used_percent: 25 }, + primary_window: { limit_window_seconds: 18000, used_percent: 15 }, + secondary_window: { limit_window_seconds: 604800, used_percent: 25 }, }, rate_limit_reset_credits: { available_count: 2 }, }), @@ -123,8 +123,8 @@ test("getCodexUsage omits bankedResetCredits and stays intact when absent", asyn JSON.stringify({ plan_type: "plus", rate_limit: { - primary_window: { used_percent: 15 }, - secondary_window: { used_percent: 25 }, + primary_window: { limit_window_seconds: 18000, used_percent: 15 }, + secondary_window: { limit_window_seconds: 604800, used_percent: 25 }, }, }), { status: 200, headers: { "content-type": "application/json" } } @@ -146,8 +146,16 @@ test("fetchCodexQuota surfaces bankedResetCredits from the dual-window parser", new Response( JSON.stringify({ rate_limit: { - primary_window: { used_percent: 70, reset_after_seconds: 45 }, - secondary_window: { used_percent: 20, reset_after_seconds: 300 }, + primary_window: { + limit_window_seconds: 18000, + used_percent: 70, + reset_after_seconds: 45, + }, + secondary_window: { + limit_window_seconds: 604800, + used_percent: 20, + reset_after_seconds: 300, + }, }, rate_limit_reset_credits: { available_count: 4 }, rate_limit_reached_type: { type: "primary_window" }, @@ -180,8 +188,8 @@ test("fetchCodexQuota omits bankedResetCredits when the payload does not have it new Response( JSON.stringify({ rate_limit: { - primary_window: { used_percent: 30 }, - secondary_window: { used_percent: 10 }, + primary_window: { limit_window_seconds: 18000, used_percent: 30 }, + secondary_window: { limit_window_seconds: 604800, used_percent: 10 }, }, }), { status: 200, headers: { "content-type": "application/json" } } diff --git a/tests/unit/codex-dual-toggle-policy-18330.test.ts b/tests/unit/codex-dual-toggle-policy-18330.test.ts new file mode 100644 index 000000000000..c52071de78c7 --- /dev/null +++ b/tests/unit/codex-dual-toggle-policy-18330.test.ts @@ -0,0 +1,231 @@ +import assert from "node:assert/strict"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; +import test from "node:test"; + +const home = fs.mkdtempSync(path.join(os.tmpdir(), "codex-dual-toggle-")); +process.env.HOME = home; +process.env.DATA_DIR = path.join(home, "data"); +process.env.DISABLE_SQLITE_AUTO_BACKUP = "true"; +const { evaluateQuotaCutoff, preflightQuota, registerQuotaFetcher } = + await import("../../open-sse/services/quotaPreflight.ts"); +const { markQuotaHealthy, unmarkQuotaHealthy } = + await import("../../src/domain/quotaCacheState.ts"); +const { resolveQuotaLimitPolicy } = await import("../../src/sse/services/auth.ts"); +const core = await import("../../src/lib/db/core.ts"); +const resetAt = new Date(Date.now() + 600_000).toISOString(); +const thresholds = { resolveMinRemainingPercent: () => 10, isConfiguredCutoff: () => true }; +function quota(windows: Record, limitReached = false) { + return { + used: 100, + total: 100, + percentUsed: Math.max(...Object.values(windows)), + limitReached, + windows: Object.fromEntries( + Object.entries(windows).map(([name, percentUsed]) => [name, { percentUsed, resetAt }]) + ), + }; +} +function scope(use5h: boolean, useWeekly: boolean, extra: Record = {}) { + return { + provider: "codex", + requestedModel: "gpt-5.5", + providerSpecificData: { + codexLimitPolicy: { use5h, useWeekly }, + allowPaidCredits: false, + ...extra, + }, + }; +} +test.after(() => { + core.resetDbInstance(); + fs.rmSync(home, { recursive: true, force: true }); +}); + +test("dual5h100 weekly85 with tracking5hOFF weeklyON should remain eligible", () => { + assert.equal( + evaluateQuotaCutoff(quota({ session: 1, weekly: 0.85 }), thresholds, scope(false, true)) + .proceed, + true + ); +}); +test("disabled window cannot return through aggregate limitReached", () => { + assert.equal( + evaluateQuotaCutoff(quota({ session: 1, weekly: 0.85 }, true), thresholds, scope(false, true)) + .proceed, + true + ); +}); +for (const use5h of [false, true]) { + for (const useWeekly of [false, true]) { + for (const [session, weekly] of [ + [1, 0.85], + [0.85, 1], + [1, 1], + [0.85, 0.85], + ]) { + test(`explicit cutoffs respect toggles ${use5h}/${useWeekly} at used ${session}/${weekly}`, () => { + const actual = evaluateQuotaCutoff( + quota({ session, weekly }), + thresholds, + scope(use5h, useWeekly) + ); + assert.equal(actual.proceed, !((use5h && session >= 0.9) || (useWeekly && weekly >= 0.9))); + }); + } + } +} +for (const healthy of [false, true]) { + for (const [used, allowed] of [ + [0.89, true], + [0.9, false], + [0.94, false], + ] as const) { + test(`weekly cutoff boundary ${used}, recent success ${healthy}`, () => { + unmarkQuotaHealthy("dual-healthy"); + if (healthy) markQuotaHealthy("dual-healthy"); + const actual = evaluateQuotaCutoff(quota({ session: 1, weekly: used }), thresholds, { + ...scope(false, true), + connectionId: "dual-healthy", + }); + assert.equal(actual.proceed, allowed); + }); + } +} +test("both toggles off do not suppress exhausted unknown or custom feature windows", () => { + for (const key of [ + "unknown_primary", + "gpt_5_3_codex_spark_unknown_primary", + "code_review", + "monthly", + ]) { + const actual = evaluateQuotaCutoff( + quota({ session: 1, weekly: 1, [key]: 1 }, true), + thresholds, + scope(false, false) + ); + assert.equal(actual.proceed, false); + assert.equal(actual.windowName, key); + } +}); +test("unattributed exhaustion remains enforced without removed windows", () => { + for (const windows of [{ weekly: 0.85 }, { unknown_primary: 0.85 }]) { + assert.equal( + evaluateQuotaCutoff(quota(windows, true), thresholds, scope(false, true)).proceed, + false + ); + } + assert.equal( + evaluateQuotaCutoff( + { used: 0, total: 100, percentUsed: 0, limitReached: true }, + thresholds, + scope(false, false) + ).proceed, + false + ); + assert.equal( + evaluateQuotaCutoff({ used: 100, total: 100, percentUsed: 1 }, thresholds, scope(false, false)) + .proceed, + false + ); +}); +test("both disabled known windows do not fall back to aggregate exhaustion", () => { + assert.equal( + evaluateQuotaCutoff(quota({ session: 1, weekly: 1 }, true), thresholds, scope(false, false)) + .proceed, + true + ); +}); +test("Spark windows honor the same toggles without dropping their identity", () => { + const spark = { ...scope(false, true), requestedModel: "gpt-5.3-codex-spark" }; + assert.equal( + evaluateQuotaCutoff( + quota({ gpt_5_3_codex_spark_session: 1, gpt_5_3_codex_spark_weekly: 0.85 }, true), + thresholds, + spark + ).proceed, + true + ); + const blocked = evaluateQuotaCutoff( + quota({ gpt_5_3_codex_spark_session: 0.85, gpt_5_3_codex_spark_weekly: 1 }), + thresholds, + spark + ); + assert.equal(blocked.proceed, false); + assert.equal(blocked.windowName, "gpt_5_3_codex_spark_weekly"); +}); +test("only explicit false disables tracking, malformed policy stays default-on", () => { + for (const codexLimitPolicy of [null, [], "false", { use5h: "false", useWeekly: 0 }]) { + assert.equal( + evaluateQuotaCutoff( + quota({ session: 1 }), + thresholds, + scope(true, true, { codexLimitPolicy }) + ).proceed, + false + ); + } +}); +test("cached policy preserves custom windows and excludes disabled aliases", () => { + const policy = resolveQuotaLimitPolicy("codex", { + codexLimitPolicy: { use5h: false, useWeekly: true }, + limitPolicy: { + windows: ["Session (5h)", "5h", "gpt_5_3_codex_spark_session", "unknown_primary", "monthly"], + }, + }); + assert.deepEqual(policy.windows, ["unknown_primary", "monthly", "weekly"]); +}); +test("other providers do not inherit Codex toggles", () => { + assert.equal( + evaluateQuotaCutoff(quota({ session: 1 }), thresholds, { + ...scope(false, false), + provider: "claude", + }).proceed, + false + ); +}); +test("paid credit consent and full opt-out retain their existing precedence", () => { + const observed = { + ...quota({ session: 1, weekly: 1 }), + paidCredits: { hasCredits: true, unlimited: false, overageLimitReached: false, balance: 20 }, + }; + assert.equal(evaluateQuotaCutoff(observed, thresholds, scope(true, true)).proceed, false); + assert.equal( + evaluateQuotaCutoff(observed, thresholds, scope(true, true, { allowPaidCredits: true })) + .proceed, + true + ); + assert.equal( + evaluateQuotaCutoff(observed, thresholds, { + ...scope(true, true, { allowPaidCredits: true }), + requestedModel: "gpt-5.3-codex-spark", + }).proceed, + false + ); + assert.equal( + evaluateQuotaCutoff( + observed, + thresholds, + scope(true, true, { quotaPreflightEnabled: false, limitPolicy: { enabled: false } }) + ).proceed, + true + ); +}); +test("preflight warning and cutoff both ignore disabled windows", async () => { + registerQuotaFetcher("codex", async () => quota({ session: 1, weekly: 0.4 }, true)); + const warnings: unknown[][] = []; + const original = console.warn; + console.warn = (...args: unknown[]) => { + warnings.push(args); + }; + try { + assert.equal( + (await preflightQuota("codex", "dual-warning", scope(false, true), thresholds)).proceed, + true + ); + assert.deepEqual(warnings, []); + } finally { + console.warn = original; + } +}); diff --git a/tests/unit/codex-dual-toggle-routing-18330.test.ts b/tests/unit/codex-dual-toggle-routing-18330.test.ts new file mode 100644 index 000000000000..aca674913612 --- /dev/null +++ b/tests/unit/codex-dual-toggle-routing-18330.test.ts @@ -0,0 +1,206 @@ +import assert from "node:assert/strict"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; +import test from "node:test"; + +const isolatedHome = fs.mkdtempSync(path.join(os.tmpdir(), "codex-dual-routing-18330-")); +process.env.HOME = isolatedHome; +process.env.DATA_DIR = path.join(isolatedHome, "data"); +process.env.DISABLE_SQLITE_AUTO_BACKUP = "true"; +process.env.OMNIROUTE_QUOTA_FETCH_MIN_INTERVAL_MS = "0"; + +const core = await import("../../src/lib/db/core.ts"); +const providers = await import("../../src/lib/db/providers.ts"); +const settings = await import("../../src/lib/db/settings.ts"); +const auth = await import("../../src/sse/services/auth.ts"); +const cache = await import("../../src/domain/quotaCache.ts"); +const { fetchCodexQuota, unregisterCodexConnection } = + await import("../../open-sse/services/codexQuotaFetcher.ts"); +const { registerQuotaFetcher } = await import("../../open-sse/services/quotaPreflight.ts"); +const originalFetch = globalThis.fetch; +const connections: string[] = []; +const usage = new Map(); +const calls: string[] = []; + +async function seed( + session: number, + weekly: number, + priority: number, + use5h: boolean, + useWeekly: boolean +) { + const connection = await providers.createProviderConnection({ + provider: "codex", + authType: "oauth", + isActive: true, + testStatus: "active", + name: `dual-${session}-${weekly}-${priority}`, + accessToken: `fixture-${priority}`, + priority, + quotaWindowThresholds: { session: 10, weekly: 10 }, + providerSpecificData: { + codexLimitPolicy: { use5h, useWeekly }, + allowPaidCredits: false, + limitPolicy: { enabled: true, thresholdPercent: 90 }, + }, + }); + connections.push(connection.id); + usage.set(connection.id, { session, weekly }); + cache.setQuotaCache(connection.id, "codex", { + session: { + remainingPercentage: 100 - session, + resetAt: new Date(Date.now() + 600_000).toISOString(), + }, + weekly: { + remainingPercentage: 100 - weekly, + resetAt: new Date(Date.now() + 600_000).toISOString(), + }, + }); + return connection; +} +test.beforeEach(async () => { + for (const id of connections.splice(0)) { + unregisterCodexConnection(id); + await providers.deleteProviderConnection(id); + } + cache.__clearForTests(); + calls.length = 0; + registerQuotaFetcher("codex", async (id, connection) => { + calls.push(id); + globalThis.fetch = async () => + Response.json({ + rate_limit: { + limit_reached: true, + primary_window: { + used_percent: usage.get(id)?.session, + limit_window_seconds: 18000, + reset_after_seconds: 600, + }, + secondary_window: { + used_percent: usage.get(id)?.weekly, + limit_window_seconds: 604800, + reset_after_seconds: 600, + }, + }, + credits: { has_credits: true, unlimited: false, balance: "20" }, + }); + return fetchCodexQuota(id, connection); + }); +}); +test.after(() => { + globalThis.fetch = originalFetch; + for (const id of connections) unregisterCodexConnection(id); + core.resetDbInstance(); + fs.rmSync(isolatedHome, { recursive: true, force: true }); +}); + +for (const enabled of [false, true]) { + for (const use5h of [false, true]) { + test(`routing respects disabled ${use5h ? "weekly" : "session"} with global preflight ${enabled}`, async () => { + await settings.updateSettings({ + fallbackStrategy: "priority", + resilienceSettings: { quotaPreflight: { enabled } }, + }); + const selectedAccount = await seed(use5h ? 85 : 100, use5h ? 100 : 85, 1, use5h, !use5h); + await seed(85, 85, 2, true, true); + const selected = await auth.getProviderCredentialsWithQuotaPreflight( + "codex", + null, + null, + "gpt-5.5" + ); + assert.equal(selected?.connectionId, selectedAccount.id); + assert.deepEqual(calls, [selectedAccount.id]); + selected?.releaseOAuthSession?.(); + }); + } +} +test("cached selection skips an enabled exhausted window but accepts disabled exhaustion", async () => { + const low = await seed(100, 94, 1, false, true); + const healthy = await seed(100, 85, 2, false, true); + assert.equal(auth.evaluateQuotaLimitPolicy("codex", low).blocked, true); + assert.equal(auth.evaluateQuotaLimitPolicy("codex", healthy).blocked, false); + const selected = await auth.getProviderCredentialsWithQuotaPreflight( + "codex", + null, + null, + "gpt-5.5" + ); + assert.equal(selected?.connectionId, healthy.id); + assert.deepEqual(calls, [healthy.id]); + selected?.releaseOAuthSession?.(); +}); +test("both toggles disabled preserve eligibility despite explicit cutoffs and aggregate flag", async () => { + const account = await seed(100, 100, 1, false, false); + const selected = await auth.getProviderCredentialsWithQuotaPreflight( + "codex", + null, + null, + "gpt-5.5" + ); + assert.equal(selected?.connectionId, account.id); + assert.deepEqual(calls, [account.id]); + selected?.releaseOAuthSession?.(); +}); +test("all accounts below enabled weekly cutoff are excluded", async () => { + await seed(100, 94, 1, false, true); + await seed(100, 90, 2, false, true); + const selected = await auth.getProviderCredentialsWithQuotaPreflight( + "codex", + null, + null, + "gpt-5.5" + ); + assert.equal(selected?.allRateLimited, true); + assert.equal(selected?.connectionId, undefined); +}); + +test("cached observations remain intact for independent policy decisions", () => { + const id = "dual-shared-observation"; + const resetAt = new Date(Date.now() + 600_000).toISOString(); + cache.setQuotaCache(id, "codex", { + session: { remainingPercentage: 0, resetAt }, + weekly: { remainingPercentage: 0, resetAt }, + }); + const disabled = { codexLimitPolicy: { use5h: false, useWeekly: false } }; + const enabled = { codexLimitPolicy: { use5h: true, useWeekly: true } }; + for (const model of ["gpt-5.5", null]) { + assert.equal(cache.isQuotaExhaustedForRequest(id, "codex", model, disabled), false); + assert.equal(cache.isQuotaExhaustedForRequest(id, "codex", model, enabled), true); + assert.equal(cache.isQuotaExhaustedForRequest(id, "codex", model, disabled), false); + } + assert.equal(cache.getQuotaCache(id)?.quotas.session.remainingPercentage, 0); +}); +test("unknown cached exhaustion and an unattributed upstream 429 survive disabled toggles", () => { + const disabled = { codexLimitPolicy: { use5h: false, useWeekly: false } }; + const resetAt = new Date(Date.now() + 600_000).toISOString(); + cache.setQuotaCache("dual-unknown", "codex", { + session: { remainingPercentage: 0, resetAt }, + unknown_primary: { remainingPercentage: 0, resetAt }, + }); + assert.equal( + cache.isQuotaExhaustedForRequest("dual-unknown", "codex", "gpt-5.5", disabled), + true + ); + cache.markAccountExhaustedFrom429("dual-no-windows", "codex"); + assert.equal( + cache.isQuotaExhaustedForRequest("dual-no-windows", "codex", "gpt-5.5", disabled), + true + ); +}); +test("cached Spark identity remains separate while respecting toggles", () => { + const resetAt = new Date(Date.now() + 600_000).toISOString(); + cache.setQuotaCache("dual-spark", "codex", { + session: { remainingPercentage: 0, resetAt }, + weekly: { remainingPercentage: 0, resetAt }, + gpt_5_3_codex_spark_session: { remainingPercentage: 0, resetAt }, + gpt_5_3_codex_spark_weekly: { remainingPercentage: 15, resetAt }, + }); + const policy = { codexLimitPolicy: { use5h: false, useWeekly: true } }; + assert.equal( + cache.isQuotaExhaustedForRequest("dual-spark", "codex", "gpt-5.3-codex-spark", policy), + false + ); + assert.equal(cache.isQuotaExhaustedForRequest("dual-spark", "codex", "gpt-5.5", policy), true); +}); diff --git a/tests/unit/codex-paid-credits.test.ts b/tests/unit/codex-paid-credits.test.ts index 881291feb4da..622b064aa9f8 100644 --- a/tests/unit/codex-paid-credits.test.ts +++ b/tests/unit/codex-paid-credits.test.ts @@ -30,7 +30,10 @@ const credits = { hasCredits: true, unlimited: false, overageLimitReached: false test("Codex paid credits preserve Business availability without inventing a balance", () => { const parsed = buildCodexUsageQuotas({ credits: { has_credits: true, unlimited: false, overage_limit_reached: false, balance: null }, - rate_limit: { limit_reached: true, primary_window: { used_percent: 100 } }, + rate_limit: { + limit_reached: true, + primary_window: { used_percent: 100, limit_window_seconds: 18000 }, + }, rate_limit_reset_credits: { available_count: 3 }, }); assert.deepEqual(parsed.paidCredits, credits); diff --git a/tests/unit/codex-pro-weekly-window-18326.test.ts b/tests/unit/codex-pro-weekly-window-18326.test.ts new file mode 100644 index 000000000000..b6700991591d --- /dev/null +++ b/tests/unit/codex-pro-weekly-window-18326.test.ts @@ -0,0 +1,180 @@ +import assert from "node:assert/strict"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; +import test from "node:test"; + +// Refs #18326. Synthetic validation-plan fixtures, not a captured reporter response. +const home = fs.mkdtempSync(path.join(os.tmpdir(), "codex-issue-18326-")); +process.env.HOME = home; +process.env.DATA_DIR = path.join(home, "data"); +process.env.DISABLE_SQLITE_AUTO_BACKUP = "true"; +process.env.OMNIROUTE_QUOTA_FETCH_MIN_INTERVAL_MS = "0"; + +const { fetchCodexQuota, unregisterCodexConnection } = + await import("../../open-sse/services/codexQuotaFetcher.ts"); +const { getCodexUsage } = await import("../../open-sse/services/usage/codex.ts"); +const { buildCodexUsageQuotas } = await import("../../open-sse/services/codexUsageQuotas.ts"); +const { projectCodexAccountPool } = await import("../../open-sse/services/codexAccount/index.ts"); +const { resetDbInstance } = await import("../../src/lib/db/core.ts"); +const ids = new Set(); +const resetSeconds = Math.floor(Date.now() / 1000) + 600; +const resetAt = new Date(resetSeconds * 1000).toISOString(); +const window = (used: number, seconds?: number) => ({ + used_percent: used, + ...(seconds === undefined ? {} : { limit_window_seconds: seconds }), + reset_at: resetSeconds, +}); +const connection = { + id: "issue-18326-pool", + provider: "codex", + providerSpecificData: { + codexLimitPolicy: { use5h: false, useWeekly: true }, + allowPaidCredits: false, + }, +}; + +test.after(() => { + for (const id of ids) unregisterCodexConnection(id); + resetDbInstance(); + fs.rmSync(home, { recursive: true, force: true }); +}); + +for (const fixture of [ + { + name: "Pro weekly-only primary", + body: { plan_type: "pro", rate_limit: { primary_window: window(94, 604800) } }, + session: null, + weekly: 94, + }, + { + name: "Pro weekly-only camelCase", + body: { + planType: "pro", + rateLimit: { + primaryWindow: { usedPercent: 94, windowSeconds: "604800", resetAt: resetSeconds }, + }, + }, + session: null, + weekly: 94, + }, + { + name: "Plus dual-window control", + body: { + plan_type: "plus", + rate_limit: { + primary_window: window(20, 18000), + secondary_window: window(94, 604800), + }, + }, + session: 20, + weekly: 94, + }, + { + name: "Pro reversed dual-window control", + body: { + plan_type: "pro", + rate_limit: { + primary_window: window(94, 604800), + secondary_window: window(20, 18000), + }, + }, + session: 20, + weekly: 94, + }, +]) { + test(`#18326 ${fixture.name}: fetcher, usage badge and account pool agree`, async (t) => { + let requests = 0; + t.mock.method(globalThis, "fetch", async (input: string) => { + assert.equal(input, "https://chatgpt.com/backend-api/wham/usage"); + requests += 1; + return Response.json(fixture.body); + }); + const id = `issue-18326-${ids.size}`; + ids.add(id); + const routing = await fetchCodexQuota(id, { + accessToken: "synthetic-offline-token", + providerSpecificData: connection.providerSpecificData, + }); + const usage = await getCodexUsage("synthetic-offline-token", connection.providerSpecificData); + assert.ok(routing); + assert.ok("quotas" in usage && usage.quotas); + assert.equal(requests, 2, "both public fetch paths exercised the synthetic response"); + assert.equal(usage.plan, routing.plan); + const pool = projectCodexAccountPool(connection, Date.now(), { + quotas: usage.quotas, + fetchedAt: new Date().toISOString(), + }); + const normal = pool.children.find((child) => child.key.scope === "codex"); + assert.ok(normal); + for (const [key, slot, expected] of [ + ["session", "5h", fixture.session], + ["weekly", "7d", fixture.weekly], + ] as const) { + const badge = usage.quotas[key]; + const projected = normal.quota.windows[slot]; + if (expected === null) { + assert.equal(badge, undefined, "missing 5h quota must not become a usage badge"); + assert.equal(projected, null, "missing 5h quota must remain absent in the pool"); + assert.equal(routing.windows?.[key], undefined); + assert.equal(routing.window5h, undefined); + } else { + assert.equal(badge?.used, expected); + assert.equal(badge?.total, 100); + assert.equal(badge?.resetAt, resetAt); + assert.equal(projected?.usedPercentage, expected); + assert.equal(projected?.resetAt, badge.resetAt); + assert.equal(routing.windows?.[key]?.percentUsed, expected / 100); + } + } + }); +} + +for (const seconds of [undefined, 3600]) { + test(`#18326 duration ${seconds}: Pro plan alone cannot manufacture a 5h or 7d slot`, async (t) => { + const body = { plan_type: "pro", rate_limit: { primary_window: window(94, seconds) } }; + t.mock.method(globalThis, "fetch", async () => Response.json(body)); + const id = `issue-18326-${ids.size}`; + ids.add(id); + const routing = await fetchCodexQuota(id, { accessToken: "synthetic-offline-token" }); + assert.ok(routing); + assert.equal(routing.percentUsed, 0.94, "unknown duration must retain measured usage"); + assert.equal(routing.window5h, undefined); + assert.equal(routing.window7d, undefined); + const { quotas } = buildCodexUsageQuotas(body); + assert.equal(quotas.unknown_primary?.used, 94); + assert.equal(quotas.session, undefined); + assert.equal(quotas.weekly, undefined); + const pool = projectCodexAccountPool(connection, Date.now(), { + quotas, + fetchedAt: new Date().toISOString(), + }); + for (const child of pool.children) { + assert.equal(child.quota.windows["5h"], null); + assert.equal(child.quota.windows["7d"], null); + } + }); +} + +test("#18326 Spark weekly evidence does not replace the normal account's weekly usage", () => { + const { quotas } = buildCodexUsageQuotas({ + plan_type: "pro", + rate_limit: { primary_window: window(12, 604800) }, + additional_rate_limits: [ + { + limit_id: "codex_bengalfox", + rate_limit: { primary_window: window(94, 604800) }, + }, + ], + }); + const pool = projectCodexAccountPool(connection, Date.now(), { + quotas, + fetchedAt: new Date().toISOString(), + }); + const normal = pool.children.find((child) => child.key.scope === "codex"); + const spark = pool.children.find((child) => child.key.scope === "spark"); + assert.equal(normal?.quota.windows["7d"]?.usedPercentage, 12); + assert.equal(spark?.quota.windows["7d"]?.usedPercentage, 94); + assert.equal(normal?.quota.windows["5h"], null); + assert.equal(spark?.quota.windows["5h"], null); +}); diff --git a/tests/unit/codex-quota-extracted-parser.test.ts b/tests/unit/codex-quota-extracted-parser.test.ts new file mode 100644 index 000000000000..b6b3b06bfe50 --- /dev/null +++ b/tests/unit/codex-quota-extracted-parser.test.ts @@ -0,0 +1,65 @@ +import assert from "node:assert/strict"; +import test from "node:test"; +import { parseCodexUsageResponse } from "../../open-sse/services/codexQuotaParser.ts"; + +for (const camelCase of [false, true]) { + test(`extracted parser preserves plan and duration-based weekly identity (${camelCase})`, () => { + const window = camelCase + ? { usedPercent: "94", windowSeconds: "604800", resetAfterSeconds: 600 } + : { used_percent: 94, limit_window_seconds: 604800, reset_after_seconds: 600 }; + const payload = camelCase + ? { planType: " pro ", rateLimit: { primaryWindow: window, allowed: false } } + : { plan_type: " pro ", rate_limit: { primary_window: window, allowed: false } }; + const quota = parseCodexUsageResponse(payload); + assert.equal(quota?.plan, "pro"); + assert.equal(quota?.limitReached, true); + assert.equal(quota?.windows?.weekly?.percentUsed, 0.94); + assert.equal(quota?.window7d?.percentUsed, 0.94); + assert.equal(quota?.window5h, undefined); + assert.equal(quota?.windows?.session, undefined); + }); +} + +test("extracted parser rejects malformed measurements and truthy non-boolean flags", () => { + assert.equal( + parseCodexUsageResponse({ rate_limit: { primary_window: { used_percent: "94junk" } } }), + null + ); + const quota = parseCodexUsageResponse({ + rate_limit: { + limit_reached: "false", + primary_window: { used_percent: 94, limit_window_seconds: 3600 }, + }, + }); + assert.equal(quota?.limitReached, false); + assert.equal(quota?.window5h, undefined); + assert.equal(quota?.window7d, undefined); + assert.equal(quota?.windows?.unknown_primary?.percentUsed, 0.94); +}); + +test("extracted parser selects Spark by duration without mixing normal model quota", () => { + const payload = { + plan_type: "plus", + rate_limit: { + primary_window: { used_percent: 20, limit_window_seconds: 18000 }, + }, + additional_rate_limits: [ + { + limit_id: "codex_bengalfox", + rate_limit: { + primary_window: { used_percent: 94, limit_window_seconds: 604800 }, + }, + }, + ], + }; + const spark = parseCodexUsageResponse(payload, "gpt-5.3-codex-spark"); + assert.equal(spark?.window7d?.percentUsed, 0.94); + assert.equal(spark?.window5h, undefined); + assert.equal(spark?.windows?.gpt_5_3_codex_spark_weekly?.percentUsed, 0.94); + assert.equal(spark?.windows?.session, undefined); + assert.equal(spark?.allWindows?.session?.percentUsed, 0.2); + const normal = parseCodexUsageResponse(payload, "gpt-5.5"); + assert.equal(normal?.window5h?.percentUsed, 0.2); + assert.equal(normal?.window7d, undefined); + assert.equal(normal?.windows?.gpt_5_3_codex_spark_weekly, undefined); +}); diff --git a/tests/unit/codex-quota-fetcher.test.ts b/tests/unit/codex-quota-fetcher.test.ts index 0b2a218670d7..08a7d06b9b20 100644 --- a/tests/unit/codex-quota-fetcher.test.ts +++ b/tests/unit/codex-quota-fetcher.test.ts @@ -33,7 +33,10 @@ test("fetchCodexQuota returns null when no registered credentials exist", async test("fetchCodexQuota exposes available paid credits without changing exhausted subscription windows", async () => { globalThis.fetch = async () => Response.json({ - rate_limit: { limit_reached: true, primary_window: { used_percent: 100 } }, + rate_limit: { + limit_reached: true, + primary_window: { limit_window_seconds: 18000, used_percent: 100 }, + }, credits: { has_credits: true, unlimited: false, overage_limit_reached: false, balance: null }, }); const connectionId = "codex-paid-credits-fetch"; @@ -61,10 +64,12 @@ test("fetchCodexQuota can read credentials directly from the provided connection JSON.stringify({ rate_limit: { primary_window: { + limit_window_seconds: 18000, used_percent: 70, reset_after_seconds: 45, }, secondary_window: { + limit_window_seconds: 604800, used_percent: 20, reset_after_seconds: 300, }, @@ -108,10 +113,12 @@ test("fetchCodexQuota parses dual-window usage, forwards workspace headers, and JSON.stringify({ rate_limit: { primary_window: { + limit_window_seconds: 18000, used_percent: 80, reset_after_seconds: 60, }, secondary_window: { + limit_window_seconds: 604800, used_percent: 40, reset_at: Math.floor((Date.now() + 3600_000) / 1000), }, @@ -152,8 +159,16 @@ test("fetchCodexQuota evaluates normal and Spark windows independently by reques return new Response( JSON.stringify({ rate_limit: { - primary_window: { used_percent: 20, reset_after_seconds: 60 }, - secondary_window: { used_percent: 30, reset_after_seconds: 120 }, + primary_window: { + limit_window_seconds: 18000, + used_percent: 20, + reset_after_seconds: 60, + }, + secondary_window: { + limit_window_seconds: 604800, + used_percent: 30, + reset_after_seconds: 120, + }, }, additional_rate_limits: [ { @@ -161,8 +176,16 @@ test("fetchCodexQuota evaluates normal and Spark windows independently by reques limit_name: "GPT-5.3-Codex-Spark", metered_feature: "gpt_5_3_codex_spark", rate_limit: { - primary_window: { used_percent: 100, reset_after_seconds: 300 }, - secondary_window: { used_percent: 40, reset_after_seconds: 600 }, + primary_window: { + limit_window_seconds: 18000, + used_percent: 100, + reset_after_seconds: 300, + }, + secondary_window: { + limit_window_seconds: 604800, + used_percent: 40, + reset_after_seconds: 600, + }, }, }, ], @@ -255,7 +278,11 @@ test("registerCodexQuotaFetcher exposes Codex quota to preflight and monitor flo new Response( JSON.stringify({ rate_limit: { - primary_window: { used_percent: 100, reset_after_seconds: 90 }, + primary_window: { + limit_window_seconds: 18000, + used_percent: 100, + reset_after_seconds: 90, + }, }, }), { diff --git a/tests/unit/codex-quota-signal-normalization.test.ts b/tests/unit/codex-quota-signal-normalization.test.ts new file mode 100644 index 000000000000..274716f310df --- /dev/null +++ b/tests/unit/codex-quota-signal-normalization.test.ts @@ -0,0 +1,335 @@ +import assert from "node:assert/strict"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; +import test from "node:test"; + +const directory = fs.mkdtempSync(path.join(os.tmpdir(), "codex-quota-signals-")); +process.env.DATA_DIR = directory; +process.env.DISABLE_SQLITE_AUTO_BACKUP = "true"; +process.env.OMNIROUTE_QUOTA_FETCH_MIN_INTERVAL_MS = "0"; +const { parseCodexQuotaHeaders, getCodexDualWindowCooldownMs } = + await import("../../open-sse/executors/codex/quota.ts"); +const fetcher = await import("../../open-sse/services/codexQuotaFetcher.ts"); +const { buildCodexUsageQuotas } = await import("../../open-sse/services/codexUsageQuotas.ts"); +const { getCodexUsage, setCodexUsageFetchForTests } = + await import("../../open-sse/services/usage/codex.ts"); +const { persistCodexChildQuotaResponse, persistCodexChildCooldown, getCodexChildCooldown } = + await import("../../open-sse/services/codexAccount/index.ts"); +const providers = await import("../../src/lib/db/providers.ts"); +const { resetDbInstance } = await import("../../src/lib/db/core.ts"); + +type JsonRecord = Record; +const originalFetch = globalThis.fetch; +const sparkModel = "gpt-5.3-codex-spark"; +const connections = new Set(); +const soon = Math.floor((Date.now() + 60_000) / 1000); +const later = Math.floor((Date.now() + 600_000) / 1000); +const iso = (seconds: number) => new Date(seconds * 1000).toISOString(); +const official = (primary = "99", secondary = "20", family = "codex") => ({ + [`x-${family}-primary-used-percent`]: primary, + [`x-${family}-primary-window-minutes`]: "300", + [`x-${family}-primary-reset-at`]: String(soon), + [`x-${family}-secondary-used-percent`]: secondary, + [`x-${family}-secondary-window-minutes`]: "10080", + [`x-${family}-secondary-reset-at`]: String(later), +}); + +async function fetchPayload(payload: unknown, requestedModel = "gpt-5.5") { + const id = `quota-signals-${connections.size}`; + connections.add(id); + globalThis.fetch = async () => Response.json(payload); + return fetcher.fetchCodexQuota(id, { + accessToken: "synthetic-quota-signal-token", + providerSpecificData: { workspaceId: "synthetic-workspace" }, + requestedModel, + }); +} + +test.afterEach(() => { + globalThis.fetch = originalFetch; + setCodexUsageFetchForTests(null); +}); +test.after(() => { + for (const id of connections) fetcher.unregisterCodexConnection(id); + resetDbInstance(); + fs.rmSync(directory, { recursive: true, force: true }); +}); + +test("official primary headers convert percentages once and use epoch seconds", () => { + const result = parseCodexQuotaHeaders(official()); + assert.ok(result); + assert.equal(result.usage5h, 99); + assert.equal(result.limit5h, 100); + assert.equal(result.resetAt5h, iso(soon)); + assert.equal(result.usage7d, 20); + assert.equal(result.limit7d, 100); + assert.equal(result.resetAt7d, iso(later)); + const cooldown = getCodexDualWindowCooldownMs(result); + assert.equal(cooldown.window, "5h"); + assert.ok(cooldown.cooldownMs > 0 && cooldown.cooldownMs <= 60_000); +}); + +test("official secondary exhaustion wins without confusing percent and token counts", () => { + const result = parseCodexQuotaHeaders(official("1", "100")); + assert.ok(result); + assert.equal(getCodexDualWindowCooldownMs(result).window, "7d"); +}); + +test("official fractional and explicit zero percentages are preserved", () => { + const result = parseCodexQuotaHeaders(official("23.75", "0")); + assert.ok(result); + assert.equal(result.usage5h / result.limit5h, 0.2375); + assert.equal(result.usage7d, 0); + assert.equal(result.limit7d, 100); +}); + +test("HTTP header names are case insensitive", () => { + const headers = Object.fromEntries( + Object.entries(official()).map(([k, v]) => [k.toUpperCase(), v]) + ); + assert.equal(parseCodexQuotaHeaders(headers)?.usage5h, 99); +}); + +test("actual duration descriptors, not primary position, select the fixed legacy slots", () => { + const headers = { + ...official("100", "10"), + "x-codex-primary-window-minutes": "10080", + "x-codex-secondary-window-minutes": "300", + }; + const result = parseCodexQuotaHeaders(headers); + assert.ok(result); + assert.equal(result.usage7d, 100); + assert.equal(result.resetAt7d, iso(soon)); + assert.equal(result.usage5h, 10); + assert.equal(result.resetAt5h, iso(later)); +}); + +test("a nonstandard duration is not falsely persisted as a 5h window", () => { + assert.equal( + parseCodexQuotaHeaders({ + "x-codex-primary-used-percent": "100", + "x-codex-primary-window-minutes": "60", + "x-codex-primary-reset-at": String(soon), + }), + null + ); +}); + +test("unknown meter families and credits-only headers are not normal subscription health", () => { + assert.equal(parseCodexQuotaHeaders(official("100", "100", "future-meter")), null); + assert.equal( + parseCodexQuotaHeaders({ + "x-codex-credits-unlimited": "true", + "x-codex-credits-has-credits": "true", + }), + null + ); +}); + +test("legacy token-count headers keep their units", () => { + const result = parseCodexQuotaHeaders({ + "x-codex-5h-usage": "150", + "x-codex-5h-limit": "200", + "x-codex-5h-reset-at": iso(soon), + }); + assert.ok(result); + assert.equal(result.usage5h, 150); + assert.equal(result.limit5h, 200); + assert.equal(result.resetAt5h, iso(soon)); +}); + +for (const usage of ["", "NaN", "98junk", "Infinity", "-1"]) { + test(`malformed legacy usage ${JSON.stringify(usage)} remains unknown`, () => { + assert.equal( + parseCodexQuotaHeaders({ "x-codex-5h-usage": usage, "x-codex-5h-limit": "100" }), + null + ); + }); +} + +test("a legacy reset with no usage/limit evidence is unknown, not zero used", () => { + assert.equal(parseCodexQuotaHeaders({ "x-codex-5h-reset-at": iso(soon) }), null); +}); + +test("impossible legacy reset timestamps cannot create an absurd cooldown", () => { + const result = parseCodexQuotaHeaders({ + "x-codex-5h-usage": "100", + "x-codex-5h-limit": "100", + "x-codex-5h-reset-at": "9999-01-01T00:00:00.000Z", + }); + assert.ok(result); + assert.equal(result.resetAt5h, null); + assert.equal(getCodexDualWindowCooldownMs(result).cooldownMs, 0); +}); + +for (const value of [undefined, null, "", "garbage", "12junk", true, -1, 101, [], {}]) { + test(`malformed usage percentage ${JSON.stringify(value)} is not healthy`, async () => { + const window = { limit_window_seconds: 18000, used_percent: value, reset_after_seconds: 60 }; + const payload = { rate_limit: { primary_window: window } }; + assert.equal(await fetchPayload(payload), null); + assert.deepEqual(buildCodexUsageQuotas(payload).quotas, {}); + }); +} + +test("one malformed window cannot fabricate health or hide a valid exhausted sibling", async () => { + const payload = { + rate_limit: { + primary_window: { limit_window_seconds: 18000, used_percent: "bad", reset_after_seconds: 60 }, + secondary_window: { + limit_window_seconds: 604800, + used_percent: 100, + reset_after_seconds: 600, + }, + }, + }; + const result = await fetchPayload(payload); + assert.ok(result); + assert.equal(result.percentUsed, 1); + assert.equal(result.windows?.session, undefined); + assert.equal(result.windows?.weekly.percentUsed, 1); + const display = buildCodexUsageQuotas(payload).quotas; + assert.equal(display.session, undefined); + assert.equal(display.weekly.used, 100); +}); + +test("explicit zero and camelCase compatibility are evidence, not missing data", async () => { + const result = await fetchPayload({ + rateLimit: { + primaryWindow: { limitWindowSeconds: 18000, usedPercent: "0", resetAfterSeconds: 60 }, + }, + }); + assert.ok(result); + assert.equal(result.percentUsed, 0); + assert.equal(result.windows?.session.percentUsed, 0); +}); + +test("usage allowed=false is an explicit hard stop, even below the percentage threshold", async () => { + const result = await fetchPayload({ + rate_limit: { + allowed: false, + primary_window: { limit_window_seconds: 18000, used_percent: 20 }, + }, + }); + assert.equal(result?.limitReached, true); +}); + +test("usage API projects explicit allowed=false as a hard stop", async () => { + setCodexUsageFetchForTests(async () => + Response.json({ + rate_limit: { + allowed: false, + primary_window: { limit_window_seconds: 18000, used_percent: 20 }, + }, + }) + ); + const result = await getCodexUsage("synthetic-usage-token"); + assert.equal("limitReached" in result && result.limitReached, true); +}); + +test("usage string false is not a boolean hard stop", async () => { + const result = await fetchPayload({ + rate_limit: { + limit_reached: "false", + primary_window: { limit_window_seconds: 18000, used_percent: 20 }, + }, + }); + assert.equal(result?.limitReached, false); +}); + +for (const reset of ["12junk", -1, 1e20, Date.now() + 60_000]) { + test(`usage reset_at is epoch seconds, not milliseconds or parseFloat ${reset}`, async () => { + const payload = { + rate_limit: { + primary_window: { limit_window_seconds: 18000, used_percent: 100, reset_at: reset }, + }, + }; + const result = await fetchPayload(payload); + assert.ok(result, "valid exhaustion evidence survives a malformed optional reset"); + assert.equal(result.percentUsed, 1); + assert.equal(result.resetAt, null); + assert.equal(buildCodexUsageQuotas(payload).quotas.session.resetAt, null); + }); +} + +test("paid/unlimited credits and banked reset credits do not erase subscription exhaustion", async () => { + const payload = { + rate_limit: { + primary_window: { limit_window_seconds: 18000, used_percent: 100, reset_after_seconds: 60 }, + }, + credits: { has_credits: true, unlimited: true, balance: "42" }, + rate_limit_reset_credits: { available_count: 3 }, + }; + const result = await fetchPayload(payload); + assert.equal(result?.percentUsed, 1); + assert.equal(result?.bankedResetCredits, 3); + assert.equal(buildCodexUsageQuotas(payload).quotas.session.unlimited, false); +}); + +test("normal and Spark usage stay independent when one scope is malformed", async () => { + const payload = { + rate_limit: { primary_window: { limit_window_seconds: 18000, used_percent: "bad" } }, + additional_rate_limits: [ + { + limit_id: "codex_bengalfox", + rate_limit: { + primary_window: { + limit_window_seconds: 18000, + used_percent: 100, + reset_after_seconds: 60, + }, + }, + }, + ], + }; + assert.equal(await fetchPayload(payload), null); + const spark = await fetchPayload(payload, sparkModel); + assert.equal(spark?.percentUsed, 1); + assert.equal(spark?.windows?.session, undefined); + assert.equal(spark?.windows?.gpt_5_3_codex_spark_session.percentUsed, 1); +}); + +test("official headers reach scoped persistence without clearing a sibling cooldown", async () => { + const connection = await providers.createProviderConnection({ + provider: "codex", + authType: "oauth", + name: "signal-consumer-fixture", + accessToken: "synthetic-consumer-token", + providerSpecificData: { unrelated: "keep" }, + }); + const id = String(connection.id); + const sparkUntil = iso(later); + await persistCodexChildCooldown({ + connectionId: id, + model: sparkModel, + rateLimitedUntil: sparkUntil, + }); + const result = await persistCodexChildQuotaResponse({ + connectionId: id, + model: "gpt-5.5", + status: 429, + headers: official(), + }); + assert.ok(result); + assert.equal(result.scope, "codex"); + const current = await providers.getProviderConnectionById(id); + assert.equal(getCodexChildCooldown(current as never, "gpt-5.5"), iso(soon)); + assert.equal(getCodexChildCooldown(current as never, sparkModel), sparkUntil); + assert.equal((current.providerSpecificData as JsonRecord).unrelated, "keep"); +}); + +test("normal HTTP telemetry is not written to the Spark child just because Spark was requested", async () => { + const connection = await providers.createProviderConnection({ + provider: "codex", + authType: "oauth", + name: "signal-spark-fixture", + accessToken: "synthetic-spark-token", + }); + const result = await persistCodexChildQuotaResponse({ + connectionId: String(connection.id), + model: sparkModel, + status: 429, + headers: official(), + }); + assert.equal(result, null); +}); diff --git a/tests/unit/codex-quota-window-identity-18330.test.ts b/tests/unit/codex-quota-window-identity-18330.test.ts new file mode 100644 index 000000000000..54febcd5cef5 --- /dev/null +++ b/tests/unit/codex-quota-window-identity-18330.test.ts @@ -0,0 +1,227 @@ +import assert from "node:assert/strict"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; +import test from "node:test"; + +const isolatedHome = fs.mkdtempSync(path.join(os.tmpdir(), "codex-weekly-18330-")); +process.env.HOME = isolatedHome; +process.env.DATA_DIR = path.join(isolatedHome, "data"); +process.env.DISABLE_SQLITE_AUTO_BACKUP = "true"; +process.env.OMNIROUTE_QUOTA_FETCH_MIN_INTERVAL_MS = "0"; + +const { fetchCodexQuota, unregisterCodexConnection } = + await import("../../open-sse/services/codexQuotaFetcher.ts"); +const { evaluateQuotaCutoff, preflightQuota, registerQuotaFetcher } = + await import("../../open-sse/services/quotaPreflight.ts"); +const { buildCodexUsageQuotas } = await import("../../open-sse/services/codexUsageQuotas.ts"); +const { parseCodexQuotaHeaders } = await import("../../open-sse/executors/codex/quota.ts"); +const { projectCodexAccountPool } = await import("../../open-sse/services/codexAccount/index.ts"); +const { markQuotaHealthy } = await import("../../src/domain/quotaCacheState.ts"); +const { resetDbInstance } = await import("../../src/lib/db/core.ts"); +const originalFetch = globalThis.fetch; +const ids = new Set(); +const thresholds = { + resolveMinRemainingPercent: (name: string | null) => (name === "weekly" ? 10 : 0), + isConfiguredCutoff: (name: string | null) => name === "weekly", +}; +const window = (used: number, seconds?: number) => ({ + used_percent: used, + ...(seconds ? { limit_window_seconds: seconds } : {}), + reset_after_seconds: 600, +}); +async function readQuota(rate_limit: unknown, requestedModel?: string) { + const id = `identity-${ids.size}`; + ids.add(id); + globalThis.fetch = async () => Response.json({ rate_limit }); + const connection = { accessToken: "offline-fixture", providerSpecificData: {}, requestedModel }; + const quota = await fetchCodexQuota(id, connection); + assert.ok(quota); + return { id, quota, connection }; +} +test.afterEach(() => { + globalThis.fetch = originalFetch; +}); +test.after(() => { + for (const id of ids) unregisterCodexConnection(id); + resetDbInstance(); + fs.rmSync(isolatedHome, { recursive: true, force: true }); +}); + +for (const used of [94, 90, 85]) { + test(`weekly-only primary ${used}% enforces weekly cutoff after recent success`, async () => { + const { id, quota } = await readQuota({ primary_window: window(used, 604800) }); + markQuotaHealthy(id); + assert.equal( + evaluateQuotaCutoff(quota, thresholds, { provider: "codex", connectionId: id }).proceed, + used < 90 + ); + assert.equal(quota.windows?.weekly?.percentUsed, used / 100); + assert.equal(quota.windows?.session, undefined); + assert.equal(quota.window5h, undefined); + assert.equal(quota.window7d?.percentUsed, used / 100); + }); +} +test("reversed windows preserve their duration identity", async () => { + const { quota } = await readQuota({ + primary_window: window(94, 604800), + secondary_window: window(20, 18000), + }); + assert.equal(quota.windows?.weekly?.percentUsed, 0.94); + assert.equal(quota.windows?.session?.percentUsed, 0.2); + assert.equal(quota.window5h?.percentUsed, 0.2); + assert.equal(quota.window7d?.percentUsed, 0.94); +}); +for (const duration of [undefined, 3600]) { + test(`unknown or unsupported duration ${duration} does not fabricate fixed windows`, async () => { + const { quota } = await readQuota({ primary_window: window(100, duration) }); + assert.equal(quota.window5h, undefined); + assert.equal(quota.window7d, undefined); + assert.equal(quota.windows?.session, undefined); + assert.equal(quota.windows?.weekly, undefined); + assert.equal(quota.percentUsed, 1, "unknown duration keeps valid exhaustion evidence"); + }); +} +test("weekly usage badge and account pool agree without a synthetic 5h row", () => { + const { quotas } = buildCodexUsageQuotas({ rate_limit: { primary_window: window(94, 604800) } }); + assert.equal(quotas.weekly?.used, 94); + assert.equal(quotas.session, undefined); + const pool = projectCodexAccountPool( + { id: "offline-pool", provider: "codex", providerSpecificData: {} }, + Date.now(), + { quotas, fetchedAt: new Date().toISOString() } + ); + const child = pool.children.find((child) => child.key.scope === "codex"); + assert.equal(child?.quota.windows["7d"]?.usedPercentage, 94); + assert.equal(child?.quota.windows["5h"], null); +}); +for (const enabled of [false, true]) { + test(`explicit weekly cutoff is evaluated when preflight runs with legacy flag ${enabled}`, async () => { + const { id, quota, connection } = await readQuota({ primary_window: window(94, 604800) }); + registerQuotaFetcher("codex", async () => quota); + const result = await preflightQuota( + "codex", + id, + { ...connection, providerSpecificData: { quotaPreflightEnabled: enabled } }, + thresholds + ); + assert.equal(result.proceed, false); + assert.equal(result.windowName, "weekly"); + }); +} + +for (const minutes of [undefined, "60", "bad"]) { + test(`official headers with duration ${minutes} do not fabricate 5h/7d`, () => { + const headers: Record = { "x-codex-primary-used-percent": "94" }; + if (minutes !== undefined) headers["x-codex-primary-window-minutes"] = minutes; + assert.equal(parseCodexQuotaHeaders(headers), null); + }); +} +test("alternate duration keys preserve weekly classification and Spark scope", async () => { + globalThis.fetch = async () => + Response.json({ + rateLimit: { primaryWindow: { usedPercent: 20, windowSeconds: 18000 } }, + additionalRateLimits: [ + { + limitId: "codex_bengalfox", + rateLimit: { + primaryWindow: { usedPercent: 94, windowSeconds: "604800", resetAfterSeconds: 600 }, + }, + }, + ], + }); + const id = "identity-spark-alias"; + ids.add(id); + const quota = await fetchCodexQuota(id, { + accessToken: "offline", + requestedModel: "gpt-5.3-codex-spark", + }); + assert.equal(quota?.windows?.gpt_5_3_codex_spark_weekly?.percentUsed, 0.94); + assert.equal(quota?.windows?.gpt_5_3_codex_spark_session, undefined); + assert.equal(quota?.windows?.session, undefined); + const display = buildCodexUsageQuotas({ + additionalRateLimits: [ + { + limitId: "codex_bengalfox", + rateLimit: { primaryWindow: { usedPercent: 94, windowSeconds: "604800" } }, + }, + ], + }).quotas; + assert.equal(display.gpt_5_3_codex_spark_weekly?.used, 94); + assert.equal(display.gpt_5_3_codex_spark_weekly?.displayName, "GPT-5.3-Codex-Spark Weekly"); +}); + +for (const key of [ + "limit_window_seconds", + "limitWindowSeconds", + "window_seconds", + "windowSeconds", +]) { + test(`parsed duration ${key} keeps quota keys and display labels aligned`, () => { + const quotas = buildCodexUsageQuotas({ + rateLimit: { primaryWindow: { usedPercent: 20, [key]: "2592000" } }, + additionalRateLimits: [ + { + limitId: "codex_bengalfox", + rateLimit: { primaryWindow: { usedPercent: 94, [key]: "604800" } }, + }, + { + limitName: "gpt-reserve", + rateLimit: { primaryWindow: { usedPercent: 10, [key]: "2592000" } }, + }, + ], + }).quotas; + assert.equal(quotas.unknown_primary?.displayName, "Monthly"); + assert.equal(quotas.gpt_5_3_codex_spark_weekly?.displayName, "GPT-5.3-Codex-Spark Weekly"); + assert.equal(quotas.reserve_session?.displayName, "Luna Reserve Monthly"); + }); +} + +for (const duration of [undefined, 3600]) { + test(`unknown usage duration ${duration} cannot hydrate the pool 5h or weekly slots`, () => { + const { quotas } = buildCodexUsageQuotas({ + rate_limit: { primary_window: window(94, duration) }, + }); + assert.equal(quotas.session, undefined); + assert.equal(quotas.weekly, undefined); + assert.equal(quotas.unknown_primary.used, 94); + const pool = projectCodexAccountPool( + { id: "offline-unknown", provider: "codex", providerSpecificData: {} }, + Date.now(), + { quotas, fetchedAt: new Date().toISOString() } + ); + for (const child of pool.children) { + assert.equal(child.quota.windows["5h"], null); + assert.equal(child.quota.windows["7d"], null); + } + }); +} +test("equal usage keeps the weekly dominant reset for reversed windows", async () => { + const resetWeekly = Math.floor(Date.now() / 1000) + 5000; + const { quota } = await readQuota({ + primary_window: { ...window(90, 604800), reset_at: resetWeekly }, + secondary_window: window(90, 18000), + }); + assert.equal(quota.resetAt, new Date(resetWeekly * 1000).toISOString()); +}); + +test("public cooldown and scoring consumers tolerate absent fixed windows", async () => { + const { getCodexQuotaCooldownMs } = await import("../../open-sse/services/codexQuotaFetcher.ts"); + const { resolveQuotaWindowByName, resolveResetAwareConfig } = + await import("../../open-sse/services/combo/quotaScoring.ts"); + const { scoreQuotaPace } = await import("../../open-sse/services/combo/quotaPaceScoring.ts"); + const { quota } = await readQuota({ primary_window: window(100) }); + assert.equal( + getCodexQuotaCooldownMs(quota), + 0, + "no fixed-window cooldown is invented for an unknown duration" + ); + assert.equal(resolveQuotaWindowByName(quota, "session"), null); + assert.equal(resolveQuotaWindowByName(quota, "weekly"), null); + assert.equal(scoreQuotaPace(quota, resolveResetAwareConfig({}), Date.now()).score, 0); + assert.equal( + evaluateQuotaCutoff(quota).proceed, + false, + "known exhaustion still reaches generic cutoff fallback" + ); +}); diff --git a/tests/unit/codex-quota-wire-normalizer.test.ts b/tests/unit/codex-quota-wire-normalizer.test.ts new file mode 100644 index 000000000000..6adbfd97077d --- /dev/null +++ b/tests/unit/codex-quota-wire-normalizer.test.ts @@ -0,0 +1,203 @@ +import assert from "node:assert/strict"; +import test from "node:test"; +import { + parseCodexOfficialQuotaHeaders, + parseCodexQuotaWindow, + parseCodexRateLimitEvent, + parseCodexUsageSignal, +} from "../../open-sse/services/codexQuotaSignals.ts"; + +const now = Date.parse("2026-10-28T00:00:00.000Z"); +const reset = now / 1000 + 120; +const sparkModel = "gpt-5.3-codex-spark"; +const window = { used_percent: 99, reset_at: reset, limit_window_seconds: 18000 }; +const eventWindow = { used_percent: 99, reset_at: reset, window_minutes: 300 }; + +// These are new pure-parser controls, in addition to the existing-entrypoint +// red/green regression. They do not claim live WS persistence/admission coverage. +test("usage, HTTP and WS normalize to the same fraction, duration and epoch reset", () => { + const usage = parseCodexUsageSignal({ rate_limit: { primary_window: window } }, null, now); + const headers = parseCodexOfficialQuotaHeaders( + { + "x-codex-primary-used-percent": "99", + "x-codex-primary-window-minutes": "300", + "x-codex-primary-reset-at": String(reset), + }, + null, + now + ); + const websocket = parseCodexRateLimitEvent( + { type: "codex.rate_limits", rate_limits: { primary: eventWindow } }, + now + ); + assert.ok(usage && headers && websocket); + assert.deepEqual(usage.windows, headers.windows); + assert.deepEqual(usage.windows, websocket.windows); + assert.equal(usage.windows[0].percentUsed, 0.99); + assert.equal(usage.windows[0].usedPercent, 99); + assert.equal(usage.windows[0].windowSeconds, 18000); + assert.equal(usage.windows[0].resetAt, new Date(reset * 1000).toISOString()); +}); + +test("unknown named HTTP and WS meters do not become normal Codex", () => { + assert.equal( + parseCodexOfficialQuotaHeaders({ "x-other-primary-used-percent": "100" }, null, now), + null + ); + assert.equal( + parseCodexRateLimitEvent( + { + type: "codex.rate_limits", + metered_limit_name: "other", + rate_limits: { primary: eventWindow }, + }, + now + ), + null + ); +}); + +test("Spark HTTP meter is selected only for the Spark request scope", () => { + const headers = { + "x-codex-primary-used-percent": "10", + "x-codex-bengalfox-primary-used-percent": "100", + "x-codex-bengalfox-primary-window-minutes": "300", + }; + assert.equal(parseCodexOfficialQuotaHeaders(headers, null, now)?.windows[0].percentUsed, 0.1); + const spark = parseCodexOfficialQuotaHeaders(headers, sparkModel, now); + assert.equal(spark?.scope, "spark"); + assert.equal(spark?.limitId, "codex_bengalfox"); + assert.equal(spark?.windows[0].percentUsed, 1); +}); + +test("named Spark WS events retain scope and real nonstandard window duration", () => { + const spark = parseCodexRateLimitEvent( + { + type: "codex.rate_limits", + metered_limit_name: "codex_bengalfox", + rate_limits: { secondary: { used_percent: 100, window_minutes: 60, reset_at: reset } }, + }, + now + ); + assert.equal(spark?.scope, "spark"); + assert.equal(spark?.windows[0].id, "secondary"); + assert.equal(spark?.windows[0].windowSeconds, 3600); +}); + +test("a bad WS primary does not discard a valid exhausted secondary", () => { + const result = parseCodexRateLimitEvent( + { + type: "codex.rate_limits", + rate_limits: { + primary: { used_percent: "NaN" }, + secondary: { ...eventWindow, used_percent: 100 }, + }, + }, + now + ); + assert.equal(result?.windows.length, 1); + assert.equal(result?.windows[0].id, "secondary"); + assert.equal(result?.windows[0].percentUsed, 1); +}); + +test("credits-only, coupon-only, empty and unrelated WS frames are not subscription health", () => { + for (const payload of [ + {}, + null, + { type: "rate_limits.updated", rate_limits: { primary: eventWindow } }, + { type: "codex.rate_limits", credits: { has_credits: true, unlimited: true, balance: "4" } }, + { type: "codex.rate_limits", rate_limit_reset_credits: { available_count: 9 } }, + ]) + assert.equal(parseCodexRateLimitEvent(payload, now), null); +}); + +test("unlimited paid credits do not mark exhausted subscription telemetry unlimited", () => { + const result = parseCodexRateLimitEvent( + { + type: "codex.rate_limits", + rate_limits: { primary: { used_percent: 100 } }, + credits: { has_credits: true, unlimited: true }, + }, + now + ); + assert.equal(result?.windows[0].percentUsed, 1); + assert.equal(Object.hasOwn(result, "unlimited"), false); +}); + +test("null additional meters do not invalidate normal usage", () => { + const result = parseCodexUsageSignal( + { rate_limit: { primary_window: window }, additional_rate_limits: null }, + null, + now + ); + assert.equal(result?.windows[0].percentUsed, 0.99); +}); + +test("Spark usage never falls back to a normal or unknown additional meter", () => { + assert.equal( + parseCodexUsageSignal( + { + rate_limit: { primary_window: window }, + additional_rate_limits: [{ limit_id: "other", rate_limit: { primary_window: window } }], + }, + sparkModel, + now + ), + null + ); +}); + +test("percentage strings have full decimal validation, not parseFloat prefixes", () => { + for (const used_percent of [null, "", "12oops", true, [], {}, -1, 101, NaN, Infinity]) { + assert.equal(parseCodexQuotaWindow({ used_percent }, "primary", now), null); + } + assert.equal(parseCodexQuotaWindow({ used_percent: "0.5" }, "primary", now)?.percentUsed, 0.005); + assert.equal(parseCodexQuotaWindow({ used_percent: 0 }, "primary", now)?.percentUsed, 0); +}); + +test("bad optional dates and durations keep exhaustion but cannot become enormous cooldowns", () => { + const result = parseCodexQuotaWindow( + { used_percent: 100, reset_at: 1e20, limit_window_seconds: 1e20 }, + "primary", + now + ); + assert.equal(result?.percentUsed, 1); + assert.equal(result?.resetAt, null); + assert.equal(result?.windowSeconds, null); + assert.equal(result?.durationProvided, true); +}); + +test("reset unit conversions are explicit and relative zero means now", () => { + assert.equal( + parseCodexQuotaWindow({ used_percent: 1, reset_after_seconds: 120 }, "primary", now)?.resetAt, + new Date(now + 120000).toISOString() + ); + assert.equal( + parseCodexQuotaWindow({ used_percent: 1, reset_after_seconds: 0 }, "primary", now)?.resetAt, + new Date(now).toISOString() + ); + assert.equal( + parseCodexQuotaWindow({ used_percent: 1, reset_at: now + 120000 }, "primary", now)?.resetAt, + null + ); +}); + +test("31-day proxy scheduling guard rejects absurd relative resets, not valid percentages", () => { + assert.equal( + parseCodexQuotaWindow({ used_percent: 100, reset_after_seconds: 32 * 86400 }, "primary", now) + ?.resetAt, + null + ); + assert.ok( + parseCodexQuotaWindow({ used_percent: 100, reset_after_seconds: 30 * 86400 }, "primary", now) + ?.resetAt + ); +}); + +test("explicit boolean allowance stays separate from unknown quota windows", () => { + const result = parseCodexUsageSignal({ rate_limit: { allowed: false } }, null, now); + assert.equal(result?.allowed, false); + assert.equal(result?.limitReached, true); + assert.deepEqual(result?.windows, []); + assert.equal(parseCodexUsageSignal({ rate_limit: { allowed: "false" } }, null, now), null); +}); diff --git a/tests/unit/codex-reset-credits.test.ts b/tests/unit/codex-reset-credits.test.ts index 79ea9e1dc0c4..bebd9d3fae3c 100644 --- a/tests/unit/codex-reset-credits.test.ts +++ b/tests/unit/codex-reset-credits.test.ts @@ -99,8 +99,8 @@ function mockResetThenUsage(opts: { opts.usageBody ?? { plan_type: "plus", rate_limit: { - primary_window: { used_percent: 0 }, - secondary_window: { used_percent: 0 }, + primary_window: { used_percent: 0, limit_window_seconds: 18000 }, + secondary_window: { used_percent: 0, limit_window_seconds: 604800 }, }, } ), @@ -152,8 +152,8 @@ test("consumeCodexResetCredit fetches a credit id, posts it, then refreshes usag JSON.stringify({ plan_type: "plus", rate_limit: { - primary_window: { used_percent: 0 }, - secondary_window: { used_percent: 40 }, + primary_window: { used_percent: 0, limit_window_seconds: 18000 }, + secondary_window: { used_percent: 40, limit_window_seconds: 604800 }, }, rate_limit_reset_credits: { available_count: 1 }, }), @@ -198,7 +198,7 @@ test("consumeCodexResetCredit accepts alreadyRedeemed as success", async () => { if (String(url).includes("/backend-api/wham/usage")) { return new Response( JSON.stringify({ - rate_limit: { primary_window: { used_percent: 5 } }, + rate_limit: { primary_window: { used_percent: 5, limit_window_seconds: 18000 } }, }), { status: 200, headers: { "content-type": "application/json" } } ); @@ -463,8 +463,8 @@ test("#12951 consumeCodexResetCredit alreadyRedeemed still needs healthy usage t usageBody: { plan_type: "plus", rate_limit: { - primary_window: { used_percent: 100 }, - secondary_window: { used_percent: 100 }, + primary_window: { used_percent: 100, limit_window_seconds: 18000 }, + secondary_window: { used_percent: 100, limit_window_seconds: 604800 }, }, }, }); @@ -560,8 +560,8 @@ test("#4305 consumeCodexResetCredit keeps model lockouts while refreshed usage i usageBody: { plan_type: "plus", rate_limit: { - primary_window: { used_percent: 100 }, - secondary_window: { used_percent: 100 }, + primary_window: { used_percent: 100, limit_window_seconds: 18000 }, + secondary_window: { used_percent: 100, limit_window_seconds: 604800 }, }, }, }); diff --git a/tests/unit/codex-usage-quotas-latent-window.test.ts b/tests/unit/codex-usage-quotas-latent-window.test.ts index 78a5c1b56f2f..0c6402d7bf4d 100644 --- a/tests/unit/codex-usage-quotas-latent-window.test.ts +++ b/tests/unit/codex-usage-quotas-latent-window.test.ts @@ -12,15 +12,14 @@ * Separately, the window display label must follow the real window duration * (`limit_window_seconds`) rather than assuming primary=session / secondary= * weekly by position — so a 7-day primary_window is labeled "Weekly", not - * "Session". The internal `session`/`weekly` keys (routing semantics) are - * unchanged; only the display label is corrected. + * "Session". Routing keys now follow the same duration identity (#18326). */ import test from "node:test"; import assert from "node:assert/strict"; import { buildCodexUsageQuotas } from "../../open-sse/services/codexUsageQuotas.ts"; import { - CODEX_SPARK_QUOTA_SESSION, + CODEX_SPARK_QUOTA_WEEKLY, CODEX_SPARK_DISPLAY_NAME, } from "../../open-sse/config/codexQuotaScopes.ts"; @@ -47,8 +46,8 @@ test("omits a latent, never-used spark window (used 0% + full-window reset)", () }, ], }); - assert.equal(quotas[CODEX_SPARK_QUOTA_SESSION], undefined, "latent spark window must be hidden"); - assert.ok(quotas.session, "the main session window is still present"); + assert.equal(quotas[CODEX_SPARK_QUOTA_WEEKLY], undefined, "latent spark window must be hidden"); + assert.ok(quotas.weekly, "the main weekly window is still present"); }); test("includes the spark window once it has actually been used", () => { @@ -70,8 +69,8 @@ test("includes the spark window once it has actually been used", () => { }, ], }); - assert.ok(quotas[CODEX_SPARK_QUOTA_SESSION], "used spark window must be shown"); - assert.equal(quotas[CODEX_SPARK_QUOTA_SESSION].used, 12); + assert.ok(quotas[CODEX_SPARK_QUOTA_WEEKLY], "used spark window must be shown"); + assert.equal(quotas[CODEX_SPARK_QUOTA_WEEKLY].used, 12); }); test("spark display name comes from the payload limit_name, falling back to the constant", () => { @@ -81,12 +80,20 @@ test("spark display name comes from the payload limit_name, falling back to the limit_name: "GPT-9.9-Codex-Nova", metered_feature: "codex_bengalfox", rate_limit: { - primary_window: { used_percent: 5, limit_window_seconds: WEEK, reset_after_seconds: 1000 }, + primary_window: { + used_percent: 5, + limit_window_seconds: WEEK, + reset_after_seconds: 1000, + }, }, }, ], - }).quotas[CODEX_SPARK_QUOTA_SESSION]; - assert.equal(withName?.displayName, "GPT-9.9-Codex-Nova", "uses payload limit_name"); + }).quotas[CODEX_SPARK_QUOTA_WEEKLY]; + assert.equal( + withName?.displayName, + "GPT-9.9-Codex-Nova Weekly", + "uses payload limit_name and duration" + ); const withoutName = buildCodexUsageQuotas({ additional_rate_limits: [ @@ -94,12 +101,20 @@ test("spark display name comes from the payload limit_name, falling back to the metered_feature: "codex_bengalfox", model: "codex-spark", rate_limit: { - primary_window: { used_percent: 5, limit_window_seconds: WEEK, reset_after_seconds: 1000 }, + primary_window: { + used_percent: 5, + limit_window_seconds: WEEK, + reset_after_seconds: 1000, + }, }, }, ], - }).quotas[CODEX_SPARK_QUOTA_SESSION]; - assert.equal(withoutName?.displayName, CODEX_SPARK_DISPLAY_NAME, "falls back to constant"); + }).quotas[CODEX_SPARK_QUOTA_WEEKLY]; + assert.equal( + withoutName?.displayName, + `${CODEX_SPARK_DISPLAY_NAME} Weekly`, + "falls back to constant" + ); }); test("labels a 7-day primary_window as Weekly, not Session", () => { @@ -109,8 +124,8 @@ test("labels a 7-day primary_window as Weekly, not Session", () => { secondary_window: null, }, }); - assert.ok(quotas.session, "primary window is keyed 'session' (routing semantics unchanged)"); - assert.equal(quotas.session.displayName, "Weekly", "but displayed as Weekly by real duration"); + assert.ok(quotas.weekly, "weekly primary carries the weekly routing key"); + assert.equal(quotas.session, undefined, "weekly primary never fabricates a session"); }); test("leaves a genuine 5h session window with the default label", () => { diff --git a/tests/unit/codex-usage-quotas-review-window.test.ts b/tests/unit/codex-usage-quotas-review-window.test.ts index 27d796a248a7..3bdc275d54fc 100644 --- a/tests/unit/codex-usage-quotas-review-window.test.ts +++ b/tests/unit/codex-usage-quotas-review-window.test.ts @@ -53,7 +53,7 @@ test("buildCodexUsageQuotas surfaces the secondary code_review window", () => { test("buildCodexUsageQuotas reads review windows from additional_rate_limits", () => { const { quotas } = buildCodexUsageQuotas({ rate_limit: { - primary_window: { used_percent: 10 }, + primary_window: { used_percent: 10, limit_window_seconds: 18000 }, }, additional_rate_limits: [ { @@ -76,8 +76,8 @@ test("buildCodexUsageQuotas reads review windows from additional_rate_limits", ( test("buildCodexUsageQuotas leaves review windows undefined when payload is silent", () => { const { quotas } = buildCodexUsageQuotas({ rate_limit: { - primary_window: { used_percent: 10 }, - secondary_window: { used_percent: 20 }, + primary_window: { used_percent: 10, limit_window_seconds: 18000 }, + secondary_window: { used_percent: 20, limit_window_seconds: 604800 }, }, }); diff --git a/tests/unit/codex-usage-windows.test.ts b/tests/unit/codex-usage-windows.test.ts index e45c51c56224..3106fdd2b9c2 100644 --- a/tests/unit/codex-usage-windows.test.ts +++ b/tests/unit/codex-usage-windows.test.ts @@ -47,6 +47,6 @@ describe("Codex usage windows", () => { }); assert.equal(quotas.session.windowSeconds, 18_000); - assert.equal(quotas.weekly.windowSeconds, null); + assert.equal(quotas.unknown_secondary.windowSeconds, null); }); }); diff --git a/tests/unit/codex-weekly-cutoff-18330.test.ts b/tests/unit/codex-weekly-cutoff-18330.test.ts new file mode 100644 index 000000000000..ebd792fc7f01 --- /dev/null +++ b/tests/unit/codex-weekly-cutoff-18330.test.ts @@ -0,0 +1,187 @@ +import assert from "node:assert/strict"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; +import test from "node:test"; + +// Refs #18330: 94% used, 6% remaining, saved 10% weekly cutoff, 5h/paid credits off. +const home = fs.mkdtempSync(path.join(os.tmpdir(), "codex-issue-18330-")); +process.env.HOME = home; +process.env.DATA_DIR = path.join(home, "data"); +process.env.DISABLE_SQLITE_AUTO_BACKUP = "true"; +process.env.OMNIROUTE_QUOTA_FETCH_MIN_INTERVAL_MS = "0"; + +const core = await import("../../src/lib/db/core.ts"); +const providers = await import("../../src/lib/db/providers.ts"); +const settings = await import("../../src/lib/db/settings.ts"); +const auth = await import("../../src/sse/services/auth.ts"); +const cache = await import("../../src/domain/quotaCache.ts"); +const { fetchCodexQuota, unregisterCodexConnection } = + await import("../../open-sse/services/codexQuotaFetcher.ts"); +const { registerQuotaFetcher } = await import("../../open-sse/services/quotaPreflight.ts"); +const connections: string[] = []; +const usage = new Map(); +const calls: string[] = []; + +async function seed( + weeklyUsed: number, + priority: number, + options: { + cachedUsed?: number; + cutoff?: number; + providerSpecificData?: Record; + } = {} +) { + const token = `synthetic-issue-18330-${connections.length}`; + const connection = await providers.createProviderConnection({ + provider: "codex", + authType: "oauth", + isActive: true, + testStatus: "active", + name: `issue-18330-${priority}`, + accessToken: token, + priority, + quotaWindowThresholds: { weekly: options.cutoff ?? 10 }, + providerSpecificData: { + codexLimitPolicy: { use5h: false, useWeekly: true }, + allowPaidCredits: false, + ...options.providerSpecificData, + }, + }); + connections.push(connection.id); + usage.set(token, weeklyUsed); + if (options.cachedUsed !== undefined) { + cache.setQuotaCache(connection.id, "codex", { + weekly: { + remainingPercentage: 100 - options.cachedUsed, + resetAt: new Date(Date.now() + 600_000).toISOString(), + }, + }); + } + cache.markQuotaHealthy(connection.id); + const saved = await providers.getProviderConnectionById(connection.id); + assert.deepEqual(saved?.quotaWindowThresholds, { weekly: options.cutoff ?? 10 }); + return connection; +} + +async function select() { + const selected = await auth.getProviderCredentialsWithQuotaPreflight( + "codex", + null, + null, + "gpt-5.5" + ); + selected?.releaseOAuthSession?.(); + return selected; +} + +test.beforeEach(async (t) => { + for (const id of connections.splice(0)) { + unregisterCodexConnection(id); + await providers.deleteProviderConnection(id); + } + usage.clear(); + calls.length = 0; + cache.__clearForTests(); + t.mock.method(globalThis, "fetch", async (input: string, init: RequestInit) => { + assert.equal(input, "https://chatgpt.com/backend-api/wham/usage"); + const token = new Headers(init.headers).get("authorization")?.replace(/^Bearer /, ""); + assert.ok(token && usage.has(token), "only a seeded offline account may be queried"); + return Response.json({ + plan_type: "pro", + rate_limit: { + primary_window: { + used_percent: usage.get(token), + limit_window_seconds: 604800, + reset_after_seconds: 600, + }, + }, + credits: { has_credits: true, unlimited: false, balance: "20" }, + }); + }); + registerQuotaFetcher("codex", async (id, connection) => { + calls.push(id); + return fetchCodexQuota(id, connection); + }); + await settings.updateSettings({ + fallbackStrategy: "priority", + resilienceSettings: { quotaPreflight: { enabled: false } }, + }); +}); + +test.after(() => { + for (const id of connections) unregisterCodexConnection(id); + core.resetDbInstance(); + fs.rmSync(home, { recursive: true, force: true }); +}); + +for (const enabled of [false, true]) { + for (const weeklyUsed of [94, 90, 85]) { + test(`#18330 live ${weeklyUsed}% used overrides old healthy evidence; global preflight ${enabled}`, async () => { + await settings.updateSettings({ + resilienceSettings: { quotaPreflight: { enabled } }, + }); + const account = await seed(weeklyUsed, 1, { cachedUsed: 50 }); + const fallback = weeklyUsed >= 90 ? await seed(20, 2) : null; + const selected = await select(); + assert.equal(selected?.connectionId, fallback?.id ?? account.id); + assert.deepEqual(calls, fallback ? [account.id, fallback.id] : [account.id]); + assert.deepEqual( + (await providers.getProviderConnectionById(account.id))?.quotaWindowThresholds, + { weekly: 10 }, + "quota selection must not rewrite the saved cutoff" + ); + }); + } + for (const strategy of ["priority", "round-robin", "p2c"]) { + test(`#18330 cached weekly policy filters before ${strategy} selection; global preflight ${enabled}`, async () => { + await settings.updateSettings({ + fallbackStrategy: strategy, + resilienceSettings: { quotaPreflight: { enabled } }, + }); + const providerSpecificData = { + limitPolicy: { enabled: true, thresholdPercent: 90, windows: ["weekly"] }, + }; + const low = await seed(94, 1, { cachedUsed: 94, providerSpecificData }); + const healthy = await seed(85, 2, { cachedUsed: 85, providerSpecificData }); + assert.equal(auth.evaluateQuotaLimitPolicy("codex", low).blocked, true); + assert.equal(auth.evaluateQuotaLimitPolicy("codex", healthy).blocked, false); + const selected = await select(); + assert.equal(selected?.connectionId, healthy.id); + assert.deepEqual(calls, [healthy.id], "filtered accounts must not be probed or selected"); + }); + } + test(`#18330 no eligible accounts returns a retry result, not the below-cutoff account; global ${enabled}`, async () => { + await settings.updateSettings({ + resilienceSettings: { quotaPreflight: { enabled } }, + }); + const low = await seed(94, 1); + const boundary = await seed(90, 2); + const selected = await select(); + assert.equal(selected?.allRateLimited, true); + assert.equal(selected?.connectionId, undefined); + assert.deepEqual(calls, [low.id, boundary.id]); + }); + test(`#18330 saving a stricter weekly cutoff affects the next selection; global ${enabled}`, async () => { + await settings.updateSettings({ + resilienceSettings: { quotaPreflight: { enabled } }, + }); + const account = await seed(94, 1, { cachedUsed: 50, cutoff: 5 }); + assert.equal((await select())?.connectionId, account.id, "6% remains above a 5% cutoff"); + await providers.updateProviderConnection(account.id, { quotaWindowThresholds: { weekly: 10 } }); + const fallback = await seed(85, 2); + assert.equal((await select())?.connectionId, fallback.id, "saved 10% cutoff takes effect"); + assert.deepEqual(calls, [account.id, account.id, fallback.id]); + }); +} + +for (const providerSpecificData of [ + { quotaPreflightEnabled: false, allowPaidCredits: false }, + { quotaPreflightEnabled: true, allowPaidCredits: true }, +]) { + test(`#18330 explicit ${providerSpecificData.allowPaidCredits ? "paid-credit consent" : "preflight opt-out"} remains intentional`, async () => { + const account = await seed(94, 1, { providerSpecificData }); + assert.equal((await select())?.connectionId, account.id); + assert.deepEqual(calls, providerSpecificData.allowPaidCredits ? [account.id] : []); + }); +} diff --git a/tests/unit/codex-weekly-routing-18330.test.ts b/tests/unit/codex-weekly-routing-18330.test.ts new file mode 100644 index 000000000000..a6e10e88f996 --- /dev/null +++ b/tests/unit/codex-weekly-routing-18330.test.ts @@ -0,0 +1,132 @@ +import assert from "node:assert/strict"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; +import test from "node:test"; + +const isolatedHome = fs.mkdtempSync(path.join(os.tmpdir(), "codex-weekly-18330-")); +process.env.HOME = isolatedHome; +process.env.DATA_DIR = path.join(isolatedHome, "data"); +process.env.DISABLE_SQLITE_AUTO_BACKUP = "true"; +process.env.OMNIROUTE_QUOTA_FETCH_MIN_INTERVAL_MS = "0"; + +const core = await import("../../src/lib/db/core.ts"); +const providers = await import("../../src/lib/db/providers.ts"); +const settings = await import("../../src/lib/db/settings.ts"); +const auth = await import("../../src/sse/services/auth.ts"); +const cache = await import("../../src/domain/quotaCache.ts"); +const { fetchCodexQuota, unregisterCodexConnection } = + await import("../../open-sse/services/codexQuotaFetcher.ts"); +const { registerQuotaFetcher } = await import("../../open-sse/services/quotaPreflight.ts"); +const originalFetch = globalThis.fetch; +const connections: string[] = []; +const usage = new Map(); +const calls: string[] = []; + +async function seed(used: number, priority: number, extra: Record = {}) { + const connection = await providers.createProviderConnection({ + provider: "codex", + authType: "oauth", + isActive: true, + testStatus: "active", + name: `weekly-${used}-${priority}`, + accessToken: `fixture-${used}-${priority}`, + priority, + quotaWindowThresholds: { weekly: 10 }, + providerSpecificData: { + codexLimitPolicy: { use5h: false, useWeekly: true }, + allowPaidCredits: false, + ...extra, + }, + }); + connections.push(connection.id); + usage.set(connection.id, used); + cache.markQuotaHealthy(connection.id); + return connection; +} +test.beforeEach(async () => { + for (const id of connections.splice(0)) { + unregisterCodexConnection(id); + await providers.deleteProviderConnection(id); + } + cache.__clearForTests(); + calls.length = 0; + registerQuotaFetcher("codex", async (id, connection) => { + calls.push(id); + globalThis.fetch = async () => + Response.json({ + rate_limit: { + primary_window: { + used_percent: usage.get(id), + limit_window_seconds: 604800, + reset_after_seconds: 600, + }, + }, + credits: { has_credits: true, unlimited: false, balance: "20" }, + }); + return fetchCodexQuota(id, connection); + }); +}); +test.after(() => { + globalThis.fetch = originalFetch; + for (const id of connections) unregisterCodexConnection(id); + core.resetDbInstance(); + fs.rmSync(isolatedHome, { recursive: true, force: true }); +}); + +for (const enabled of [false, true]) { + test(`routing honors per-account weekly cutoff with global preflight ${enabled}`, async () => { + await settings.updateSettings({ + fallbackStrategy: "priority", + resilienceSettings: { quotaPreflight: { enabled } }, + }); + const low = await seed(94, 1); + const healthy = await seed(85, 2); + const selected = await auth.getProviderCredentialsWithQuotaPreflight( + "codex", + null, + null, + "gpt-5.5" + ); + assert.equal(selected?.connectionId, healthy.id); + assert.deepEqual(calls, [low.id, healthy.id]); + selected?.releaseOAuthSession?.(); + }); +} +test("all accounts at or below the weekly minimum return no eligible account", async () => { + const low = await seed(94, 1); + const boundary = await seed(90, 2); + const selected = await auth.getProviderCredentialsWithQuotaPreflight( + "codex", + null, + null, + "gpt-5.5" + ); + assert.equal(selected?.allRateLimited, true); + assert.equal(selected?.connectionId, undefined); + assert.deepEqual(calls, [low.id, boundary.id]); +}); +test("explicit connection preflight opt-out keeps its documented bypass", async () => { + const low = await seed(94, 1, { quotaPreflightEnabled: false }); + const selected = await auth.getProviderCredentialsWithQuotaPreflight( + "codex", + null, + null, + "gpt-5.5" + ); + assert.equal(selected?.connectionId, low.id); + assert.deepEqual(calls, []); + selected?.releaseOAuthSession?.(); +}); +test("explicit paid-credit consent can use credits below subscription cutoff", async () => { + const paid = await seed(94, 1, { allowPaidCredits: true }); + const selected = await auth.getProviderCredentialsWithQuotaPreflight( + "codex", + null, + null, + "gpt-5.5" + ); + assert.equal(selected?.connectionId, paid.id); + assert.deepEqual(calls, [paid.id]); + selected?.releaseOAuthSession?.(); +}); diff --git a/tests/unit/combo-strategies.test.ts b/tests/unit/combo-strategies.test.ts index ef8e270f2c4c..26fe33ac08ec 100644 --- a/tests/unit/combo-strategies.test.ts +++ b/tests/unit/combo-strategies.test.ts @@ -117,11 +117,15 @@ function codexQuota({ return { rate_limit: { primary_window: { + limit_window_seconds: 18000, used_percent: used5h, + limit_window_seconds: 18000, reset_after_seconds: reset5hSeconds, }, secondary_window: { + limit_window_seconds: 604800, used_percent: used7d, + limit_window_seconds: 604800, reset_after_seconds: reset7dSeconds, }, }, diff --git a/tests/unit/combo/quota-weighted-pace-plan.test.ts b/tests/unit/combo/quota-weighted-pace-plan.test.ts index e5cc6504a842..ec576cce365f 100644 --- a/tests/unit/combo/quota-weighted-pace-plan.test.ts +++ b/tests/unit/combo/quota-weighted-pace-plan.test.ts @@ -260,8 +260,10 @@ test("live Codex plan: fetch preserves trimmed plan_type and planType, omits inv test("live Codex single-window Pro: real fetch and pace draw use 0.6/100 times ten", async () => { const pro = await liveQuota(usagePayload({ plan_type: "pro" })); - assert.deepEqual(pro.window5h, { percentUsed: 0.4, resetAt: resetAt(100) }); - assert.deepEqual(pro.window7d, { percentUsed: 0, resetAt: null }); + assert.equal(pro.window5h, undefined); + assert.equal(pro.window7d?.percentUsed, 0.4); + assert.equal(pro.window7d?.resetAt, resetAt(100)); + assert.deepEqual(Object.keys(pro.windows ?? {}), ["weekly"]); const targets = await seedTargets([quotaAt("plus"), pro]); // Plus owns 1/11, not 1/2; straddling that threshold proves the real projection's weight. assert.deepEqual(await draw(targets, 1 / 11 - 1e-8), [0, 1]); diff --git a/tests/unit/headroom-codex-quota-snapshot-6379.test.ts b/tests/unit/headroom-codex-quota-snapshot-6379.test.ts index 85ac9fa922e0..588b00ad0ce6 100644 --- a/tests/unit/headroom-codex-quota-snapshot-6379.test.ts +++ b/tests/unit/headroom-codex-quota-snapshot-6379.test.ts @@ -83,8 +83,8 @@ function target(connectionId: string) { function usageResponse(primaryUsedPercent: number, secondaryUsedPercent: number) { return { rate_limit: { - primary_window: { used_percent: primaryUsedPercent }, - secondary_window: { used_percent: secondaryUsedPercent }, + primary_window: { limit_window_seconds: 18000, used_percent: primaryUsedPercent }, + secondary_window: { limit_window_seconds: 604800, used_percent: secondaryUsedPercent }, }, }; } diff --git a/tests/unit/quota-availability-codex-cache.test.ts b/tests/unit/quota-availability-codex-cache.test.ts index bfff2d40045e..13fbc203a2d7 100644 --- a/tests/unit/quota-availability-codex-cache.test.ts +++ b/tests/unit/quota-availability-codex-cache.test.ts @@ -27,8 +27,16 @@ test("Codex upstream limit flag survives cache writes and server reconstruction" const wire = { rate_limit: { limit_reached: true, - primary_window: { used_percent: 20, reset_at: (now + 3600000) / 1000 }, - secondary_window: { used_percent: 20, reset_at: (now + 144 * 3600000) / 1000 }, + primary_window: { + used_percent: 20, + reset_at: (now + 3600000) / 1000, + limit_window_seconds: 18000, + }, + secondary_window: { + used_percent: 20, + reset_at: (now + 144 * 3600000) / 1000, + limit_window_seconds: 604800, + }, }, rate_limit_reached_type: "weekly", }; @@ -100,6 +108,54 @@ test("Codex upstream limit flag survives cache writes and server reconstruction" } }); +for (const measuredWeekly of [false, true]) { + test(`Codex neutral cached windows keep exhaustion evidence without invented slots, weekly ${measuredWeekly}`, () => { + const cached = { + quotas: { + unknown_primary: { + used: 100, + total: 100, + windowSeconds: null, + }, + ...(measuredWeekly + ? { weekly: { used: 20, total: 100, windowSeconds: 604800 } } + : { unknown_secondary: { used: 20, total: 100, windowSeconds: 3600 } }), + }, + }; + const quota = convertCachedUsageToQuotaInfo("codex", cached, "gpt-5.4"); + assert.ok(quota); + assert.equal(quota.percentUsed, 1); + assert.equal(quota.window5h, undefined); + assert.equal(quota.window7d?.percentUsed, measuredWeekly ? 0.2 : undefined); + assert.equal(Object.keys(quota.windows ?? {}).length, 2); + assert.equal(quota.windows?.unknown_primary.percentUsed, 1); + }); +} + +test("Codex neutral Spark cache evidence remains separate from the ordinary scope", () => { + const cached = { + quotas: { + session: { used: 20, total: 100 }, + gpt_5_3_codex_spark_unknown_secondary: { + used: 100, + total: 100, + windowSeconds: 3600, + }, + gpt_5_3_codex_spark_session: { used: 30, total: 100 }, + }, + }; + const ordinary = convertCachedUsageToQuotaInfo("codex", cached, "gpt-5.4"); + const spark = convertCachedUsageToQuotaInfo("codex", cached, "gpt-5.3-codex-spark"); + assert.ok(ordinary); + assert.ok(spark); + assert.equal(ordinary.percentUsed, 0.2); + assert.equal(spark.percentUsed, 1); + assert.equal(spark.window5h?.percentUsed, 0.3); + assert.equal(spark.window7d, undefined); + assert.equal(evaluateQuotaCutoff(ordinary, policy, "codex").proceed, true); + assert.equal(evaluateQuotaCutoff(spark, policy, "codex").proceed, false); +}); + test("Codex legacy cache entries without a limit flag retain their existing eligibility", () => { const now = Date.now(); const usage = toProviderLimitsCacheEntry( @@ -112,6 +168,8 @@ test("Codex legacy cache entries without a limit flag retain their existing elig ); const quota = convertCachedUsageToQuotaInfo("codex", usage, "gpt-5.4"); assert.equal(quota?.limitReached, false); + assert.equal(quota?.window5h?.percentUsed, 0.2); + assert.equal(quota?.window7d, undefined); assert.equal( buildConnectionQuotaAvailability( { id: "synthetic-codex-legacy", provider: "codex" }, diff --git a/tests/unit/quota-availability-spark-limit.test.ts b/tests/unit/quota-availability-spark-limit.test.ts index a9078d395e7f..fd12690fa49d 100644 --- a/tests/unit/quota-availability-spark-limit.test.ts +++ b/tests/unit/quota-availability-spark-limit.test.ts @@ -30,7 +30,11 @@ for (const flagName of ["limit_reached", "limitReached"]) { for (const reached of [true, false]) { test(`Spark ${flagName}=${reached} survives extraction, both cache writes and JSON reads`, async () => { const now = Date.now(); - const window = { used_percent: 20, reset_at: (now + 3600000) / 1000 }; + const window = { + used_percent: 20, + limit_window_seconds: 18000, + reset_at: (now + 3600000) / 1000, + }; const wire = { rate_limit: { limit_reached: false, primary_window: window }, additional_rate_limits: [ @@ -44,6 +48,8 @@ for (const flagName of ["limit_reached", "limitReached"]) { setCodexUsageFetchForTests(async () => Response.json(wire)); try { const usage = await getCodexUsage("synthetic-token"); + assert.ok("quotas" in usage); + assert.equal(usage.quotas.gpt_5_3_codex_spark_session?.windowSeconds, 18000); const entry = toProviderLimitsCacheEntry(usage, "manual", new Date(now).toISOString()); const verdict = buildConnectionQuotaAvailability(connection, entry, policy); const routing = evaluateQuotaCutoff( diff --git a/tests/unit/quota-window-label.test.ts b/tests/unit/quota-window-label.test.ts index 83b7b8e7405f..2d9e657946ab 100644 --- a/tests/unit/quota-window-label.test.ts +++ b/tests/unit/quota-window-label.test.ts @@ -47,9 +47,9 @@ test("buildCodexUsageQuotas labels a 30-day primary window Monthly, not Weekly", }, }, }); - assert.ok(quotas.session); - assert.equal(quotas.session.displayName, "Monthly"); - assert.equal(quotas.session.windowSeconds, 2_592_000); + assert.ok(quotas.unknown_primary); + assert.equal(quotas.unknown_primary.displayName, "Monthly"); + assert.equal(quotas.unknown_primary.windowSeconds, 2_592_000); }); test("AUTH reasons match usage duration labels for a 30-day Codex session window", () => { diff --git a/tests/unit/usage-service-hardening.test.ts b/tests/unit/usage-service-hardening.test.ts index 344fa30cbf1f..17d5d1fad49f 100644 --- a/tests/unit/usage-service-hardening.test.ts +++ b/tests/unit/usage-service-hardening.test.ts @@ -639,10 +639,12 @@ test("usage service covers Codex, Kiro and Kimi usage parsing and error branches limit_reached: false, primary_window: { used_percent: 25, + limit_window_seconds: 18000, reset_after_seconds: 30, }, secondary_window: { used_percent: 50, + limit_window_seconds: 604800, reset_at: Math.floor(Date.now() / 1000) + 120, }, }, @@ -661,10 +663,12 @@ test("usage service covers Codex, Kiro and Kimi usage parsing and error branches rate_limit: { primary_window: { used_percent: 90, + limit_window_seconds: 18000, reset_after_seconds: 60, }, secondary_window: { used_percent: 20, + limit_window_seconds: 604800, reset_after_seconds: 600, }, }, From d89e08facfece73d0c1e00aa302af8e3d6cc511d Mon Sep 17 00:00:00 2001 From: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> Date: Wed, 4 Nov 2026 13:44:12 -0300 Subject: [PATCH 13/20] fix(discovery): resolve Mistral context window, capabilities, and output limits (#18510) --- .../fixes/18510-mistral-discovery-metadata.md | 1 + config/quality/eslint-suppressions.json | 5 - open-sse/config/audioRegistry.ts | 16 ++ open-sse/config/ocrRegistry.ts | 9 +- .../providers/registry/mistral/index.ts | 68 ++++++- src/app/api/v1/ocr/route.ts | 6 +- src/lib/providerModels/modelDiscovery.ts | 66 ++++-- src/lib/providerModels/modelEndpointGuard.ts | 4 +- .../constants/modelSupportedEndpoints.ts | 6 +- .../combo-ref-vision-bridge-13927.test.ts | 4 +- .../visionBridge-combo-reroute.test.ts | 2 +- ...idge-mixed-combo-keeps-image-18008.test.ts | 4 +- .../mistral-discovery-capabilities.test.ts | 192 ++++++++++++++++++ .../ocr-route-endpoint-gate-18510.test.ts | 116 +++++++++++ .../unit/synced-row-models-body-18510.test.ts | 117 +++++++++++ 15 files changed, 582 insertions(+), 34 deletions(-) create mode 100644 changelog.d/fixes/18510-mistral-discovery-metadata.md create mode 100644 tests/unit/mistral-discovery-capabilities.test.ts create mode 100644 tests/unit/ocr-route-endpoint-gate-18510.test.ts create mode 100644 tests/unit/synced-row-models-body-18510.test.ts diff --git a/changelog.d/fixes/18510-mistral-discovery-metadata.md b/changelog.d/fixes/18510-mistral-discovery-metadata.md new file mode 100644 index 000000000000..c81c47142e9f --- /dev/null +++ b/changelog.d/fixes/18510-mistral-discovery-metadata.md @@ -0,0 +1 @@ +- **fix(discovery):** Mistral API models discovered via `/v1/models` now map `max_context_length` to the real 256K / 1M context windows instead of defaulting to 128K, parse boolean `capabilities` to stamp operation-specific endpoints (`audio-transcriptions`, `audio-speech`, `ocr`) so Voxtral and Mistral OCR models are not misclassified as chat models, and preserve uncapped output limits when upstream publishes no separate output limit. ([#18510](https://github.com/cheaper-inference/OmniRoute/pull/18510)) diff --git a/config/quality/eslint-suppressions.json b/config/quality/eslint-suppressions.json index 223a30fc0d30..5eba5b39b91f 100644 --- a/config/quality/eslint-suppressions.json +++ b/config/quality/eslint-suppressions.json @@ -1053,11 +1053,6 @@ "count": 1 } }, - "src/app/api/v1/ocr/route.ts": { - "@typescript-eslint/no-unused-vars": { - "count": 1 - } - }, "src/app/api/v1/providers/[provider]/embeddings/route.ts": { "@typescript-eslint/no-unused-vars": { "count": 2 diff --git a/open-sse/config/audioRegistry.ts b/open-sse/config/audioRegistry.ts index cdbce3392ff6..4fb033be2ad7 100644 --- a/open-sse/config/audioRegistry.ts +++ b/open-sse/config/audioRegistry.ts @@ -84,6 +84,18 @@ export const AUDIO_TRANSCRIPTION_PROVIDERS: Record = { ], }, + mistral: { + id: "mistral", + baseUrl: "https://api.mistral.ai/v1/audio/transcriptions", + authType: "apikey", + authHeader: "bearer", + models: [ + { id: "voxtral-mini-latest", name: "Voxtral Mini Transcribe 2" }, + { id: "voxtral-mini-2602", name: "Voxtral Mini Transcribe 2" }, + { id: "voxtral-mini-transcribe-3", name: "Voxtral Mini Transcribe 3" }, + ], + }, + openrouter: { id: "openrouter", baseUrl: "https://openrouter.ai/api/v1/audio/transcriptions", @@ -395,6 +407,10 @@ export const AUDIO_SPEECH_PROVIDERS: Record = { ], }, + // Mistral is deliberately absent. Its /v1/audio/speech takes `voice_id` and answers + // JSON `{ "audio_data": "" }`, while the default path below sends `voice` and + // streams the body back as audio. Listing it needs a dedicated format handler. + hyperbolic: { id: "hyperbolic", baseUrl: "https://api.hyperbolic.xyz/v1/audio/generation", diff --git a/open-sse/config/ocrRegistry.ts b/open-sse/config/ocrRegistry.ts index fdcbf13739e7..7c3e3f13a4b0 100644 --- a/open-sse/config/ocrRegistry.ts +++ b/open-sse/config/ocrRegistry.ts @@ -186,7 +186,10 @@ export const OCR_PROVIDERS: Record = { baseUrl: "https://api.mistral.ai/v1/ocr", authType: "apikey", authHeader: "bearer", - models: [{ id: "mistral-ocr-latest", name: "Mistral OCR" }], + models: [ + { id: "mistral-ocr-latest", name: "Mistral OCR" }, + { id: "mistral-ocr-2512", name: "Mistral OCR 3" }, + ], }, "azure-document-intelligence": { id: "azure-document-intelligence", @@ -234,6 +237,10 @@ export function parseOcrModel(modelStr: string | null | undefined): ParsedOcrMod } } + if (modelStr.startsWith("mistral-ocr-")) { + return { provider: "mistral", model: modelStr }; + } + return { provider: null, model: modelStr }; } diff --git a/open-sse/config/providers/registry/mistral/index.ts b/open-sse/config/providers/registry/mistral/index.ts index bc03d044bd17..c3c436889bd6 100644 --- a/open-sse/config/providers/registry/mistral/index.ts +++ b/open-sse/config/providers/registry/mistral/index.ts @@ -8,10 +8,70 @@ export const mistralProvider: RegistryEntry = { baseUrl: "https://api.mistral.ai/v1/chat/completions", authType: "apikey", authHeader: "bearer", + defaultContextLength: 262144, models: [ - { id: "mistral-large-latest", name: "Mistral Large 3" }, - { id: "mistral-medium-3-5", name: "Mistral Medium 3.5" }, - { id: "mistral-small-latest", name: "Mistral Small 4" }, - { id: "codestral-latest", name: "Codestral" }, + { + id: "mistral-large-latest", + name: "Mistral Large 3", + contextLength: 262144, + supportsVision: true, + toolCalling: true, + }, + { + id: "mistral-large-4", + name: "Mistral Large 4", + contextLength: 1048576, + supportsVision: true, + toolCalling: true, + supportsReasoning: true, + }, + { + id: "mistral-medium-3-5", + name: "Mistral Medium 3.5", + contextLength: 262144, + supportsVision: true, + toolCalling: true, + supportsReasoning: true, + }, + { + id: "mistral-small-latest", + name: "Mistral Small 4", + contextLength: 262144, + supportsVision: true, + toolCalling: true, + supportsReasoning: true, + }, + { + id: "codestral-latest", + name: "Codestral", + contextLength: 131072, + toolCalling: true, + }, + // No static vision flag on these rows: it would beat a synced row's verdict for the id. + { + id: "ministral-14b-latest", + name: "Ministral 3 14B", + contextLength: 262144, + toolCalling: true, + }, + { + id: "ministral-8b-latest", + name: "Ministral 3 8B", + contextLength: 262144, + toolCalling: true, + }, + { + id: "ministral-3b-latest", + name: "Ministral 3 3B", + contextLength: 262144, + toolCalling: true, + }, + { + id: "voxtral-small-latest", + name: "Voxtral Small", + contextLength: 32768, + toolCalling: true, + supportsAudio: true, + }, ], }; diff --git a/src/app/api/v1/ocr/route.ts b/src/app/api/v1/ocr/route.ts index f9e83e6ead85..07b40c4e32c2 100644 --- a/src/app/api/v1/ocr/route.ts +++ b/src/app/api/v1/ocr/route.ts @@ -24,6 +24,7 @@ import { isAllRateLimitedCredentials, rateLimitedProviderResponse, } from "@/app/api/v1/_shared/rateLimit"; +import { getRequestedModelEndpointMismatch } from "@/lib/providerModels/modelEndpointGuard"; export { resolveVertexOcrAccessToken }; @@ -72,7 +73,7 @@ export async function OPTIONS() { * POST /v1/ocr — document OCR * Mistral OCR API compatible. */ -async function postHandler(request, context) { +async function postHandler(request, _context) { let rawBody; try { rawBody = await request.json(); @@ -88,6 +89,9 @@ async function postHandler(request, context) { const model = body.model || "mistral-ocr-latest"; + const endpointMismatch = await getRequestedModelEndpointMismatch(model, "ocr"); + if (endpointMismatch) return errorResponse(HTTP_STATUS.BAD_REQUEST, endpointMismatch); + // Enforce API key policies (model restrictions + budget limits) const policy = await enforceApiKeyPolicy(request, model); if (policy.rejection) return policy.rejection; diff --git a/src/lib/providerModels/modelDiscovery.ts b/src/lib/providerModels/modelDiscovery.ts index 0661ecaf93ac..a72383ebdb4d 100644 --- a/src/lib/providerModels/modelDiscovery.ts +++ b/src/lib/providerModels/modelDiscovery.ts @@ -67,6 +67,9 @@ function labelsIncludeVision(value: unknown): boolean { export function detectVisionInput(record: JsonRecord): boolean { if (record.supportsVision === true || record.vision === true) return true; + const caps = asRecord(record.capabilities); + if (caps.vision === true) return true; + const architecture = asRecord(record.architecture); if (modalitiesIncludeImage(architecture.input_modalities)) return true; if (modalitiesIncludeImage(record.input_modalities)) return true; @@ -467,10 +470,12 @@ export function detectModelModality( ); // An explicit chat endpoint is authoritative: a model that upstream says serves - // chat (e.g. `supportedEndpoints: ["chat", "embeddings"]`) must never be - // downgraded to embedding/rerank/image by the id/label heuristics below — + // chat (e.g. `supportedEndpoints: ["chat", "embeddings"]` or `capabilities.completion_chat`) + // must never be downgraded to embedding/rerank/image by the id/label heuristics below — // that would drop it from the chat catalog (#14159 re-land of #12630). - const hasChatEndpoint = rawEndpoints.some((endpoint) => CHAT_ENDPOINT_HINTS.has(endpoint)); + const hasChatEndpoint = + rawEndpoints.some((endpoint) => CHAT_ENDPOINT_HINTS.has(endpoint)) || + caps.completion_chat === true; const isRerank = !hasChatEndpoint && @@ -589,6 +594,15 @@ export function normalizeDiscoveredModels( : undefined; const explicitInputTypes = Array.isArray(record.supportedInputTypes); + const caps = asRecord(record.capabilities); + const declaredEndpointsFromCaps: string[] = []; + if (caps.audio_speech === true) declaredEndpointsFromCaps.push("audio-speech"); + if (caps.audio_transcription === true) declaredEndpointsFromCaps.push("audio-transcriptions"); + if (caps.ocr === true) declaredEndpointsFromCaps.push("ocr"); + if (caps.completion_chat === true) declaredEndpointsFromCaps.push("chat"); + + const hasNonChatCaps = declaredEndpointsFromCaps.some((e) => e !== "chat"); + const supportedEndpoints = Array.isArray(record.supportedEndpoints) ? Array.from( new Set( @@ -597,16 +611,18 @@ export function normalizeDiscoveredModels( .filter((endpoint): endpoint is string => Boolean(endpoint)) ) ).sort() - : modality.isEmbedding - ? ["embeddings"] - : modality.isRerank - ? ["rerank"] - : modality.isImage - ? ["images"] - : undefined; + : hasNonChatCaps + ? Array.from(new Set(declaredEndpointsFromCaps)).sort() + : modality.isEmbedding + ? ["embeddings"] + : modality.isRerank + ? ["rerank"] + : modality.isImage + ? ["images"] + : undefined; // #16929 provenance. Only a parser that genuinely reads the upstream's own - // capability declaration (e.g. Gemini supportedGenerationMethods) stamps + // capability declaration (e.g. Gemini supportedGenerationMethods or Mistral capabilities) stamps // "declared" explicitly on the record. A bare supportedEndpoints array is NOT // proof of declaration: OpenAI/OpenRouter/Ollama/codex/compatible parsers fill it // from heuristics or fixed defaults, so it stays unmarked (= defaulted, never @@ -617,10 +633,12 @@ export function normalizeDiscoveredModels( record.endpointCapabilitySource === "declared" || record.endpointCapabilitySource === "inferred" ? record.endpointCapabilitySource - : !Array.isArray(record.supportedEndpoints) && - (modality.isEmbedding || modality.isRerank || modality.isImage) - ? "inferred" - : undefined; + : !Array.isArray(record.supportedEndpoints) && hasNonChatCaps + ? "declared" + : !Array.isArray(record.supportedEndpoints) && + (modality.isEmbedding || modality.isRerank || modality.isImage) + ? "inferred" + : undefined; const apiFormat = toNonEmptyString(record.apiFormat) || @@ -647,8 +665,14 @@ export function normalizeDiscoveredModels( // Anthropic Models API reports the window as `max_input_tokens` and the output // cap as `max_tokens`. #14159 briefly treated `max_tokens` as a window candidate; // that mapped Claude Opus 5 to 128K instead of 1M. Do not put `max_tokens` here. + // Mistral's /v1/models cards carry the total window as `max_context_length` + // (BaseModelCard — the same semantic as a plain `context_length`: the combined + // prompt+output budget). Without it, a synced Mistral model reached the catalog + // with no window at all and the read path handed out the 128K default, masking + // the real 256K/1M windows. const contextWindow = firstPositiveNumber( record.context_length, + record.max_context_length, record.contextLength, record.contextWindow, record.max_model_len, @@ -666,6 +690,12 @@ export function normalizeDiscoveredModels( record.inputTokenLimit, ...(isVertexProvider ? [] : [contextWindow]) ); + // Mistral publishes NO per-model output cap in its model cards — the context + // window is the combined prompt+output budget, so `max_context_length` above + // must never feed an output limit. Omitting `outputTokenLimit` (spread below + // stays closed) is the correct behavior: `capMaxOutputTokens` then leaves the + // requested value uncapped, and read paths that require a number fall back to + // their own defaults. Never fabricate one here. const outputTokenLimit = firstPositiveNumber( record.outputTokenLimit, record.max_output_tokens, @@ -726,7 +756,11 @@ export function normalizeDiscoveredModels( ? { supportsThinking: true } : {}), ...(record.alwaysThinking === true ? { alwaysThinking: true } : {}), - ...(typeof record.supportsTools === "boolean" ? { supportsTools: record.supportsTools } : {}), + ...(typeof record.supportsTools === "boolean" + ? { supportsTools: record.supportsTools } + : providerId === "mistral" && caps.function_calling === true + ? { supportsTools: true } + : {}), ...(typeof record.supportsVideo === "boolean" ? { supportsVideo: record.supportsVideo } : {}), // Keep a paid verdict through the cache round trip: dropping false would // let a :free suffix or a prior optimistic flag grant access again. diff --git a/src/lib/providerModels/modelEndpointGuard.ts b/src/lib/providerModels/modelEndpointGuard.ts index 6fea4a406dd9..97ddb64cb5ec 100644 --- a/src/lib/providerModels/modelEndpointGuard.ts +++ b/src/lib/providerModels/modelEndpointGuard.ts @@ -26,7 +26,8 @@ export type RequestedEndpoint = | "videos" | "music" | "audio-speech" - | "audio-transcriptions"; + | "audio-transcriptions" + | "ocr"; type TaggedModel = { supportedEndpoints?: readonly string[]; @@ -43,6 +44,7 @@ const ENDPOINT_ROUTE: Record = { music: "/v1/music/generations", "audio-speech": "/v1/audio/speech", "audio-transcriptions": "/v1/audio/transcriptions", + ocr: "/v1/ocr", }; function supportsRequestedEndpoint(supported: string[], requested: RequestedEndpoint): boolean { diff --git a/src/shared/constants/modelSupportedEndpoints.ts b/src/shared/constants/modelSupportedEndpoints.ts index c325d82c8359..9ee6977ed968 100644 --- a/src/shared/constants/modelSupportedEndpoints.ts +++ b/src/shared/constants/modelSupportedEndpoints.ts @@ -9,6 +9,7 @@ export const MODEL_SUPPORTED_ENDPOINT_VALUES = [ "audio-transcriptions", "music", "images-generations", + "ocr", // System One typed-decision models (TypeSafe Jev, …) — POST /v1/systemone. // Not chat-capable; see classifyModelSupportedEndpoints. "decisions", @@ -39,7 +40,7 @@ export function normalizeModelSupportedEndpoints(endpoints: readonly string[]): } export function classifyModelSupportedEndpoints(endpoints: readonly string[]): { - type?: "embedding" | "rerank" | "image" | "video" | "audio" | "music" | "decisions"; + type?: "embedding" | "rerank" | "image" | "video" | "audio" | "music" | "ocr" | "decisions"; subtype?: "speech" | "transcription"; } { // "chat" and "responses" are text-conversation endpoints; they carry no @@ -58,6 +59,7 @@ export function classifyModelSupportedEndpoints(endpoints: readonly string[]): { ["videos", "video"], ["video", "video"], ["music", "music"], + ["ocr", "ocr"], ] as const ).find(([endpoint]) => endpoints.includes(endpoint)); if (primary) return { type: primary[1] }; @@ -97,6 +99,8 @@ export function defaultEndpointsForProviderNodeApiType( return ["audio-transcriptions"]; case "images-generations": return ["images"]; + case "ocr": + return ["ocr"]; case "decisions": return ["decisions"]; default: diff --git a/tests/unit/combo-ref-vision-bridge-13927.test.ts b/tests/unit/combo-ref-vision-bridge-13927.test.ts index 6bb4fed6827b..540d0901639b 100644 --- a/tests/unit/combo-ref-vision-bridge-13927.test.ts +++ b/tests/unit/combo-ref-vision-bridge-13927.test.ts @@ -63,7 +63,7 @@ test("#13927: combo-ref to a mixed-capability inner combo resolves to process", name: "inner-mixed-combo-13927", models: [ { providerId: "command-code", model: "gpt-5.5", weight: 1 }, - { providerId: "mistral", model: "mistral-large-latest", weight: 1 }, + { providerId: "mistral", model: "codestral-latest", weight: 1 }, ], }); @@ -84,7 +84,7 @@ test("#13927: combo-ref to a mixed-capability inner combo resolves to process", test("#13927: combo-ref to a zero-vision inner combo resolves to no-vision", async () => { await combosDb.createCombo({ name: "inner-zero-vision-combo-13927", - models: [{ providerId: "mistral", model: "mistral-large-latest", weight: 1 }], + models: [{ providerId: "mistral", model: "codestral-latest", weight: 1 }], }); await combosDb.createCombo({ diff --git a/tests/unit/guardrails/visionBridge-combo-reroute.test.ts b/tests/unit/guardrails/visionBridge-combo-reroute.test.ts index 065105201d31..32ed60ca487c 100644 --- a/tests/unit/guardrails/visionBridge-combo-reroute.test.ts +++ b/tests/unit/guardrails/visionBridge-combo-reroute.test.ts @@ -58,7 +58,7 @@ async function createCombo(name, models, overrides = {}) { const VISION_MODEL = "openai/gpt-4o"; const TEXT_MODEL_A = "google/gemma-2-27b"; -const TEXT_MODEL_B = "mistral/mistral-large-latest"; +const TEXT_MODEL_B = "mistral/codestral-latest"; // Fail loudly if the static vision heuristic drifts: these fixtures drive // every assertion in this file. diff --git a/tests/unit/guardrails/visionBridge-mixed-combo-keeps-image-18008.test.ts b/tests/unit/guardrails/visionBridge-mixed-combo-keeps-image-18008.test.ts index eca2c6d60a6b..ae98d7e1cb05 100644 --- a/tests/unit/guardrails/visionBridge-mixed-combo-keeps-image-18008.test.ts +++ b/tests/unit/guardrails/visionBridge-mixed-combo-keeps-image-18008.test.ts @@ -54,7 +54,7 @@ test("mixed combo (vision head + text-only tail), describe fails", async () => { strategy: "priority", models: [ { providerId: "command-code", model: "gpt-5.5", weight: 1 }, - { providerId: "mistral", model: "mistral-large-latest", weight: 1 }, + { providerId: "mistral", model: "codestral-latest", weight: 1 }, ], }); const decision = await getComboVisionBridgeDecision("mixed-18008"); @@ -94,7 +94,7 @@ test("combo dispatch already filters text-only targets for a raw image body (why combo: { name: "mixed-18008", strategy: "priority", - models: ["command-code/gpt-5.5", "mistral/mistral-large-latest"], + models: ["command-code/gpt-5.5", "mistral/codestral-latest"], config: { maxRetries: 0 }, }, handleSingleModel: async (_b: unknown, modelStr: string) => { diff --git a/tests/unit/mistral-discovery-capabilities.test.ts b/tests/unit/mistral-discovery-capabilities.test.ts new file mode 100644 index 000000000000..911083fa6c9e --- /dev/null +++ b/tests/unit/mistral-discovery-capabilities.test.ts @@ -0,0 +1,192 @@ +import { test } from "node:test"; +import assert from "node:assert/strict"; + +import { detectVisionInput, normalizeDiscoveredModels } from "@/lib/providerModels/modelDiscovery"; +import { + AUDIO_SPEECH_PROVIDERS, + parseTranscriptionModel, + parseSpeechModel, +} from "@omniroute/open-sse/config/audioRegistry.ts"; +import { parseOcrModel } from "@omniroute/open-sse/config/ocrRegistry.ts"; +import { mistralProvider } from "@omniroute/open-sse/config/providers/registry/mistral/index.ts"; + +// Mistral's /v1/models cards (BaseModelCard per the Mistral OpenAPI spec) carry +// `max_context_length` (total prompt+output window) and a `capabilities` object of +// booleans: completion_chat, vision, ocr, audio_transcription, audio_speech, ... +// There is NO per-model output cap and NO modalities array on the card. + +test("bug 1: max_context_length maps to the context window", () => { + const [small] = normalizeDiscoveredModels([ + { id: "mistral-small-latest", object: "model", max_context_length: 262144 }, + ]); + assert.equal(small.inputTokenLimit, 262144); + + const [glm] = normalizeDiscoveredModels([{ id: "glm-5-2", max_context_length: 1048576 }]); + assert.equal(glm.inputTokenLimit, 1048576); +}); + +test("bug 3: max_context_length is a window, never an output cap", () => { + const [row] = normalizeDiscoveredModels([ + { id: "mistral-medium-latest", object: "model", max_context_length: 262144 }, + ]); + assert.equal(row.outputTokenLimit, undefined); +}); + +test("bug 2: capabilities.vision marks a Mistral card as vision-capable", () => { + assert.equal(detectVisionInput({ id: "pixtral", capabilities: { vision: true } }), true); + assert.equal(detectVisionInput({ id: "text-only", capabilities: { vision: false } }), false); + + const [row] = normalizeDiscoveredModels([ + { + id: "pixtral-large-latest", + object: "model", + capabilities: { completion_chat: true, vision: true }, + max_context_length: 131072, + }, + ]); + assert.equal(row.supportsVision, true); +}); + +test("bug 2: audio_speech capability stamps the speech endpoint", () => { + const [row] = normalizeDiscoveredModels([ + { + id: "voxtral-mini-tts-latest", + object: "model", + capabilities: { completion_chat: false, audio_speech: true }, + }, + ]); + assert.deepEqual(row.supportedEndpoints, ["audio-speech"]); + assert.equal(row.endpointCapabilitySource, "declared"); +}); + +test("bug 2: audio_transcription capability stamps the transcriptions endpoint", () => { + const [row] = normalizeDiscoveredModels([ + { + id: "voxtral-mini-latest", + object: "model", + capabilities: { completion_chat: false, audio_transcription: true }, + }, + ]); + assert.deepEqual(row.supportedEndpoints, ["audio-transcriptions"]); + assert.equal(row.endpointCapabilitySource, "declared"); +}); + +test("bug 2: declared chat plus audio stamps both endpoints", () => { + const [row] = normalizeDiscoveredModels([ + { + id: "voxtral-small-latest", + object: "model", + capabilities: { completion_chat: true, audio_transcription: true }, + }, + ]); + assert.deepEqual(row.supportedEndpoints, ["audio-transcriptions", "chat"]); + assert.equal(row.endpointCapabilitySource, "declared"); +}); + +test("bug 2: declared chat protects a model from embedding id heuristics", () => { + // "text-embedding-*" matches the id heuristic; an upstream completion_chat:true + // declaration is authoritative and must win. + const [row] = normalizeDiscoveredModels([ + { id: "text-embedding-guard", capabilities: { completion_chat: true } }, + ]); + assert.equal(row.modelType, undefined); + assert.equal(row.supportedEndpoints, undefined); +}); + +test("bug 2: chat-only declarations keep the tip row shape", () => { + const [row] = normalizeDiscoveredModels([ + { + id: "mistral-small-latest", + object: "model", + capabilities: { completion_chat: true, function_calling: true }, + }, + ]); + assert.equal(row.supportedEndpoints, undefined); + assert.equal(row.endpointCapabilitySource, undefined); + assert.equal(row.modelType, undefined); +}); + +test("bug 2: cards without capabilities keep the tip row shape", () => { + const [row] = normalizeDiscoveredModels([{ id: "mistral-medium-latest", object: "model" }]); + assert.equal(row.supportedEndpoints, undefined); + assert.equal(row.endpointCapabilitySource, undefined); +}); + +test("bug 2: ocr capability stamps the ocr endpoint", () => { + const [row] = normalizeDiscoveredModels([ + { + id: "mistral-ocr-latest", + object: "model", + capabilities: { completion_chat: false, ocr: true }, + }, + ]); + assert.deepEqual(row.supportedEndpoints, ["ocr"]); + assert.equal(row.endpointCapabilitySource, "declared"); +}); + +test("bug 2: function_calling marks a Mistral card as supportsTools, and only a Mistral card", () => { + const card = { + id: "mistral-large-latest", + object: "model", + capabilities: { completion_chat: true, function_calling: true }, + }; + assert.equal(normalizeDiscoveredModels([card], "mistral")[0].supportsTools, true); + assert.equal(normalizeDiscoveredModels([card], "openrouter")[0].supportsTools, undefined); + assert.equal(normalizeDiscoveredModels([card])[0].supportsTools, undefined); +}); + +test("bug 1: mistral-large-4 maps to 1048576 (1M) context window", () => { + const [row] = normalizeDiscoveredModels([ + { id: "mistral-large-4", object: "model", max_context_length: 1048576 }, + ]); + assert.equal(row.inputTokenLimit, 1048576); +}); + +test("mistral audio and ocr registries correctly parse mistral models", () => { + const stt = parseTranscriptionModel("voxtral-mini-latest"); + assert.equal(stt.provider, "mistral"); + assert.equal(stt.model, "voxtral-mini-latest"); + + const ocr = parseOcrModel("mistral-ocr-latest"); + assert.equal(ocr.provider, "mistral"); + assert.equal(ocr.model, "mistral-ocr-latest"); + + const ocrVersioned = parseOcrModel("mistral-ocr-2512"); + assert.equal(ocrVersioned.provider, "mistral"); + assert.equal(ocrVersioned.model, "mistral-ocr-2512"); +}); + +test("mistral speech stays out of the generic path until it has a format handler", () => { + // Mistral takes `voice_id` and answers JSON base64 `audio_data`; the default speech + // path sends `voice` and streams the body back as audio. + assert.equal(AUDIO_SPEECH_PROVIDERS.mistral, undefined); + assert.equal(parseSpeechModel("mistral/voxtral-mini-tts-3-0").provider, null); + assert.equal(parseSpeechModel("voxtral-mini-tts-latest").provider, null); +}); + +test("static mistral rows carry the ids and windows of Mistral's model cards", () => { + const rows = new Map(mistralProvider.models.map((model) => [model.id, model])); + assert.deepEqual(Object.fromEntries([...rows].map(([id, model]) => [id, model.contextLength])), { + "mistral-large-latest": 262144, + "mistral-large-4": 1048576, + "mistral-medium-3-5": 262144, + "mistral-small-latest": 262144, + "codestral-latest": 131072, + "ministral-14b-latest": 262144, + "ministral-8b-latest": 262144, + "ministral-3b-latest": 262144, + "voxtral-small-latest": 32768, + }); + // Retired by Mistral: pixtral-large on 2026-05-31, devstral-latest on 2026-07-31 + // (#18755 / #18792); the ids would only answer an upstream error. + assert.equal(rows.has("pixtral-large-latest"), false); + assert.equal(rows.has("devstral-latest"), false); +}); + +test("a capability flag Mistral does not publish adds no tool support", () => { + // ModelCapabilities in Mistral's OpenAPI spec has `function_calling`, not `tools`. + const [row] = normalizeDiscoveredModels([ + { id: "some-model", object: "model", capabilities: { tools: true } }, + ]); + assert.equal(row.supportsTools, undefined); +}); diff --git a/tests/unit/ocr-route-endpoint-gate-18510.test.ts b/tests/unit/ocr-route-endpoint-gate-18510.test.ts new file mode 100644 index 000000000000..f39a22ee7bb2 --- /dev/null +++ b/tests/unit/ocr-route-endpoint-gate-18510.test.ts @@ -0,0 +1,116 @@ +/** + * #18510: /v1/ocr joins the endpoint capability gate (#16929). The route answers its + * local 400 only with MODEL_ENDPOINT_CAPABILITY_GATE_ENABLED on and only for a model + * whose trusted catalog row excludes OCR. Flag off must dispatch exactly as before. + */ +import test from "node:test"; +import assert from "node:assert/strict"; + +const FLAG = "MODEL_ENDPOINT_CAPABILITY_GATE_ENABLED"; +delete process.env[FLAG]; + +const { resetDbInstance } = await import("../../src/lib/db/core.ts"); +const { createProviderConnection } = await import("../../src/lib/db/providers.ts"); +const { replaceSyncedAvailableModelsForConnection } = await import("../../src/lib/db/models.ts"); +const { POST } = await import("../../src/app/api/v1/ocr/route.ts"); + +const CHAT_ONLY = "mistral-small-latest"; +const OCR_MODEL = "mistral-ocr-2512"; +const UNDECLARED = "mistral-medium-latest"; + +const originalFetch = globalThis.fetch; +let upstreamCalls = 0; + +function withFlag(value: "true" | undefined) { + if (value === undefined) delete process.env[FLAG]; + else process.env[FLAG] = value; +} + +function ocrRequest(model: string) { + return new Request("http://localhost/v1/ocr", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + model, + document: { type: "document_url", document_url: "https://example.com/a.pdf" }, + }), + }); +} + +test.before(async () => { + globalThis.fetch = async () => { + upstreamCalls++; + return new Response(JSON.stringify({ pages: [], model: "stub" }), { + status: 200, + headers: { "content-type": "application/json" }, + }); + }; + const connection = await createProviderConnection({ + provider: "mistral", + authType: "apikey", + name: "mistral-18510", + apiKey: "mistral-18510-key", + isActive: true, + }); + await replaceSyncedAvailableModelsForConnection("mistral", connection.id, [ + { + id: CHAT_ONLY, + name: CHAT_ONLY, + source: "imported", + supportedEndpoints: ["chat"], + endpointCapabilitySource: "declared", + }, + { + id: OCR_MODEL, + name: OCR_MODEL, + source: "imported", + supportedEndpoints: ["ocr"], + endpointCapabilitySource: "declared", + }, + { id: UNDECLARED, name: UNDECLARED, source: "imported" }, + ]); +}); + +test.beforeEach(() => { + upstreamCalls = 0; + withFlag(undefined); +}); + +test.after(() => { + withFlag(undefined); + globalThis.fetch = originalFetch; + resetDbInstance(); +}); + +test("flag off: a chat-only model still dispatches, no local 400", async () => { + const response = await POST(ocrRequest(`mistral/${CHAT_ONLY}`)); + await response.body?.cancel(); + assert.notEqual(response.status, 400); + assert.equal(upstreamCalls, 1); +}); + +test("flag on: a model declared chat-only answers 400 before any upstream call", async () => { + withFlag("true"); + const response = await POST(ocrRequest(`mistral/${CHAT_ONLY}`)); + const body = await response.json(); + assert.equal(response.status, 400); + assert.match(body.error.message, /does not serve POST \/v1\/ocr/); + assert.match(body.error.message, /Use POST \/v1\/chat\/completions instead/); + assert.equal(upstreamCalls, 0); +}); + +test("flag on: a model declared for OCR dispatches", async () => { + withFlag("true"); + const response = await POST(ocrRequest(`mistral/${OCR_MODEL}`)); + await response.body?.cancel(); + assert.equal(response.status, 200); + assert.equal(upstreamCalls, 1); +}); + +test("flag on: a model with no declared endpoints passes the gate", async () => { + withFlag("true"); + const response = await POST(ocrRequest(`mistral/${UNDECLARED}`)); + await response.body?.cancel(); + assert.equal(response.status, 200); + assert.equal(upstreamCalls, 1); +}); diff --git a/tests/unit/synced-row-models-body-18510.test.ts b/tests/unit/synced-row-models-body-18510.test.ts new file mode 100644 index 000000000000..1530a3596f07 --- /dev/null +++ b/tests/unit/synced-row-models-body-18510.test.ts @@ -0,0 +1,117 @@ +/** + * #18510 must not change what /v1/models serves for a synced row, other than the Mistral + * context window it fixes. Each expected row is the full body the release tip serves, + * `created` aside, through the real discovery and catalog paths. Enrichment overwrites + * capabilities.tool_calling on these rows, so the helper tests pin the sync layer itself. + */ +import test from "node:test"; +import assert from "node:assert/strict"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; + +const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-18510-")); +process.env.DATA_DIR = TEST_DATA_DIR; + +const core = await import("../../src/lib/db/core.ts"); +const providersDb = await import("../../src/lib/db/providers.ts"); +const modelsDb = await import("../../src/lib/db/models.ts"); +const { normalizeDiscoveredModels } = + await import("../../src/lib/providerModels/modelDiscovery.ts"); +const v1ModelsCatalog = await import("../../src/app/api/v1/models/catalog.ts"); +const { buildSyncedCapabilities, mergeSyncedCapabilities } = + await import("../../src/app/api/v1/models/syncedCapabilities.ts"); +const { resolveModelDisplayName } = await import("../../src/shared/utils/modelDisplayName.ts"); + +const TOOL_CARD = { completion_chat: true, function_calling: true }; + +async function resetStorage() { + core.resetDbInstance(); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); + fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); + v1ModelsCatalog.__resetCatalogBuilderRunsForTest(); +} + +async function seedSynced(provider: string, rows: unknown[]) { + const connection = await providersDb.createProviderConnection({ + provider, + authType: "apikey", + name: `${provider}-body-conn`, + apiKey: "sk-test", + isActive: true, + testStatus: "active", + }); + await modelsDb.replaceSyncedAvailableModelsForConnection( + provider, + (connection as { id: string }).id, + normalizeDiscoveredModels(rows, provider) + ); +} + +async function catalogRow(id: string) { + const response = await v1ModelsCatalog.getUnifiedModelsResponse( + new Request("http://localhost/api/v1/models") + ); + assert.equal(response.status, 200); + const body = (await response.json()) as { data: Array> }; + const row = body.data.find((model) => model.id === id); + assert.ok(row, `expected ${id} in /v1/models`); + const { created: _created, ...rest } = row; + return rest; +} + +function syncedRowBody(provider: string, model: string, contextLength = 128000) { + return { + id: `${provider}/${model}`, + object: "model", + owned_by: provider, + permission: [], + root: model, + parent: null, + capabilities: { tool_calling: true, reasoning: true }, + // The tip publishes a readable name for a synced row that has none (#18354). + name: resolveModelDisplayName(model), + context_length: contextLength, + }; +} + +test.beforeEach(resetStorage); + +test.after(() => { + core.resetDbInstance(); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); +}); + +test("another provider's synced rows keep the tip's body, whatever they declare", async () => { + await seedSynced("openrouter", [ + { id: "probe/declared-18510", supportsTools: true }, + { id: "probe/card-18510", capabilities: TOOL_CARD }, + { id: "probe/refused-18510", supportsTools: false }, + ]); + for (const model of ["probe/declared-18510", "probe/card-18510", "probe/refused-18510"]) { + assert.deepEqual(await catalogRow(`openrouter/${model}`), syncedRowBody("openrouter", model)); + } +}); + +test("a synced row cannot turn on tools for a model the registry refuses them", async () => { + await seedSynced("adapta-web", [{ id: "adapta-one", name: "One", supportsTools: true }]); + const row = await catalogRow("adapta-web/adapta-one"); + assert.deepEqual(row.capabilities, { tool_calling: false, reasoning: true }); +}); + +test("a Mistral card differs from the tip only by its context window", async () => { + await seedSynced("mistral", [{ id: "mistral-synced-18510", capabilities: TOOL_CARD }]); + assert.deepEqual( + await catalogRow("mistral/mistral-synced-18510"), + syncedRowBody("mistral", "mistral-synced-18510", 262144) + ); +}); + +test("the sync layer does not publish tool_calling from supportsTools", () => { + const row = { id: "probe-18510", supportsTools: true }; + assert.equal(buildSyncedCapabilities(row, "openrouter"), undefined); + assert.equal(mergeSyncedCapabilities(undefined, row, "openrouter"), undefined); + assert.deepEqual(mergeSyncedCapabilities({ tool_calling: false }, row, "openrouter"), { + tool_calling: false, + }); +}); From 5e72d7bc506a8c0293e4c06c8b4766c10ae5e352 Mon Sep 17 00:00:00 2001 From: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> Date: Wed, 4 Nov 2026 13:44:13 -0300 Subject: [PATCH 14/20] fix(sse): scope request-rejected refusals to the model and never ban the connection (#18574) --- .../18574-request-rejected-streak-scope.md | 1 + docs/architecture/RESILIENCE_GUIDE.md | 2 +- open-sse/config/errorConfig.ts | 7 +- open-sse/handlers/chatCore.ts | 4 +- .../chatCore/requestRejectedFailure.ts | 76 +++++---- open-sse/services/requestRejectedStreak.ts | 119 +++++++++----- src/sse/handlers/chatHelpers.ts | 8 +- src/sse/services/auth.ts | 17 ++ stryker.conf.json | 1 + ...st-not-allowed-cooldown-escalation.test.ts | 117 ++++++++------ ...est-rejected-never-bans-connection.test.ts | 141 ++++++++++++++++ ...equest-rejected-streak-model-scope.test.ts | 151 ++++++++++++++++++ ...ejected-streak-success-clear-scope.test.ts | 44 +++++ tests/unit/request-rejected-streak.test.ts | 96 ++++++----- 14 files changed, 617 insertions(+), 167 deletions(-) create mode 100644 changelog.d/fixes/18574-request-rejected-streak-scope.md create mode 100644 tests/unit/request-rejected-never-bans-connection.test.ts create mode 100644 tests/unit/request-rejected-streak-model-scope.test.ts create mode 100644 tests/unit/request-rejected-streak-success-clear-scope.test.ts diff --git a/changelog.d/fixes/18574-request-rejected-streak-scope.md b/changelog.d/fixes/18574-request-rejected-streak-scope.md new file mode 100644 index 000000000000..058d1c3d7e58 --- /dev/null +++ b/changelog.d/fixes/18574-request-rejected-streak-scope.md @@ -0,0 +1 @@ +- **fix(sse):** an Anthropic OAuth `403 "Request not allowed"` (`REQUEST_REJECTED`) no longer deactivates the whole connection. The refusal was accumulating on a connection-wide streak, so a single upstream rejection disabled every account behind that provider; it is now scoped to the model that was refused, and the sibling models on the same connection stay routable. The streak still escalates to a longer cooldown for the refused model, and a success clears it. diff --git a/docs/architecture/RESILIENCE_GUIDE.md b/docs/architecture/RESILIENCE_GUIDE.md index 8d95b9651cce..343b8ce57583 100644 --- a/docs/architecture/RESILIENCE_GUIDE.md +++ b/docs/architecture/RESILIENCE_GUIDE.md @@ -168,7 +168,7 @@ apply (`open-sse/executors/kiro/reasoning.ts`). **Terminal states (NOT cooldowns):** -- `banned` — set by banned-keyword / account-ban detection (see [BAN_DETECTION](../security/BAN_DETECTION.md)), and by three consecutive upstream per-request refusals (`request_rejected`, e.g. Anthropic OAuth 403 "Request not allowed" — `open-sse/services/requestRejectedStreak.ts`); a single refusal only cools the connection down +- `banned` — set by banned-keyword / account-ban detection (see [BAN_DETECTION](../security/BAN_DETECTION.md)). Upstream per-request refusals (`request_rejected`, e.g. Anthropic OAuth 403 "Request not allowed") never set it: they are counted per connection and model and take that model out of rotation with a growing cooldown capped at `REQUEST_REJECTED_MAX_COOLDOWN_MS` (1 h), or cool the connection when no model is known — `open-sse/services/requestRejectedStreak.ts` - `expired` (transitions to terminal after bounded retries — `EXPIRED_RETRY_MAX = 3` with exponential backoff — so transient OAuth errors can self-heal before the account is permanently deactivated) - `credits_exhausted` diff --git a/open-sse/config/errorConfig.ts b/open-sse/config/errorConfig.ts index faed4e1fcd19..46ee8bbe6c32 100644 --- a/open-sse/config/errorConfig.ts +++ b/open-sse/config/errorConfig.ts @@ -75,9 +75,10 @@ export const COOLDOWN_MS = { transient: TRANSIENT_COOLDOWN_MS, requestNotAllowed: 5 * 1000, // Anthropic OAuth 403 "Request not allowed" (#12859): a per-request refusal - // on a healthy token. chatCore excludes the connection for requestRejected - // after the first refusal, requestRejectedRepeat after the second, and bans - // it on the third consecutive one (services/requestRejectedStreak.ts). + // on a healthy token. chatCore excludes the model for requestRejected after + // the first refusal, requestRejectedRepeat after the second, and a longer + // capped cooldown on the third consecutive one; it never bans the connection + // (services/requestRejectedStreak.ts). requestRejected: 5 * 60 * 1000, requestRejectedRepeat: 15 * 60 * 1000, rateLimit: 2 * 60 * 1000, diff --git a/open-sse/handlers/chatCore.ts b/open-sse/handlers/chatCore.ts index acbb39f937be..a852a3105e4f 100644 --- a/open-sse/handlers/chatCore.ts +++ b/open-sse/handlers/chatCore.ts @@ -3076,9 +3076,11 @@ async function handleChatCoreInner({ suffix: "trying other accounts", }); } else if (errorType === PROVIDER_ERROR_TYPES.REQUEST_REJECTED) { - // Per-request refusal (#12859): growing cooldown, streak → banned. + // Per-request refusal (#12859): growing MODEL-scoped cooldown. Never a ban. await handleRequestRejectedFailure({ connectionId: errorConnectionId, + provider, + targetModel, statusCode, message: persistentMessage, }); diff --git a/open-sse/handlers/chatCore/requestRejectedFailure.ts b/open-sse/handlers/chatCore/requestRejectedFailure.ts index 50988ba11f47..b0209dce37da 100644 --- a/open-sse/handlers/chatCore/requestRejectedFailure.ts +++ b/open-sse/handlers/chatCore/requestRejectedFailure.ts @@ -1,8 +1,8 @@ import { getProviderConnectionById, updateProviderConnection } from "@/lib/db/providers"; import { shouldIsolateProbeFailures } from "@/shared/utils/probeOrigin"; -import { writeTerminalStatus } from "@/shared/utils/terminalStatus"; import { PROVIDER_ERROR_TYPES } from "../../services/errorClassifier.ts"; +import { lockModel } from "../../services/accountFallback.ts"; import { sanitizeErrorMessage } from "../../utils/error.ts"; import { hasRequestRejectedStreak, @@ -15,26 +15,38 @@ import { * OAuth 403 "Request not allowed"). * * The upstream refused THIS request, not the credential: the same token serves - * the next request. One refusal must not ban the connection — but a run of - * them is enforcement, and re-sending every request into it would be wrong. - * So: exclude the connection for a short, growing cooldown and escalate to - * `banned` only for consecutive refusals (services/requestRejectedStreak). + * the next request. One refusal must not ban the connection — and a run of them + * must not either, because a refusal answers *what was asked*. So the streak is + * scoped to `connectionId|model` and its longest rung is a cooldown: the model + * leaves the rotation for a while and the connection, including its sibling + * models, stays active (services/requestRejectedStreak). + * + * With a resolved `targetModel` the cooldown is a model lockout, which is the + * mechanism that already means "this model on this connection is out of + * rotation" everywhere else in the routing path. Without one (a caller that + * cannot attribute the refusal) it falls back to the connection-level cooldown, + * which is strictly better than the old terminal write but still excludes the + * whole connection — so callers SHOULD pass the model. * * Probe-origin failures (dashboard test-all) are recorded on the connection - * but never touch the streak, cool it down or ban it (#9817). + * but never touch the streak, cool it down or lock a model (#9817). */ export async function handleRequestRejectedFailure(params: { connectionId: string; + provider: string; + targetModel?: string | null; statusCode: number; message: string; }): Promise { - const { connectionId, statusCode, message } = params; + const { connectionId, provider, statusCode, message } = params; + const targetModel = params.targetModel || null; const nowIso = new Date().toISOString(); // Sanitize at the write, not only at the caller: chatCore already hands in its // projected persistentMessage, but every `lastError` persistence branch must be // safe on its own (docs/security/ERROR_SANITIZATION.md) so a future caller that // forwards a raw upstream body cannot leak it into the stored connection state. const persistentMessage = sanitizeErrorMessage(message) || "Provider request failed"; + const modelLabel = targetModel ? ` model ${targetModel}` : ""; if (await shouldIsolateProbeFailures()) { await updateProviderConnection(connectionId, { @@ -44,48 +56,48 @@ export async function handleRequestRejectedFailure(params: { errorCode: statusCode, }); console.warn( - `[provider] Node ${connectionId} probe refused by upstream (${statusCode}) — connection stays active` + `[provider] Node ${connectionId}${modelLabel} probe refused by upstream (${statusCode}) — connection stays active` ); return; } // First refusal seen by this process: pick up one that happened before a // restart so a crash loop cannot reset the count on every boot. - if (!hasRequestRejectedStreak(connectionId)) { - try { - const row = await getProviderConnectionById(connectionId); - if (row?.lastErrorType === PROVIDER_ERROR_TYPES.REQUEST_REJECTED && row.lastErrorAt) { - seedRequestRejectedStreak(connectionId, Date.parse(String(row.lastErrorAt))); - } - } catch { - // best-effort — the in-memory streak still works without the seed + if (!hasRequestRejectedStreak(connectionId, targetModel)) { + const row = await getProviderConnectionById(connectionId).catch(() => null); + if ( + row?.lastErrorType === PROVIDER_ERROR_TYPES.REQUEST_REJECTED && + row.lastErrorAt && + // The persisted row is per connection, not per model: only seed when the + // connection row itself is the scope (no model to attribute it to). + !targetModel + ) { + seedRequestRejectedStreak(connectionId, null, Date.parse(String(row.lastErrorAt))); } } - const verdict = recordRequestRejected(connectionId); - const windowH = Math.round(verdict.windowMs / 3_600_000); + const verdict = recordRequestRejected(connectionId, targetModel); if (!verdict.counted) { console.warn( - `[provider] Node ${connectionId} request refused by upstream (${statusCode}) while already excluded — same episode, not counted (refusal ${verdict.streak}/${verdict.threshold})` + `[provider] Node ${connectionId}${modelLabel} request refused by upstream (${statusCode}) while already excluded — same episode, not counted (refusal ${verdict.streak}/${verdict.threshold})` ); return; } - if (verdict.escalate) { - await writeTerminalStatus( - connectionId, - { - testStatus: "banned", - isActive: false, - lastError: `${persistentMessage} (${verdict.streak} consecutive refusals within ${windowH}h — treated as upstream enforcement)`, - lastErrorType: PROVIDER_ERROR_TYPES.FORBIDDEN, - errorCode: String(statusCode), - }, - "production" - ); + if (targetModel) { + // Model-scoped exclusion: the rest of the connection keeps routing. The + // cooldown ladder caps at a long-but-temporary rung, never a terminal state. + lockModel(provider, connectionId, targetModel, "request_rejected", verdict.cooldownMs); + await updateProviderConnection(connectionId, { + lastErrorType: PROVIDER_ERROR_TYPES.REQUEST_REJECTED, + lastError: persistentMessage, + lastErrorAt: nowIso, + errorCode: statusCode, + }); + const seconds = Math.ceil(verdict.cooldownMs / 1000); console.warn( - `[provider] Node ${connectionId} refused ${verdict.streak}x in a row (${statusCode}) — disabling, reconnect required` + `[provider] Node ${connectionId} request refused by upstream (${statusCode}) for model ${targetModel} — model out of rotation for ${seconds}s (refusal ${verdict.streak}/${verdict.threshold}), connection stays active` ); return; } diff --git a/open-sse/services/requestRejectedStreak.ts b/open-sse/services/requestRejectedStreak.ts index ee40edab968c..7ae4da3444ec 100644 --- a/open-sse/services/requestRejectedStreak.ts +++ b/open-sse/services/requestRejectedStreak.ts @@ -1,49 +1,68 @@ import { COOLDOWN_MS } from "../config/errorConfig.ts"; /** - * Per-connection streak of upstream per-request refusals (REQUEST_REJECTED — - * today Anthropic's OAuth `403 "Request not allowed"`, #12859). + * Per-connection-and-model streak of upstream per-request refusals + * (REQUEST_REJECTED — today Anthropic's OAuth `403 "Request not allowed"`, + * #12859). * * A single refusal is a hiccup: the same token serves the next request. A run * of them is enforcement, and re-sending every request into it is the wrong - * thing to do to an OAuth account. So the connection is excluded for a short, - * growing cooldown after each refusal, and only a streak of *consecutive* - * refusals escalates to the terminal `banned` state that used to fire on the - * first one. + * thing to do to an OAuth account. So the refusals are excluded for a short, + * growing cooldown. + * + * Scope is `connectionId|model`. A refusal answers *what was asked*, not *who + * asked*: a model the plan does not include is refused on every request while + * a sibling model on the same token keeps working. Keying only by connection + * let three refusals on one unavailable model reach the threshold and write the + * whole account as `banned` / `isActive: false`, which nothing in the routing + * path undoes — the operator had to reconnect by hand. A refusal on one model + * now excludes only that model. + * + * The ladder never escalates to a terminal state. The threshold rung is a long + * cooldown (`REQUEST_REJECTED_MAX_COOLDOWN_MS`), which stops the hammering just + * as effectively and heals without an operator. A `banned` account was also a + * false signal to the operator: the credential was never revoked. * * What counts as one refusal: one cooldown *episode*. Responses that land while - * the connection is already excluded were in flight before the cooldown was - * written (parallel sessions, fan-out) — they are the same event, not new - * evidence, and are ignored. A successful response on the connection clears - * the streak (chatHelpers onRequestSuccess); the window below is only a - * staleness bound so an ancient refusal can never be the first of a streak. + * the model is already excluded were in flight before the cooldown was written + * (parallel sessions, fan-out) — they are the same event, not new evidence, and + * are ignored. A successful response on the model clears the streak + * (chatHelpers onRequestSuccess); the window below is only a staleness bound so + * an ancient refusal can never be the first of a streak. * * State is in-memory on purpose. A restart forgets the streak, which errs on - * the side of a few more cooldowns before escalation — never on the side of a - * ban the operator has to undo by hand. The caller may re-seed one refusal + * the side of a few more cooldowns before the long rung — never on the side of + * state the operator has to undo by hand. The caller may re-seed one refusal * from the persisted row (`seedRequestRejectedStreak`) to bound a crash loop. */ /** A refusal older than this cannot start or continue a streak. */ export const REQUEST_REJECTED_WINDOW_MS = 24 * 60 * 60 * 1000; -/** Consecutive refusals at which the connection is written as `banned`. */ +/** Consecutive refusals at which the model earns the longest cooldown. */ export const REQUEST_REJECTED_ESCALATION_THRESHOLD = 3; -/** Cooldown after the 1st and 2nd refusal of a streak; the 3rd escalates. */ +/** Longest rung of the ladder: the threshold cooldown. Never a terminal write. */ +export const REQUEST_REJECTED_MAX_COOLDOWN_MS = COOLDOWN_MS.requestRejectedRepeat * 4; +/** Cooldowns after the 1st, 2nd and threshold refusal of a streak. */ export const REQUEST_REJECTED_COOLDOWNS_MS = [ COOLDOWN_MS.requestRejected, COOLDOWN_MS.requestRejectedRepeat, + REQUEST_REJECTED_MAX_COOLDOWN_MS, ]; export interface RequestRejectedVerdict { /** 1-based position of this refusal in the current streak. */ streak: number; - /** How long to exclude the connection from selection; 0 when escalating. */ + /** How long to exclude this model from selection. Always > 0. */ cooldownMs: number; - /** True when the streak reached the threshold — caller writes the terminal state. */ + /** + * Always false. Kept in the verdict so callers keep one code path; the + * threshold now produces `REQUEST_REJECTED_MAX_COOLDOWN_MS` instead of a + * terminal `banned` write. + */ escalate: boolean; /** - * False when the refusal landed while the connection was already in a - * cooldown written by an earlier refusal: same event, nothing to persist. + * False when the refusal landed while the model was already in a cooldown + * written by an earlier refusal: same event, nothing to persist. */ counted: boolean; windowMs: number; @@ -59,6 +78,14 @@ interface StreakState { const streaks = new Map(); let clock: () => number = () => Date.now(); +/** + * Scope key. `model` is optional so a caller that cannot resolve the target + * keeps the pre-existing connection-wide behaviour instead of throwing. + */ +function streakKey(connectionId: string, model?: string | null): string { + return model ? `${connectionId}|${model}` : connectionId; +} + function cooldownForStreak(streak: number): number { const step = Math.min(streak, REQUEST_REJECTED_COOLDOWNS_MS.length) - 1; return REQUEST_REJECTED_COOLDOWNS_MS[step]; @@ -66,9 +93,11 @@ function cooldownForStreak(streak: number): number { export function recordRequestRejected( connectionId: string, + model?: string | null, now: number = clock() ): RequestRejectedVerdict { - const existing = streaks.get(connectionId); + const key = streakKey(connectionId, model); + const existing = streaks.get(key); const base = { windowMs: REQUEST_REJECTED_WINDOW_MS, threshold: REQUEST_REJECTED_ESCALATION_THRESHOLD, @@ -78,7 +107,7 @@ export function recordRequestRejected( // In flight before the cooldown was written — same episode. return { streak: existing.count, - cooldownMs: Math.max(0, existing.cooldownUntil - now), + cooldownMs: Math.max(1, existing.cooldownUntil - now), escalate: false, counted: false, ...base, @@ -87,45 +116,59 @@ export function recordRequestRejected( const continues = !!existing && now - existing.lastRefusalAt < REQUEST_REJECTED_WINDOW_MS; const streak = continues ? existing.count + 1 : 1; - const escalate = streak >= REQUEST_REJECTED_ESCALATION_THRESHOLD; - - if (escalate) { - // The terminal state takes over; a reconnect starts from a clean slate. - streaks.delete(connectionId); - return { streak, cooldownMs: 0, escalate: true, counted: true, ...base }; - } - const cooldownMs = cooldownForStreak(streak); - streaks.set(connectionId, { count: streak, lastRefusalAt: now, cooldownUntil: now + cooldownMs }); + streaks.set(key, { count: streak, lastRefusalAt: now, cooldownUntil: now + cooldownMs }); return { streak, cooldownMs, escalate: false, counted: true, ...base }; } /** * Restore one refusal that happened before this process started (read from * the persisted connection row), so a crash loop cannot reset the count on - * every boot. No-op when the connection already has in-memory state or the - * refusal is outside the window. + * every boot. No-op when the model already has in-memory state or the refusal + * is outside the window. */ export function seedRequestRejectedStreak( connectionId: string, + model: string | null | undefined, lastRefusalAt: number, now: number = clock() ): boolean { - if (streaks.has(connectionId)) return false; + const key = streakKey(connectionId, model); + if (streaks.has(key)) return false; if (!Number.isFinite(lastRefusalAt) || now - lastRefusalAt >= REQUEST_REJECTED_WINDOW_MS) { return false; } - streaks.set(connectionId, { count: 1, lastRefusalAt, cooldownUntil: lastRefusalAt }); + streaks.set(key, { count: 1, lastRefusalAt, cooldownUntil: lastRefusalAt }); return true; } -export function hasRequestRejectedStreak(connectionId: string): boolean { - return streaks.has(connectionId); +/** + * Whether the connection has any tracked streak. Passing `model` narrows the + * question to that model only. + */ +export function hasRequestRejectedStreak(connectionId: string, model?: string | null): boolean { + if (model) return streaks.has(streakKey(connectionId, model)); + for (const key of streaks.keys()) { + if (key === connectionId || key.startsWith(`${connectionId}|`)) return true; + } + return false; } -/** Forget a connection's streak — on a successful response or an operator reset. */ -export function clearRequestRejectedStreak(connectionId: string): void { +/** + * Forget a streak — on a successful response or an operator reset. Passing + * `model` clears only that model; omitting it clears every model of the + * connection. + */ +export function clearRequestRejectedStreak(connectionId: string, model?: string | null): void { + if (model) { + streaks.delete(streakKey(connectionId, model)); + return; + } streaks.delete(connectionId); + const prefix = `${connectionId}|`; + for (const key of [...streaks.keys()]) { + if (key.startsWith(prefix)) streaks.delete(key); + } } /** Test-only: wipe all streaks and restore the real clock. */ diff --git a/src/sse/handlers/chatHelpers.ts b/src/sse/handlers/chatHelpers.ts index c6f83bf55f57..3a5ca9fb2a07 100644 --- a/src/sse/handlers/chatHelpers.ts +++ b/src/sse/handlers/chatHelpers.ts @@ -569,8 +569,12 @@ export async function executeChatWithBreaker({ }, onRequestSuccess: async () => { if (isShadowTraffic) return; - // Only a real success ends the per-request refusal streak (#12859). - if (credentials.connectionId) clearRequestRejectedStreak(credentials.connectionId); + // Only a real success ends the per-request refusal streak (#12859), and only for + // the model that succeeded: a sibling model's success must not reset another + // model's refusal count (the streak is keyed by connection + model). + if (credentials.connectionId) { + clearRequestRejectedStreak(credentials.connectionId, model); + } await clearAccountError(credentials.connectionId, credentials); await clearAntigravityVerificationUrl( provider, diff --git a/src/sse/services/auth.ts b/src/sse/services/auth.ts index 2d4fb21ea17a..f6f5017a3bda 100644 --- a/src/sse/services/auth.ts +++ b/src/sse/services/auth.ts @@ -2544,6 +2544,23 @@ export async function markAccountUnavailable( }; } + // Per-request refusal (#12859/#18574): chatCore already excluded this exact model with a + // `request_rejected` lockout and recorded the refusal on the connection. Now that the + // exclusion is model-scoped the connection carries no cooldown, so the guard above no + // longer catches the duplicate — without this one the generic path below would overwrite + // `lastErrorType` with `server_error` and stack a second 5s lockout on the same model. + if ( + provider && + model && + getModelLockoutInfo(provider, connectionId, model)?.reason === "request_rejected" + ) { + log.info( + "AUTH", + `${connectionId.slice(0, 8)} ${provider}:${model} already excluded by a request_rejected lockout, skipping duplicate mark` + ); + return { shouldFallback: true, cooldownMs: 0 }; + } + // T09: Codex scope-aware lockout guard (codex vs spark independent pools). if (provider === "codex" && typeof model === "string" && model.trim().length > 0) { const scopeRateLimitedUntil = conn ? getCodexChildCooldown(conn, model) : null; diff --git a/stryker.conf.json b/stryker.conf.json index eb1c17d5001b..f266f9aed186 100644 --- a/stryker.conf.json +++ b/stryker.conf.json @@ -76,6 +76,7 @@ "tests/unit/codex-reasoning-replay-retry.test.ts", "tests/unit/authz/local-only-bypass-spawn-denylist.test.ts", "tests/unit/request-logger-url-secret-masking.test.ts", + "tests/unit/request-rejected-never-bans-connection.test.ts", "tests/unit/claude-thinking-continuity.test.ts", "tests/unit/anthropic-thinking-modified-recovery.test.ts", "tests/unit/anthropic-thinking-modified-review-17344.test.ts", diff --git a/tests/unit/anthropic-request-not-allowed-cooldown-escalation.test.ts b/tests/unit/anthropic-request-not-allowed-cooldown-escalation.test.ts index 29d2da53ed22..9fbc739ccf10 100644 --- a/tests/unit/anthropic-request-not-allowed-cooldown-escalation.test.ts +++ b/tests/unit/anthropic-request-not-allowed-cooldown-escalation.test.ts @@ -1,11 +1,17 @@ /** * #12859 end-to-end through the real chat route: an Anthropic OAuth - * `403 "Request not allowed"` must not ban the `claude` connection on the - * first response — it excludes the connection for a short, growing cooldown - * and only three consecutive refusals (no success in between) escalate to - * `banned`. A burst of requests while the connection is already excluded is - * one episode, and a healthy response resets the streak. Any other claude 403 - * keeps the previous behaviour (banned on the first one). + * `403 "Request not allowed"` must not ban the `claude` connection. + * + * Contract after #18574 (owner decision, 2026-11-04 — supersedes the "banned on + * the 3rd consecutive refusal" rung of #12864): the refusal answers *what was + * asked*, so the streak is scoped to connection + model and the exclusion is a + * MODEL lockout (`lockModel`), never a connection-level state. The connection + * stays `active` (no `unavailable`, no `rateLimitedUntil`, never `banned`); the + * model leaves the rotation for 5 min, then 15 min, then the capped 1 h rung + * (`REQUEST_REJECTED_MAX_COOLDOWN_MS`) on the 3rd consecutive refusal. A burst of + * requests while the model is already excluded is one episode, and a healthy + * response on the model resets the streak. Any other claude 403 keeps the + * previous behaviour (banned on the first one). * * Pattern mirrors tests/unit/probe-gate-autodisable.test.ts: temp DATA_DIR, * real SQLite, `globalThis.fetch` mocked as the upstream. The model id must @@ -21,8 +27,7 @@ const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-rr-escala process.env.DATA_DIR = TEST_DATA_DIR; const core = await import("../../src/lib/db/core.ts"); -const { createProviderConnection, updateProviderConnection } = - await import("../../src/lib/db/providers.ts"); +const { createProviderConnection } = await import("../../src/lib/db/providers.ts"); const { buildInternalChatRequest } = await import("../../src/lib/api/modelTestRunner.ts"); const chatRouteModule = await import("../../src/app/api/v1/chat/completions/route.ts"); const postChatCompletion = chatRouteModule.POST; @@ -32,11 +37,17 @@ const { __resetRequestRejectedStreaksForTests, __setRequestRejectedClockForTests, hasRequestRejectedStreak, + REQUEST_REJECTED_MAX_COOLDOWN_MS, } = await import("../../open-sse/services/requestRejectedStreak.ts"); +const { isModelLocked, getModelLockoutInfo, clearModelLock, clearAllModelLockouts } = + await import("../../open-sse/services/accountFallback.ts"); const { COOLDOWN_MS } = await import("../../open-sse/config/errorConfig.ts"); const originalFetch = globalThis.fetch; const MODEL = "claude/claude-sonnet-5"; +// What chatCore hands to lockModel / the streak: provider id + bare model id. +const LOCK_PROVIDER = "claude"; +const LOCK_MODEL = "claude-sonnet-5"; // The streak's own clock is advanced when a test "elapses" a cooldown, so the // in-memory episode boundary moves together with the DB row. @@ -45,6 +56,7 @@ let clockOffsetMs = 0; test.beforeEach(() => { resetAllCircuitBreakers(); __resetRequestRejectedStreaksForTests(); + clearAllModelLockouts(); clockOffsetMs = 0; __setRequestRejectedClockForTests(() => Date.now() + clockOffsetMs); invalidateDbCache("connections"); @@ -124,111 +136,122 @@ async function sendOnce(connId: string): Promise { ); } -function cooldownMsOf(row: Row): number { - const until = row?.rate_limited_until; - assert.equal(typeof until, "string", "a cooldown must be persisted"); - const untilMs = Date.parse(until as string); - assert.ok(Number.isFinite(untilMs), `rate_limited_until must be ISO, got ${String(until)}`); - return untilMs - Date.now(); -} - -function assertCoolingDown(row: Row, expectedMs: number, label: string) { +/** + * The refusal excluded the MODEL, not the connection: the row stays active with + * no cooldown fields, the refusal is only recorded for the dashboard, and the + * model lockout carries the expected remaining time. + */ +function assertModelCoolingDown(connId: string, expectedMs: number, label: string) { + const row = readRow(connId); assert.equal(row?.is_active, 1, `${label}: connection stays active`); - assert.equal(row?.test_status, "unavailable", `${label}: cooling down, not banned`); + assert.equal(row?.test_status, "active", `${label}: connection is not cooled down or banned`); + assert.equal(row?.rate_limited_until, null, `${label}: no connection-wide cooldown`); assert.equal(row?.last_error_type, "request_rejected", `${label}: error type`); assert.equal(Number(row?.error_code), 403, `${label}: error code`); assert.equal(typeof row?.last_error_at, "string", `${label}: last_error_at written`); - const ms = cooldownMsOf(row); + assert.equal(isModelLocked(LOCK_PROVIDER, connId, LOCK_MODEL), true, `${label}: model locked`); + const info = getModelLockoutInfo(LOCK_PROVIDER, connId, LOCK_MODEL); + assert.equal(info?.reason, "request_rejected", `${label}: lockout reason`); + const ms = info?.remainingMs ?? 0; assert.ok( Math.abs(ms - expectedMs) < 30_000, - `${label}: cooldown ≈ ${expectedMs / 60000}min expected, got ${Math.round(ms / 1000)}s` + `${label}: model cooldown ≈ ${expectedMs / 60000}min expected, got ${Math.round(ms / 1000)}s` ); } -/** What the recovery tick does once the window has elapsed — not a success. */ +/** What lockout expiry does once the window has elapsed — not a success. */ async function elapseCooldown(connId: string, cooldownMs: number) { clockOffsetMs += cooldownMs + 1000; - await updateProviderConnection(connId, { testStatus: "active", rateLimitedUntil: null }); + clearModelLock(LOCK_PROVIDER, connId, LOCK_MODEL); invalidateDbCache("connections"); } -test("claude 403 'Request not allowed': 5 min, then 15 min, banned on the 3rd consecutive refusal", async () => { +test("claude 403 'Request not allowed': model out 5 min, then 15 min, then 1 h — connection never banned (#18574)", async () => { const connId = await createClaudeConnection(); mockAnthropic403("Request not allowed"); const first = await sendOnce(connId); assert.notEqual(first.status, 200); - assertCoolingDown(readRow(connId), COOLDOWN_MS.requestRejected, "1st refusal"); + assertModelCoolingDown(connId, COOLDOWN_MS.requestRejected, "1st refusal"); await elapseCooldown(connId, COOLDOWN_MS.requestRejected); await sendOnce(connId); - assertCoolingDown(readRow(connId), COOLDOWN_MS.requestRejectedRepeat, "2nd refusal"); + assertModelCoolingDown(connId, COOLDOWN_MS.requestRejectedRepeat, "2nd refusal"); await elapseCooldown(connId, COOLDOWN_MS.requestRejectedRepeat); await sendOnce(connId); + // The 3rd consecutive refusal used to ban the connection (#12864). Now it is + // the longest rung of the ladder: a capped cooldown on the model, nothing terminal. + assertModelCoolingDown(connId, REQUEST_REJECTED_MAX_COOLDOWN_MS, "3rd refusal"); const row = readRow(connId); - assert.equal(row?.test_status, "banned", "3rd consecutive refusal: terminal"); - assert.equal(row?.is_active, 0); - assert.match(String(row?.last_error), /3 consecutive refusals/); - assert.equal(hasRequestRejectedStreak(connId), false, "escalation clears the streak"); + assert.notEqual(row?.test_status, "banned", "3rd consecutive refusal: never terminal"); + assert.equal(row?.is_active, 1); + assert.equal( + hasRequestRejectedStreak(connId, LOCK_MODEL), + true, + "the streak keeps counting; it is not cleared by a ban" + ); }); -test("requests arriving while the connection is already excluded are one episode, not new refusals", async () => { +test("requests arriving while the model is already excluded are one episode, not new refusals", async () => { const connId = await createClaudeConnection(); mockAnthropic403("Request not allowed"); upstreamCalls = 0; await sendOnce(connId); - const afterFirst = readRow(connId); - assertCoolingDown(afterFirst, COOLDOWN_MS.requestRejected, "1st refusal"); + assertModelCoolingDown(connId, COOLDOWN_MS.requestRejected, "1st refusal"); // The mocked fetch also serves auxiliary calls (identity bootstrap), so only // the delta matters below. const callsAfterFirst = upstreamCalls; + const lockedUntilAfterFirst = + Date.now() + (getModelLockoutInfo(LOCK_PROVIDER, connId, LOCK_MODEL)?.remainingMs ?? 0); - // Follow-ups pinned to the same connection (the internal request carries the - // connection id, so selection does not filter it) all get the 403 while the - // cooldown is running — like parallel sessions that were in flight when the - // first 403 came back. They are one episode: nothing is re-persisted and, - // above all, three of them must not ban. + // Follow-ups pinned to the same connection all get the 403 (or are turned + // away by the model lockout) while the cooldown is running — like parallel + // sessions that were in flight when the first 403 came back. They are one + // episode: the lockout is not extended and, above all, three of them must + // not escalate the ladder. for (let i = 0; i < 3; i += 1) await sendOnce(connId); const afterBurst = readRow(connId); assert.ok( upstreamCalls > callsAfterFirst, "the burst did reach the upstream (pinned connection)" ); - assert.equal(afterBurst?.test_status, "unavailable", "still just cooling down, not banned"); + assert.equal(afterBurst?.test_status, "active", "connection untouched by the burst"); assert.equal(afterBurst?.is_active, 1); - assert.equal( - afterBurst?.rate_limited_until, - afterFirst?.rate_limited_until, - "cooldown unchanged by the burst" + assert.equal(afterBurst?.rate_limited_until, null); + const lockedUntilAfterBurst = + Date.now() + (getModelLockoutInfo(LOCK_PROVIDER, connId, LOCK_MODEL)?.remainingMs ?? 0); + assert.ok( + Math.abs(lockedUntilAfterBurst - lockedUntilAfterFirst) < 5_000, + "model lockout unchanged by the burst" ); - assert.equal(hasRequestRejectedStreak(connId), true, "streak still open at 1"); + assert.equal(hasRequestRejectedStreak(connId, LOCK_MODEL), true, "streak still open at 1"); // Once the cooldown has elapsed, the next refusal is the 2nd of the streak. await elapseCooldown(connId, COOLDOWN_MS.requestRejected); await sendOnce(connId); - assertCoolingDown(readRow(connId), COOLDOWN_MS.requestRejectedRepeat, "2nd episode"); + assertModelCoolingDown(connId, COOLDOWN_MS.requestRejectedRepeat, "2nd episode"); }); test("a successful response resets the streak — sporadic refusals never accumulate", async () => { const connId = await createClaudeConnection(); mockAnthropic403("Request not allowed"); await sendOnce(connId); - assertCoolingDown(readRow(connId), COOLDOWN_MS.requestRejected, "1st refusal"); + assertModelCoolingDown(connId, COOLDOWN_MS.requestRejected, "1st refusal"); await elapseCooldown(connId, COOLDOWN_MS.requestRejected); mockAnthropic200(); const ok = await sendOnce(connId); assert.equal(ok.status, 200, "healthy response goes through"); - assert.equal(hasRequestRejectedStreak(connId), false, "success clears the streak"); + assert.equal(hasRequestRejectedStreak(connId, LOCK_MODEL), false, "success clears the streak"); const clean = readRow(connId); assert.equal(clean?.test_status, "active"); assert.equal(clean?.rate_limited_until, null); mockAnthropic403("Request not allowed"); await sendOnce(connId); - assertCoolingDown(readRow(connId), COOLDOWN_MS.requestRejected, "refusal after success"); + assertModelCoolingDown(connId, COOLDOWN_MS.requestRejected, "refusal after success"); }); test("any other claude 403 still bans on the first response (regression guard)", async () => { diff --git a/tests/unit/request-rejected-never-bans-connection.test.ts b/tests/unit/request-rejected-never-bans-connection.test.ts new file mode 100644 index 000000000000..a5825ad8f0e1 --- /dev/null +++ b/tests/unit/request-rejected-never-bans-connection.test.ts @@ -0,0 +1,141 @@ +/** + * #12859 follow-up, handler level: called with a resolved targetModel, + * handleRequestRejectedFailure must exclude the MODEL and leave the connection + * active. The pre-fix handler called `writeTerminalStatus(..., isActive:false)` + * on the third consecutive refusal and rewrote `testStatus: "banned"`, which + * nothing in the routing path undoes. + * + * This suite stubs the DB module so it can observe the exact rows the handler + * would write, without a SQLite handle. + */ +import test from "node:test"; +import assert from "node:assert/strict"; +import { registerHooks } from "node:module"; + +const DB_STUB = "file:///__test__/db-providers-stub.mjs"; +const written = []; +globalThis.__OMNI_TEST_REJECTED_WRITES__ = written; + +registerHooks({ + resolve(specifier, context, nextResolve) { + if (specifier === "@/lib/db/providers") { + return { url: DB_STUB, shortCircuit: true }; + } + return nextResolve(specifier, context); + }, + load(url, context, nextLoad) { + if (url === DB_STUB) { + return { + format: "module", + shortCircuit: true, + source: ` + export async function updateProviderConnection(id, patch) { + globalThis.__OMNI_TEST_REJECTED_WRITES__.push({ id, patch }); + } + export async function getProviderConnectionById() { return null; } + export async function setConnectionRateLimitUntil() { return undefined; } + export async function markConnectionTestUnavailable() { return undefined; } + export async function getProviderConnections() { return []; } + export async function getProviderConnectionByIdSync() { return null; } + export async function createProviderConnection() { return null; } + export async function deleteProviderConnection() { return true; } + export async function markConnectionActive() { return undefined; } + export async function getProviderCredentials() { return null; } + export async function updateProviderConnectionCredentials() { return undefined; } + export async function getConnectionsByProvider() { return []; } + export async function resetProviderConnectionError() { return undefined; } + export async function touchConnectionLastUsed() { return undefined; } + export async function getConnectionById() { return null; } + export async function updateConnection() { return undefined; } + `, + }; + } + return nextLoad(url, context); + }, +}); + +const { handleRequestRejectedFailure } = + await import("../../open-sse/handlers/chatCore/requestRejectedFailure.ts"); +const { isModelLocked, clearAllModelLockouts } = + await import("../../open-sse/services/accountFallback.ts"); +const { __resetRequestRejectedStreaksForTests, __setRequestRejectedClockForTests } = + await import("../../open-sse/services/requestRejectedStreak.ts"); + +const PROVIDER = "anthropic"; +const CONN = "conn-claude"; +const REFUSED_MODEL = "claude-sonnet-4-6"; +const SIBLING_MODEL = "claude-opus-4-6"; + +test.beforeEach(() => { + globalThis.__OMNI_TEST_REJECTED_WRITES__ = written; + written.length = 0; + clearAllModelLockouts(); + __resetRequestRejectedStreaksForTests(); +}); + +test("#12859: repeated refusals on one model never disable the connection", async (t) => { + // Successive refusals land after the previous cooldown expires, otherwise they + // are the same in-flight episode and never reach the threshold at all. + t.mock.timers.enable({ apis: ["Date"], now: 1_000_000 }); + for (let i = 0; i < 4; i++) { + await handleRequestRejectedFailure({ + connectionId: CONN, + provider: PROVIDER, + targetModel: REFUSED_MODEL, + statusCode: 403, + message: "Request not allowed", + }); + t.mock.timers.tick(60 * 60 * 1000); + } + + for (const { patch } of written) { + assert.notEqual(patch.testStatus, "banned", "a per-request refusal is not a ban"); + assert.equal(patch.isActive, undefined, "the connection must never be deactivated"); + assert.equal( + patch.rateLimitedUntil, + undefined, + "exclusion is model-scoped, not connection-wide" + ); + } + + assert.equal(isModelLocked(PROVIDER, CONN, REFUSED_MODEL), true, "the refused model is out"); + assert.equal(isModelLocked(PROVIDER, CONN, SIBLING_MODEL), false, "the sibling keeps routing"); +}); + +test("#12859: the refusal is still recorded on the connection for the dashboard", async () => { + await handleRequestRejectedFailure({ + connectionId: CONN, + provider: PROVIDER, + targetModel: REFUSED_MODEL, + statusCode: 403, + message: "Request not allowed", + }); + + assert.ok(written.length >= 1); + const last = written[written.length - 1].patch; + assert.equal(last.lastErrorType, "request_rejected", "classifier type is preserved for the UI"); + assert.equal(last.errorCode, 403); + assert.match(String(last.lastError), /Request not allowed/); +}); + +test("#12859: without a target model the handler degrades to a cooldown, never a ban", async (t) => { + t.mock.timers.enable({ apis: ["Date"], now: 1_000_000 }); + for (let i = 0; i < 4; i++) { + await handleRequestRejectedFailure({ + connectionId: CONN, + provider: PROVIDER, + targetModel: null, + statusCode: 403, + message: "Request not allowed", + }); + t.mock.timers.tick(60 * 60 * 1000); + } + + for (const { patch } of written) { + assert.notEqual(patch.testStatus, "banned"); + assert.equal(patch.isActive, undefined); + } + const last = written[written.length - 1].patch; + assert.equal(last.testStatus, "unavailable", "a cooldown is the connection-level fallback"); + assert.ok(last.rateLimitedUntil, "with a recoverable expiry"); +}); diff --git a/tests/unit/request-rejected-streak-model-scope.test.ts b/tests/unit/request-rejected-streak-model-scope.test.ts new file mode 100644 index 000000000000..c4261f2dea87 --- /dev/null +++ b/tests/unit/request-rejected-streak-model-scope.test.ts @@ -0,0 +1,151 @@ +/** + * #12859 follow-up: the REQUEST_REJECTED streak is keyed by connection, but an + * upstream per-request refusal is scoped to what was actually asked. Two failure + * shapes put the wrong thing into the count: + * + * 1. A provider whose refusals are model-specific (today: a model the plan does + * not include, a model gated behind an entitlement) refuses model B three + * times while model A works fine. The connection-wide streak reaches the + * threshold and the account is written `banned` / `isActive: false` — every + * model, including the healthy one, is disabled until the operator reconnects + * by hand. A refusal on one model must never disable the connection. + * 2. The escalation is unrecoverable from the routing side: nothing in the + * routing path reactivates the connection, so the streak's last step deletes + * the operator's account. A long cooldown achieves the same "stop hammering + * the upstream" goal and heals on its own. + * + * The streak therefore belongs to `connectionId|model`, and the ladder ends in a + * long cooldown instead of a ban. + */ +import test from "node:test"; +import assert from "node:assert/strict"; + +const { + recordRequestRejected, + seedRequestRejectedStreak, + hasRequestRejectedStreak, + clearRequestRejectedStreak, + __resetRequestRejectedStreaksForTests, + REQUEST_REJECTED_ESCALATION_THRESHOLD, + REQUEST_REJECTED_COOLDOWNS_MS, + REQUEST_REJECTED_MAX_COOLDOWN_MS, +} = await import("../../open-sse/services/requestRejectedStreak.ts"); +const { COOLDOWN_MS } = await import("../../open-sse/config/errorConfig.ts"); + +const MIN = 60 * 1000; +const T0 = 1_000_000; +const CONN = "conn-a"; +const MODEL_A = "claude-opus-4-6"; +const MODEL_B = "claude-sonnet-4-6"; + +test.beforeEach(() => __resetRequestRejectedStreaksForTests()); + +test("the ladder never escalates to a terminal state", () => { + assert.equal( + REQUEST_REJECTED_COOLDOWNS_MS.length, + REQUEST_REJECTED_ESCALATION_THRESHOLD, + "every rung of the ladder — including the threshold one — must be a cooldown" + ); + assert.equal( + REQUEST_REJECTED_MAX_COOLDOWN_MS, + COOLDOWN_MS.requestRejectedRepeat * 4, + "the last rung caps at a long-but-temporary cooldown" + ); +}); + +test("three consecutive refusals on ONE model stay a cooldown — never a ban", () => { + let t = T0; + const verdicts = []; + for (let i = 0; i < REQUEST_REJECTED_ESCALATION_THRESHOLD; i++) { + const v = recordRequestRejected(CONN, MODEL_B, t); + verdicts.push(v); + assert.equal(v.escalate, false, `refusal ${i + 1} must not escalate`); + assert.ok(v.cooldownMs > 0, `refusal ${i + 1} must still exclude the model`); + t += v.cooldownMs + 1; + } + assert.deepEqual( + verdicts.map((v) => v.streak), + [1, 2, 3] + ); + assert.equal( + verdicts[2].cooldownMs, + REQUEST_REJECTED_MAX_COOLDOWN_MS, + "the threshold refusal is the longest cooldown, not a terminal write" + ); +}); + +test("a refusal on one model does not accumulate refusals from a sibling model", () => { + let t = T0; + // Model B is refused repeatedly; model A keeps being refused too, but each + // model's streak is its own. + for (let i = 0; i < REQUEST_REJECTED_ESCALATION_THRESHOLD; i++) { + const v = recordRequestRejected(CONN, MODEL_B, t); + assert.equal(v.streak, i + 1, "model B counts only model B refusals"); + t += v.cooldownMs + 1; + } + const sibling = recordRequestRejected(CONN, MODEL_A, t); + assert.equal(sibling.streak, 1, "model A has never been refused before"); + assert.equal( + sibling.cooldownMs, + REQUEST_REJECTED_COOLDOWNS_MS[0], + "model A starts at the first rung" + ); +}); + +test("the same model on another connection has its own streak", () => { + const first = recordRequestRejected(CONN, MODEL_B, T0); + const other = recordRequestRejected("conn-b", MODEL_B, T0 + first.cooldownMs + 1); + assert.equal(other.streak, 1); +}); + +test("a success on one model clears only that model", () => { + let t = T0; + const b1 = recordRequestRejected(CONN, MODEL_B, t); + recordRequestRejected(CONN, MODEL_A, t + 1); + clearRequestRejectedStreak(CONN, MODEL_B); + assert.equal(hasRequestRejectedStreak(CONN, MODEL_B), false, "model B cleared"); + assert.equal(hasRequestRejectedStreak(CONN, MODEL_A), true, "model A still tracked"); + const next = recordRequestRejected(CONN, MODEL_B, t + b1.cooldownMs + 2); + assert.equal(next.streak, 1); + t = 0; +}); + +test("a connection-wide clear forgets every model of that connection", () => { + recordRequestRejected(CONN, MODEL_A, T0); + recordRequestRejected(CONN, MODEL_B, T0); + clearRequestRejectedStreak(CONN); + assert.equal(hasRequestRejectedStreak(CONN, MODEL_A), false); + assert.equal(hasRequestRejectedStreak(CONN, MODEL_B), false); +}); + +test("a seeded refusal restores only the model it was recorded for", () => { + const lastRefusalAt = T0 - 10 * MIN; + assert.equal(seedRequestRejectedStreak(CONN, MODEL_B, lastRefusalAt, T0), true); + assert.equal(hasRequestRejectedStreak(CONN, MODEL_B), true); + assert.equal(hasRequestRejectedStreak(CONN, MODEL_A), false, "the sibling is untouched"); + + const v = recordRequestRejected(CONN, MODEL_B, T0); + assert.equal(v.streak, 2, "the seeded refusal counts as the first of the streak"); + assert.equal(v.counted, true); +}); + +test("a refusal with no model keeps working (connection-scoped fallback)", () => { + const v = recordRequestRejected(CONN, undefined, T0); + assert.equal(v.streak, 1); + assert.equal(v.counted, true); + assert.equal(hasRequestRejectedStreak(CONN), true, "a connection-wide lookup sees it"); +}); + +test("refusals that land during the cooldown are the same episode, per model", () => { + const first = recordRequestRejected(CONN, MODEL_B, T0); + for (const s of [1, 2, 3]) { + const v = recordRequestRejected(CONN, MODEL_B, T0 + s * 1000); + assert.equal(v.counted, false); + assert.equal(v.streak, 1); + assert.ok(v.cooldownMs > 0 && v.cooldownMs <= first.cooldownMs); + } + // The sibling model is not excluded by model B's cooldown. + const sibling = recordRequestRejected(CONN, MODEL_A, T0 + 1000); + assert.equal(sibling.counted, true, "model A is a fresh episode"); + assert.equal(sibling.streak, 1); +}); diff --git a/tests/unit/request-rejected-streak-success-clear-scope.test.ts b/tests/unit/request-rejected-streak-success-clear-scope.test.ts new file mode 100644 index 000000000000..b46905c545e4 --- /dev/null +++ b/tests/unit/request-rejected-streak-success-clear-scope.test.ts @@ -0,0 +1,44 @@ +// #18574 — a successful request must clear the refusal streak only for the model that +// succeeded. chatHelpers' onRequestSuccess used to call clearRequestRejectedStreak() with +// no model, which wipes every `connectionId|*` streak, so interleaved traffic on a healthy +// sibling model kept resetting the refused model's count. +import test from "node:test"; +import assert from "node:assert/strict"; +import fs from "node:fs"; +import path from "node:path"; + +import { + __resetRequestRejectedStreaksForTests, + clearRequestRejectedStreak, + hasRequestRejectedStreak, + recordRequestRejected, +} from "../../open-sse/services/requestRejectedStreak.ts"; + +const CONN = "conn-18574"; + +test.afterEach(() => __resetRequestRejectedStreaksForTests()); + +test("clearing one model's streak leaves a sibling model's streak intact", () => { + recordRequestRejected(CONN, "model-a", Date.now()); + recordRequestRejected(CONN, "model-b", Date.now()); + + clearRequestRejectedStreak(CONN, "model-a"); + + assert.equal(hasRequestRejectedStreak(CONN, "model-a"), false); + assert.equal(hasRequestRejectedStreak(CONN, "model-b"), true); +}); + +test("chatHelpers onRequestSuccess clears the streak for the succeeded model only", () => { + const helpers = fs.readFileSync( + path.join(process.cwd(), "src/sse/handlers/chatHelpers.ts"), + "utf8" + ); + const start = helpers.indexOf("onRequestSuccess: async () => {"); + assert.ok(start > 0, "onRequestSuccess not found in chatHelpers.ts"); + const end = helpers.indexOf("onStreamFailure:", start); + assert.match( + helpers.slice(start, end), + /clearRequestRejectedStreak\(\s*credentials\.connectionId,\s*model\s*\)/, + "the success hook must scope the clear to the model that succeeded" + ); +}); diff --git a/tests/unit/request-rejected-streak.test.ts b/tests/unit/request-rejected-streak.test.ts index 432c07fe413b..4eca78785d7e 100644 --- a/tests/unit/request-rejected-streak.test.ts +++ b/tests/unit/request-rejected-streak.test.ts @@ -1,9 +1,10 @@ /** - * open-sse/services/requestRejectedStreak.ts (#12859): per-connection streak - * of upstream per-request refusals → growing cooldown, escalation only for - * consecutive refusals. One cooldown episode counts once: refusals landing - * while the connection is already excluded were in flight before the cooldown - * and are not new evidence. + * open-sse/services/requestRejectedStreak.ts (#12859): per-connection-and-model + * streak of upstream per-request refusals → growing cooldown. The ladder never + * escalates to a terminal state: the threshold refusal earns the longest + * (temporary) rung. One cooldown episode counts once: refusals landing while + * the model is already excluded were in flight before the cooldown and are not + * new evidence. */ import test from "node:test"; import assert from "node:assert/strict"; @@ -22,19 +23,18 @@ const { COOLDOWN_MS } = await import("../../open-sse/config/errorConfig.ts"); const MIN = 60 * 1000; const T0 = 1_000_000; +const MODEL = "claude-sonnet-4-6"; test.beforeEach(() => __resetRequestRejectedStreaksForTests()); -test("ladder comes from COOLDOWN_MS and has one rung per non-escalating refusal", () => { - assert.deepEqual(REQUEST_REJECTED_COOLDOWNS_MS, [ - COOLDOWN_MS.requestRejected, - COOLDOWN_MS.requestRejectedRepeat, - ]); - assert.equal(REQUEST_REJECTED_COOLDOWNS_MS.length, REQUEST_REJECTED_ESCALATION_THRESHOLD - 1); +test("ladder comes from COOLDOWN_MS and has one rung per refusal", () => { + assert.equal(REQUEST_REJECTED_COOLDOWNS_MS[0], COOLDOWN_MS.requestRejected); + assert.equal(REQUEST_REJECTED_COOLDOWNS_MS[1], COOLDOWN_MS.requestRejectedRepeat); + assert.equal(REQUEST_REJECTED_COOLDOWNS_MS.length, REQUEST_REJECTED_ESCALATION_THRESHOLD); }); test("first refusal: first cooldown rung, counted, no escalation", () => { - const v = recordRequestRejected("conn-a", T0); + const v = recordRequestRejected("conn-a", MODEL, T0); assert.equal(v.streak, 1); assert.equal(v.counted, true); assert.equal(v.escalate, false); @@ -43,74 +43,84 @@ test("first refusal: first cooldown rung, counted, no escalation", () => { assert.equal(v.windowMs, REQUEST_REJECTED_WINDOW_MS); }); -test("refusals that land while the connection is already excluded are the same episode", () => { - const first = recordRequestRejected("conn-a", T0); +test("refusals that land while the model is already excluded are the same episode", () => { + const first = recordRequestRejected("conn-a", MODEL, T0); // Three parallel requests that were in flight when the first 403 came back. - const inFlight = [1, 2, 3].map((s) => recordRequestRejected("conn-a", T0 + s * 1000)); + const inFlight = [1, 2, 3].map((s) => recordRequestRejected("conn-a", MODEL, T0 + s * 1000)); for (const v of inFlight) { assert.equal(v.counted, false, "not new evidence"); assert.equal(v.streak, 1, "streak stays at 1"); - assert.equal(v.escalate, false, "a burst must never ban"); + assert.equal(v.escalate, false, "a burst must never lock the model out at the threshold"); assert.ok(v.cooldownMs > 0 && v.cooldownMs <= first.cooldownMs, "remaining cooldown"); } }); -test("consecutive episodes grow the cooldown and escalate at the threshold", () => { - const first = recordRequestRejected("conn-a", T0); - const second = recordRequestRejected("conn-a", T0 + first.cooldownMs + 1); +test("consecutive episodes grow the cooldown up to the longest rung", () => { + const first = recordRequestRejected("conn-a", MODEL, T0); + const second = recordRequestRejected("conn-a", MODEL, T0 + first.cooldownMs + 1); assert.equal(second.streak, 2); assert.equal(second.counted, true); assert.equal(second.cooldownMs, COOLDOWN_MS.requestRejectedRepeat); assert.equal(second.escalate, false); - const third = recordRequestRejected("conn-a", T0 + first.cooldownMs + second.cooldownMs + 2); + const third = recordRequestRejected( + "conn-a", + MODEL, + T0 + first.cooldownMs + second.cooldownMs + 2 + ); assert.equal(third.streak, 3); - assert.equal(third.escalate, true, "third consecutive refusal escalates"); - assert.equal(third.cooldownMs, 0, "terminal state replaces the cooldown"); - assert.equal(hasRequestRejectedStreak("conn-a"), false, "escalation clears the streak"); + assert.equal(third.escalate, false, "the threshold is a long cooldown, never a ban"); + assert.equal( + third.cooldownMs, + REQUEST_REJECTED_COOLDOWNS_MS[REQUEST_REJECTED_COOLDOWNS_MS.length - 1], + "the threshold refusal earns the longest rung" + ); - // A reconnected connection starts from 1 again. - const afterBan = recordRequestRejected("conn-a", T0 + 60 * MIN); - assert.equal(afterBan.streak, 1); - assert.equal(afterBan.escalate, false); + // Past the threshold the cooldown stays capped instead of growing forever. + const fourth = recordRequestRejected("conn-a", MODEL, T0 + 24 * 60 * MIN - 1); + assert.equal(fourth.escalate, false); + assert.equal(fourth.cooldownMs, third.cooldownMs, "capped at the longest rung"); }); test("a refusal outside the staleness window starts a new streak", () => { - recordRequestRejected("conn-a", T0); - const late = recordRequestRejected("conn-a", T0 + REQUEST_REJECTED_WINDOW_MS + 1); + recordRequestRejected("conn-a", MODEL, T0); + const late = recordRequestRejected("conn-a", MODEL, T0 + REQUEST_REJECTED_WINDOW_MS + 1); assert.equal(late.streak, 1); assert.equal(late.cooldownMs, COOLDOWN_MS.requestRejected); }); test("a success (clearRequestRejectedStreak) resets the count", () => { - const first = recordRequestRejected("conn-a", T0); - clearRequestRejectedStreak("conn-a"); - const next = recordRequestRejected("conn-a", T0 + first.cooldownMs + 1); + const first = recordRequestRejected("conn-a", MODEL, T0); + clearRequestRejectedStreak("conn-a", MODEL); + const next = recordRequestRejected("conn-a", MODEL, T0 + first.cooldownMs + 1); assert.equal(next.streak, 1, "sporadic refusals separated by successes never accumulate"); assert.equal(next.cooldownMs, COOLDOWN_MS.requestRejected); }); test("streaks are per connection", () => { - const a1 = recordRequestRejected("conn-a", T0); - recordRequestRejected("conn-a", T0 + a1.cooldownMs + 1); - const other = recordRequestRejected("conn-b", T0 + a1.cooldownMs + 2); + const a1 = recordRequestRejected("conn-a", MODEL, T0); + recordRequestRejected("conn-a", MODEL, T0 + a1.cooldownMs + 1); + const other = recordRequestRejected("conn-b", MODEL, T0 + a1.cooldownMs + 2); assert.equal(other.streak, 1); }); test("seedRequestRejectedStreak restores one refusal from before a restart", () => { const lastRefusalAt = T0 - 10 * MIN; - assert.equal(seedRequestRejectedStreak("conn-a", lastRefusalAt, T0), true); - assert.equal(hasRequestRejectedStreak("conn-a"), true); - const v = recordRequestRejected("conn-a", T0); + assert.equal(seedRequestRejectedStreak("conn-a", MODEL, lastRefusalAt, T0), true); + assert.equal(hasRequestRejectedStreak("conn-a", MODEL), true); + const v = recordRequestRejected("conn-a", MODEL, T0); assert.equal(v.streak, 2, "the seeded refusal counts as the first of the streak"); assert.equal(v.counted, true, "the seeded refusal's cooldown is not re-applied"); }); test("seedRequestRejectedStreak ignores stale rows and existing state", () => { - assert.equal(seedRequestRejectedStreak("conn-a", T0 - REQUEST_REJECTED_WINDOW_MS, T0), false); - assert.equal(seedRequestRejectedStreak("conn-a", Number.NaN, T0), false); - assert.equal(hasRequestRejectedStreak("conn-a"), false); + assert.equal( + seedRequestRejectedStreak("conn-a", MODEL, T0 - REQUEST_REJECTED_WINDOW_MS, T0), + false + ); + assert.equal(seedRequestRejectedStreak("conn-a", MODEL, Number.NaN, T0), false); + assert.equal(hasRequestRejectedStreak("conn-a", MODEL), false); - recordRequestRejected("conn-b", T0); - assert.equal(seedRequestRejectedStreak("conn-b", T0 - MIN, T0), false, "already tracked"); + recordRequestRejected("conn-b", MODEL, T0); + assert.equal(seedRequestRejectedStreak("conn-b", MODEL, T0 - MIN, T0), false, "already tracked"); }); From f02016d8aba62625471ab68f0c778d139ea25aa8 Mon Sep 17 00:00:00 2001 From: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> Date: Wed, 4 Nov 2026 13:44:17 -0300 Subject: [PATCH 15/20] fix(api): enforce the visible body limit for streamed chat requests (#18527) --- .env.example | 4 +- .../18527-saved-body-limit-llm-routes.md | 1 + deploy/kubernetes/overlays/k8s/ingress.yaml | 1 + docs/guides/TROUBLESHOOTING.md | 8 +- docs/ops/KUBERNETES_DEPLOYMENT_GUIDE.md | 3 +- docs/reference/ENVIRONMENT.md | 4 +- .../settings/components/RequestLimitsTab.tsx | 1 + src/i18n/messages/am.json | 1 + src/i18n/messages/ar.json | 1 + src/i18n/messages/az.json | 1 + src/i18n/messages/bg.json | 1 + src/i18n/messages/bn.json | 1 + src/i18n/messages/bs.json | 1 + src/i18n/messages/cs.json | 1 + src/i18n/messages/da.json | 1 + src/i18n/messages/de.json | 1 + src/i18n/messages/el.json | 1 + src/i18n/messages/en.json | 1 + src/i18n/messages/es.json | 1 + src/i18n/messages/et.json | 1 + src/i18n/messages/fa.json | 1 + src/i18n/messages/fi.json | 1 + src/i18n/messages/fr.json | 1 + src/i18n/messages/ga.json | 1 + src/i18n/messages/gu.json | 1 + src/i18n/messages/ha.json | 1 + src/i18n/messages/he.json | 1 + src/i18n/messages/hi.json | 1 + src/i18n/messages/hr.json | 1 + src/i18n/messages/hu.json | 1 + src/i18n/messages/hy.json | 1 + src/i18n/messages/id.json | 1 + src/i18n/messages/ig.json | 1 + src/i18n/messages/it.json | 1 + src/i18n/messages/ja.json | 1 + src/i18n/messages/ka.json | 1 + src/i18n/messages/km.json | 1 + src/i18n/messages/kn.json | 1 + src/i18n/messages/ko.json | 1 + src/i18n/messages/lt.json | 1 + src/i18n/messages/lv.json | 1 + src/i18n/messages/ml.json | 1 + src/i18n/messages/mr.json | 1 + src/i18n/messages/ms.json | 1 + src/i18n/messages/mt.json | 1 + src/i18n/messages/my.json | 1 + src/i18n/messages/ne.json | 1 + src/i18n/messages/nl.json | 1 + src/i18n/messages/no.json | 1 + src/i18n/messages/or.json | 1 + src/i18n/messages/pa.json | 1 + src/i18n/messages/phi.json | 1 + src/i18n/messages/pl.json | 1 + src/i18n/messages/pt-BR.json | 1 + src/i18n/messages/pt.json | 1 + src/i18n/messages/ro.json | 1 + src/i18n/messages/ru.json | 1 + src/i18n/messages/si.json | 1 + src/i18n/messages/sk.json | 1 + src/i18n/messages/sl.json | 1 + src/i18n/messages/sr.json | 1 + src/i18n/messages/sv.json | 1 + src/i18n/messages/sw.json | 1 + src/i18n/messages/ta.json | 1 + src/i18n/messages/te.json | 1 + src/i18n/messages/th.json | 1 + src/i18n/messages/tr.json | 1 + src/i18n/messages/uk-UA.json | 1 + src/i18n/messages/ur.json | 1 + src/i18n/messages/uz.json | 1 + src/i18n/messages/vi.json | 1 + src/i18n/messages/yo.json | 1 + src/i18n/messages/zh-CN.json | 1 + src/i18n/messages/zh-TW.json | 1 + src/lib/db/readCache.ts | 11 + src/lib/db/settings.ts | 22 +- .../deploy/kubernetes/manifestGenerator.ts | 5 +- src/server/authz/pipeline.ts | 4 +- src/shared/middleware/bodySizeGuard.ts | 23 +- src/shared/middleware/chatBodyAdmission.ts | 27 +- tests/unit/body-size-guard.test.ts | 28 +++ tests/unit/chat-hard-max-body-env.test.ts | 56 +++++ tests/unit/unified-request-body-limit.test.ts | 236 ++++++++++++++++++ 83 files changed, 487 insertions(+), 14 deletions(-) create mode 100644 changelog.d/fixes/18527-saved-body-limit-llm-routes.md create mode 100644 tests/unit/chat-hard-max-body-env.test.ts create mode 100644 tests/unit/unified-request-body-limit.test.ts diff --git a/.env.example b/.env.example index c5c4c62e237b..11d9937d9a01 100644 --- a/.env.example +++ b/.env.example @@ -523,7 +523,8 @@ ALLOW_API_KEY_REVEAL=false # Used by: src/shared/utils/apiKeyPolicy.ts — checkRateLimit() fallback. # DEFAULT_RATE_LIMIT_PER_DAY=1000 -# Maximum request body size in bytes (rejects larger payloads). +# Maximum request body size in bytes (rejects larger payloads). A saved maxBodySizeMb wins. +# Chat Completions and Responses keep a 50 MB default until a limit is saved. # Used by: src/shared/middleware/bodySizeGuard.ts — prevents oversized uploads. # Default: 10485760 (10 MB) # MAX_BODY_SIZE_BYTES=10485760 @@ -538,6 +539,7 @@ ALLOW_API_KEY_REVEAL=false # as structure-heavy. Default 262144 (256 KB). # OMNIROUTE_CHAT_LARGE_BODY_BYTES=262144 # Actual-byte hard cap enforced during bounded ingestion. Default 52428800 (50 MB). +# A saved maxBodySizeMb replaces the default; when set here, this also caps that saved value. # OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES=52428800 # Legacy request-COUNT cap (#503-fanout). Now binds only when explicitly set here — # left unset, heavyweight admission is gated by OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES below diff --git a/changelog.d/fixes/18527-saved-body-limit-llm-routes.md b/changelog.d/fixes/18527-saved-body-limit-llm-routes.md new file mode 100644 index 000000000000..a2b7b5d65b57 --- /dev/null +++ b/changelog.d/fixes/18527-saved-body-limit-llm-routes.md @@ -0,0 +1 @@ +- **fix(api):** a saved request body limit now also governs `/v1/chat/completions` and `/v1/responses`, for the header check and for the bytes actually read, including chunked bodies. **Upgrade note:** a limit saved earlier now applies to these two routes, so an install with a saved limit below 50 MB starts getting `413` (`PAYLOAD_TOO_LARGE`) on chat bodies above it. To raise it, save a higher value at `/dashboard/settings/advanced` or `PATCH /api/settings` with `maxBodySizeMb`; 50 restores the previous chat limit. Installs that never saved one keep the 50 MB default, and `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES` keeps working and also caps a saved limit ([#18527](https://github.com/cheaper-inference/OmniRoute/pull/18527)) — thanks @shevar0 diff --git a/deploy/kubernetes/overlays/k8s/ingress.yaml b/deploy/kubernetes/overlays/k8s/ingress.yaml index b7f458d888fd..4567bc1d6956 100644 --- a/deploy/kubernetes/overlays/k8s/ingress.yaml +++ b/deploy/kubernetes/overlays/k8s/ingress.yaml @@ -20,6 +20,7 @@ metadata: # src/shared/middleware/chatBodyAdmission.ts). A smaller value here makes # nginx return 413 for bodies the application would have accepted, and the # app's own 413 handling — which tells the client to compact — never runs. + # Saving a maxBodySizeMb above 50 MB also needs this limit raised. nginx.ingress.kubernetes.io/proxy-body-size: "50m" spec: ingressClassName: nginx diff --git a/docs/guides/TROUBLESHOOTING.md b/docs/guides/TROUBLESHOOTING.md index 37531e851e0a..5b8bf19b9e2a 100644 --- a/docs/guides/TROUBLESHOOTING.md +++ b/docs/guides/TROUBLESHOOTING.md @@ -59,7 +59,13 @@ export OMNIROUTE_CHAT_ADMISSION_QUEUE_MS=20000 # raise the bounded wait past the Set these in the OmniRoute process environment (the daemon, e.g. via the LaunchAgent plist or `systemctl edit`), then restart OmniRoute. The rotation flag is the single highest-leverage lever: it converts a hard failure into a transparent retry against a healthy provider in the pool. -**Note**: `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` caps how many heavyweight — long-context — requests run at once; the bound is an admission gate, not a provider rate limiter. **#503-fanout update:** this var is no longer set by default (it now binds only when explicitly configured, as above) — heavyweight admission is instead gated by an auto-derived byte budget (`OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES`) that scales itself from the host's real memory ceiling, so a fresh deployment should see far fewer `503 chat_admission_busy` rejects without setting this var at all; explicitly setting it here still works exactly as documented. Explicit byte-budget overrides clamp to 8 MiB–2 GiB. A `413 body_exceeds_budget` is not transient: increase that byte budget, lower `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES`, or increase the process memory ceiling. An `inflight_bytes_budget` shed is temporary contention and remains retryable. The per-provider rate limiting (`open-sse/services/rateLimitManager.ts`) is governed separately by `RATE_LIMIT_MAX_WAIT_MS`, `RATE_LIMIT_MAX_QUEUE_DEPTH`, and `RATE_LIMIT_AUTO_ENABLE` — see `.env.example`. +**Note**: `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` caps how many heavyweight — long-context — requests run at once; the bound is an admission gate, not a provider rate limiter. **#503-fanout update:** this var is no longer set by default (it now binds only when explicitly configured, as above) — heavyweight admission is instead gated by an auto-derived byte budget (`OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES`) that scales itself from the host's real memory ceiling, so a fresh deployment should see far fewer `503 chat_admission_busy` rejects without setting this var at all; explicitly setting it here still works exactly as documented. Explicit byte-budget overrides clamp to 8 MiB–2 GiB. A `413 body_exceeds_budget` is not transient: increase that byte budget, lower `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES` or the saved request body limit, or increase the process memory ceiling. An `inflight_bytes_budget` shed is temporary contention and remains retryable. The per-provider rate limiting (`open-sse/services/rateLimitManager.ts`) is governed separately by `RATE_LIMIT_MAX_WAIT_MS`, `RATE_LIMIT_MAX_QUEUE_DEPTH`, and `RATE_LIMIT_AUTO_ENABLE` — see `.env.example`. + +**Upgrade note: a saved request body limit now also applies to chat.** A limit saved earlier under Settings > Advanced > Request Body Limit (`maxBodySizeMb`) now also applies to `/v1/chat/completions` and `/v1/responses`. Before this change those two routes accepted up to 50 MB whatever was saved, so an install with a lower saved limit starts rejecting chat bodies above it. An install that never saved a limit keeps the 50 MB default. + +The rejection is an HTTP `413` with `error.code` set to `PAYLOAD_TOO_LARGE` and `error.type` set to `payload_too_large`. The message names the limit in force: `Request body too large. Maximum allowed: 10 MB` when the declared `Content-Length` is over it, or `Request body too large for chat completions (max 10 MB).` when the bytes actually read are. This is not the same as `413 body_exceeds_budget` above, which comes from the memory budget and is not changed by this setting. + +To raise the limit, save a higher value at `/dashboard/settings/advanced`, or send `PATCH /api/settings` with `{"maxBodySizeMb": 50}`. Values from 1 to 500 MB are accepted, it applies without a restart, and 50 restores the previous chat limit. Two other limits can still stop a larger body: `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES`, when set, also caps a saved limit, and the Kubernetes ingress annotation `proxy-body-size` (`50m` in the shipped manifests) has to be raised separately for anything above 50 MB. **How to verify it worked**: run your agent/cron twice in quick succession and confirm both succeed. Before the fix, the second run typically throws `429`/`401`. After the fix, failures (if any) are retried transparently and the call completes. You can also `curl /monitoring/health` and watch the `rateLimitedUntil` field on the provider connections and the `circuitBreakers.providerBreakers[].state` for the affected providers — the state is one of `CLOSED`, `DEGRADED`, `OPEN`, or `HALF_OPEN` (see `src/shared/utils/circuitBreaker.ts`), and a provider that keeps failing will flip `CLOSED → DEGRADED → OPEN` before the reset window lets a probe through (`HALF_OPEN`). diff --git a/docs/ops/KUBERNETES_DEPLOYMENT_GUIDE.md b/docs/ops/KUBERNETES_DEPLOYMENT_GUIDE.md index 4ad158651616..d6383969986d 100644 --- a/docs/ops/KUBERNETES_DEPLOYMENT_GUIDE.md +++ b/docs/ops/KUBERNETES_DEPLOYMENT_GUIDE.md @@ -248,7 +248,8 @@ in the overlay's `resources:` and set both hosts. The body-size annotation matters on the API Ingress specifically: it must not sit below `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES` (50 MB default), or nginx returns 413 for requests the application would have accepted — and the app's own 413 -response, which tells the client to compact and retry, never runs. +response, which tells the client to compact and retry, never runs. Saving a +`maxBodySizeMb` above 50 MB in the dashboard needs this annotation raised as well. ### k3s: Traefik timeouts diff --git a/docs/reference/ENVIRONMENT.md b/docs/reference/ENVIRONMENT.md index ba58acd93e98..c1ca71191799 100644 --- a/docs/reference/ENVIRONMENT.md +++ b/docs/reference/ENVIRONMENT.md @@ -233,9 +233,9 @@ OmniRoute uses **SQLite** (via `better-sqlite3`) for all persistence. These vari | `ALLOW_API_KEY_REVEAL` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | Allows revealing full API key values in the Dashboard UI. Configurable from Dashboard Feature Flags; 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). | | `DEFAULT_RATE_LIMIT_PER_DAY` | _(unset = unlimited)_ | `src/shared/utils/apiKeyPolicy.ts` | Fallback per-day request budget applied to API keys whose `rate_limits` column is null. Unset or empty: no implicit cap (#2289, #11017). `0` is the same (unlimited). Positive integer N enables N/day, 5N/week, 20N/month. Malformed non-empty values fall back to the legacy 1000/day, 5000/week, 20000/month windows. | -| `MAX_BODY_SIZE_BYTES` | `10485760` (10 MB) | `src/shared/middleware/bodySizeGuard.ts` | Maximum allowed request body size. Rejects payloads exceeding this limit. | +| `MAX_BODY_SIZE_BYTES` | `10485760` (10 MB) | `src/shared/middleware/bodySizeGuard.ts` | Default for the visible `maxBodySizeMb` setting; a saved setting takes precedence. `/api/v1/chat/completions` and `/api/v1/responses` keep a 50 MB default until a limit is saved; a saved limit then replaces it, up or down, for the header guard and for actual-byte chat admission alike. | | `OMNIROUTE_CHAT_LARGE_BODY_BYTES` | `262144` (256 KB) | `src/shared/middleware/chatBodyAdmission.ts` | Actual request bodies at or above this threshold take the atomic process-local heavyweight admission lease before JSON parsing (BYTE path, including `POST /v1/responses`). Same [#10437](https://github.com/diegosouzapw/OmniRoute/pull/10437) healthy-headroom escape as structure-heavy; still bounded by `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / [#10110](https://github.com/diegosouzapw/OmniRoute/issues/10110) so [#7849](https://github.com/diegosouzapw/OmniRoute/issues/7849) does not return. | -| `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES` | `52428800` (50 MB) | `src/shared/middleware/chatBodyAdmission.ts` | Chat-route hard cap enforced against bytes read during bounded ingestion, including requests with missing, invalid, or dishonest `Content-Length`; excess receives `413`. | +| `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES` | `52428800` (50 MB) | `src/shared/middleware/chatBodyAdmission.ts` | Chat-route hard cap enforced against bytes read during bounded ingestion, including requests with missing, invalid, or dishonest `Content-Length`; excess receives `413`. A saved `maxBodySizeMb` replaces the 50 MB default; when this variable is set, it also caps that saved value. | | `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` | _(unset — no request-count cap)_ | `src/shared/middleware/chatBodyAdmission.ts` | **#503-fanout:** this legacy request-COUNT cap now binds only when explicitly set. Left unset (the default), heavyweight chat admission is instead gated by `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` — an auto-derived BYTE budget sized from the process's real memory ceiling in **one process** (one V8 heap). Two overlapping ~750k-token `/v1/responses` abort ~12 Gi heaps (#7849) — a **memory-budget** warning, not a hard product max of 2. A healthy process (heap below the shed ratio) MAY admit more concurrent long `/v1/responses` via `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM`. Tens of long SSE clients (40–50) is heap + `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / #10110, not “max 2”. Blindly raising this to “use the host” reintroduces #7849. Multiply **heaps** with **N independent `DATA_DIR`s** (#11024); never `replicas>1` on one SQLite file. | | `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` | _(auto-derived)_ | `src/shared/middleware/admissionBudget.ts` | **#503-fanout:** override for the auto-derived ingest byte budget (25% of the tighter V8/cgroup memory ceiling divided by 8x transient amplification). Derived and explicit values clamp to 8 MiB–2 GiB. A body larger than the effective budget fails immediately with `413 body_exceeds_budget`; contention between individually serviceable bodies remains retryable `503`. 40–50 concurrent long SSE clients is this budget + heap, not a hard “max 2”. Read `chatAdmission.maxInflightBytes` / `budgetSource` / `pressureSeverity` at `/api/monitoring/health` before tuning. | | `OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO` | `0.75` | `src/shared/middleware/chatBodyAdmission.ts` | Heap-pressure shed ratio (`heapUsed / heap_size_limit`) for BYTE and STRUCTURE heavyweight admission (#10183, #10268, #10437). A concurrent heavyweight request past `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` is only shed with the retryable `503` when the heap is ALSO at or above this ratio; on a healthy heap it is admitted via healthy-headroom instead. | diff --git a/src/app/(dashboard)/dashboard/settings/components/RequestLimitsTab.tsx b/src/app/(dashboard)/dashboard/settings/components/RequestLimitsTab.tsx index 33c6f3ab420f..5e25fb097f87 100644 --- a/src/app/(dashboard)/dashboard/settings/components/RequestLimitsTab.tsx +++ b/src/app/(dashboard)/dashboard/settings/components/RequestLimitsTab.tsx @@ -145,6 +145,7 @@ export default function RequestLimitsTab() { )} +

{t("requestBodyLimitChatHint")}

{message && (

> { }); } +/** + * getCachedSettings() plus `savedMaxBodySizeMb`, for the request body limit checks. It shares the + * settings cache, so a saved limit applies as soon as updateSettings() invalidates it. + */ +export async function getCachedBodySizeSettings(): Promise> { + return settingsCache.load("bodySizeSettings", async () => { + const { getSettings, getSavedRequestBodyLimitMb } = await import("@/lib/db/settings"); + return { ...(await getSettings()), savedMaxBodySizeMb: getSavedRequestBodyLimitMb() }; + }); +} + /** * Cached wrapper for getPricing. * Longer TTL since pricing rarely changes mid-session. diff --git a/src/lib/db/settings.ts b/src/lib/db/settings.ts index c10f479d93ac..f878126d2fc4 100644 --- a/src/lib/db/settings.ts +++ b/src/lib/db/settings.ts @@ -17,7 +17,10 @@ import { encrypt, decrypt } from "./encryption"; import { getProxyRegistryGeneration, resolveProxyForScopeFromRegistry } from "./proxies"; import { isEgressBucketedLockScope } from "@omniroute/open-sse/config/providerErrorRules.ts"; import { getComboModelProvider as getComboEntryProvider } from "@/lib/combos/steps"; -import { requestBodyLimitMbFromEnv } from "@/shared/constants/bodySize"; +import { + normalizeRequestBodyLimitMb, + requestBodyLimitMbFromEnv, +} from "@/shared/constants/bodySize"; import { DEFAULT_RESPONSES_PREVIOUS_RESPONSE_ID_MODE } from "@/shared/constants/responsesPreviousResponseId"; import { decodeUserinfo } from "@/shared/utils/decodeUserinfo"; import { type JsonRecord, toRecord } from "./settings/shared"; @@ -357,6 +360,23 @@ export async function getSettings() { return settings; } +/** + * The `maxBodySizeMb` an operator saved, or null if they never did. getSettings() cannot tell these + * apart because it fills the key with the default. + */ +export function getSavedRequestBodyLimitMb(): number | null { + const row = getDbInstance() + .prepare("SELECT value FROM key_value WHERE namespace = 'settings' AND key = 'maxBodySizeMb'") + .get(); + const rawValue = toRecord(row).value; + if (typeof rawValue !== "string") return null; + try { + return normalizeRequestBodyLimitMb(JSON.parse(rawValue)); + } catch { + return null; + } +} + export async function updateSettings( updates: Record, options?: { expectedRevision?: number } diff --git a/src/lib/deploy/kubernetes/manifestGenerator.ts b/src/lib/deploy/kubernetes/manifestGenerator.ts index 702d57886182..e9c791edc894 100644 --- a/src/lib/deploy/kubernetes/manifestGenerator.ts +++ b/src/lib/deploy/kubernetes/manifestGenerator.ts @@ -243,8 +243,9 @@ export function ingressAnnotationsFor(className: string | undefined): Record | undefined> { try { - return await getCachedSettings(); + return await getCachedBodySizeSettings(); } catch (error) { console.warn( "[Authz] Failed to load request body limit settings:", diff --git a/src/shared/middleware/bodySizeGuard.ts b/src/shared/middleware/bodySizeGuard.ts index 5a8d9571aaaa..a1ec7bab87d4 100644 --- a/src/shared/middleware/bodySizeGuard.ts +++ b/src/shared/middleware/bodySizeGuard.ts @@ -28,7 +28,10 @@ export const MAX_BODY_BYTES_AUDIO = 100 * 1024 * 1024; /** Larger limit for file uploads: 500 MB */ export const MAX_BODY_BYTES_FILE = 500 * 1024 * 1024; -/** Larger limit for LLM request payloads: 50 MB */ +/** + * Default limit for LLM request payloads: 50 MB. A saved `maxBodySizeMb` replaces it, up or down; + * without one, this stays the floor so long-context agent payloads keep working. + */ export const MAX_BODY_BYTES_LLM_API = 50 * 1024 * 1024; /** Fixed limit for the loopback-only Video Bridge extraction broker: 50 MB. */ @@ -52,6 +55,8 @@ type BodySizeRule = { limit: number; exactPath?: boolean; fixedLimit?: boolean; + /** A saved `maxBodySizeMb` replaces this limit instead of only raising it. */ + savedLimitWins?: boolean; }; const ROUTE_LIMITS: BodySizeRule[] = [ @@ -62,8 +67,8 @@ const ROUTE_LIMITS: BodySizeRule[] = [ fixedLimit: true, }, { prefix: "/api/db-backups/import", limit: MAX_BODY_BYTES_IMPORT }, - { prefix: "/api/v1/chat/completions", limit: MAX_BODY_BYTES_LLM_API }, - { prefix: "/api/v1/responses", limit: MAX_BODY_BYTES_LLM_API }, + { prefix: "/api/v1/chat/completions", limit: MAX_BODY_BYTES_LLM_API, savedLimitWins: true }, + { prefix: "/api/v1/responses", limit: MAX_BODY_BYTES_LLM_API, savedLimitWins: true }, { prefix: "/api/v1/images", limit: MAX_BODY_BYTES_MEDIA }, { prefix: "/api/v1/videos", limit: MAX_BODY_BYTES_MEDIA }, { prefix: "/api/v1/audio/transcriptions", limit: MAX_BODY_BYTES_AUDIO }, @@ -77,6 +82,16 @@ export function getConfiguredBodySizeLimitBytes(settings?: Record): number | null { + const savedMb = normalizeRequestBodyLimitMb(settings?.savedMaxBodySizeMb); + return savedMb === null ? null : requestBodyLimitMbToBytes(savedMb); +} + /** * Resolve the body size limit for a request path. */ @@ -87,6 +102,8 @@ export function getBodySizeLimit(pathname: string, settings?: Record { + try { + const saved = getSavedBodySizeLimitBytes(await getCachedBodySizeSettings()); + return saved === null + ? CHAT_HARD_MAX_BODY_BYTES + : Math.min(saved, CHAT_EXPLICIT_HARD_MAX_BODY_BYTES); + } catch (error) { + shedLog.warn({ err: error }, "Failed to load request body limit; using the chat default"); + return CHAT_HARD_MAX_BODY_BYTES; + } +} + /** * Decode a compressed body exactly once, expanding to at most `hardMaxBytes` (decompression- * bomb guard). Identity bodies pass through as the same buffer, with no copy. @@ -1065,7 +1089,8 @@ export async function admitChatRequest( const controller = options.controller ?? perConnectionAdmissionController.getController(sessionId); const largeBodyBytes = options.largeBodyBytes ?? CHAT_LARGE_BODY_BYTES; - const hardMaxBytes = options.hardMaxBytes ?? CHAT_HARD_MAX_BODY_BYTES; + // Snapshot the limit once per request: a body in flight keeps the limit it started under. + const hardMaxBytes = options.hardMaxBytes ?? (await getChatBodyLimitBytes()); const queueMs = options.queueMs ?? 0; const internalBypass = isInternalAdmissionBypass(request); const contentLength = parseContentLength(request.headers.get("content-length")); diff --git a/tests/unit/body-size-guard.test.ts b/tests/unit/body-size-guard.test.ts index 79a1a4820019..089b72588c38 100644 --- a/tests/unit/body-size-guard.test.ts +++ b/tests/unit/body-size-guard.test.ts @@ -275,3 +275,31 @@ test("image edit body reader does not enforce an OmniRoute media limit", async ( ); assert.deepEqual(body, new Uint8Array([1, 2, 3, 4])); }); + +test("a saved limit replaces the LLM route default, up or down", () => { + for (const pathname of ["/api/v1/responses", "/api/v1/chat/completions"]) { + assert.equal( + getBodySizeLimit(pathname, { maxBodySizeMb: 10, savedMaxBodySizeMb: 10 }), + requestBodyLimitMbToBytes(10) + ); + assert.equal( + getBodySizeLimit(pathname, { maxBodySizeMb: 100, savedMaxBodySizeMb: 100 }), + requestBodyLimitMbToBytes(100) + ); + assert.equal( + getBodySizeLimit(pathname, { maxBodySizeMb: 10, savedMaxBodySizeMb: null }), + MAX_BODY_BYTES_LLM_API + ); + } +}); + +test("a saved limit stays a lower bound for the dedicated upload routes", () => { + assert.equal( + getBodySizeLimit("/api/v1/audio/transcriptions", { maxBodySizeMb: 1, savedMaxBodySizeMb: 1 }), + MAX_BODY_BYTES_AUDIO + ); + assert.equal( + getBodySizeLimit("/api/v1/files", { maxBodySizeMb: 1, savedMaxBodySizeMb: 1 }), + MAX_BODY_BYTES_FILE + ); +}); diff --git a/tests/unit/chat-hard-max-body-env.test.ts b/tests/unit/chat-hard-max-body-env.test.ts new file mode 100644 index 000000000000..d60f0a014d17 --- /dev/null +++ b/tests/unit/chat-hard-max-body-env.test.ts @@ -0,0 +1,56 @@ +// OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES is read at import, so this needs its own process. +import assert from "node:assert/strict"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; +import test from "node:test"; + +const MIB = 1024 * 1024; +const directory = fs.mkdtempSync(path.join(os.tmpdir(), "omni-chat-hard-max-env-")); +process.env.DATA_DIR = directory; +process.env.OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES = String(2 * MIB); +process.env.APP_LOG_TO_FILE = "false"; +process.env.DISABLE_SQLITE_AUTO_BACKUP = "true"; + +const core = await import("../../src/lib/db/core.ts"); +const { updateSettings } = await import("../../src/lib/db/settings.ts"); +const { admitChatRequest, ChatAdmissionController } = + await import("../../src/shared/middleware/chatBodyAdmission.ts"); + +async function admit(bytes: number) { + const controller = new ChatAdmissionController(1); + const result = await admitChatRequest( + new Request("http://localhost/v1/responses", { + method: "POST", + headers: { "content-type": "application/json", "content-length": String(bytes) }, + body: new Uint8Array(bytes).fill(120), + }), + { controller } + ); + if (result.admit) result.lease?.release(); + return result; +} + +test.after(() => { + core.resetDbInstance(); + fs.rmSync(directory, { recursive: true, force: true, maxRetries: 10, retryDelay: 100 }); +}); + +test("the env variable keeps setting the chat cap when no limit is saved", async () => { + assert.equal((await admit(2 * MIB)).admit, true); + const rejected = await admit(2 * MIB + 1); + assert.equal(rejected.admit, false); + if (!rejected.admit) assert.equal(rejected.response.status, 413); +}); + +test("a saved limit lowers the chat cap below the env variable", async () => { + await updateSettings({ maxBodySizeMb: 1 }); + assert.equal((await admit(MIB + 1)).admit, false); + assert.equal((await admit(MIB)).admit, true); +}); + +test("a saved limit cannot raise the chat cap above an explicit env variable", async () => { + await updateSettings({ maxBodySizeMb: 100 }); + assert.equal((await admit(2 * MIB)).admit, true); + assert.equal((await admit(2 * MIB + 1)).admit, false); +}); diff --git a/tests/unit/unified-request-body-limit.test.ts b/tests/unit/unified-request-body-limit.test.ts new file mode 100644 index 000000000000..3c2920b4c2df --- /dev/null +++ b/tests/unit/unified-request-body-limit.test.ts @@ -0,0 +1,236 @@ +import assert from "node:assert/strict"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; +import test from "node:test"; +import zlib from "node:zlib"; + +const MIB = 1024 * 1024; +const directory = fs.mkdtempSync(path.join(os.tmpdir(), "omni-unified-body-limit-")); +process.env.DATA_DIR = directory; +process.env.MAX_BODY_SIZE_BYTES = String(3 * MIB); +delete process.env.OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES; +process.env.APP_LOG_TO_FILE = "false"; +process.env.DISABLE_SQLITE_AUTO_BACKUP = "true"; + +const core = await import("../../src/lib/db/core.ts"); +const { getSettings, updateSettings } = await import("../../src/lib/db/settings.ts"); +const { getCachedBodySizeSettings } = await import("../../src/lib/db/readCache.ts"); +const { checkBodySize, getBodySizeLimit } = + await import("../../src/shared/middleware/bodySizeGuard.ts"); +const { admitChatRequest, ChatAdmissionController, resolveSelfLoopBearer } = + await import("../../src/shared/middleware/chatBodyAdmission.ts"); + +const paths = [ + "/api/v1/chat/completions", + "/api/v1/responses", + "/api/v1/messages", + "/api/v1/completions", + "/api/v1/providers/openai/chat/completions", + "/api/v1/relay/chat/completions", + "/api/v1/responses/input_tokens", + "/v1/chat/completions", + "/v1/responses", + "/responses", +]; + +function request(pathname: string, bytes: number, declared?: number, internal = false) { + let cancelled = false; + let pulls = 0; + const headers = new Headers({ "content-type": "application/json" }); + if (declared !== undefined) headers.set("content-length", String(declared)); + if (internal) { + headers.set("x-omniroute-admission-bypass", "internal"); + headers.set("authorization", `Bearer ${resolveSelfLoopBearer()}`); + } + const body = new ReadableStream( + { + pull(controller) { + pulls += 1; + controller.enqueue(new Uint8Array(bytes).fill(120)); + controller.close(); + }, + cancel() { + cancelled = true; + }, + }, + { highWaterMark: 0 } + ); + return { + value: new Request(`http://localhost${pathname}`, { + method: "POST", + headers, + body, + duplex: "half", + } as RequestInit & { duplex: "half" }), + cancelled: () => cancelled, + pulls: () => pulls, + }; +} + +async function admit(incoming: Request) { + const controller = new ChatAdmissionController(1); + const result = await admitChatRequest(incoming, { controller }); + if (result.admit) result.lease?.release(); + assert.equal(controller.activeHeavy, 0, "no count lease may leak"); + assert.equal(controller.inflightBytes, 0, "no ingest reservation may leak"); + return result; +} + +test.after(async () => { + const { waitForCallLogSaves } = await import("../../src/lib/usage/callLogs.ts"); + await waitForCallLogSaves(2_000); + core.resetDbInstance(); + fs.rmSync(directory, { recursive: true, force: true, maxRetries: 10, retryDelay: 100 }); +}); + +// These two run first, before any test saves a limit: nothing here can delete a saved setting. +test("without a saved limit the LLM routes keep 50 MiB, whatever the env default says", async () => { + assert.equal((await getSettings()).maxBodySizeMb, 3, "the env still seeds the visible setting"); + const settings = await getCachedBodySizeSettings(); + assert.equal(settings.savedMaxBodySizeMb, null, "the seeded default does not count as saved"); + assert.equal(getBodySizeLimit("/api/v1/responses", settings), 50 * MIB); + assert.equal(getBodySizeLimit("/api/v1/chat/completions", settings), 50 * MIB); + assert.equal(getBodySizeLimit("/api/v1/messages", settings), 3 * MIB); + + const aboveEnvDefault = 3 * MIB + 1; + const accepted = await admit( + request("/api/v1/responses", aboveEnvDefault, aboveEnvDefault).value + ); + assert.equal(accepted.admit, true); +}); + +test("without a saved limit chat admission still rejects above 50 MiB", async () => { + const incoming = request("/api/v1/responses", 2, 50 * MIB + 1); + const result = await admit(incoming.value); + assert.equal(result.admit, false); + if (result.admit) return; + assert.equal(result.response.status, 413); + assert.match((await result.response.json()).error.message, /50 MB/); + assert.equal(incoming.pulls(), 0, "oversized declaration is rejected before ingestion"); +}); + +for (const pathname of paths) { + test(`${pathname}: a saved limit has no route floor or independent chat ceiling`, async () => { + await updateSettings({ maxBodySizeMb: 1 }); + const limit = getBodySizeLimit(pathname, await getCachedBodySizeSettings()); + assert.equal(limit, MIB); + const incoming = request(pathname, 2, MIB + 1); + assert.equal(checkBodySize(incoming.value, limit)?.status, 413); + const result = await admit(incoming.value); + assert.equal(result.admit, false); + if (result.admit) return; + assert.equal(result.response.status, 413); + assert.match((await result.response.json()).error.message, /1 MB/); + assert.equal(incoming.pulls(), 0); + }); +} + +for (const declared of [undefined, 1]) { + for (const internal of [false, true]) { + test(`actual-byte guard honors saved limit (declared=${declared}, internal=${internal})`, async () => { + await updateSettings({ maxBodySizeMb: 1 }); + const incoming = request("/v1/responses", MIB + 1, declared, internal); + const result = await admit(incoming.value); + assert.equal(result.admit, false); + if (result.admit) return; + assert.equal(result.response.status, 413); + const wire = await result.response.text(); + assert.equal(JSON.parse(wire).error.code, "PAYLOAD_TOO_LARGE"); + assert.doesNotMatch(wire, /at \/|at [A-Z]:\\|\.ts:\d+/); + assert.equal(incoming.pulls(), 1); + }); + } +} + +test("exact saved boundary is admitted, including chunked bodies and self-loops", async () => { + await updateSettings({ maxBodySizeMb: 1 }); + for (const declared of [undefined, MIB]) { + for (const internal of [false, true]) { + const result = await admit(request("/v1/messages", MIB, declared, internal).value); + assert.equal(result.admit, true); + if (result.admit) assert.equal((await result.request.arrayBuffer()).byteLength, MIB); + } + } +}); + +test("saving a larger limit immediately admits a body above the former 50 MiB ceiling", async () => { + await updateSettings({ maxBodySizeMb: 100 }); + const bytes = 51 * MIB; + const limit = getBodySizeLimit("/api/v1/responses", await getCachedBodySizeSettings()); + assert.equal(limit, 100 * MIB); + const incoming = request("/api/v1/responses", bytes, bytes); + assert.equal(checkBodySize(incoming.value, limit), null); + const result = await admit(incoming.value); + assert.equal(result.admit, true); + if (result.admit) assert.equal((await result.request.arrayBuffer()).byteLength, bytes); +}); + +test("the same wrapper picks up saved increases and decreases without a process restart", async () => { + const { withChatAdmission } = await import("../../src/shared/middleware/withChatAdmission.ts"); + let calls = 0; + const wrapped = withChatAdmission( + async (incoming: Request) => { + calls += 1; + return Response.json({ bytes: (await incoming.arrayBuffer()).byteLength }); + }, + { controller: new ChatAdmissionController(1) } + ); + const bytes = MIB + 1; + await updateSettings({ maxBodySizeMb: 1 }); + assert.equal((await wrapped(request("/v1/messages", bytes).value)).status, 413); + assert.equal(calls, 0); + await updateSettings({ maxBodySizeMb: 2 }); + const accepted = await wrapped(request("/v1/messages", bytes).value); + assert.equal(accepted.status, 200); + assert.deepEqual(await accepted.json(), { bytes }); + assert.equal(calls, 1); + await updateSettings({ maxBodySizeMb: 1 }); + assert.equal((await wrapped(request("/v1/messages", bytes).value)).status, 413); + assert.equal(calls, 1); +}); + +test("an admitted stream keeps its limit snapshot while subsequent requests see a decrease", async () => { + await updateSettings({ maxBodySizeMb: 2 }); + let started!: () => void; + const reading = new Promise((resolve) => (started = resolve)); + let stream!: ReadableStreamDefaultController; + const incoming = new Request("http://localhost/v1/responses", { + method: "POST", + body: new ReadableStream( + { + start(controller) { + stream = controller; + }, + pull() { + started(); + }, + }, + { highWaterMark: 0 } + ), + duplex: "half", + } as RequestInit & { duplex: "half" }); + const pending = admit(incoming); + await reading; + await updateSettings({ maxBodySizeMb: 1 }); + stream.enqueue(new Uint8Array(MIB + 1)); + stream.close(); + assert.equal((await pending).admit, true); + assert.equal((await admit(request("/v1/responses", MIB + 1).value)).admit, false); +}); + +test("a compressed body expanding past the saved limit gets a 413", async () => { + await updateSettings({ maxBodySizeMb: 1 }); + const compressed = zlib.gzipSync(Buffer.alloc(MIB + 1, 120)); + assert.ok(compressed.byteLength < MIB, "the compressed bytes alone must fit the limit"); + const incoming = new Request("http://localhost/v1/responses", { + method: "POST", + headers: { "content-type": "application/json", "content-encoding": "gzip" }, + body: compressed, + }); + const result = await admit(incoming); + assert.equal(result.admit, false); + if (result.admit) return; + assert.equal(result.response.status, 413); + assert.match((await result.response.json()).error.message, /1 MB/); +}); From 91468c9eda56a044f94b40d254bda1f2f53fd1f1 Mon Sep 17 00:00:00 2001 From: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> Date: Wed, 4 Nov 2026 13:44:18 -0300 Subject: [PATCH 16/20] fix(docs): disable next-themes storage in docs RootProvider (#18612) --- .../fixes/18612-docs-theme-storage-key.md | 1 + src/app/docs/DocsThemeSwitch.tsx | 85 ++++++++++ src/app/docs/layout.tsx | 10 +- .../unit/ui/docs-theme-switch-18078.test.tsx | 151 ++++++++++++++++++ 4 files changed, 243 insertions(+), 4 deletions(-) create mode 100644 changelog.d/fixes/18612-docs-theme-storage-key.md create mode 100644 src/app/docs/DocsThemeSwitch.tsx create mode 100644 tests/unit/ui/docs-theme-switch-18078.test.tsx diff --git a/changelog.d/fixes/18612-docs-theme-storage-key.md b/changelog.d/fixes/18612-docs-theme-storage-key.md new file mode 100644 index 000000000000..52b514e8deff --- /dev/null +++ b/changelog.d/fixes/18612-docs-theme-storage-key.md @@ -0,0 +1 @@ +- **fix(docs):** the docs layout no longer mounts the Fumadocs next-themes provider, whose `theme` storage key collided with the root theme store; a light/dark switch bound to the root theme store (and the `d` hotkey) replaces the Fumadocs one, so `/docs` follows the dashboard theme (#18612, refs #18078) diff --git a/src/app/docs/DocsThemeSwitch.tsx b/src/app/docs/DocsThemeSwitch.tsx new file mode 100644 index 000000000000..a4d35ca0bfcd --- /dev/null +++ b/src/app/docs/DocsThemeSwitch.tsx @@ -0,0 +1,85 @@ +"use client"; + +import { useEffect, useSyncExternalStore } from "react"; +import { Moon, Sun } from "lucide-react"; +import { useTranslations } from "next-intl"; +import { useTheme } from "@/shared/hooks/useTheme"; +import { cn } from "@/shared/utils/cn"; + +const noopSubscribe = () => () => {}; +const getMountedSnapshot = () => true; +const getServerMountedSnapshot = () => false; + +/** The active icon stays neutral until mount: the persisted theme is unknown during SSR. */ +function useIsMounted() { + return useSyncExternalStore(noopSubscribe, getMountedSnapshot, getServerMountedSnapshot); +} + +const ICON_CLASS = "size-6.5 p-1.5 text-fd-muted-foreground"; +const ACTIVE_ICON_CLASS = "bg-fd-accent text-fd-accent-foreground"; + +/** + * Light/dark switch for the docs layout, bound to the root `themeStore`. + * + * `RootProvider theme={{ enabled: false }}` removes the next-themes provider, and without it the + * Fumadocs default switch gets a no-op `setTheme`. This takes its slot and keeps its markup. + * It goes through `setTheme` with the resolved value because the store's `toggleTheme` reads the + * raw `theme`: from "system" on a dark OS, that first click would set "dark" and change nothing. + */ +export default function DocsThemeSwitch({ className }: { className?: string }) { + const { isDark, setTheme } = useTheme(); + const mounted = useIsMounted(); + const t = useTranslations("header"); + + return ( + + ); +} + +function isTypingTarget(target: EventTarget | null) { + if (!(target instanceof HTMLElement)) return false; + if (target.isContentEditable) return true; + if (["INPUT", "TEXTAREA", "SELECT"].includes(target.tagName)) return true; + return target.closest('[role="dialog"]') !== null; +} + +/** + * Keeps the "d" hotkey that Fumadocs mounts only inside its next-themes provider. It lives apart + * from the switch because the sidebar renders that slot twice (desktop and mobile), and one + * listener per slot would toggle twice. + */ +export function DocsThemeHotKey() { + const { isDark, setTheme } = useTheme(); + + useEffect(() => { + const onKeyDown = (event: KeyboardEvent) => { + if (event.defaultPrevented || event.isComposing) return; + if (event.metaKey || event.ctrlKey || event.altKey) return; + if (event.key.toLowerCase() !== "d" || isTypingTarget(event.target)) return; + event.preventDefault(); + setTheme(isDark ? "light" : "dark"); + }; + window.addEventListener("keydown", onKeyDown); + return () => window.removeEventListener("keydown", onKeyDown); + }, [isDark, setTheme]); + + return null; +} diff --git a/src/app/docs/layout.tsx b/src/app/docs/layout.tsx index 244b18f3eb22..decddea662c0 100644 --- a/src/app/docs/layout.tsx +++ b/src/app/docs/layout.tsx @@ -6,6 +6,7 @@ import type { BaseLayoutProps } from "fumadocs-ui/layouts/shared"; import { Suspense } from "react"; import LanguageSelector from "@/shared/components/LanguageSelector"; import { getTranslations } from "next-intl/server"; +import DocsThemeSwitch, { DocsThemeHotKey } from "./DocsThemeSwitch"; export async function generateMetadata() { const t = await getTranslations("docs"); @@ -46,20 +47,21 @@ export default async function Layout({ children }: { children: ReactNode }) { }, ], githubUrl: "https://github.com/cheaper-inference/OmniRoute", + slots: { themeSwitch: DocsThemeSwitch }, }; return ( + // class. next-themes would use the same "theme" storage key as themeStore. + theme={{ enabled: false }} search={{ options: { api: "/docs/api/search", }, }} > + {children} diff --git a/tests/unit/ui/docs-theme-switch-18078.test.tsx b/tests/unit/ui/docs-theme-switch-18078.test.tsx new file mode 100644 index 000000000000..e8dd6a1bac44 --- /dev/null +++ b/tests/unit/ui/docs-theme-switch-18078.test.tsx @@ -0,0 +1,151 @@ +// @vitest-environment jsdom +import fs from "node:fs"; +import path from "node:path"; +import React, { act } from "react"; +import { createRoot } from "react-dom/client"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; + +vi.mock("next-intl", () => ({ + useTranslations: () => (key: string) => key, +})); + +const containers: Array<{ root: ReturnType; element: HTMLDivElement }> = []; + +function stubSystemTheme(prefersDark: boolean) { + vi.stubGlobal("matchMedia", (query: string) => ({ + matches: prefersDark && query.includes("dark"), + media: query, + addEventListener: () => {}, + removeEventListener: () => {}, + })); +} + +/** Fresh modules per test: the store's persist middleware hydrates at import time. */ +async function load() { + vi.resetModules(); + const switchModule = await import("@/app/docs/DocsThemeSwitch"); + const { default: useThemeStore } = await import("@/store/themeStore"); + return { ...switchModule, useThemeStore }; +} + +function render(node: React.ReactNode) { + const element = document.createElement("div"); + document.body.appendChild(element); + const root = createRoot(element); + act(() => { + root.render(node); + }); + containers.push({ root, element }); + return element; +} + +function persistedTheme() { + return JSON.parse(localStorage.getItem("theme") ?? "{}")?.state?.theme; +} + +function isHtmlDark() { + return document.documentElement.classList.contains("dark"); +} + +describe("docs theme switch is bound to the root themeStore (#18078)", () => { + beforeEach(() => { + localStorage.clear(); + document.documentElement.className = ""; + delete document.documentElement.dataset.theme; + stubSystemTheme(false); + }); + + afterEach(() => { + for (const { root, element } of containers.splice(0)) { + act(() => root.unmount()); + element.remove(); + } + vi.unstubAllGlobals(); + }); + + it("a click flips and persists in the shape the pre-hydration script reads", async () => { + const { default: DocsThemeSwitch } = await load(); + const element = render(); + const button = element.querySelector("button[data-theme-toggle]") as HTMLButtonElement; + + expect(isHtmlDark()).toBe(false); + expect(button.getAttribute("aria-label")).toBe("switchToDarkMode"); + + act(() => button.click()); + expect(isHtmlDark()).toBe(true); + expect(document.documentElement.dataset.theme).toBe("dark"); + expect(persistedTheme()).toBe("dark"); + expect(button.getAttribute("aria-label")).toBe("switchToLightMode"); + + act(() => button.click()); + expect(isHtmlDark()).toBe(false); + expect(persistedTheme()).toBe("light"); + }); + + it("from the default 'system' theme on a dark OS, the first click changes the page", async () => { + stubSystemTheme(true); + const { default: DocsThemeSwitch, useThemeStore } = await load(); + const element = render(); + + expect(useThemeStore.getState().theme).toBe("system"); + expect(isHtmlDark()).toBe(true); + + act(() => (element.querySelector("button") as HTMLButtonElement).click()); + expect(isHtmlDark()).toBe(false); + expect(persistedTheme()).toBe("light"); + }); + + it("marks the active icon and merges the className the Fumadocs slot passes", async () => { + const { default: DocsThemeSwitch, useThemeStore } = await load(); + const element = render(); + const button = element.querySelector("button") as HTMLButtonElement; + const [sun, moon] = Array.from(button.querySelectorAll("svg")); + + expect(button.className).toContain("ms-auto"); + expect(sun.getAttribute("class")).toContain("bg-fd-accent"); + expect(moon.getAttribute("class")).not.toContain("bg-fd-accent"); + + act(() => useThemeStore.getState().setTheme("dark")); + expect(sun.getAttribute("class")).not.toContain("bg-fd-accent"); + expect(moon.getAttribute("class")).toContain("bg-fd-accent"); + }); + + it("the 'd' hotkey toggles once, and stays out of the way while typing", async () => { + const { DocsThemeHotKey } = await load(); + const element = render( + <> + + + + ); + + act(() => { + window.dispatchEvent(new KeyboardEvent("keydown", { key: "d" })); + }); + expect(isHtmlDark()).toBe(true); + + act(() => { + window.dispatchEvent(new KeyboardEvent("keydown", { key: "d", ctrlKey: true })); + }); + expect(isHtmlDark()).toBe(true); + + const input = element.querySelector("input") as HTMLInputElement; + act(() => { + input.dispatchEvent(new KeyboardEvent("keydown", { key: "d", bubbles: true })); + }); + expect(isHtmlDark()).toBe(true); + }); + + it("the docs layout disables next-themes and fills the slot with the store-bound switch", () => { + // Source pin: the layout is an async server component that imports the generated Fumadocs + // source, so it cannot be rendered in jsdom. The switch itself is exercised above. + const layout = fs.readFileSync(path.resolve("src/app/docs/layout.tsx"), "utf8"); + + expect(layout).toMatch(/theme=\{\{ enabled: false \}\}/); + expect(layout).toMatch(/slots: \{ themeSwitch: DocsThemeSwitch \}/); + expect(layout).toMatch(//); + // Ignored once the provider is disabled, so keeping them would mislead readers. + expect(layout).not.toMatch(/defaultTheme:/); + expect(layout).not.toMatch(/attribute: "class"/); + }); +}); From 5e8603d936a7b74013af2c5b23fd71e4b9da8030 Mon Sep 17 00:00:00 2001 From: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> Date: Wed, 4 Nov 2026 13:44:19 -0300 Subject: [PATCH 17/20] fix(sse): preserve Claude reasoning effort for Codex (#18788) --- .../fixes/18788-codex-claude-effort.md | 1 + open-sse/executors/codex/reasoningPolicy.ts | 3 +- .../handlers/chatCore/executionCredentials.ts | 26 ++- .../chatcore-execution-credentials.test.ts | 108 +++++++++++ .../claude-codex-effort-18746.test.ts | 167 ++++++++++++++++++ .../codex-reasoning-wire-whitelist.test.ts | 119 +++++++++++++ 6 files changed, 416 insertions(+), 8 deletions(-) create mode 100644 changelog.d/fixes/18788-codex-claude-effort.md create mode 100644 tests/unit/chatcore/claude-codex-effort-18746.test.ts diff --git a/changelog.d/fixes/18788-codex-claude-effort.md b/changelog.d/fixes/18788-codex-claude-effort.md new file mode 100644 index 000000000000..e4caf10366f3 --- /dev/null +++ b/changelog.d/fixes/18788-codex-claude-effort.md @@ -0,0 +1 @@ +- **fix(sse):** Preserve explicit Claude reasoning effort when routing to Codex, without allowing connection defaults to override it ([#18788](https://github.com/cheaper-inference/OmniRoute/pull/18788)) — thanks @trespassmk diff --git a/open-sse/executors/codex/reasoningPolicy.ts b/open-sse/executors/codex/reasoningPolicy.ts index ebd31f4ab765..dfb00a607765 100644 --- a/open-sse/executors/codex/reasoningPolicy.ts +++ b/open-sse/executors/codex/reasoningPolicy.ts @@ -146,7 +146,8 @@ function requestedEffort( restoreOriginal: boolean ): WireEffort | undefined { if (restoreOriginal && Object.hasOwn(metadata, "requestedThinkingEffort")) { - return effort(metadata.requestedThinkingEffort); + const originalEffort = effort(metadata.requestedThinkingEffort); + if (originalEffort !== undefined) return originalEffort; } return effort(reasoning.effort) ?? effort(body.reasoning_effort); } diff --git a/open-sse/handlers/chatCore/executionCredentials.ts b/open-sse/handlers/chatCore/executionCredentials.ts index 6b73c31e2242..ea08666506c9 100644 --- a/open-sse/handlers/chatCore/executionCredentials.ts +++ b/open-sse/handlers/chatCore/executionCredentials.ts @@ -75,6 +75,21 @@ function applyKimiExecutionMetadata( ); } +/** Claude Messages `output_config.effort`; blank or non-string values do not count. */ +function claudeOutputEffort(requestBody: Record | undefined): string | undefined { + const outputConfig = requestBody?.output_config; + if (!outputConfig || typeof outputConfig !== "object") return undefined; + const effort = (outputConfig as Record).effort; + return typeof effort === "string" && effort.trim() ? effort : undefined; +} + +/** OpenAI Responses `reasoning.effort`. */ +function nestedReasoningEffort(requestBody: Record | undefined): unknown { + const reasoning = requestBody?.reasoning; + if (!reasoning || typeof reasoning !== "object") return undefined; + return (reasoning as Record).effort; +} + /** Catalog reasoning metadata for the Codex executor (discovered tiers + the caller's raw effort). */ function buildCodexThinkingMetadata( provider: string | null | undefined, @@ -82,18 +97,15 @@ function buildCodexThinkingMetadata( requestBody: Record | undefined ): Record | null { if (provider !== "codex" || !Array.isArray(modelInfo?.supportedThinkingEfforts)) return null; + const claudeEffort = claudeOutputEffort(requestBody); + const nestedEffort = nestedReasoningEffort(requestBody); + const requestedThinkingEffort = claudeEffort ?? nestedEffort ?? requestBody?.reasoning_effort; return { model: modelInfo.model, supportedThinkingEfforts: modelInfo.supportedThinkingEfforts, defaultThinkingEffort: modelInfo.defaultThinkingEffort, resolvedThinkingEffort: modelInfo.resolvedThinkingEffort, - ...(requestBody - ? { - requestedThinkingEffort: - (requestBody.reasoning as Record | undefined)?.effort ?? - requestBody.reasoning_effort, - } - : {}), + ...(requestedThinkingEffort !== undefined ? { requestedThinkingEffort } : {}), }; } diff --git a/tests/unit/chatcore-execution-credentials.test.ts b/tests/unit/chatcore-execution-credentials.test.ts index cf6b5137b295..6b3dc3fcccb8 100644 --- a/tests/unit/chatcore-execution-credentials.test.ts +++ b/tests/unit/chatcore-execution-credentials.test.ts @@ -6,6 +6,7 @@ // session-id threading. import { test } from "node:test"; import assert from "node:assert/strict"; +import { z } from "zod"; import { getExecutionConnectionId, resolveExecutionCredentials, @@ -191,3 +192,110 @@ test("Kimi Code k3 exposes its documented efforts from the offline policy before defaultThinkingEffort: "max", }); }); + +const codexBase = { + ...base, + provider: "codex", + targetFormat: RESPONSES, + modelInfo: { + model: "gpt-5.6-sol", + supportedThinkingEfforts: ["low", "medium", "high", "xhigh"], + defaultThinkingEffort: "high", + }, +}; + +function codexMetadata(requestBody?: Record) { + const out = resolveExecutionCredentials({ ...codexBase, requestBody }); + return z.record(z.string(), z.unknown()).parse(out.providerSpecificData._omnirouteCodexThinking); +} + +const effortCarriers: Array<{ + name: string; + body: Record; + expected: string; +}> = [ + { + name: "Claude output_config", + body: { output_config: { effort: "medium" } }, + expected: "medium", + }, + { name: "nested OpenAI", body: { reasoning: { effort: "high" } }, expected: "high" }, + { name: "flat OpenAI", body: { reasoning_effort: "low" }, expected: "low" }, + { + name: "Claude over conflicting OpenAI carriers", + body: { + output_config: { effort: "medium" }, + reasoning: { effort: "high" }, + reasoning_effort: "low", + }, + expected: "medium", + }, + { + name: "nested over flat OpenAI", + body: { reasoning: { effort: "high" }, reasoning_effort: "low" }, + expected: "high", + }, +]; + +for (const { name, body, expected } of effortCarriers) { + test(`Codex metadata preserves ${name} effort (#18746)`, () => { + assert.equal(codexMetadata(body).requestedThinkingEffort, expected); + }); +} + +const unusableClaudeEfforts: Array<[string, unknown]> = [ + ["undefined", undefined], + ["null", null], + ["empty", ""], + ["blank", " "], + ["number", 42], + ["object", {}], + ["array", []], +]; + +for (const [name, value] of unusableClaudeEfforts) { + for (const carrier of ["nested", "flat"]) { + test(`unusable ${name} Claude effort retains ${carrier} original effort (#18746)`, () => { + const body = { + output_config: { effort: value }, + ...(carrier === "nested" + ? { reasoning: { effort: "high" }, reasoning_effort: "low" } + : { reasoning_effort: "high" }), + }; + assert.equal(codexMetadata(body).requestedThinkingEffort, "high"); + }); + } +} + +test("empty Codex request omits own requestedThinkingEffort metadata (#18746)", () => { + assert.equal(Object.hasOwn(codexMetadata({}), "requestedThinkingEffort"), false); +}); + +test("missing Codex request retains catalog metadata without an effort property", () => { + assert.deepEqual(codexMetadata(), { + model: "gpt-5.6-sol", + supportedThinkingEfforts: ["low", "medium", "high", "xhigh"], + defaultThinkingEffort: "high", + resolvedThinkingEffort: undefined, + }); +}); + +test("Codex metadata capture does not mutate input credentials or request", () => { + const credentials = { providerSpecificData: { requestDefaults: { reasoningEffort: "low" } } }; + const requestBody = { output_config: { effort: "medium" } }; + const originalCredentials = structuredClone(credentials); + const originalRequest = structuredClone(requestBody); + const out = resolveExecutionCredentials({ ...codexBase, credentials, requestBody }); + assert.ok(Object.hasOwn(out.providerSpecificData, "_omnirouteCodexThinking")); + assert.deepEqual(credentials, originalCredentials); + assert.deepEqual(requestBody, originalRequest); +}); + +test("non-Codex credentials never capture Codex effort metadata", () => { + const out = resolveExecutionCredentials({ + ...codexBase, + provider: "openai", + requestBody: { output_config: { effort: "medium" } }, + }); + assert.deepEqual(out.providerSpecificData, { foo: "bar" }); +}); diff --git a/tests/unit/chatcore/claude-codex-effort-18746.test.ts b/tests/unit/chatcore/claude-codex-effort-18746.test.ts new file mode 100644 index 000000000000..ec418c3bb563 --- /dev/null +++ b/tests/unit/chatcore/claude-codex-effort-18746.test.ts @@ -0,0 +1,167 @@ +import assert from "node:assert/strict"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; +import test from "node:test"; +import { z } from "zod"; + +const environmentKeys = [ + "DATA_DIR", + "DISABLE_SQLITE_AUTO_BACKUP", + "REQUIRE_API_KEY", + "API_KEY_SECRET", + "OMNIROUTE_RELAY_BACKEND", + "ENABLE_TLS_FINGERPRINT", +]; +const originalEnvironment = new Map( + environmentKeys.map((key) => [key, process.env[key]]) +); +const dataDir = fs.mkdtempSync(path.join(os.tmpdir(), "claude-codex-effort-18746-")); +process.env.DATA_DIR = dataDir; +process.env.DISABLE_SQLITE_AUTO_BACKUP = "true"; +process.env.REQUIRE_API_KEY = "false"; +process.env.API_KEY_SECRET = "test-only-claude-codex-effort-18746"; +process.env.OMNIROUTE_RELAY_BACKEND = "ts"; +process.env.ENABLE_TLS_FINGERPRINT = "false"; + +const wireSchema = z.object({ + model: z.string(), + reasoning: z.object({ effort: z.string(), summary: z.string().optional() }), +}); +const captured: Array> = []; +const originalFetch = globalThis.fetch; +const mockFetch: typeof globalThis.fetch = async (url, init) => { + // Never delegate to real fetch, including unexpected startup/provider requests. + assert.equal(String(url), "https://chatgpt.com/backend-api/codex/responses"); + assert.equal(init?.method, "POST"); + assert.equal(typeof init?.body, "string"); + const parsed: unknown = JSON.parse(String(init.body)); + captured.push(wireSchema.parse(parsed)); + const response = { + id: "resp_18746", + object: "response", + status: "completed", + model: "gpt-5.6-sol", + output: [ + { + type: "message", + id: "msg_18746", + role: "assistant", + status: "completed", + content: [{ type: "output_text", text: "OK", annotations: [] }], + }, + ], + usage: { input_tokens: 5, output_tokens: 1, total_tokens: 6 }, + }; + const events = [ + { type: "response.output_text.delta", delta: "OK", output_index: 0, content_index: 0 }, + { type: "response.completed", response }, + ]; + return new Response(events.map((event) => `data: ${JSON.stringify(event)}\n\n`).join(""), { + headers: { "Content-Type": "text/event-stream" }, + }); +}; +globalThis.fetch = mockFetch; + +const { resetDbInstance } = await import("../../../src/lib/db/core.ts"); +const { createProviderConnection } = await import("../../../src/lib/db/providers.ts"); +const { updateSettings } = await import("../../../src/lib/db/settings.ts"); +const { waitForCallLogSaves } = await import("../../../src/lib/usage/callLogs.ts"); +const { normalizeCodexModelsResponse } = + await import("../../../src/app/api/providers/[id]/models/discovery/codex.ts"); +const { persistDiscoveredModels } = + await import("../../../src/lib/providerModels/modelDiscovery.ts"); +const { getThinkingBudgetConfig, setThinkingBudgetConfig, ThinkingMode } = + await import("../../../open-sse/services/thinkingBudget.ts"); +const { POST } = await import("../../../src/app/api/v1/messages/route.ts"); +const originalThinkingConfig = getThinkingBudgetConfig(); + +test.before(async () => { + // proxyFetch installs a global wrapper during imports; replace it at the I/O seam. + globalThis.fetch = mockFetch; + setThinkingBudgetConfig({ mode: ThinkingMode.PASSTHROUGH }); + await updateSettings({ semanticCacheEnabled: false }); + const connection = await createProviderConnection({ + provider: "codex", + authType: "oauth", + name: "claude-codex-effort-18746", + accessToken: "test-only-codex-token", + isActive: true, + providerSpecificData: { requestDefaults: { reasoningEffort: "low" } }, + }); + await persistDiscoveredModels( + "codex", + connection.id, + normalizeCodexModelsResponse({ + models: [ + { + slug: "gpt-5.6-sol", + supported_reasoning_levels: ["low", "medium", "high", "xhigh"], + default_reasoning_level: "high", + }, + ], + }) + ); +}); + +test.after(async () => { + assert.ok(await waitForCallLogSaves(5_000), "call-log writes must finish before DB cleanup"); + globalThis.fetch = originalFetch; + setThinkingBudgetConfig(originalThinkingConfig); + resetDbInstance(); + fs.rmSync(dataDir, { recursive: true, force: true }); + for (const [key, value] of originalEnvironment) { + if (value === undefined) delete process.env[key]; + else process.env[key] = value; + } +}); + +test("Messages route sends Claude medium effort to Codex despite connection default low (#18746)", async () => { + const result = await POST( + new Request("http://localhost/api/v1/messages", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + model: "cx/gpt-5.6-sol", + max_tokens: 1024, + stream: true, + messages: [{ role: "user", content: "Reply briefly." }], + thinking: { type: "adaptive" }, + output_config: { effort: "medium" }, + }), + }) + ); + const responseText = await result.text(); + assert.equal(result.status, 200, responseText); + assert.equal(captured.length, 1, "the real pipeline must dispatch exactly one Codex request"); + assert.equal(captured[0].model, "gpt-5.6-sol"); + assert.equal(captured[0].reasoning.effort, "medium"); + assert.equal(captured[0].reasoning.summary, "auto"); +}); + +test("Messages route preserves nested high effort when the Claude carrier is empty (#18746)", async () => { + const before = captured.length; + const result = await POST( + new Request("http://localhost/api/v1/messages", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + model: "cx/gpt-5.6-sol", + max_tokens: 1024, + stream: true, + messages: [{ role: "user", content: "Reply briefly to the mixed-carrier request." }], + thinking: { type: "adaptive" }, + output_config: { effort: "" }, + reasoning: { effort: "high" }, + }), + }) + ); + const responseText = await result.text(); + assert.equal(result.status, 200, responseText); + assert.equal(captured.length, before + 1, "the mixed-carrier request must reach Codex once"); + const wire = captured.at(-1); + assert.ok(wire); + assert.equal(wire.model, "gpt-5.6-sol"); + assert.equal(wire.reasoning.effort, "high"); + assert.equal(wire.reasoning.summary, "auto"); +}); diff --git a/tests/unit/codex-reasoning-wire-whitelist.test.ts b/tests/unit/codex-reasoning-wire-whitelist.test.ts index 2ef09c3e1883..749470fffde0 100644 --- a/tests/unit/codex-reasoning-wire-whitelist.test.ts +++ b/tests/unit/codex-reasoning-wire-whitelist.test.ts @@ -166,3 +166,122 @@ test("catalog-backed enabled:false with no effort resolves to none and drops oth ); assert.deepEqual(body.reasoning, { effort: "none" }); }); + +const catalogThinking = { + model: "gpt-5.6-sol", + supportedThinkingEfforts: ["low", "medium", "high", "xhigh", "max"], + defaultThinkingEffort: "high", +}; + +const unusableEfforts: Array<[string, unknown]> = [ + ["undefined", undefined], + ["null", null], + ["empty", ""], + ["blank", " "], + // Only non-budget numbers are unusable: a safe non-negative integer is a valid custom + // effort budget on the Codex wire (#18329) and is restored as the caller's own choice. + ["negative number", -1], + ["fractional number", 1.5], + ["NaN", Number.NaN], + ["object", {}], + ["array", []], +]; + +for (const [name, value] of unusableEfforts) { + for (const carrier of ["nested", "flat"]) { + test(`own ${name} metadata falls back to ${carrier} translated effort (#18746)`, () => { + const body: Record = { + model: "gpt-5.6-sol", + ...(carrier === "nested" + ? { reasoning: { effort: "medium", summary: "auto" } } + : { reasoning_effort: "medium", reasoning: { summary: "auto" } }), + }; + applyCodexReasoningSelection( + "gpt-5.6-sol", + body, + { ...catalogThinking, requestedThinkingEffort: value }, + "low", + true + ); + assert.deepEqual(body.reasoning, { effort: "medium", summary: "auto" }); + }); + } +} + +test("missing effort metadata preserves translated effort over connection defaults", () => { + const body: Record = { model: "gpt-5.6-sol", reasoning_effort: "medium" }; + applyCodexReasoningSelection("gpt-5.6-sol", body, catalogThinking, "low", true); + assert.deepEqual(body.reasoning, { effort: "medium" }); +}); + +for (const allowDefaults of [true, false]) { + test(`original native max is restored only in passthrough mode (allowDefaults=${allowDefaults})`, () => { + const body: Record = { + model: "gpt-5.6-sol", + reasoning: { effort: "xhigh", summary: "auto" }, + }; + applyCodexReasoningSelection( + "gpt-5.6-sol", + body, + { ...catalogThinking, requestedThinkingEffort: "max" }, + "low", + allowDefaults + ); + assert.deepEqual(body.reasoning, { + effort: allowDefaults ? "max" : "xhigh", + summary: "auto", + }); + }); +} + +for (const forcedEffort of [undefined, "low"]) { + test(`catalog suffix outranks explicit effort; server force outranks suffix (force=${forcedEffort})`, () => { + const body: Record = { + model: "gpt-5.6-sol", + reasoning: { effort: "medium" }, + }; + applyCodexReasoningSelection( + "gpt-5.6-sol", + body, + { ...catalogThinking, requestedThinkingEffort: "medium", resolvedThinkingEffort: "high" }, + "low", + true, + forcedEffort + ); + assert.equal(body.model, "gpt-5.6-sol"); + assert.deepEqual(body.reasoning, { effort: forcedEffort ?? "high" }); + }); +} + +for (const disabled of [{ effort: "none" }, { enabled: false }]) { + test(`explicit ${JSON.stringify(disabled)} beats connection defaults with unusable metadata`, () => { + const body: Record = { model: "gpt-5.6-sol", reasoning: disabled }; + applyCodexReasoningSelection( + "gpt-5.6-sol", + body, + { ...catalogThinking, requestedThinkingEffort: undefined }, + "low", + true + ); + assert.deepEqual(body.reasoning, { effort: "none" }); + }); +} + +const defaultCases = [ + { connection: "low", catalog: "high", expected: "low" }, + { connection: undefined, catalog: "high", expected: "high" }, + { connection: undefined, catalog: undefined, expected: "medium" }, +]; +for (const { connection, catalog, expected } of defaultCases) { + test(`no-effort request uses connection=${connection}, catalog=${catalog}, fallback=${expected}`, () => { + const body: Record = { model: "gpt-5.6-sol" }; + applyCodexReasoningSelection( + "gpt-5.6-sol", + body, + { ...catalogThinking, defaultThinkingEffort: catalog }, + connection, + true + ); + assert.deepEqual(body.reasoning, { effort: expected }); + }); +} From a3e5dfbd241e86fee046d7e2a6e970e43cbec0b8 Mon Sep 17 00:00:00 2001 From: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> Date: Wed, 4 Nov 2026 13:44:22 -0300 Subject: [PATCH 18/20] feat(dashboard): add host and process system metrics cards to home page (#18393) --- .../18393-home-system-metrics-cards.md | 1 + docs/openapi.yaml | 65 ++++++ skills/omni-db-backups/SKILL.md | 11 + .../home/HomeSystemMetricsCards.tsx | 217 ++++++++++++++++++ src/app/(dashboard)/home/page.tsx | 4 + src/app/api/system/metrics/route.ts | 123 ++++++++++ src/i18n/messages/am.json | 11 +- src/i18n/messages/ar.json | 11 +- src/i18n/messages/az.json | 11 +- src/i18n/messages/bg.json | 11 +- src/i18n/messages/bn.json | 11 +- src/i18n/messages/bs.json | 11 +- src/i18n/messages/cs.json | 11 +- src/i18n/messages/da.json | 11 +- src/i18n/messages/de.json | 11 +- src/i18n/messages/el.json | 11 +- src/i18n/messages/en.json | 11 +- src/i18n/messages/es.json | 11 +- src/i18n/messages/et.json | 11 +- src/i18n/messages/fa.json | 11 +- src/i18n/messages/fi.json | 11 +- src/i18n/messages/fr.json | 11 +- src/i18n/messages/ga.json | 11 +- src/i18n/messages/gu.json | 11 +- src/i18n/messages/ha.json | 11 +- src/i18n/messages/he.json | 11 +- src/i18n/messages/hi.json | 11 +- src/i18n/messages/hr.json | 11 +- src/i18n/messages/hu.json | 11 +- src/i18n/messages/hy.json | 11 +- src/i18n/messages/id.json | 11 +- src/i18n/messages/ig.json | 11 +- src/i18n/messages/it.json | 11 +- src/i18n/messages/ja.json | 11 +- src/i18n/messages/ka.json | 11 +- src/i18n/messages/km.json | 11 +- src/i18n/messages/kn.json | 11 +- src/i18n/messages/ko.json | 11 +- src/i18n/messages/lt.json | 11 +- src/i18n/messages/lv.json | 11 +- src/i18n/messages/ml.json | 11 +- src/i18n/messages/mr.json | 11 +- src/i18n/messages/ms.json | 11 +- src/i18n/messages/mt.json | 11 +- src/i18n/messages/my.json | 11 +- src/i18n/messages/ne.json | 11 +- src/i18n/messages/nl.json | 11 +- src/i18n/messages/no.json | 11 +- src/i18n/messages/or.json | 11 +- src/i18n/messages/pa.json | 11 +- src/i18n/messages/phi.json | 11 +- src/i18n/messages/pl.json | 11 +- src/i18n/messages/pt-BR.json | 11 +- src/i18n/messages/pt.json | 11 +- src/i18n/messages/ro.json | 11 +- src/i18n/messages/ru.json | 11 +- src/i18n/messages/si.json | 11 +- src/i18n/messages/sk.json | 11 +- src/i18n/messages/sl.json | 11 +- src/i18n/messages/sr.json | 11 +- src/i18n/messages/sv.json | 11 +- src/i18n/messages/sw.json | 11 +- src/i18n/messages/ta.json | 11 +- src/i18n/messages/te.json | 11 +- src/i18n/messages/th.json | 11 +- src/i18n/messages/tr.json | 11 +- src/i18n/messages/uk-UA.json | 11 +- src/i18n/messages/ur.json | 11 +- src/i18n/messages/uz.json | 11 +- src/i18n/messages/vi.json | 11 +- src/i18n/messages/yo.json | 11 +- src/i18n/messages/zh-CN.json | 11 +- src/i18n/messages/zh-TW.json | 11 +- tests/unit/system-metrics-route.test.ts | 127 ++++++++++ .../ui/home-system-metrics-cards.test.tsx | 174 ++++++++++++++ 75 files changed, 1392 insertions(+), 67 deletions(-) create mode 100644 changelog.d/features/18393-home-system-metrics-cards.md create mode 100644 src/app/(dashboard)/home/HomeSystemMetricsCards.tsx create mode 100644 src/app/api/system/metrics/route.ts create mode 100644 tests/unit/system-metrics-route.test.ts create mode 100644 tests/unit/ui/home-system-metrics-cards.test.tsx diff --git a/changelog.d/features/18393-home-system-metrics-cards.md b/changelog.d/features/18393-home-system-metrics-cards.md new file mode 100644 index 000000000000..4f574925cc57 --- /dev/null +++ b/changelog.d/features/18393-home-system-metrics-cards.md @@ -0,0 +1 @@ +- **feat(dashboard):** the home page gets two cards with host and OmniRoute process CPU and memory, served by the new management-tier `GET /api/system/metrics`; the cards poll every 3.5 s and pause while the tab is hidden ([#18393](https://github.com/cheaper-inference/OmniRoute/pull/18393)) — thanks @rafacpti23 diff --git a/docs/openapi.yaml b/docs/openapi.yaml index 869f2ee4bcb4..f769fbeb2017 100644 --- a/docs/openapi.yaml +++ b/docs/openapi.yaml @@ -14524,6 +14524,71 @@ paths: responses: "200": description: OK + /api/system/metrics: + get: + tags: + - System + summary: Host and OmniRoute process CPU and memory + description: >- + Management-tier (dashboard session or management API key) snapshot behind the home-page + system metrics cards. CPU percentages are deltas since the previous call that was at least + 400 ms earlier, so the first call after process start (and any call inside the 400 ms + window) returns the last computed value, which is `0` before the first delta. Host values + come from `os.cpus()` / `os.totalmem()` / `os.freemem()` and describe the host, not a + container limit; the process CPU percentage is normalised by the host core count. Sent + with `Cache-Control: no-store, no-cache, must-revalidate`. + responses: + "200": + description: Host and process metrics + content: + application/json: + schema: + type: object + required: [host, process] + properties: + host: + type: object + required: [cpuPercent, memory, cpuCount, platform] + properties: + cpuPercent: + type: number + minimum: 0 + maximum: 100 + description: Host CPU busy share since the previous sample, 0-100. + memory: + type: object + required: [totalBytes, usedBytes, freeBytes, usedPercent] + properties: + totalBytes: { type: integer } + usedBytes: { type: integer } + freeBytes: { type: integer } + usedPercent: { type: number, minimum: 0, maximum: 100 } + cpuCount: { type: integer, minimum: 0 } + platform: + type: string + description: Node.js `process.platform`. + process: + type: object + required: [cpuPercent, memory, uptimeSeconds, pid] + properties: + cpuPercent: + type: number + minimum: 0 + maximum: 100 + description: OmniRoute process CPU share of the whole host, 0-100. + memory: + type: object + required: [rssBytes, heapUsedBytes, heapTotalBytes] + properties: + rssBytes: { type: integer } + heapUsedBytes: { type: integer } + heapTotalBytes: { type: integer } + uptimeSeconds: { type: integer } + pid: { type: integer } + "401": + $ref: "#/components/responses/ManagementAuthenticationRequired" + "403": + $ref: "#/components/responses/ManagementInvalidToken" /api/system/version: get: tags: diff --git a/skills/omni-db-backups/SKILL.md b/skills/omni-db-backups/SKILL.md index 148bc02c67cd..032604b6a47e 100644 --- a/skills/omni-db-backups/SKILL.md +++ b/skills/omni-db-backups/SKILL.md @@ -34,6 +34,17 @@ curl -X POST http://localhost:20128/api/system/env/repair \ -d '{}' ``` +### GET /api/system/metrics + +Host and OmniRoute process CPU and memory + +Management-tier (dashboard session or management API key) snapshot behind the home-page system metrics cards. CPU percentages are deltas since the previous call that was at least 400 ms earlier, so the first call after process start (and any call inside the 400 ms window) returns the last computed value, which is `0` before the first delta. Host values come from `os.cpus()` / `os.totalmem()` / `os.freemem()` and describe the host, not a container limit; the process CPU percentage is normalised by the host core count. Sent with `Cache-Control: no-store, no-cache, must-revalidate`. + +```bash +curl http://localhost:20128/api/system/metrics \ + -H "Authorization: Bearer $OMNIROUTE_TOKEN" +``` + ### GET /api/system/version GET system › version diff --git a/src/app/(dashboard)/home/HomeSystemMetricsCards.tsx b/src/app/(dashboard)/home/HomeSystemMetricsCards.tsx new file mode 100644 index 000000000000..3e154a4a5a70 --- /dev/null +++ b/src/app/(dashboard)/home/HomeSystemMetricsCards.tsx @@ -0,0 +1,217 @@ +"use client"; + +import { useCallback, useEffect, useState } from "react"; +import { useTranslations } from "next-intl"; +import Icon from "@/shared/components/Icon"; + +export interface SystemMetrics { + host: { + cpuPercent: number; + memory: { + totalBytes: number; + usedBytes: number; + freeBytes: number; + usedPercent: number; + }; + cpuCount: number; + platform: string; + }; + process: { + cpuPercent: number; + memory: { + rssBytes: number; + heapUsedBytes: number; + heapTotalBytes: number; + }; + uptimeSeconds: number; + pid: number; + }; +} + +function formatBytes(bytes: number = 0): string { + if (bytes <= 0) return "0 MB"; + const gb = bytes / (1024 * 1024 * 1024); + if (gb >= 1) { + return `${gb.toFixed(1)} GB`; + } + const mb = bytes / (1024 * 1024); + return `${Math.round(mb)} MB`; +} + +function getUsageBarColor(percent: number): string { + if (percent >= 85) return "bg-error"; + if (percent >= 65) return "bg-warning"; + return "bg-success"; +} + +const POLL_INTERVAL_MS = 3500; + +function UsageBar({ percent }: { percent: number }) { + return ( +

+
+
+ ); +} + +interface MetricCardProps { + icon: string; + title: string; + activeTitle: string; + subtitle: string; + hostLabel: string; + value: string; + percent: number; + breakdownLabel: string; + breakdownValue: string; +} + +function MetricCard({ + icon, + title, + activeTitle, + subtitle, + hostLabel, + value, + percent, + breakdownLabel, + breakdownValue, +}: MetricCardProps) { + return ( +
+
+
+
+ +
+
+
+ {title} + +
+ {subtitle} +
+
+ +
+ {value} + + {hostLabel} + +
+
+ + + + {/* Breakdown for OmniRoute process */} +
+ {breakdownLabel} + {breakdownValue} +
+
+ ); +} + +function useSystemMetrics(): SystemMetrics | null { + const [metrics, setMetrics] = useState(null); + + const fetchMetrics = useCallback(async (signal?: AbortSignal) => { + try { + const res = await fetch("/api/system/metrics", { + signal, + cache: "no-store", + }); + if (!res.ok) return; + const data = (await res.json()) as SystemMetrics; + setMetrics(data); + } catch (err) { + const isAbort = err instanceof DOMException && err.name === "AbortError"; + if (!isAbort) { + // Silently tolerate transient scrape errors + } + } + }, []); + + useEffect(() => { + let cancelled = false; + let timeoutId: ReturnType | null = null; + let controller: AbortController | null = null; + + const tick = async () => { + if (document.visibilityState === "visible") { + const currentController = new AbortController(); + controller = currentController; + await fetchMetrics(currentController.signal); + if (controller === currentController) controller = null; + } + if (!cancelled) { + timeoutId = setTimeout(tick, POLL_INTERVAL_MS); + } + }; + + tick(); + + return () => { + cancelled = true; + if (timeoutId) clearTimeout(timeoutId); + controller?.abort(); + }; + }, [fetchMetrics]); + + return metrics; +} + +export default function HomeSystemMetricsCards() { + const t = useTranslations("home"); + const metrics = useSystemMetrics(); + const loading = t("systemMetricsLoading"); + const placeholder = "--"; + const host = metrics?.host; + const proc = metrics?.process; + + return ( +
+ + +
+ ); +} diff --git a/src/app/(dashboard)/home/page.tsx b/src/app/(dashboard)/home/page.tsx index f55a0ba6b8ef..cbd0c003c764 100644 --- a/src/app/(dashboard)/home/page.tsx +++ b/src/app/(dashboard)/home/page.tsx @@ -7,6 +7,7 @@ import CheaperInferenceSponsorBanner from "../dashboard/CheaperInferenceSponsorB import VscodeCopilotBanner from "../dashboard/VscodeCopilotBanner"; import NewsBanner from "../dashboard/NewsBanner"; import FirstRunReadinessCard from "../dashboard/FirstRunReadinessCard"; +import HomeSystemMetricsCards from "./HomeSystemMetricsCards"; import UnpricedUsageBanner from "../dashboard/UnpricedUsageBanner"; export const dynamic = "force-dynamic"; @@ -24,6 +25,9 @@ export default async function HomePage() { +
+ +
); diff --git a/src/app/api/system/metrics/route.ts b/src/app/api/system/metrics/route.ts new file mode 100644 index 000000000000..5c68a5f9e497 --- /dev/null +++ b/src/app/api/system/metrics/route.ts @@ -0,0 +1,123 @@ +import { NextResponse } from "next/server"; +import os from "node:os"; + +export const dynamic = "force-dynamic"; + +interface CpuSnapshot { + total: number; + idle: number; +} + +let lastSample: { + time: number; + processCpu: NodeJS.CpuUsage; + hostCpu: CpuSnapshot; +} | null = null; + +let cachedMetrics = { + hostCpuPercent: 0, + processCpuPercent: 0, +}; + +function getHostCpuSnapshot(): CpuSnapshot { + let total = 0; + let idle = 0; + const cpus = os.cpus() || []; + for (const cpu of cpus) { + for (const type in cpu.times) { + total += cpu.times[type as keyof typeof cpu.times]; + } + idle += cpu.times.idle; + } + return { total, idle }; +} + +function updateCpuMetrics() { + const now = Date.now(); + const currentHost = getHostCpuSnapshot(); + const currentProcess = process.cpuUsage(); + + if (!lastSample) { + lastSample = { + time: now, + processCpu: currentProcess, + hostCpu: currentHost, + }; + return cachedMetrics; + } + + const timeDeltaMs = now - lastSample.time; + + if (timeDeltaMs >= 400) { + const hostTotalDelta = currentHost.total - lastSample.hostCpu.total; + const hostIdleDelta = currentHost.idle - lastSample.hostCpu.idle; + const hostCpuPercent = + hostTotalDelta > 0 + ? Math.max(0, Math.min(100, Math.round((1 - hostIdleDelta / hostTotalDelta) * 1000) / 10)) + : cachedMetrics.hostCpuPercent; + + const processDelta = process.cpuUsage(lastSample.processCpu); + const cpuCount = Math.max(1, (os.cpus() || []).length); + const totalAvailableMicros = timeDeltaMs * 1000 * cpuCount; + const processMicros = processDelta.user + processDelta.system; + const processCpuPercent = + totalAvailableMicros > 0 + ? Math.max(0, Math.min(100, Math.round((processMicros / totalAvailableMicros) * 1000) / 10)) + : cachedMetrics.processCpuPercent; + + cachedMetrics = { + hostCpuPercent, + processCpuPercent, + }; + + lastSample = { + time: now, + processCpu: currentProcess, + hostCpu: currentHost, + }; + } + + return cachedMetrics; +} + +export async function GET() { + const { hostCpuPercent, processCpuPercent } = updateCpuMetrics(); + + const totalMem = os.totalmem(); + const freeMem = os.freemem(); + const usedMem = Math.max(0, totalMem - freeMem); + const hostMemPercent = totalMem > 0 ? Math.round((usedMem / totalMem) * 1000) / 10 : 0; + + const procMem = process.memoryUsage(); + + return NextResponse.json( + { + host: { + cpuPercent: hostCpuPercent, + memory: { + totalBytes: totalMem, + usedBytes: usedMem, + freeBytes: freeMem, + usedPercent: hostMemPercent, + }, + cpuCount: (os.cpus() || []).length, + platform: process.platform, + }, + process: { + cpuPercent: processCpuPercent, + memory: { + rssBytes: procMem.rss, + heapUsedBytes: procMem.heapUsed, + heapTotalBytes: procMem.heapTotal, + }, + uptimeSeconds: Math.round(process.uptime()), + pid: process.pid, + }, + }, + { + headers: { + "Cache-Control": "no-store, no-cache, must-revalidate", + }, + } + ); +} diff --git a/src/i18n/messages/am.json b/src/i18n/messages/am.json index 08425a6a5b5a..6ca88ecc9112 100644 --- a/src/i18n/messages/am.json +++ b/src/i18n/messages/am.json @@ -2016,7 +2016,16 @@ "unpricedUsageModelStats": "{requests, plural, one {# ጥያቄ} other {# ጥያቄዎች}}, {keys, plural, one {# የጥቅም ገደብ ያለ ቁልፍ} other {# የጥቅም ገደብ ያሉ ቁልፎች}}", "unpricedUsageMore": "…እና {count} ተጨማሪ", "unpricedUsageConfigure": "ዋጋን ያቀናብሩ", - "unpricedUsageDismiss": "ዝጋ" + "unpricedUsageDismiss": "ዝጋ", + "systemMetricsTitle": "System metrics", + "systemMetricsActive": "Monitoring active", + "systemMetricsLoading": "Loading...", + "systemMetricsCpu": "CPU", + "systemMetricsMemory": "Memory", + "systemMetricsHost": "VPS / Host", + "systemMetricsCpuCount": "{count} vCPUs • {platform}", + "systemMetricsProcessCpu": "OmniRoute (process)", + "systemMetricsProcessMemory": "OmniRoute (RSS / Heap)" }, "analytics": { "title": "ትንታኔ", diff --git a/src/i18n/messages/ar.json b/src/i18n/messages/ar.json index f1136e07bb8e..c96314b8da30 100644 --- a/src/i18n/messages/ar.json +++ b/src/i18n/messages/ar.json @@ -2016,7 +2016,16 @@ "unpricedUsageModelStats": "{requests, plural, zero {# طلبات} one {# طلب} two {# طلبان} few {# طلبات} many {# طلبًا} other {# طلب}}، {keys, plural, zero {# مفاتيح محدودة} one {# مفتاح محدود} two {# مفتاحان محدودان} few {# مفاتيح محدودة} many {# مفتاحًا محدودًا} other {# مفتاح محدود}}", "unpricedUsageMore": "…و{count} أخرى", "unpricedUsageConfigure": "تكوين الأسعار", - "unpricedUsageDismiss": "إغلاق" + "unpricedUsageDismiss": "إغلاق", + "systemMetricsTitle": "System metrics", + "systemMetricsActive": "Monitoring active", + "systemMetricsLoading": "Loading...", + "systemMetricsCpu": "CPU", + "systemMetricsMemory": "Memory", + "systemMetricsHost": "VPS / Host", + "systemMetricsCpuCount": "{count} vCPUs • {platform}", + "systemMetricsProcessCpu": "OmniRoute (process)", + "systemMetricsProcessMemory": "OmniRoute (RSS / Heap)" }, "analytics": { "title": "التحليلات", diff --git a/src/i18n/messages/az.json b/src/i18n/messages/az.json index a26c6d7165e1..c23d54a6d99a 100644 --- a/src/i18n/messages/az.json +++ b/src/i18n/messages/az.json @@ -2016,7 +2016,16 @@ "unpricedUsageModelStats": "{requests, plural, one {# sorğu} other {# sorğular}}, {keys, plural, one {# limitli açar} other {# limitli açarlar}}", "unpricedUsageMore": "…və daha {count}", "unpricedUsageConfigure": "Qiymətləri təyin et", - "unpricedUsageDismiss": "Bağla" + "unpricedUsageDismiss": "Bağla", + "systemMetricsTitle": "System metrics", + "systemMetricsActive": "Monitoring active", + "systemMetricsLoading": "Loading...", + "systemMetricsCpu": "CPU", + "systemMetricsMemory": "Memory", + "systemMetricsHost": "VPS / Host", + "systemMetricsCpuCount": "{count} vCPUs • {platform}", + "systemMetricsProcessCpu": "OmniRoute (process)", + "systemMetricsProcessMemory": "OmniRoute (RSS / Heap)" }, "analytics": { "title": "Analitika", diff --git a/src/i18n/messages/bg.json b/src/i18n/messages/bg.json index c23bea735727..fd685e23f78f 100644 --- a/src/i18n/messages/bg.json +++ b/src/i18n/messages/bg.json @@ -2016,7 +2016,16 @@ "unpricedUsageModelStats": "{requests, plural, one {# заявка} other {# заявки}}, {keys, plural, one {# ограничен ключ} other {# ограничени ключа}}", "unpricedUsageMore": "…и още {count}", "unpricedUsageConfigure": "Настрой ценообразуването", - "unpricedUsageDismiss": "Отхвърли" + "unpricedUsageDismiss": "Отхвърли", + "systemMetricsTitle": "System metrics", + "systemMetricsActive": "Monitoring active", + "systemMetricsLoading": "Loading...", + "systemMetricsCpu": "CPU", + "systemMetricsMemory": "Memory", + "systemMetricsHost": "VPS / Host", + "systemMetricsCpuCount": "{count} vCPUs • {platform}", + "systemMetricsProcessCpu": "OmniRoute (process)", + "systemMetricsProcessMemory": "OmniRoute (RSS / Heap)" }, "analytics": { "title": "Анализ", diff --git a/src/i18n/messages/bn.json b/src/i18n/messages/bn.json index de90caf55392..a9e52041848b 100644 --- a/src/i18n/messages/bn.json +++ b/src/i18n/messages/bn.json @@ -2016,7 +2016,16 @@ "unpricedUsageModelStats": "{requests, plural, one {#টি অনুরোধ} other {#টি অনুরোধ}}, {keys, plural, one {#টি সীমাবদ্ধ API কী} other {#টি সীমাবদ্ধ API কী}}", "unpricedUsageMore": "…এবং আরও {count}", "unpricedUsageConfigure": "মূল্য কনফিগার করুন", - "unpricedUsageDismiss": "বাতিল" + "unpricedUsageDismiss": "বাতিল", + "systemMetricsTitle": "System metrics", + "systemMetricsActive": "Monitoring active", + "systemMetricsLoading": "Loading...", + "systemMetricsCpu": "CPU", + "systemMetricsMemory": "Memory", + "systemMetricsHost": "VPS / Host", + "systemMetricsCpuCount": "{count} vCPUs • {platform}", + "systemMetricsProcessCpu": "OmniRoute (process)", + "systemMetricsProcessMemory": "OmniRoute (RSS / Heap)" }, "analytics": { "title": "বিশ্লেষণ", diff --git a/src/i18n/messages/bs.json b/src/i18n/messages/bs.json index 834603dde8c0..a25036db41a0 100644 --- a/src/i18n/messages/bs.json +++ b/src/i18n/messages/bs.json @@ -2016,7 +2016,16 @@ "unpricedUsageModelStats": "{requests, plural, one {# zahtjev} other {# zahtjeva}}, {keys, plural, one {# ograničen ključ} other {# ograničena ključa}}", "unpricedUsageMore": "…i još {count}", "unpricedUsageConfigure": "Konfiguriraj cijene", - "unpricedUsageDismiss": "Zatvori" + "unpricedUsageDismiss": "Zatvori", + "systemMetricsTitle": "System metrics", + "systemMetricsActive": "Monitoring active", + "systemMetricsLoading": "Loading...", + "systemMetricsCpu": "CPU", + "systemMetricsMemory": "Memory", + "systemMetricsHost": "VPS / Host", + "systemMetricsCpuCount": "{count} vCPUs • {platform}", + "systemMetricsProcessCpu": "OmniRoute (process)", + "systemMetricsProcessMemory": "OmniRoute (RSS / Heap)" }, "analytics": { "title": "Analitika", diff --git a/src/i18n/messages/cs.json b/src/i18n/messages/cs.json index 49c5904ac278..eb8f5c027c18 100644 --- a/src/i18n/messages/cs.json +++ b/src/i18n/messages/cs.json @@ -2016,7 +2016,16 @@ "unpricedUsageModelStats": "{requests, plural, one {# požadavek} few {# požadavky} other {# požadavků}}, {keys, plural, one {# omezený klíč} few {# omezené klíče} other {# omezených klíčů}}", "unpricedUsageMore": "…a dalších {count}", "unpricedUsageConfigure": "Nastavit ceny", - "unpricedUsageDismiss": "Zavřít" + "unpricedUsageDismiss": "Zavřít", + "systemMetricsTitle": "System metrics", + "systemMetricsActive": "Monitoring active", + "systemMetricsLoading": "Loading...", + "systemMetricsCpu": "CPU", + "systemMetricsMemory": "Memory", + "systemMetricsHost": "VPS / Host", + "systemMetricsCpuCount": "{count} vCPUs • {platform}", + "systemMetricsProcessCpu": "OmniRoute (process)", + "systemMetricsProcessMemory": "OmniRoute (RSS / Heap)" }, "analytics": { "title": "Analytika", diff --git a/src/i18n/messages/da.json b/src/i18n/messages/da.json index 254d568a408b..fb6f9703213b 100644 --- a/src/i18n/messages/da.json +++ b/src/i18n/messages/da.json @@ -2016,7 +2016,16 @@ "unpricedUsageModelStats": "{requests, plural, one {# forespørgsel} other {# forespørgsler}}, {keys, plural, one {# begrænset nøgle} other {# begrænsede nøgler}}", "unpricedUsageMore": "…og {count} mere", "unpricedUsageConfigure": "Konfigurer priser", - "unpricedUsageDismiss": "Luk" + "unpricedUsageDismiss": "Luk", + "systemMetricsTitle": "System metrics", + "systemMetricsActive": "Monitoring active", + "systemMetricsLoading": "Loading...", + "systemMetricsCpu": "CPU", + "systemMetricsMemory": "Memory", + "systemMetricsHost": "VPS / Host", + "systemMetricsCpuCount": "{count} vCPUs • {platform}", + "systemMetricsProcessCpu": "OmniRoute (process)", + "systemMetricsProcessMemory": "OmniRoute (RSS / Heap)" }, "analytics": { "title": "Analyse", diff --git a/src/i18n/messages/de.json b/src/i18n/messages/de.json index 81362a31dd7b..450f785a4dea 100644 --- a/src/i18n/messages/de.json +++ b/src/i18n/messages/de.json @@ -1939,7 +1939,16 @@ "unpricedUsageModelStats": "{requests, plural, one {# Anfrage} other {# Anfragen}}, {keys, plural, one {# begrenzter Schlüssel} other {# begrenzte Schlüssel}}", "unpricedUsageMore": "…und {count} weitere", "unpricedUsageConfigure": "Preise konfigurieren", - "unpricedUsageDismiss": "Schließen" + "unpricedUsageDismiss": "Schließen", + "systemMetricsTitle": "System metrics", + "systemMetricsActive": "Monitoring active", + "systemMetricsLoading": "Loading...", + "systemMetricsCpu": "CPU", + "systemMetricsMemory": "Memory", + "systemMetricsHost": "VPS / Host", + "systemMetricsCpuCount": "{count} vCPUs • {platform}", + "systemMetricsProcessCpu": "OmniRoute (process)", + "systemMetricsProcessMemory": "OmniRoute (RSS / Heap)" }, "analytics": { "title": "Analytik", diff --git a/src/i18n/messages/el.json b/src/i18n/messages/el.json index 00ce67a665bf..945aed94a9ad 100644 --- a/src/i18n/messages/el.json +++ b/src/i18n/messages/el.json @@ -1939,7 +1939,16 @@ "unpricedUsageModelStats": "{requests, plural, one {# αίτημα} other {# αιτήματα}}, {keys, plural, one {# κλειδί με όριο} other {# κλειδιά με όριο}}", "unpricedUsageMore": "…και {count} ακόμη", "unpricedUsageConfigure": "Ρύθμιση τιμών", - "unpricedUsageDismiss": "Απόρριψη" + "unpricedUsageDismiss": "Απόρριψη", + "systemMetricsTitle": "System metrics", + "systemMetricsActive": "Monitoring active", + "systemMetricsLoading": "Loading...", + "systemMetricsCpu": "CPU", + "systemMetricsMemory": "Memory", + "systemMetricsHost": "VPS / Host", + "systemMetricsCpuCount": "{count} vCPUs • {platform}", + "systemMetricsProcessCpu": "OmniRoute (process)", + "systemMetricsProcessMemory": "OmniRoute (RSS / Heap)" }, "analytics": { "title": "Αναλυτικά στοιχεία", diff --git a/src/i18n/messages/en.json b/src/i18n/messages/en.json index d3745a5182c4..e6c8d9a0c254 100644 --- a/src/i18n/messages/en.json +++ b/src/i18n/messages/en.json @@ -2016,7 +2016,16 @@ "unpricedUsageModelStats": "{requests, plural, one {# request} other {# requests}}, {keys, plural, one {# limited key} other {# limited keys}}", "unpricedUsageMore": "…and {count} more", "unpricedUsageConfigure": "Configure pricing", - "unpricedUsageDismiss": "Dismiss" + "unpricedUsageDismiss": "Dismiss", + "systemMetricsTitle": "System metrics", + "systemMetricsActive": "Monitoring active", + "systemMetricsLoading": "Loading...", + "systemMetricsCpu": "CPU", + "systemMetricsMemory": "Memory", + "systemMetricsHost": "VPS / Host", + "systemMetricsCpuCount": "{count} vCPUs • {platform}", + "systemMetricsProcessCpu": "OmniRoute (process)", + "systemMetricsProcessMemory": "OmniRoute (RSS / Heap)" }, "analytics": { "title": "Analytics", diff --git a/src/i18n/messages/es.json b/src/i18n/messages/es.json index 7d909f8ce3b8..afccee9d443c 100644 --- a/src/i18n/messages/es.json +++ b/src/i18n/messages/es.json @@ -1939,7 +1939,16 @@ "unpricedUsageModelStats": "{requests, plural, one {# solicitud} other {# solicitudes}}, {keys, plural, one {# clave limitada} other {# claves limitadas}}", "unpricedUsageMore": "…y {count} más", "unpricedUsageConfigure": "Configurar precios", - "unpricedUsageDismiss": "Descartar" + "unpricedUsageDismiss": "Descartar", + "systemMetricsTitle": "System metrics", + "systemMetricsActive": "Monitoring active", + "systemMetricsLoading": "Loading...", + "systemMetricsCpu": "CPU", + "systemMetricsMemory": "Memory", + "systemMetricsHost": "VPS / Host", + "systemMetricsCpuCount": "{count} vCPUs • {platform}", + "systemMetricsProcessCpu": "OmniRoute (process)", + "systemMetricsProcessMemory": "OmniRoute (RSS / Heap)" }, "analytics": { "title": "Analítica", diff --git a/src/i18n/messages/et.json b/src/i18n/messages/et.json index eaa1cb049b58..ba9e9b58674b 100644 --- a/src/i18n/messages/et.json +++ b/src/i18n/messages/et.json @@ -1939,7 +1939,16 @@ "unpricedUsageModelStats": "{requests, plural, one {# päring} other {# päringut}}, {keys, plural, one {# piiratud võti} other {# piiratud võtit}}", "unpricedUsageMore": "…ja veel {count}", "unpricedUsageConfigure": "Määra hinnad", - "unpricedUsageDismiss": "Sulge" + "unpricedUsageDismiss": "Sulge", + "systemMetricsTitle": "System metrics", + "systemMetricsActive": "Monitoring active", + "systemMetricsLoading": "Loading...", + "systemMetricsCpu": "CPU", + "systemMetricsMemory": "Memory", + "systemMetricsHost": "VPS / Host", + "systemMetricsCpuCount": "{count} vCPUs • {platform}", + "systemMetricsProcessCpu": "OmniRoute (process)", + "systemMetricsProcessMemory": "OmniRoute (RSS / Heap)" }, "analytics": { "title": "Analüütika", diff --git a/src/i18n/messages/fa.json b/src/i18n/messages/fa.json index 132acf33044d..9989c8f4bef0 100644 --- a/src/i18n/messages/fa.json +++ b/src/i18n/messages/fa.json @@ -1939,7 +1939,16 @@ "unpricedUsageModelStats": "{requests, plural, one {# درخواست} other {# درخواست‌ها}}, {keys, plural, one {# کلید API محدود} other {# کلیدهای API محدود}}", "unpricedUsageMore": "…و {count} مورد دیگر", "unpricedUsageConfigure": "پیکربندی قیمت‌گذاری", - "unpricedUsageDismiss": "بستن" + "unpricedUsageDismiss": "بستن", + "systemMetricsTitle": "System metrics", + "systemMetricsActive": "Monitoring active", + "systemMetricsLoading": "Loading...", + "systemMetricsCpu": "CPU", + "systemMetricsMemory": "Memory", + "systemMetricsHost": "VPS / Host", + "systemMetricsCpuCount": "{count} vCPUs • {platform}", + "systemMetricsProcessCpu": "OmniRoute (process)", + "systemMetricsProcessMemory": "OmniRoute (RSS / Heap)" }, "analytics": { "title": "تحلیلها", diff --git a/src/i18n/messages/fi.json b/src/i18n/messages/fi.json index 60b54ceab82e..b407bd94a841 100644 --- a/src/i18n/messages/fi.json +++ b/src/i18n/messages/fi.json @@ -1939,7 +1939,16 @@ "unpricedUsageModelStats": "{requests, plural, one {# pyyntö} other {# pyyntöä}}, {keys, plural, one {# rajoitettu API-avain} other {# rajoitettua API-avainta}}", "unpricedUsageMore": "…ja {count} lisää", "unpricedUsageConfigure": "Määritä hinnat", - "unpricedUsageDismiss": "Sulje" + "unpricedUsageDismiss": "Sulje", + "systemMetricsTitle": "System metrics", + "systemMetricsActive": "Monitoring active", + "systemMetricsLoading": "Loading...", + "systemMetricsCpu": "CPU", + "systemMetricsMemory": "Memory", + "systemMetricsHost": "VPS / Host", + "systemMetricsCpuCount": "{count} vCPUs • {platform}", + "systemMetricsProcessCpu": "OmniRoute (process)", + "systemMetricsProcessMemory": "OmniRoute (RSS / Heap)" }, "analytics": { "title": "Analytiikka", diff --git a/src/i18n/messages/fr.json b/src/i18n/messages/fr.json index 8e9802c641e2..b9f7bab2b0c4 100644 --- a/src/i18n/messages/fr.json +++ b/src/i18n/messages/fr.json @@ -1939,7 +1939,16 @@ "unpricedUsageModelStats": "{requests, plural, one {# requête} other {# requêtes}}, {keys, plural, one {# clé limitée} other {# clés limitées}}", "unpricedUsageMore": "…et {count} de plus", "unpricedUsageConfigure": "Configurer la tarification", - "unpricedUsageDismiss": "Fermer" + "unpricedUsageDismiss": "Fermer", + "systemMetricsTitle": "System metrics", + "systemMetricsActive": "Monitoring active", + "systemMetricsLoading": "Loading...", + "systemMetricsCpu": "CPU", + "systemMetricsMemory": "Memory", + "systemMetricsHost": "VPS / Host", + "systemMetricsCpuCount": "{count} vCPUs • {platform}", + "systemMetricsProcessCpu": "OmniRoute (process)", + "systemMetricsProcessMemory": "OmniRoute (RSS / Heap)" }, "analytics": { "title": "Analyse", diff --git a/src/i18n/messages/ga.json b/src/i18n/messages/ga.json index 9431fd73b3af..3049dde75be9 100644 --- a/src/i18n/messages/ga.json +++ b/src/i18n/messages/ga.json @@ -1939,7 +1939,16 @@ "unpricedUsageModelStats": "{requests, plural, one {# iarratas} other {# iarratais}}, {keys, plural, one {# eochair theoranta} other {# eochracha theoranta}}", "unpricedUsageMore": "…agus {count} eile", "unpricedUsageConfigure": "Cumraigh praghsáil", - "unpricedUsageDismiss": "Dún" + "unpricedUsageDismiss": "Dún", + "systemMetricsTitle": "System metrics", + "systemMetricsActive": "Monitoring active", + "systemMetricsLoading": "Loading...", + "systemMetricsCpu": "CPU", + "systemMetricsMemory": "Memory", + "systemMetricsHost": "VPS / Host", + "systemMetricsCpuCount": "{count} vCPUs • {platform}", + "systemMetricsProcessCpu": "OmniRoute (process)", + "systemMetricsProcessMemory": "OmniRoute (RSS / Heap)" }, "analytics": { "title": "Anailísíocht", diff --git a/src/i18n/messages/gu.json b/src/i18n/messages/gu.json index ec310db40f21..46f9fa69bbf6 100644 --- a/src/i18n/messages/gu.json +++ b/src/i18n/messages/gu.json @@ -1939,7 +1939,16 @@ "unpricedUsageModelStats": "{requests, plural, one {# વિનંતી} other {# વિનંતીઓ}}, {keys, plural, one {# મર્યાદિત કી} other {# મર્યાદિત કીઓ}}", "unpricedUsageMore": "…અને {count} વધુ", "unpricedUsageConfigure": "કિંમત સેટ કરો", - "unpricedUsageDismiss": "બંદ કરો" + "unpricedUsageDismiss": "બંદ કરો", + "systemMetricsTitle": "System metrics", + "systemMetricsActive": "Monitoring active", + "systemMetricsLoading": "Loading...", + "systemMetricsCpu": "CPU", + "systemMetricsMemory": "Memory", + "systemMetricsHost": "VPS / Host", + "systemMetricsCpuCount": "{count} vCPUs • {platform}", + "systemMetricsProcessCpu": "OmniRoute (process)", + "systemMetricsProcessMemory": "OmniRoute (RSS / Heap)" }, "analytics": { "title": "વિશ્લેષણ", diff --git a/src/i18n/messages/ha.json b/src/i18n/messages/ha.json index 73ae0d9f2e1c..459f5ebad0e7 100644 --- a/src/i18n/messages/ha.json +++ b/src/i18n/messages/ha.json @@ -1939,7 +1939,16 @@ "unpricedUsageModelStats": "{requests, plural, one {# buƙata} other {# buƙatu}}, {keys, plural, one {# maɓalli mai iyaka} other {# maɓallan masu iyaka}}", "unpricedUsageMore": "…da {count} ƙari", "unpricedUsageConfigure": "Saita farashi", - "unpricedUsageDismiss": "Rufe" + "unpricedUsageDismiss": "Rufe", + "systemMetricsTitle": "System metrics", + "systemMetricsActive": "Monitoring active", + "systemMetricsLoading": "Loading...", + "systemMetricsCpu": "CPU", + "systemMetricsMemory": "Memory", + "systemMetricsHost": "VPS / Host", + "systemMetricsCpuCount": "{count} vCPUs • {platform}", + "systemMetricsProcessCpu": "OmniRoute (process)", + "systemMetricsProcessMemory": "OmniRoute (RSS / Heap)" }, "analytics": { "title": "Nazari", diff --git a/src/i18n/messages/he.json b/src/i18n/messages/he.json index ad3f07a57a25..d7d5cc76b17f 100644 --- a/src/i18n/messages/he.json +++ b/src/i18n/messages/he.json @@ -1939,7 +1939,16 @@ "unpricedUsageModelStats": "{requests, plural, one {# בקשה} other {# בקשות}}, {keys, plural, one {# מפתח מוגבל} other {# מפתחות מוגבלים}}", "unpricedUsageMore": "…ועוד {count}", "unpricedUsageConfigure": "הגדר תמחור", - "unpricedUsageDismiss": "התעלם" + "unpricedUsageDismiss": "התעלם", + "systemMetricsTitle": "System metrics", + "systemMetricsActive": "Monitoring active", + "systemMetricsLoading": "Loading...", + "systemMetricsCpu": "CPU", + "systemMetricsMemory": "Memory", + "systemMetricsHost": "VPS / Host", + "systemMetricsCpuCount": "{count} vCPUs • {platform}", + "systemMetricsProcessCpu": "OmniRoute (process)", + "systemMetricsProcessMemory": "OmniRoute (RSS / Heap)" }, "analytics": { "title": "אנליטיקס", diff --git a/src/i18n/messages/hi.json b/src/i18n/messages/hi.json index 6af1ddeab149..8c43ab370ce1 100644 --- a/src/i18n/messages/hi.json +++ b/src/i18n/messages/hi.json @@ -1939,7 +1939,16 @@ "unpricedUsageModelStats": "{requests, plural, one {# अनुरोध} other {# अनुरोध}}, {keys, plural, one {# सीमित कुंजी} other {# सीमित कुंजियाँ}}", "unpricedUsageMore": "…और {count} और", "unpricedUsageConfigure": "मूल्य निर्धारण कॉन्फ़िगर करें", - "unpricedUsageDismiss": "खारिज करें" + "unpricedUsageDismiss": "खारिज करें", + "systemMetricsTitle": "System metrics", + "systemMetricsActive": "Monitoring active", + "systemMetricsLoading": "Loading...", + "systemMetricsCpu": "CPU", + "systemMetricsMemory": "Memory", + "systemMetricsHost": "VPS / Host", + "systemMetricsCpuCount": "{count} vCPUs • {platform}", + "systemMetricsProcessCpu": "OmniRoute (process)", + "systemMetricsProcessMemory": "OmniRoute (RSS / Heap)" }, "analytics": { "title": "विश्लेषिकी", diff --git a/src/i18n/messages/hr.json b/src/i18n/messages/hr.json index 98070bbc0a12..bdf0079774d8 100644 --- a/src/i18n/messages/hr.json +++ b/src/i18n/messages/hr.json @@ -1939,7 +1939,16 @@ "unpricedUsageModelStats": "{requests, plural, one {# zahtjev} other {# zahtjeva}}, {keys, plural, one {# ograničeni ključ} other {# ograničenih ključeva}}", "unpricedUsageMore": "…i još {count}", "unpricedUsageConfigure": "Konfiguriraj cijene", - "unpricedUsageDismiss": "Zatvori" + "unpricedUsageDismiss": "Zatvori", + "systemMetricsTitle": "System metrics", + "systemMetricsActive": "Monitoring active", + "systemMetricsLoading": "Loading...", + "systemMetricsCpu": "CPU", + "systemMetricsMemory": "Memory", + "systemMetricsHost": "VPS / Host", + "systemMetricsCpuCount": "{count} vCPUs • {platform}", + "systemMetricsProcessCpu": "OmniRoute (process)", + "systemMetricsProcessMemory": "OmniRoute (RSS / Heap)" }, "analytics": { "title": "Analitika", diff --git a/src/i18n/messages/hu.json b/src/i18n/messages/hu.json index e0f21acc3f03..7a8b0fcf304f 100644 --- a/src/i18n/messages/hu.json +++ b/src/i18n/messages/hu.json @@ -1939,7 +1939,16 @@ "unpricedUsageModelStats": "{requests, plural, one {# kérés} other {# kérések}}, {keys, plural, one {# korlátozott kulcs} other {# korlátozott kulcsok}}", "unpricedUsageMore": "…és még {count} további", "unpricedUsageConfigure": "Árazás beállítása", - "unpricedUsageDismiss": "Bezárás" + "unpricedUsageDismiss": "Bezárás", + "systemMetricsTitle": "System metrics", + "systemMetricsActive": "Monitoring active", + "systemMetricsLoading": "Loading...", + "systemMetricsCpu": "CPU", + "systemMetricsMemory": "Memory", + "systemMetricsHost": "VPS / Host", + "systemMetricsCpuCount": "{count} vCPUs • {platform}", + "systemMetricsProcessCpu": "OmniRoute (process)", + "systemMetricsProcessMemory": "OmniRoute (RSS / Heap)" }, "analytics": { "title": "Analitika", diff --git a/src/i18n/messages/hy.json b/src/i18n/messages/hy.json index ec38a68e03a5..c93974be33ab 100644 --- a/src/i18n/messages/hy.json +++ b/src/i18n/messages/hy.json @@ -1939,7 +1939,16 @@ "unpricedUsageModelStats": "{requests, plural, one {# հարցում} other {# հարցումներ}}, {keys, plural, one {# սահմանափակ բանալի} other {# սահմանափակ բանալիներ}}", "unpricedUsageMore": "…և ևս {count}", "unpricedUsageConfigure": "Կարգավորել գները", - "unpricedUsageDismiss": "Փակել" + "unpricedUsageDismiss": "Փակել", + "systemMetricsTitle": "System metrics", + "systemMetricsActive": "Monitoring active", + "systemMetricsLoading": "Loading...", + "systemMetricsCpu": "CPU", + "systemMetricsMemory": "Memory", + "systemMetricsHost": "VPS / Host", + "systemMetricsCpuCount": "{count} vCPUs • {platform}", + "systemMetricsProcessCpu": "OmniRoute (process)", + "systemMetricsProcessMemory": "OmniRoute (RSS / Heap)" }, "analytics": { "title": "Վերլուծություն", diff --git a/src/i18n/messages/id.json b/src/i18n/messages/id.json index 173d8a242809..e949675cc4ca 100644 --- a/src/i18n/messages/id.json +++ b/src/i18n/messages/id.json @@ -1939,7 +1939,16 @@ "unpricedUsageModelStats": "{requests, plural, one {# permintaan} other {# permintaan}}, {keys, plural, one {# kunci terbatas} other {# kunci terbatas}}", "unpricedUsageMore": "…dan {count} lagi", "unpricedUsageConfigure": "Konfigurasikan harga", - "unpricedUsageDismiss": "Tutup" + "unpricedUsageDismiss": "Tutup", + "systemMetricsTitle": "System metrics", + "systemMetricsActive": "Monitoring active", + "systemMetricsLoading": "Loading...", + "systemMetricsCpu": "CPU", + "systemMetricsMemory": "Memory", + "systemMetricsHost": "VPS / Host", + "systemMetricsCpuCount": "{count} vCPUs • {platform}", + "systemMetricsProcessCpu": "OmniRoute (process)", + "systemMetricsProcessMemory": "OmniRoute (RSS / Heap)" }, "analytics": { "title": "Analisis", diff --git a/src/i18n/messages/ig.json b/src/i18n/messages/ig.json index 2383361c7497..bbc51115e7d2 100644 --- a/src/i18n/messages/ig.json +++ b/src/i18n/messages/ig.json @@ -1939,7 +1939,16 @@ "unpricedUsageModelStats": "{requests, plural, one {# arịrịọ} other {# arịrịọ}}, {keys, plural, one {# igodo nwere oke} other {# igodo nwere oke}}", "unpricedUsageMore": "…na {count} ọzọ", "unpricedUsageConfigure": "Tọọ ọnụahịa", - "unpricedUsageDismiss": "Mechie" + "unpricedUsageDismiss": "Mechie", + "systemMetricsTitle": "System metrics", + "systemMetricsActive": "Monitoring active", + "systemMetricsLoading": "Loading...", + "systemMetricsCpu": "CPU", + "systemMetricsMemory": "Memory", + "systemMetricsHost": "VPS / Host", + "systemMetricsCpuCount": "{count} vCPUs • {platform}", + "systemMetricsProcessCpu": "OmniRoute (process)", + "systemMetricsProcessMemory": "OmniRoute (RSS / Heap)" }, "analytics": { "title": "Nnyocha Data", diff --git a/src/i18n/messages/it.json b/src/i18n/messages/it.json index 5a8715410d25..b92517f7c4fb 100644 --- a/src/i18n/messages/it.json +++ b/src/i18n/messages/it.json @@ -2016,7 +2016,16 @@ "unpricedUsageModelStats": "{requests, plural, one {# richiesta} other {# richieste}}, {keys, plural, one {# chiave limitata} other {# chiavi limitate}}", "unpricedUsageMore": "…e {count} in più", "unpricedUsageConfigure": "Configura i prezzi", - "unpricedUsageDismiss": "Chiudi" + "unpricedUsageDismiss": "Chiudi", + "systemMetricsTitle": "System metrics", + "systemMetricsActive": "Monitoring active", + "systemMetricsLoading": "Loading...", + "systemMetricsCpu": "CPU", + "systemMetricsMemory": "Memory", + "systemMetricsHost": "VPS / Host", + "systemMetricsCpuCount": "{count} vCPUs • {platform}", + "systemMetricsProcessCpu": "OmniRoute (process)", + "systemMetricsProcessMemory": "OmniRoute (RSS / Heap)" }, "analytics": { "title": "Analitica", diff --git a/src/i18n/messages/ja.json b/src/i18n/messages/ja.json index d469f9d6253b..cb2f11b3c08f 100644 --- a/src/i18n/messages/ja.json +++ b/src/i18n/messages/ja.json @@ -1939,7 +1939,16 @@ "unpricedUsageModelStats": "{requests, plural, other {#件のリクエスト}}、{keys, plural, other {上限付きキー #個}}", "unpricedUsageMore": "…ほか {count} 件", "unpricedUsageConfigure": "価格を設定", - "unpricedUsageDismiss": "閉じる" + "unpricedUsageDismiss": "閉じる", + "systemMetricsTitle": "System metrics", + "systemMetricsActive": "Monitoring active", + "systemMetricsLoading": "Loading...", + "systemMetricsCpu": "CPU", + "systemMetricsMemory": "Memory", + "systemMetricsHost": "VPS / Host", + "systemMetricsCpuCount": "{count} vCPUs • {platform}", + "systemMetricsProcessCpu": "OmniRoute (process)", + "systemMetricsProcessMemory": "OmniRoute (RSS / Heap)" }, "analytics": { "title": "分析", diff --git a/src/i18n/messages/ka.json b/src/i18n/messages/ka.json index 7ac9ad80f658..f3f2bbe1cab9 100644 --- a/src/i18n/messages/ka.json +++ b/src/i18n/messages/ka.json @@ -2016,7 +2016,16 @@ "unpricedUsageModelStats": "{requests, plural, one {# მოთხოვნა} other {# მოთხოვნები}}, {keys, plural, one {# შეზღუდული კლავი} other {# შეზღუდული კლავები}}", "unpricedUsageMore": "…და კიდევ {count}", "unpricedUsageConfigure": "ფასის კონფიგურაცია", - "unpricedUsageDismiss": "დახურვა" + "unpricedUsageDismiss": "დახურვა", + "systemMetricsTitle": "System metrics", + "systemMetricsActive": "Monitoring active", + "systemMetricsLoading": "Loading...", + "systemMetricsCpu": "CPU", + "systemMetricsMemory": "Memory", + "systemMetricsHost": "VPS / Host", + "systemMetricsCpuCount": "{count} vCPUs • {platform}", + "systemMetricsProcessCpu": "OmniRoute (process)", + "systemMetricsProcessMemory": "OmniRoute (RSS / Heap)" }, "analytics": { "title": "ანალიტიკა", diff --git a/src/i18n/messages/km.json b/src/i18n/messages/km.json index 90426a261b7d..029ba471c69a 100644 --- a/src/i18n/messages/km.json +++ b/src/i18n/messages/km.json @@ -2016,7 +2016,16 @@ "unpricedUsageModelStats": "{requests, plural, one {# សំណើ} other {# សំណើ}}, {keys, plural, one {# កូនសោដែលមានដែនកំណត់} other {# កូនសោដែលមានដែនកំណត់}}", "unpricedUsageMore": "… និង {count} ផ្សេងទៀត", "unpricedUsageConfigure": "កំណត់តម្លៃ", - "unpricedUsageDismiss": "បិទ" + "unpricedUsageDismiss": "បិទ", + "systemMetricsTitle": "System metrics", + "systemMetricsActive": "Monitoring active", + "systemMetricsLoading": "Loading...", + "systemMetricsCpu": "CPU", + "systemMetricsMemory": "Memory", + "systemMetricsHost": "VPS / Host", + "systemMetricsCpuCount": "{count} vCPUs • {platform}", + "systemMetricsProcessCpu": "OmniRoute (process)", + "systemMetricsProcessMemory": "OmniRoute (RSS / Heap)" }, "analytics": { "title": "ការវិភាគ", diff --git a/src/i18n/messages/kn.json b/src/i18n/messages/kn.json index 4809e3b05c71..f98dadfcbff7 100644 --- a/src/i18n/messages/kn.json +++ b/src/i18n/messages/kn.json @@ -1939,7 +1939,16 @@ "unpricedUsageModelStats": "{requests, plural, one {# ವಿನಂತಿ} other {# ವಿನಂತಿಗಳು}}, {keys, plural, one {# ಮಿತಿ ಇರುವ API ಕೀ} other {# ಮಿತಿ ಇರುವ API ಕೀಗಳು}}", "unpricedUsageMore": "…ಮತ್ತು {count} ಇನ್ನಷ್ಟು", "unpricedUsageConfigure": "ಬೆಲೆ ಸಂರಚಿಸಿ", - "unpricedUsageDismiss": "ರದ್ದು ಮಾಡಿ" + "unpricedUsageDismiss": "ರದ್ದು ಮಾಡಿ", + "systemMetricsTitle": "System metrics", + "systemMetricsActive": "Monitoring active", + "systemMetricsLoading": "Loading...", + "systemMetricsCpu": "CPU", + "systemMetricsMemory": "Memory", + "systemMetricsHost": "VPS / Host", + "systemMetricsCpuCount": "{count} vCPUs • {platform}", + "systemMetricsProcessCpu": "OmniRoute (process)", + "systemMetricsProcessMemory": "OmniRoute (RSS / Heap)" }, "analytics": { "title": "ವಿಶ್ಲೇಷಣೆ", diff --git a/src/i18n/messages/ko.json b/src/i18n/messages/ko.json index 553ea2a73279..f010eaf2d130 100644 --- a/src/i18n/messages/ko.json +++ b/src/i18n/messages/ko.json @@ -1939,7 +1939,16 @@ "unpricedUsageModelStats": "{requests, plural, one {#건 요청} other {#건 요청}}, {keys, plural, one {#개의 제한된 키} other {#개의 제한된 키}}", "unpricedUsageMore": "…그리고 {count}개 더", "unpricedUsageConfigure": "가격 구성", - "unpricedUsageDismiss": "닫기" + "unpricedUsageDismiss": "닫기", + "systemMetricsTitle": "System metrics", + "systemMetricsActive": "Monitoring active", + "systemMetricsLoading": "Loading...", + "systemMetricsCpu": "CPU", + "systemMetricsMemory": "Memory", + "systemMetricsHost": "VPS / Host", + "systemMetricsCpuCount": "{count} vCPUs • {platform}", + "systemMetricsProcessCpu": "OmniRoute (process)", + "systemMetricsProcessMemory": "OmniRoute (RSS / Heap)" }, "analytics": { "title": "분석", diff --git a/src/i18n/messages/lt.json b/src/i18n/messages/lt.json index 7df01bb0b20f..5a8e249c95a4 100644 --- a/src/i18n/messages/lt.json +++ b/src/i18n/messages/lt.json @@ -1939,7 +1939,16 @@ "unpricedUsageModelStats": "{requests, plural, one {# užklausa} few {# užklausos} many {# užklausų} other {# užklausų}}, {keys, plural, one {# ribotas raktas} few {# riboti raktai} many {# ribotų raktų} other {# ribotų raktų}}", "unpricedUsageMore": "…ir dar {count}", "unpricedUsageConfigure": "Nustatyti kainodarą", - "unpricedUsageDismiss": "Atmesti" + "unpricedUsageDismiss": "Atmesti", + "systemMetricsTitle": "System metrics", + "systemMetricsActive": "Monitoring active", + "systemMetricsLoading": "Loading...", + "systemMetricsCpu": "CPU", + "systemMetricsMemory": "Memory", + "systemMetricsHost": "VPS / Host", + "systemMetricsCpuCount": "{count} vCPUs • {platform}", + "systemMetricsProcessCpu": "OmniRoute (process)", + "systemMetricsProcessMemory": "OmniRoute (RSS / Heap)" }, "analytics": { "title": "Analitika", diff --git a/src/i18n/messages/lv.json b/src/i18n/messages/lv.json index 44fa846fa5f1..2af92dc2d97d 100644 --- a/src/i18n/messages/lv.json +++ b/src/i18n/messages/lv.json @@ -1939,7 +1939,16 @@ "unpricedUsageModelStats": "{requests, plural, one {# pieprasījums} other {# pieprasījumi}}, {keys, plural, one {# ierobežota atslēga} other {# ierobežotas atslēgas}}", "unpricedUsageMore": "…un vēl {count}", "unpricedUsageConfigure": "Konfigurēt cenas", - "unpricedUsageDismiss": "Aizvērt" + "unpricedUsageDismiss": "Aizvērt", + "systemMetricsTitle": "System metrics", + "systemMetricsActive": "Monitoring active", + "systemMetricsLoading": "Loading...", + "systemMetricsCpu": "CPU", + "systemMetricsMemory": "Memory", + "systemMetricsHost": "VPS / Host", + "systemMetricsCpuCount": "{count} vCPUs • {platform}", + "systemMetricsProcessCpu": "OmniRoute (process)", + "systemMetricsProcessMemory": "OmniRoute (RSS / Heap)" }, "analytics": { "title": "Analītika", diff --git a/src/i18n/messages/ml.json b/src/i18n/messages/ml.json index 04cd699c797d..13a624f2f666 100644 --- a/src/i18n/messages/ml.json +++ b/src/i18n/messages/ml.json @@ -2016,7 +2016,16 @@ "unpricedUsageModelStats": "{requests, plural, one {# അഭ്യർ‍ത്ഥന} other {# അഭ്യർത്ഥനകൾ}}, {keys, plural, one {# പരിമിത കീ} other {# പരിമിത കീകൾ}}", "unpricedUsageMore": "…കൂടാതെ {count} കൂടുതൽ", "unpricedUsageConfigure": "വില ക്രമീകരിക്കുക", - "unpricedUsageDismiss": "ഒഴിവാക്കുക" + "unpricedUsageDismiss": "ഒഴിവാക്കുക", + "systemMetricsTitle": "System metrics", + "systemMetricsActive": "Monitoring active", + "systemMetricsLoading": "Loading...", + "systemMetricsCpu": "CPU", + "systemMetricsMemory": "Memory", + "systemMetricsHost": "VPS / Host", + "systemMetricsCpuCount": "{count} vCPUs • {platform}", + "systemMetricsProcessCpu": "OmniRoute (process)", + "systemMetricsProcessMemory": "OmniRoute (RSS / Heap)" }, "analytics": { "title": "വിശകലനം", diff --git a/src/i18n/messages/mr.json b/src/i18n/messages/mr.json index 6de55f5bd9f6..2bf9bb135f01 100644 --- a/src/i18n/messages/mr.json +++ b/src/i18n/messages/mr.json @@ -1939,7 +1939,16 @@ "unpricedUsageModelStats": "{requests, plural, one {# विनंती} other {# विनंत्या}}, {keys, plural, one {# मर्यादित की} other {# मर्यादित की}}", "unpricedUsageMore": "…आणि {count} अधिक", "unpricedUsageConfigure": "किंमत विन्यास करा", - "unpricedUsageDismiss": "रद्द करा" + "unpricedUsageDismiss": "रद्द करा", + "systemMetricsTitle": "System metrics", + "systemMetricsActive": "Monitoring active", + "systemMetricsLoading": "Loading...", + "systemMetricsCpu": "CPU", + "systemMetricsMemory": "Memory", + "systemMetricsHost": "VPS / Host", + "systemMetricsCpuCount": "{count} vCPUs • {platform}", + "systemMetricsProcessCpu": "OmniRoute (process)", + "systemMetricsProcessMemory": "OmniRoute (RSS / Heap)" }, "analytics": { "title": "विश्लेषण", diff --git a/src/i18n/messages/ms.json b/src/i18n/messages/ms.json index 2495415b6367..19a1da63e177 100644 --- a/src/i18n/messages/ms.json +++ b/src/i18n/messages/ms.json @@ -1939,7 +1939,16 @@ "unpricedUsageModelStats": "{requests, plural, one {# permintaan} other {# permintaan}}, {keys, plural, one {# kunci terhad} other {# kunci terhad}}", "unpricedUsageMore": "…dan {count} lagi", "unpricedUsageConfigure": "Konfigurasikan harga", - "unpricedUsageDismiss": "Tutup" + "unpricedUsageDismiss": "Tutup", + "systemMetricsTitle": "System metrics", + "systemMetricsActive": "Monitoring active", + "systemMetricsLoading": "Loading...", + "systemMetricsCpu": "CPU", + "systemMetricsMemory": "Memory", + "systemMetricsHost": "VPS / Host", + "systemMetricsCpuCount": "{count} vCPUs • {platform}", + "systemMetricsProcessCpu": "OmniRoute (process)", + "systemMetricsProcessMemory": "OmniRoute (RSS / Heap)" }, "analytics": { "title": "Analitis", diff --git a/src/i18n/messages/mt.json b/src/i18n/messages/mt.json index 7e6d7ee10640..4fce5f8ab1eb 100644 --- a/src/i18n/messages/mt.json +++ b/src/i18n/messages/mt.json @@ -2016,7 +2016,16 @@ "unpricedUsageModelStats": "{requests, plural, one {# talba} other {# talbiet}}, {keys, plural, one {# ċavetta limitata} other {# ċwievet limitati}}", "unpricedUsageMore": "…u {count} aktar", "unpricedUsageConfigure": "Ikkonfigura prezzijiet", - "unpricedUsageDismiss": "Għalaq" + "unpricedUsageDismiss": "Għalaq", + "systemMetricsTitle": "System metrics", + "systemMetricsActive": "Monitoring active", + "systemMetricsLoading": "Loading...", + "systemMetricsCpu": "CPU", + "systemMetricsMemory": "Memory", + "systemMetricsHost": "VPS / Host", + "systemMetricsCpuCount": "{count} vCPUs • {platform}", + "systemMetricsProcessCpu": "OmniRoute (process)", + "systemMetricsProcessMemory": "OmniRoute (RSS / Heap)" }, "analytics": { "title": "Analitika", diff --git a/src/i18n/messages/my.json b/src/i18n/messages/my.json index 5d9bbfebaeb9..4e06bce0b5c3 100644 --- a/src/i18n/messages/my.json +++ b/src/i18n/messages/my.json @@ -2016,7 +2016,16 @@ "unpricedUsageModelStats": "{requests, plural, one {# တောင်းဆိုချက်} other {# တောင်းဆိုချက်များ}}, {keys, plural, one {# ကန့်သတ်ထားသော ကီး} other {# ကန့်သတ်ထားသော ကီးများ}}", "unpricedUsageMore": "…နှင့် {count} ခု အပိုရှိသည်", "unpricedUsageConfigure": "စျေးနှုန်း သတ်မှတ်ရန်", - "unpricedUsageDismiss": "ပယ်ဖျက်" + "unpricedUsageDismiss": "ပယ်ဖျက်", + "systemMetricsTitle": "System metrics", + "systemMetricsActive": "Monitoring active", + "systemMetricsLoading": "Loading...", + "systemMetricsCpu": "CPU", + "systemMetricsMemory": "Memory", + "systemMetricsHost": "VPS / Host", + "systemMetricsCpuCount": "{count} vCPUs • {platform}", + "systemMetricsProcessCpu": "OmniRoute (process)", + "systemMetricsProcessMemory": "OmniRoute (RSS / Heap)" }, "analytics": { "title": "ခွဲခြမ်းစိတ်ဖြာချက်များ", diff --git a/src/i18n/messages/ne.json b/src/i18n/messages/ne.json index c4ab0dabc35e..992d1f510940 100644 --- a/src/i18n/messages/ne.json +++ b/src/i18n/messages/ne.json @@ -1939,7 +1939,16 @@ "unpricedUsageModelStats": "{requests, plural, one {# अनुरोध} other {# अनुरोधहरू}}, {keys, plural, one {# सीमित कुञ्जी} other {# सीमित कुञ्जीहरू}}", "unpricedUsageMore": "…र {count} थप", "unpricedUsageConfigure": "मूल्य निर्धारण कन्फिगर गर्नुहोस्", - "unpricedUsageDismiss": "हटाउनुहोस्" + "unpricedUsageDismiss": "हटाउनुहोस्", + "systemMetricsTitle": "System metrics", + "systemMetricsActive": "Monitoring active", + "systemMetricsLoading": "Loading...", + "systemMetricsCpu": "CPU", + "systemMetricsMemory": "Memory", + "systemMetricsHost": "VPS / Host", + "systemMetricsCpuCount": "{count} vCPUs • {platform}", + "systemMetricsProcessCpu": "OmniRoute (process)", + "systemMetricsProcessMemory": "OmniRoute (RSS / Heap)" }, "analytics": { "title": "विश्लेषण", diff --git a/src/i18n/messages/nl.json b/src/i18n/messages/nl.json index d00a1ed564bc..40194d5e5062 100644 --- a/src/i18n/messages/nl.json +++ b/src/i18n/messages/nl.json @@ -1939,7 +1939,16 @@ "unpricedUsageModelStats": "{requests, plural, one {# verzoek} other {# verzoeken}}, {keys, plural, one {# beperkte API-sleutel} other {# beperkte API-sleutels}}", "unpricedUsageMore": "…en nog {count} meer", "unpricedUsageConfigure": "Prijzen configureren", - "unpricedUsageDismiss": "Sluiten" + "unpricedUsageDismiss": "Sluiten", + "systemMetricsTitle": "System metrics", + "systemMetricsActive": "Monitoring active", + "systemMetricsLoading": "Loading...", + "systemMetricsCpu": "CPU", + "systemMetricsMemory": "Memory", + "systemMetricsHost": "VPS / Host", + "systemMetricsCpuCount": "{count} vCPUs • {platform}", + "systemMetricsProcessCpu": "OmniRoute (process)", + "systemMetricsProcessMemory": "OmniRoute (RSS / Heap)" }, "analytics": { "title": "Analyses", diff --git a/src/i18n/messages/no.json b/src/i18n/messages/no.json index 1b7c6cce5d34..0002ffe593c1 100644 --- a/src/i18n/messages/no.json +++ b/src/i18n/messages/no.json @@ -1939,7 +1939,16 @@ "unpricedUsageModelStats": "{requests, plural, one {# forespørsel} other {# forespørsler}}, {keys, plural, one {# begrenset nøkkel} other {# begrensede nøkler}}", "unpricedUsageMore": "…og {count} til", "unpricedUsageConfigure": "Konfigurer priser", - "unpricedUsageDismiss": "Lukk" + "unpricedUsageDismiss": "Lukk", + "systemMetricsTitle": "System metrics", + "systemMetricsActive": "Monitoring active", + "systemMetricsLoading": "Loading...", + "systemMetricsCpu": "CPU", + "systemMetricsMemory": "Memory", + "systemMetricsHost": "VPS / Host", + "systemMetricsCpuCount": "{count} vCPUs • {platform}", + "systemMetricsProcessCpu": "OmniRoute (process)", + "systemMetricsProcessMemory": "OmniRoute (RSS / Heap)" }, "analytics": { "title": "Analyse", diff --git a/src/i18n/messages/or.json b/src/i18n/messages/or.json index 29a7b18d4046..cf78d83d0eea 100644 --- a/src/i18n/messages/or.json +++ b/src/i18n/messages/or.json @@ -1939,7 +1939,16 @@ "unpricedUsageModelStats": "{requests, plural, one {# ନିବେଦନ} other {# ନିବେଦନ}}, {keys, plural, one {# ସୀମିତ କୀ} other {# ସୀମିତ କୀଗୁଡିକ}}", "unpricedUsageMore": "…ଏବଂ ଆଉ {count}ଟି", "unpricedUsageConfigure": "ମୂଲ୍ୟ ବିନ୍ୟାସ କରନ୍ତୁ", - "unpricedUsageDismiss": "ବାତିଲ୍ କରନ୍ତୁ" + "unpricedUsageDismiss": "ବାତିଲ୍ କରନ୍ତୁ", + "systemMetricsTitle": "System metrics", + "systemMetricsActive": "Monitoring active", + "systemMetricsLoading": "Loading...", + "systemMetricsCpu": "CPU", + "systemMetricsMemory": "Memory", + "systemMetricsHost": "VPS / Host", + "systemMetricsCpuCount": "{count} vCPUs • {platform}", + "systemMetricsProcessCpu": "OmniRoute (process)", + "systemMetricsProcessMemory": "OmniRoute (RSS / Heap)" }, "analytics": { "title": "ବିଶ୍ଳେଷଣ", diff --git a/src/i18n/messages/pa.json b/src/i18n/messages/pa.json index 1793bcaccb85..d90135b4f980 100644 --- a/src/i18n/messages/pa.json +++ b/src/i18n/messages/pa.json @@ -2016,7 +2016,16 @@ "unpricedUsageModelStats": "{requests, plural, one {# ਬੇਨਤੀ} other {# ਬੇਨਤੀਆਂ}}, {keys, plural, one {# ਸੀਮਿਤ ਕੁੰਜੀ} other {# ਸੀਮਿਤ ਕੁੰਜੀਆਂ}}", "unpricedUsageMore": "…ਅਤੇ {count} ਹੋਰ", "unpricedUsageConfigure": "ਕੀਮਤ ਸੰਰਚਨਾ ਕਰੋ", - "unpricedUsageDismiss": "ਬੰਦ ਕਰੋ" + "unpricedUsageDismiss": "ਬੰਦ ਕਰੋ", + "systemMetricsTitle": "System metrics", + "systemMetricsActive": "Monitoring active", + "systemMetricsLoading": "Loading...", + "systemMetricsCpu": "CPU", + "systemMetricsMemory": "Memory", + "systemMetricsHost": "VPS / Host", + "systemMetricsCpuCount": "{count} vCPUs • {platform}", + "systemMetricsProcessCpu": "OmniRoute (process)", + "systemMetricsProcessMemory": "OmniRoute (RSS / Heap)" }, "analytics": { "title": "ਵਿਸ਼ਲੇਸ਼ਣ", diff --git a/src/i18n/messages/phi.json b/src/i18n/messages/phi.json index b78bca20834d..ce49bbc39e84 100644 --- a/src/i18n/messages/phi.json +++ b/src/i18n/messages/phi.json @@ -1939,7 +1939,16 @@ "unpricedUsageModelStats": "{requests, plural, one {# kahilingan} other {# mga kahilingan}}, {keys, plural, one {# limitadong key} other {# mga limitadong key}}", "unpricedUsageMore": "…at {count} pa", "unpricedUsageConfigure": "I-configure ang pagpepresyo", - "unpricedUsageDismiss": "Isara" + "unpricedUsageDismiss": "Isara", + "systemMetricsTitle": "System metrics", + "systemMetricsActive": "Monitoring active", + "systemMetricsLoading": "Loading...", + "systemMetricsCpu": "CPU", + "systemMetricsMemory": "Memory", + "systemMetricsHost": "VPS / Host", + "systemMetricsCpuCount": "{count} vCPUs • {platform}", + "systemMetricsProcessCpu": "OmniRoute (process)", + "systemMetricsProcessMemory": "OmniRoute (RSS / Heap)" }, "analytics": { "title": "Analitika", diff --git a/src/i18n/messages/pl.json b/src/i18n/messages/pl.json index 3383fea0dbf0..9318c0a29c7e 100644 --- a/src/i18n/messages/pl.json +++ b/src/i18n/messages/pl.json @@ -1939,7 +1939,16 @@ "unpricedUsageModelStats": "{requests, plural, one {# żądanie} few {# żądania} many {# żądań} other {# żądań}}, {keys, plural, one {# klucz z limitem} few {# klucze z limitem} many {# kluczy z limitem} other {# kluczy z limitem}}", "unpricedUsageMore": "…i jeszcze {count} więcej", "unpricedUsageConfigure": "Skonfiguruj ceny", - "unpricedUsageDismiss": "Zamknij" + "unpricedUsageDismiss": "Zamknij", + "systemMetricsTitle": "System metrics", + "systemMetricsActive": "Monitoring active", + "systemMetricsLoading": "Loading...", + "systemMetricsCpu": "CPU", + "systemMetricsMemory": "Memory", + "systemMetricsHost": "VPS / Host", + "systemMetricsCpuCount": "{count} vCPUs • {platform}", + "systemMetricsProcessCpu": "OmniRoute (process)", + "systemMetricsProcessMemory": "OmniRoute (RSS / Heap)" }, "analytics": { "title": "Analityka", diff --git a/src/i18n/messages/pt-BR.json b/src/i18n/messages/pt-BR.json index a7a5b88541e2..1cd82dd4f65b 100644 --- a/src/i18n/messages/pt-BR.json +++ b/src/i18n/messages/pt-BR.json @@ -1939,7 +1939,16 @@ "unpricedUsageModelStats": "{requests, plural, one {# requisição} other {# requisições}}, {keys, plural, one {# chave limitada} other {# chaves limitadas}}", "unpricedUsageMore": "…e mais {count}", "unpricedUsageConfigure": "Configurar preços", - "unpricedUsageDismiss": "Fechar" + "unpricedUsageDismiss": "Fechar", + "systemMetricsTitle": "System metrics", + "systemMetricsActive": "Monitoring active", + "systemMetricsLoading": "Loading...", + "systemMetricsCpu": "CPU", + "systemMetricsMemory": "Memory", + "systemMetricsHost": "VPS / Host", + "systemMetricsCpuCount": "{count} vCPUs • {platform}", + "systemMetricsProcessCpu": "OmniRoute (process)", + "systemMetricsProcessMemory": "OmniRoute (RSS / Heap)" }, "analytics": { "title": "Análises", diff --git a/src/i18n/messages/pt.json b/src/i18n/messages/pt.json index 0093f5d1554c..be39ea74e89a 100644 --- a/src/i18n/messages/pt.json +++ b/src/i18n/messages/pt.json @@ -2016,7 +2016,16 @@ "unpricedUsageModelStats": "{requests, plural, one {# pedido} other {# pedidos}}, {keys, plural, one {# chave limitada} other {# chaves limitadas}}", "unpricedUsageMore": "…e mais {count}", "unpricedUsageConfigure": "Configurar preços", - "unpricedUsageDismiss": "Fechar" + "unpricedUsageDismiss": "Fechar", + "systemMetricsTitle": "System metrics", + "systemMetricsActive": "Monitoring active", + "systemMetricsLoading": "Loading...", + "systemMetricsCpu": "CPU", + "systemMetricsMemory": "Memory", + "systemMetricsHost": "VPS / Host", + "systemMetricsCpuCount": "{count} vCPUs • {platform}", + "systemMetricsProcessCpu": "OmniRoute (process)", + "systemMetricsProcessMemory": "OmniRoute (RSS / Heap)" }, "analytics": { "title": "Análise", diff --git a/src/i18n/messages/ro.json b/src/i18n/messages/ro.json index 11fc86f20ab5..30e59d67606a 100644 --- a/src/i18n/messages/ro.json +++ b/src/i18n/messages/ro.json @@ -1939,7 +1939,16 @@ "unpricedUsageModelStats": "{requests, plural, one {# cerere} other {# cereri}}, {keys, plural, one {# cheie limitată} other {# chei limitate}}", "unpricedUsageMore": "…și încă {count}", "unpricedUsageConfigure": "Configurează prețurile", - "unpricedUsageDismiss": "Închide" + "unpricedUsageDismiss": "Închide", + "systemMetricsTitle": "System metrics", + "systemMetricsActive": "Monitoring active", + "systemMetricsLoading": "Loading...", + "systemMetricsCpu": "CPU", + "systemMetricsMemory": "Memory", + "systemMetricsHost": "VPS / Host", + "systemMetricsCpuCount": "{count} vCPUs • {platform}", + "systemMetricsProcessCpu": "OmniRoute (process)", + "systemMetricsProcessMemory": "OmniRoute (RSS / Heap)" }, "analytics": { "title": "Analiză", diff --git a/src/i18n/messages/ru.json b/src/i18n/messages/ru.json index 0f0cb0378d63..d62d5fe0d6d0 100644 --- a/src/i18n/messages/ru.json +++ b/src/i18n/messages/ru.json @@ -1939,7 +1939,16 @@ "unpricedUsageModelStats": "{requests, plural, one {# запрос} few {# запроса} many {# запросов} other {# запроса}}, {keys, plural, one {# ключ с лимитом} few {# ключа с лимитом} many {# ключей с лимитом} other {# ключей с лимитом}}", "unpricedUsageMore": "…и ещё {count}", "unpricedUsageConfigure": "Настроить цены", - "unpricedUsageDismiss": "Закрыть" + "unpricedUsageDismiss": "Закрыть", + "systemMetricsTitle": "System metrics", + "systemMetricsActive": "Monitoring active", + "systemMetricsLoading": "Loading...", + "systemMetricsCpu": "CPU", + "systemMetricsMemory": "Memory", + "systemMetricsHost": "VPS / Host", + "systemMetricsCpuCount": "{count} vCPUs • {platform}", + "systemMetricsProcessCpu": "OmniRoute (process)", + "systemMetricsProcessMemory": "OmniRoute (RSS / Heap)" }, "analytics": { "title": "Аналитика", diff --git a/src/i18n/messages/si.json b/src/i18n/messages/si.json index 1cedf88c7d77..47b03c5f9cd9 100644 --- a/src/i18n/messages/si.json +++ b/src/i18n/messages/si.json @@ -1939,7 +1939,16 @@ "unpricedUsageModelStats": "{requests, plural, one {# ඉල්ලීම} other {# ඉල්ලීම්}}, {keys, plural, one {# සීමිත යතුරක්} other {# සීමිත යතුරු}}", "unpricedUsageMore": "…සහ තව {count}ක්", "unpricedUsageConfigure": "මිල සකස් කරන්න", - "unpricedUsageDismiss": "අවලංගු කරන්න" + "unpricedUsageDismiss": "අවලංගු කරන්න", + "systemMetricsTitle": "System metrics", + "systemMetricsActive": "Monitoring active", + "systemMetricsLoading": "Loading...", + "systemMetricsCpu": "CPU", + "systemMetricsMemory": "Memory", + "systemMetricsHost": "VPS / Host", + "systemMetricsCpuCount": "{count} vCPUs • {platform}", + "systemMetricsProcessCpu": "OmniRoute (process)", + "systemMetricsProcessMemory": "OmniRoute (RSS / Heap)" }, "analytics": { "title": "විශ්ලේෂණ", diff --git a/src/i18n/messages/sk.json b/src/i18n/messages/sk.json index 0874add9f996..ac86128871f9 100644 --- a/src/i18n/messages/sk.json +++ b/src/i18n/messages/sk.json @@ -1939,7 +1939,16 @@ "unpricedUsageModelStats": "{requests, plural, one {# požiadavka} few {# požiadavky} other {# požiadaviek}}, {keys, plural, one {# obmedzený kľúč} few {# obmedzené kľúče} other {# obmedzených kľúčov}}", "unpricedUsageMore": "…a {count} ďalších", "unpricedUsageConfigure": "Nastaviť ceny", - "unpricedUsageDismiss": "Zatvoriť" + "unpricedUsageDismiss": "Zatvoriť", + "systemMetricsTitle": "System metrics", + "systemMetricsActive": "Monitoring active", + "systemMetricsLoading": "Loading...", + "systemMetricsCpu": "CPU", + "systemMetricsMemory": "Memory", + "systemMetricsHost": "VPS / Host", + "systemMetricsCpuCount": "{count} vCPUs • {platform}", + "systemMetricsProcessCpu": "OmniRoute (process)", + "systemMetricsProcessMemory": "OmniRoute (RSS / Heap)" }, "analytics": { "title": "Analytika", diff --git a/src/i18n/messages/sl.json b/src/i18n/messages/sl.json index 8b396edf481f..6ae76a8f8885 100644 --- a/src/i18n/messages/sl.json +++ b/src/i18n/messages/sl.json @@ -1939,7 +1939,16 @@ "unpricedUsageModelStats": "{requests, plural, one {# zahteva} two {# zahtevi} few {# zahteve} other {# zahtev}}, {keys, plural, one {# omejen ključ} two {# omejena ključa} few {# omejeni ključi} other {# omejenih ključev}}", "unpricedUsageMore": "…in še {count}", "unpricedUsageConfigure": "Konfiguriraj cene", - "unpricedUsageDismiss": "Zapri" + "unpricedUsageDismiss": "Zapri", + "systemMetricsTitle": "System metrics", + "systemMetricsActive": "Monitoring active", + "systemMetricsLoading": "Loading...", + "systemMetricsCpu": "CPU", + "systemMetricsMemory": "Memory", + "systemMetricsHost": "VPS / Host", + "systemMetricsCpuCount": "{count} vCPUs • {platform}", + "systemMetricsProcessCpu": "OmniRoute (process)", + "systemMetricsProcessMemory": "OmniRoute (RSS / Heap)" }, "analytics": { "title": "Analitika", diff --git a/src/i18n/messages/sr.json b/src/i18n/messages/sr.json index f056001f1473..223e0ea04cc2 100644 --- a/src/i18n/messages/sr.json +++ b/src/i18n/messages/sr.json @@ -1939,7 +1939,16 @@ "unpricedUsageModelStats": "{requests, plural, one {# захтев} few {# захтева} other {# захтева}}, {keys, plural, one {# ограничен кључ} few {# ограничена кључа} other {# ограничених кључева}}", "unpricedUsageMore": "…и још {count}", "unpricedUsageConfigure": "Конфигуриши цене", - "unpricedUsageDismiss": "Одбаци" + "unpricedUsageDismiss": "Одбаци", + "systemMetricsTitle": "System metrics", + "systemMetricsActive": "Monitoring active", + "systemMetricsLoading": "Loading...", + "systemMetricsCpu": "CPU", + "systemMetricsMemory": "Memory", + "systemMetricsHost": "VPS / Host", + "systemMetricsCpuCount": "{count} vCPUs • {platform}", + "systemMetricsProcessCpu": "OmniRoute (process)", + "systemMetricsProcessMemory": "OmniRoute (RSS / Heap)" }, "analytics": { "title": "Аналитика", diff --git a/src/i18n/messages/sv.json b/src/i18n/messages/sv.json index 1689caeb7036..2b6e66cd989a 100644 --- a/src/i18n/messages/sv.json +++ b/src/i18n/messages/sv.json @@ -1939,7 +1939,16 @@ "unpricedUsageModelStats": "{requests, plural, one {# begäran} other {# begäranden}}, {keys, plural, one {# begränsad nyckel} other {# begränsade nycklar}}", "unpricedUsageMore": "…och {count} till", "unpricedUsageConfigure": "Konfigurera prissättning", - "unpricedUsageDismiss": "Stäng" + "unpricedUsageDismiss": "Stäng", + "systemMetricsTitle": "System metrics", + "systemMetricsActive": "Monitoring active", + "systemMetricsLoading": "Loading...", + "systemMetricsCpu": "CPU", + "systemMetricsMemory": "Memory", + "systemMetricsHost": "VPS / Host", + "systemMetricsCpuCount": "{count} vCPUs • {platform}", + "systemMetricsProcessCpu": "OmniRoute (process)", + "systemMetricsProcessMemory": "OmniRoute (RSS / Heap)" }, "analytics": { "title": "Analys", diff --git a/src/i18n/messages/sw.json b/src/i18n/messages/sw.json index a99fdd9aefc2..47a22812485a 100644 --- a/src/i18n/messages/sw.json +++ b/src/i18n/messages/sw.json @@ -1939,7 +1939,16 @@ "unpricedUsageModelStats": "{requests, plural, one {# ombi} other {# maombi}}, {keys, plural, one {# ufunguo lenye kikomo} other {# funguo zenye kikomo}}", "unpricedUsageMore": "…na {count} zaidi", "unpricedUsageConfigure": "Sanidi bei", - "unpricedUsageDismiss": "Puuza" + "unpricedUsageDismiss": "Puuza", + "systemMetricsTitle": "System metrics", + "systemMetricsActive": "Monitoring active", + "systemMetricsLoading": "Loading...", + "systemMetricsCpu": "CPU", + "systemMetricsMemory": "Memory", + "systemMetricsHost": "VPS / Host", + "systemMetricsCpuCount": "{count} vCPUs • {platform}", + "systemMetricsProcessCpu": "OmniRoute (process)", + "systemMetricsProcessMemory": "OmniRoute (RSS / Heap)" }, "analytics": { "title": "Uchanganuzi", diff --git a/src/i18n/messages/ta.json b/src/i18n/messages/ta.json index 52d085cb4c73..6a893c25a6c5 100644 --- a/src/i18n/messages/ta.json +++ b/src/i18n/messages/ta.json @@ -1939,7 +1939,16 @@ "unpricedUsageModelStats": "{requests, plural, one {# கோரிக்கை} other {# கோரிக்கைகள்}}, {keys, plural, one {# வரையறுக்கப்பட்ட விசை} other {# வரையறுக்கப்பட்ட விசைகள்}}", "unpricedUsageMore": "…மற்றும் {count} மேலும்", "unpricedUsageConfigure": "விலை அமைக்கவும்", - "unpricedUsageDismiss": "மறுக்கவும்" + "unpricedUsageDismiss": "மறுக்கவும்", + "systemMetricsTitle": "System metrics", + "systemMetricsActive": "Monitoring active", + "systemMetricsLoading": "Loading...", + "systemMetricsCpu": "CPU", + "systemMetricsMemory": "Memory", + "systemMetricsHost": "VPS / Host", + "systemMetricsCpuCount": "{count} vCPUs • {platform}", + "systemMetricsProcessCpu": "OmniRoute (process)", + "systemMetricsProcessMemory": "OmniRoute (RSS / Heap)" }, "analytics": { "title": "பகுப்பாய்வு", diff --git a/src/i18n/messages/te.json b/src/i18n/messages/te.json index fe5b493a7b21..c99720bd79da 100644 --- a/src/i18n/messages/te.json +++ b/src/i18n/messages/te.json @@ -1939,7 +1939,16 @@ "unpricedUsageModelStats": "{requests, plural, one {# అభ్యర్థన} other {# అభ్యర్థనలు}}, {keys, plural, one {# పరిమిత కీ} other {# పరిమిత కీలు}}", "unpricedUsageMore": "…మరియు ఇంకా {count}", "unpricedUsageConfigure": "ధర కాన్ఫిగర్ చేయండి", - "unpricedUsageDismiss": "వదిలివేయండి" + "unpricedUsageDismiss": "వదిలివేయండి", + "systemMetricsTitle": "System metrics", + "systemMetricsActive": "Monitoring active", + "systemMetricsLoading": "Loading...", + "systemMetricsCpu": "CPU", + "systemMetricsMemory": "Memory", + "systemMetricsHost": "VPS / Host", + "systemMetricsCpuCount": "{count} vCPUs • {platform}", + "systemMetricsProcessCpu": "OmniRoute (process)", + "systemMetricsProcessMemory": "OmniRoute (RSS / Heap)" }, "analytics": { "title": "విశ్లేషణలు", diff --git a/src/i18n/messages/th.json b/src/i18n/messages/th.json index 647f4584f1a5..1f8de1ffa2f3 100644 --- a/src/i18n/messages/th.json +++ b/src/i18n/messages/th.json @@ -1939,7 +1939,16 @@ "unpricedUsageModelStats": "{requests, plural, one {# คำขอ} other {# คำขอ}}, {keys, plural, one {# คีย์ที่มีขีดจำกัด} other {# คีย์ที่มีขีดจำกัด}}", "unpricedUsageMore": "…และอีก {count} รายการ", "unpricedUsageConfigure": "ตั้งค่าราคา", - "unpricedUsageDismiss": "ปิด" + "unpricedUsageDismiss": "ปิด", + "systemMetricsTitle": "System metrics", + "systemMetricsActive": "Monitoring active", + "systemMetricsLoading": "Loading...", + "systemMetricsCpu": "CPU", + "systemMetricsMemory": "Memory", + "systemMetricsHost": "VPS / Host", + "systemMetricsCpuCount": "{count} vCPUs • {platform}", + "systemMetricsProcessCpu": "OmniRoute (process)", + "systemMetricsProcessMemory": "OmniRoute (RSS / Heap)" }, "analytics": { "title": "การวิเคราะห์", diff --git a/src/i18n/messages/tr.json b/src/i18n/messages/tr.json index 9a978f976761..c1d6059cc06f 100644 --- a/src/i18n/messages/tr.json +++ b/src/i18n/messages/tr.json @@ -1939,7 +1939,16 @@ "unpricedUsageModelStats": "{requests, plural, one {# istek} other {# istek}}, {keys, plural, one {# sınırlı anahtar} other {# sınırlı anahtar}}", "unpricedUsageMore": "…ve {count} tane daha", "unpricedUsageConfigure": "Fiyatlandırmayı yapılandır", - "unpricedUsageDismiss": "Kapat" + "unpricedUsageDismiss": "Kapat", + "systemMetricsTitle": "System metrics", + "systemMetricsActive": "Monitoring active", + "systemMetricsLoading": "Loading...", + "systemMetricsCpu": "CPU", + "systemMetricsMemory": "Memory", + "systemMetricsHost": "VPS / Host", + "systemMetricsCpuCount": "{count} vCPUs • {platform}", + "systemMetricsProcessCpu": "OmniRoute (process)", + "systemMetricsProcessMemory": "OmniRoute (RSS / Heap)" }, "analytics": { "title": "Analitik", diff --git a/src/i18n/messages/uk-UA.json b/src/i18n/messages/uk-UA.json index 8b655e5619a3..0834d403d19a 100644 --- a/src/i18n/messages/uk-UA.json +++ b/src/i18n/messages/uk-UA.json @@ -1939,7 +1939,16 @@ "unpricedUsageModelStats": "{requests, plural, one {# запит} few {# запити} many {# запитів} other {# запитів}}, {keys, plural, one {# ключ із лімітом} few {# ключі із лімітом} many {# ключів із лімітом} other {# ключів із лімітом}}", "unpricedUsageMore": "…та ще {count}", "unpricedUsageConfigure": "Налаштувати ціни", - "unpricedUsageDismiss": "Закрити" + "unpricedUsageDismiss": "Закрити", + "systemMetricsTitle": "System metrics", + "systemMetricsActive": "Monitoring active", + "systemMetricsLoading": "Loading...", + "systemMetricsCpu": "CPU", + "systemMetricsMemory": "Memory", + "systemMetricsHost": "VPS / Host", + "systemMetricsCpuCount": "{count} vCPUs • {platform}", + "systemMetricsProcessCpu": "OmniRoute (process)", + "systemMetricsProcessMemory": "OmniRoute (RSS / Heap)" }, "analytics": { "title": "Аналітика", diff --git a/src/i18n/messages/ur.json b/src/i18n/messages/ur.json index c808c7860f50..5bf54c1a1c35 100644 --- a/src/i18n/messages/ur.json +++ b/src/i18n/messages/ur.json @@ -1939,7 +1939,16 @@ "unpricedUsageModelStats": "{requests, plural, one {# درخواست} other {# درخواستیں}}, {keys, plural, one {# محدود کلید} other {# محدود کلیدیں}}", "unpricedUsageMore": "…اور {count} مزید", "unpricedUsageConfigure": "قیمت مقرر کریں", - "unpricedUsageDismiss": "رد کریں" + "unpricedUsageDismiss": "رد کریں", + "systemMetricsTitle": "System metrics", + "systemMetricsActive": "Monitoring active", + "systemMetricsLoading": "Loading...", + "systemMetricsCpu": "CPU", + "systemMetricsMemory": "Memory", + "systemMetricsHost": "VPS / Host", + "systemMetricsCpuCount": "{count} vCPUs • {platform}", + "systemMetricsProcessCpu": "OmniRoute (process)", + "systemMetricsProcessMemory": "OmniRoute (RSS / Heap)" }, "analytics": { "title": "تجزیات", diff --git a/src/i18n/messages/uz.json b/src/i18n/messages/uz.json index 0eb03301b727..2511d1386c9b 100644 --- a/src/i18n/messages/uz.json +++ b/src/i18n/messages/uz.json @@ -1939,7 +1939,16 @@ "unpricedUsageModelStats": "{requests, plural, one {# ta so’rov} other {# ta so’rov}}, {keys, plural, one {# ta cheklangan kalit} other {# ta cheklangan kalit}}", "unpricedUsageMore": "…va yana {count} ta", "unpricedUsageConfigure": "Narxlarni sozlash", - "unpricedUsageDismiss": "Yopish" + "unpricedUsageDismiss": "Yopish", + "systemMetricsTitle": "System metrics", + "systemMetricsActive": "Monitoring active", + "systemMetricsLoading": "Loading...", + "systemMetricsCpu": "CPU", + "systemMetricsMemory": "Memory", + "systemMetricsHost": "VPS / Host", + "systemMetricsCpuCount": "{count} vCPUs • {platform}", + "systemMetricsProcessCpu": "OmniRoute (process)", + "systemMetricsProcessMemory": "OmniRoute (RSS / Heap)" }, "analytics": { "title": "Tahlil", diff --git a/src/i18n/messages/vi.json b/src/i18n/messages/vi.json index b2c963409954..a3b32dfb9cb4 100644 --- a/src/i18n/messages/vi.json +++ b/src/i18n/messages/vi.json @@ -1939,7 +1939,16 @@ "unpricedUsageModelStats": "{requests, plural, one {# yêu cầu} other {# yêu cầu}}, {keys, plural, one {# khóa có giới hạn} other {# khóa có giới hạn}}", "unpricedUsageMore": "…và {count} mục khác", "unpricedUsageConfigure": "Cấu hình giá", - "unpricedUsageDismiss": "Bỏ qua" + "unpricedUsageDismiss": "Bỏ qua", + "systemMetricsTitle": "System metrics", + "systemMetricsActive": "Monitoring active", + "systemMetricsLoading": "Loading...", + "systemMetricsCpu": "CPU", + "systemMetricsMemory": "Memory", + "systemMetricsHost": "VPS / Host", + "systemMetricsCpuCount": "{count} vCPUs • {platform}", + "systemMetricsProcessCpu": "OmniRoute (process)", + "systemMetricsProcessMemory": "OmniRoute (RSS / Heap)" }, "analytics": { "title": "Phân tích", diff --git a/src/i18n/messages/yo.json b/src/i18n/messages/yo.json index 9f5896339d7a..fd1e0fdaed81 100644 --- a/src/i18n/messages/yo.json +++ b/src/i18n/messages/yo.json @@ -1939,7 +1939,16 @@ "unpricedUsageModelStats": "{requests, plural, one {# ìbéèrè} other {# ìbéèrè}}, {keys, plural, one {# bọtini tí ó ní ìdínà} other {# bọtini tí ó ní ìdínà}}", "unpricedUsageMore": "… àti {count} míì", "unpricedUsageConfigure": "Ṣètò ìye", - "unpricedUsageDismiss": "Fagilé" + "unpricedUsageDismiss": "Fagilé", + "systemMetricsTitle": "Àwọn ìwọ̀n ètò", + "systemMetricsActive": "Àbojútó ń ṣiṣẹ́", + "systemMetricsLoading": "Ń kojọpọ̀...", + "systemMetricsCpu": "CPU", + "systemMetricsMemory": "Ìrántí", + "systemMetricsHost": "VPS / Agbàlejò", + "systemMetricsCpuCount": "{count} vCPU • {platform}", + "systemMetricsProcessCpu": "OmniRoute (ìṣiṣẹ́)", + "systemMetricsProcessMemory": "OmniRoute (RSS Ìrántí / Òkìtì)" }, "analytics": { "title": "Ìtúpalẹ̀", diff --git a/src/i18n/messages/zh-CN.json b/src/i18n/messages/zh-CN.json index 4db681d5b24e..54b14f30bee2 100644 --- a/src/i18n/messages/zh-CN.json +++ b/src/i18n/messages/zh-CN.json @@ -2016,7 +2016,16 @@ "unpricedUsageModelStats": "{requests, plural, other {# 次请求}},{keys, plural, other {# 个受限密钥}}", "unpricedUsageMore": "…另有 {count} 个", "unpricedUsageConfigure": "配置定价", - "unpricedUsageDismiss": "关闭" + "unpricedUsageDismiss": "关闭", + "systemMetricsTitle": "System metrics", + "systemMetricsActive": "Monitoring active", + "systemMetricsLoading": "Loading...", + "systemMetricsCpu": "CPU", + "systemMetricsMemory": "Memory", + "systemMetricsHost": "VPS / Host", + "systemMetricsCpuCount": "{count} vCPUs • {platform}", + "systemMetricsProcessCpu": "OmniRoute (process)", + "systemMetricsProcessMemory": "OmniRoute (RSS / Heap)" }, "analytics": { "title": "分析", diff --git a/src/i18n/messages/zh-TW.json b/src/i18n/messages/zh-TW.json index 06c3750dcc9e..755a58623bb8 100644 --- a/src/i18n/messages/zh-TW.json +++ b/src/i18n/messages/zh-TW.json @@ -1939,7 +1939,16 @@ "unpricedUsageModelStats": "{requests, plural, other {# 次請求}},{keys, plural, other {# 個受限金鑰}}", "unpricedUsageMore": "…另有 {count} 個", "unpricedUsageConfigure": "設定定價", - "unpricedUsageDismiss": "關閉" + "unpricedUsageDismiss": "關閉", + "systemMetricsTitle": "System metrics", + "systemMetricsActive": "Monitoring active", + "systemMetricsLoading": "Loading...", + "systemMetricsCpu": "CPU", + "systemMetricsMemory": "Memory", + "systemMetricsHost": "VPS / Host", + "systemMetricsCpuCount": "{count} vCPUs • {platform}", + "systemMetricsProcessCpu": "OmniRoute (process)", + "systemMetricsProcessMemory": "OmniRoute (RSS / Heap)" }, "analytics": { "title": "分析", diff --git a/tests/unit/system-metrics-route.test.ts b/tests/unit/system-metrics-route.test.ts new file mode 100644 index 000000000000..3fe89b8f2773 --- /dev/null +++ b/tests/unit/system-metrics-route.test.ts @@ -0,0 +1,127 @@ +import test, { mock } from "node:test"; +import assert from "node:assert/strict"; +import os from "node:os"; + +test("GET /api/system/metrics returns system host and process metrics", async () => { + const { GET } = await import("../../src/app/api/system/metrics/route.ts"); + const response = await GET(); + + assert.equal(response.status, 200); + + const data = (await response.json()) as { + host: { + cpuPercent: number; + memory: { + totalBytes: number; + usedBytes: number; + freeBytes: number; + usedPercent: number; + }; + cpuCount: number; + platform: string; + }; + process: { + cpuPercent: number; + memory: { + rssBytes: number; + heapUsedBytes: number; + heapTotalBytes: number; + }; + uptimeSeconds: number; + pid: number; + }; + }; + + // Assert host metrics + assert.ok(data.host, "host metrics should be defined"); + assert.equal(typeof data.host.cpuPercent, "number"); + assert.ok(data.host.cpuPercent >= 0 && data.host.cpuPercent <= 100); + assert.ok(data.host.cpuCount > 0); + assert.equal(typeof data.host.platform, "string"); + + assert.ok(data.host.memory, "host memory should be defined"); + assert.ok(data.host.memory.totalBytes > 0); + assert.ok(data.host.memory.usedBytes >= 0); + assert.ok(data.host.memory.freeBytes >= 0); + assert.ok(data.host.memory.usedPercent >= 0 && data.host.memory.usedPercent <= 100); + + // Assert process metrics + assert.ok(data.process, "process metrics should be defined"); + assert.equal(typeof data.process.cpuPercent, "number"); + assert.ok(data.process.cpuPercent >= 0); + + assert.ok(data.process.memory, "process memory should be defined"); + assert.ok(data.process.memory.rssBytes > 0); + assert.ok(data.process.memory.heapUsedBytes > 0); + assert.ok(data.process.memory.heapTotalBytes > 0); + + assert.ok(data.process.uptimeSeconds >= 0); + assert.ok(data.process.pid > 0); +}); + +type MetricsBody = { + host: { cpuPercent: number; cpuCount: number }; + process: { cpuPercent: number }; +}; + +function fakeCpus(busyPerCore: number, idlePerCore: number, cores = 2): os.CpuInfo[] { + return Array.from({ length: cores }, () => ({ + model: "fake", + speed: 1000, + times: { user: busyPerCore, nice: 0, sys: 0, idle: idlePerCore, irq: 0 }, + })); +} + +test("GET /api/system/metrics derives host and process CPU from the delta between two samples", async (t) => { + // A fresh module instance: the route keeps its previous sample in module state, and the smoke + // test above already advanced it with the real clock. + const { GET } = await import("../../src/app/api/system/metrics/route.ts?cpu-delta"); + + let clock = 1_000_000; + let busy = 100; + let idle = 900; + let processMicros = 1_000; + t.mock.method(Date, "now", () => clock); + t.mock.method(os, "cpus", () => fakeCpus(busy, idle)); + t.mock.method(process, "cpuUsage", (previous?: NodeJS.CpuUsage) => { + const total = processMicros; + if (!previous) return { user: total, system: 0 }; + return { user: total - (previous.user + previous.system), system: 0 }; + }); + + const read = async () => (await (await GET()).json()) as MetricsBody; + + // First sample only records the baseline. + const first = await read(); + assert.equal(first.host.cpuPercent, 0); + assert.equal(first.process.cpuPercent, 0); + assert.equal(first.host.cpuCount, 2); + + // 1000 ms later: each core did +300 busy and +100 idle ticks, so 75% busy overall. + // The process burned 500 ms of CPU out of 2 cores x 1000 ms = 25%. + clock += 1_000; + busy += 300; + idle += 100; + processMicros += 500_000; + const second = await read(); + assert.equal(second.host.cpuPercent, 75); + assert.equal(second.process.cpuPercent, 25); + + // Inside the 400 ms window the cached values are returned, not a recomputation. + clock += 100; + busy += 200; + processMicros += 100_000; + const cached = await read(); + assert.equal(cached.host.cpuPercent, 75); + assert.equal(cached.process.cpuPercent, 25); + + // The next sample spans the whole 1100 ms since the last recorded one: per core +200 busy and + // +1000 idle ticks (1 - 1000/1200 = 16.7%), and 100 ms of process CPU over 2 x 1100 ms (4.5%). + clock += 1_000; + idle += 1_000; + const third = await read(); + assert.equal(third.host.cpuPercent, 16.7); + assert.equal(third.process.cpuPercent, 4.5); + + mock.restoreAll(); +}); diff --git a/tests/unit/ui/home-system-metrics-cards.test.tsx b/tests/unit/ui/home-system-metrics-cards.test.tsx new file mode 100644 index 000000000000..60b3ae376788 --- /dev/null +++ b/tests/unit/ui/home-system-metrics-cards.test.tsx @@ -0,0 +1,174 @@ +// @vitest-environment jsdom +import React, { act } from "react"; +import { createRoot } from "react-dom/client"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import enMessages from "@/i18n/messages/en.json"; + +const homeMessages = enMessages.home as Record; + +vi.mock("next-intl", () => ({ + useTranslations: () => (key: string, values?: Record) => { + const message = homeMessages[key]; + if (message === undefined) throw new Error(`missing home message: ${key}`); + return message.replace(/\{(\w+)\}/g, (_, name) => String(values?.[name])); + }, +})); + +import HomeSystemMetricsCards from "@/app/(dashboard)/home/HomeSystemMetricsCards"; + +function metricsBody(hostCpu: number, hostMemory: number) { + return { + host: { + cpuPercent: hostCpu, + memory: { + totalBytes: 8 * 1024 ** 3, + usedBytes: 2 * 1024 ** 3, + freeBytes: 6 * 1024 ** 3, + usedPercent: hostMemory, + }, + cpuCount: 4, + platform: "linux", + }, + process: { + cpuPercent: 1.5, + memory: { rssBytes: 300 * 1024 ** 2, heapUsedBytes: 120 * 1024 ** 2, heapTotalBytes: 1 }, + uptimeSeconds: 10, + pid: 1, + }, + }; +} + +function jsonResponse(body: unknown) { + return new Response(JSON.stringify(body), { status: 200 }); +} + +let visibility: DocumentVisibilityState = "visible"; +const containers: Array<{ root: ReturnType; element: HTMLDivElement }> = []; + +async function flush() { + await act(async () => { + await vi.advanceTimersByTimeAsync(0); + }); +} + +async function render() { + const element = document.createElement("div"); + document.body.appendChild(element); + const root = createRoot(element); + act(() => { + root.render(); + }); + containers.push({ root, element }); + await flush(); + return element; +} + +function bars(element: HTMLElement) { + return Array.from(element.querySelectorAll('[data-testid="usage-bar"]')); +} + +describe("HomeSystemMetricsCards", () => { + beforeEach(() => { + vi.useFakeTimers(); + visibility = "visible"; + vi.spyOn(document, "visibilityState", "get").mockImplementation(() => visibility); + }); + + afterEach(() => { + for (const { root, element } of containers.splice(0)) { + act(() => root.unmount()); + element.remove(); + } + vi.useRealTimers(); + vi.restoreAllMocks(); + vi.unstubAllGlobals(); + }); + + it("renders every label through the home message catalog, not hardcoded text", async () => { + vi.stubGlobal("fetch", vi.fn().mockResolvedValue(jsonResponse(metricsBody(12.34, 25)))); + const element = await render(); + + const text = element.textContent ?? ""; + expect(text).toContain("4 vCPUs • linux"); + expect(text).toContain("12.3%"); + expect(text).toContain("25.0%"); + expect(text).toContain("2.0 GB / 8.0 GB"); + expect(text).toContain("OmniRoute (process)"); + expect(text).toContain("300 MB (120 MB)"); + expect(text).toContain("Memory"); + expect(element.querySelector("section")?.getAttribute("aria-label")).toBe("System metrics"); + for (const dot of Array.from(element.querySelectorAll("span[title]"))) { + expect(dot.getAttribute("title")).toBe("Monitoring active"); + } + // The strings the first version of the cards hardcoded in Portuguese. + expect(text).not.toMatch(/Carregando|Memória|Processo/); + }); + + it("shows the loading label until the first sample arrives", async () => { + vi.stubGlobal("fetch", vi.fn().mockReturnValue(new Promise(() => {}))); + const element = await render(); + + expect(element.textContent).toContain("Loading..."); + expect(element.textContent).toContain("--"); + }); + + it.each([ + [64.9, "bg-success"], + [65, "bg-warning"], + [84.9, "bg-warning"], + [85, "bg-error"], + ])("colors a %s%% bar %s", async (percent, expected) => { + vi.stubGlobal("fetch", vi.fn().mockResolvedValue(jsonResponse(metricsBody(percent, percent)))); + const element = await render(); + + const rendered = bars(element); + expect(rendered).toHaveLength(2); + for (const bar of rendered) { + expect(bar.className).toContain(expected); + expect(bar.style.width).toBe(`${percent}%`); + } + }); + + it("does not poll while the tab is hidden and resumes when it is visible again", async () => { + visibility = "hidden"; + const fetchMock = vi.fn().mockImplementation(async () => jsonResponse(metricsBody(10, 10))); + vi.stubGlobal("fetch", fetchMock); + await render(); + + await act(async () => { + await vi.advanceTimersByTimeAsync(10_000); + }); + expect(fetchMock).not.toHaveBeenCalled(); + + visibility = "visible"; + await act(async () => { + await vi.advanceTimersByTimeAsync(3_500); + }); + expect(fetchMock).toHaveBeenCalledTimes(1); + + await act(async () => { + await vi.advanceTimersByTimeAsync(3_500); + }); + expect(fetchMock).toHaveBeenCalledTimes(2); + }); + + it("stops polling and aborts the in-flight request on unmount", async () => { + let signal: AbortSignal | undefined; + const fetchMock = vi.fn().mockImplementation((_url: string, init: RequestInit) => { + signal = init.signal ?? undefined; + return new Promise(() => {}); + }); + vi.stubGlobal("fetch", fetchMock); + await render(); + + const { root, element } = containers.pop()!; + act(() => root.unmount()); + element.remove(); + + expect(signal?.aborted).toBe(true); + await act(async () => { + await vi.advanceTimersByTimeAsync(10_000); + }); + expect(fetchMock).toHaveBeenCalledTimes(1); + }); +}); From 017b51f3c0e8b0cea798e2dac25d386d0168ff05 Mon Sep 17 00:00:00 2001 From: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> Date: Wed, 4 Nov 2026 13:44:23 -0300 Subject: [PATCH 19/20] feat(perf): reduce resource footprint across background schedulers, i18n, and Windows startup (#18396) --- .env.example | 5 +- ...-background-services-resource-footprint.md | 1 + contrib/windows/README.md | 39 +++- contrib/windows/start-omniroute-headless.bat | 22 +++ contrib/windows/start-omniroute-light.bat | 20 ++ contrib/windows/start-omniroute-prod.bat | 18 ++ docs/guides/HEADLESS.md | 4 +- docs/reference/ENVIRONMENT.md | 2 +- docs/reference/FEATURE_FLAGS.md | 2 +- src/i18n/request.ts | 43 +++-- src/instrumentation-node.ts | 49 ++--- src/lib/config/hotReload.ts | 59 +++--- src/lib/credentialHealth/scheduler.ts | 2 + src/lib/db/cleanup.ts | 8 + src/lib/db/vacuumScheduler.ts | 14 +- src/lib/env/backgroundServices.ts | 11 ++ src/shared/components/CloudSyncStatus.tsx | 8 +- src/shared/components/DegradationBadge.tsx | 5 +- src/shared/components/MaintenanceBanner.tsx | 7 +- src/shared/components/TokenHealthBadge.tsx | 5 +- src/shared/utils/visiblePolling.ts | 29 +++ ...round-services-disabled-schedulers.test.ts | 180 ++++++++++++++++++ tests/unit/scheduler-6h-stagger-13973.test.ts | 10 +- tests/unit/ui/visible-polling-badges.test.tsx | 61 ++++++ tests/unit/visible-polling.test.ts | 85 +++++++++ 25 files changed, 598 insertions(+), 91 deletions(-) create mode 100644 changelog.d/maintenance/18396-background-services-resource-footprint.md create mode 100644 contrib/windows/start-omniroute-headless.bat create mode 100644 contrib/windows/start-omniroute-light.bat create mode 100644 contrib/windows/start-omniroute-prod.bat create mode 100644 src/lib/env/backgroundServices.ts create mode 100644 src/shared/utils/visiblePolling.ts create mode 100644 tests/unit/background-services-disabled-schedulers.test.ts create mode 100644 tests/unit/ui/visible-polling-badges.test.tsx create mode 100644 tests/unit/visible-polling.test.ts diff --git a/.env.example b/.env.example index 11d9937d9a01..4dd23b8fba16 100644 --- a/.env.example +++ b/.env.example @@ -1381,8 +1381,9 @@ PROVIDER_LIMITS_SYNC_SPACING_MS=1500 # before the dashboard polls. Default: 5000 #PROVIDER_LIMITS_POST_USAGE_REFRESH_DELAY_MS=5000 -# Disable all background services (sync, pricing, model refresh). -# Used by: src/instrumentation-node.ts, src/lib/initCloudSync.ts +# Disable all background services (sync, pricing, model refresh, connection recovery, +# credential health, retention cleanup, scheduled VACUUM, settings hot-reload poll). +# Used by: src/lib/env/backgroundServices.ts, src/lib/initCloudSync.ts # Useful for: CI builds, test environments, or resource-constrained containers. # OMNIROUTE_DISABLE_BACKGROUND_SERVICES=false diff --git a/changelog.d/maintenance/18396-background-services-resource-footprint.md b/changelog.d/maintenance/18396-background-services-resource-footprint.md new file mode 100644 index 000000000000..54b82e75da88 --- /dev/null +++ b/changelog.d/maintenance/18396-background-services-resource-footprint.md @@ -0,0 +1 @@ +- **perf(runtime):** `OMNIROUTE_DISABLE_BACKGROUND_SERVICES` now also stops credential health checks, scheduled VACUUM, retention cleanup and the settings hot-reload poll; dashboard badges stop polling from a hidden tab and refresh when it returns; the English UI catalog is no longer merged with itself on every request; new Windows launchers for production, light and headless use ([#18396](https://github.com/cheaper-inference/OmniRoute/pull/18396)) — thanks @rafacpti23 diff --git a/contrib/windows/README.md b/contrib/windows/README.md index 8349cfb94b2a..10c3e3dc8049 100644 --- a/contrib/windows/README.md +++ b/contrib/windows/README.md @@ -1,22 +1,43 @@ # Windows helpers — run OmniRoute from a source checkout -Two double-clickable `.bat` files for Windows users who cloned the repo instead of -installing the npm package. Both resolve the repo root from their own location, so they +Double-clickable `.bat` files for Windows users who cloned the repo instead of +installing the npm package. All of them resolve the repo root from their own location, so they work from any checkout path. -| File | What it does | -| --------------------- | ---------------------------------------------------------------------------------- | -| `start-omniroute.bat` | `npm run dev` — dev server + dashboard on `http://localhost:20128` | -| `launch-claude.bat` | `node bin\omniroute.mjs launch` — opens Claude Code pointed at the local OmniRoute | +| File | What it does | +| ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------- | +| `start-omniroute-prod.bat` | `npm run start:fast`: production-mode Next server (not the standalone bundle) on a build made with `npm run build:fast`. Background services stay on | +| `start-omniroute-light.bat` | `npm run dev` with `OMNIROUTE_DISABLE_BACKGROUND_SERVICES=true` (see the trade-off below) | +| `start-omniroute-headless.bat` | `npm run dev` with `OMNIROUTE_HEADLESS=1` and `OMNIROUTE_DISABLE_BACKGROUND_SERVICES=true`: `/v1/*` only, no dashboard | +| `start-omniroute.bat` | `npm run dev`: dev server + dashboard on `http://localhost:20128` | +| `launch-claude.bat` | `node bin\omniroute.mjs launch`: opens Claude Code pointed at the local OmniRoute | Usage: 1. `npm install` once (see the note below if you are on npm ≥ 11). -2. Double-click `start-omniroute.bat` and wait for `dev server listening on http://0.0.0.0:20128`. -3. Open the dashboard, connect a provider, copy an API key from **Endpoints**. -4. Double-click `launch-claude.bat`. Extra arguments are forwarded, e.g. +2. Double-click your chosen startup script. `start-omniroute-prod.bat` needs `npm run build:fast` + first; the others compile on demand, as any dev server does. +3. Wait for `server listening on http://0.0.0.0:20128`. +4. Open the dashboard (not in headless mode) at `http://localhost:20128`, connect a provider, copy + an API key from **Endpoints**. +5. Double-click `launch-claude.bat`. Extra arguments are forwarded, e.g. `launch-claude.bat --profile glm52` after `node bin\omniroute.mjs setup-claude`. +## What the light and headless launchers turn off + +`OMNIROUTE_DISABLE_BACKGROUND_SERVICES=true` saves work by stopping every background service, which +has a cost on a long-running install: + +- Connections in cooldown are not re-validated in the background (connection recovery is off), and + quota data and provider limits are not refreshed. +- Cloud, model, free-proxy and proxy-health sync do not run. +- Retention cleanup and scheduled VACUUM do not run, so the database keeps growing. +- Credential health checks do not run, and the 5-second settings poll is off. Settings changes are + still picked up through the SQLite file watch when it is available. + +Use `start-omniroute-prod.bat` or `start-omniroute.bat` for an install you rely on day to day. See +[`docs/reference/ENVIRONMENT.md`](../../docs/reference/ENVIRONMENT.md) for the flag. + ## npm ≥ 11 skips `better-sqlite3` on Windows `better-sqlite3` is an `optionalDependency`. npm 11 blocks install scripts of optional diff --git a/contrib/windows/start-omniroute-headless.bat b/contrib/windows/start-omniroute-headless.bat new file mode 100644 index 000000000000..aeb634298f28 --- /dev/null +++ b/contrib/windows/start-omniroute-headless.bat @@ -0,0 +1,22 @@ +@echo off +setlocal +title OmniRoute Server (Headless Mode) +cd /d "%~dp0..\.." + +set OMNIROUTE_HEADLESS=1 +set OMNIROUTE_DISABLE_BACKGROUND_SERVICES=true + +echo Starting OmniRoute in Headless Mode on http://localhost:20128 ... +echo Proxy endpoints (/v1/*) are active. Dashboard is disabled. +echo Background services are off: no connection recovery, quota refresh, sync jobs, +echo retention cleanup or scheduled VACUUM. See contrib\windows\README.md. +echo Leave this window open. Close it to stop the server. +echo. +call npm run dev +if errorlevel 1 ( + echo. + echo The server exited with an error. + echo. +) +endlocal +pause diff --git a/contrib/windows/start-omniroute-light.bat b/contrib/windows/start-omniroute-light.bat new file mode 100644 index 000000000000..b72c34e6673c --- /dev/null +++ b/contrib/windows/start-omniroute-light.bat @@ -0,0 +1,20 @@ +@echo off +setlocal +title OmniRoute Server (Light Mode) +cd /d "%~dp0..\.." + +set OMNIROUTE_DISABLE_BACKGROUND_SERVICES=true + +echo Starting OmniRoute in Light Mode on http://localhost:20128 ... +echo Dashboard is active. Background services are off: no connection recovery, quota refresh, +echo sync jobs, retention cleanup or scheduled VACUUM. See contrib\windows\README.md. +echo Leave this window open. Close it to stop the server. +echo. +call npm run dev +if errorlevel 1 ( + echo. + echo The server exited with an error. + echo. +) +endlocal +pause diff --git a/contrib/windows/start-omniroute-prod.bat b/contrib/windows/start-omniroute-prod.bat new file mode 100644 index 000000000000..d5181e56b6e8 --- /dev/null +++ b/contrib/windows/start-omniroute-prod.bat @@ -0,0 +1,18 @@ +@echo off +setlocal +title OmniRoute Server (Production Mode) +cd /d "%~dp0..\.." + +echo Starting OmniRoute in Production Mode on http://localhost:20128 ... +echo Leave this window open. Close it to stop the server. +echo. +call npm run start:fast +if errorlevel 1 ( + echo. + echo The server exited with an error. + echo If this checkout has not been built yet, run first: npm run build:fast + echo Or use start-omniroute-light.bat, which does not need a build. + echo. +) +endlocal +pause diff --git a/docs/guides/HEADLESS.md b/docs/guides/HEADLESS.md index 86e4a788f17e..b74387e5be1f 100644 --- a/docs/guides/HEADLESS.md +++ b/docs/guides/HEADLESS.md @@ -192,7 +192,7 @@ After building and starting with a persistent data directory: ## See also - [ENVIRONMENT.md](../reference/ENVIRONMENT.md) — `OMNIROUTE_HEADLESS` and - `OMNIROUTE_DISABLE_BACKGROUND_SERVICES` (a separate switch that stops background schedulers - but keeps the dashboard). + `OMNIROUTE_DISABLE_BACKGROUND_SERVICES` (a separate switch that stops background schedulers, + including connection recovery, retention cleanup and scheduled VACUUM, but keeps the dashboard). - [REMOTE-MODE.md](REMOTE-MODE.md) — drive a remote (for example, headless) OmniRoute from your laptop with the `omniroute` CLI. diff --git a/docs/reference/ENVIRONMENT.md b/docs/reference/ENVIRONMENT.md index c1ca71191799..4096918e2d37 100644 --- a/docs/reference/ENVIRONMENT.md +++ b/docs/reference/ENVIRONMENT.md @@ -612,7 +612,7 @@ detection above). | `ADOBE_FIREFLY_MIN_SUBMIT_GAP_MS` | `12000` | `open-sse/services/adobeFireflySession.ts` | Minimum spacing in milliseconds between Adobe Firefly generate submissions; `0` disables spacing. | | `ADOBE_FIREFLY_BATCH_EXTRA_GAP_MS` | `15000` | `open-sse/services/adobeFireflySession.ts` | Extra quiet period in milliseconds after every third successful Adobe submission. | | `ADOBE_FIREFLY_SUBMIT_BASE_DELAY_MS` | `8000` | `open-sse/services/adobeFireflyClient.ts` | Base backoff in milliseconds after transient Adobe 408 responses; combined with submit spacing across at most five attempts. | -| `OMNIROUTE_DISABLE_BACKGROUND_SERVICES` | `false` | `src/instrumentation-node.ts` | Disable all background services (sync, pricing, model refresh). Useful for CI/test. | +| `OMNIROUTE_DISABLE_BACKGROUND_SERVICES` | `false` | `src/instrumentation-node.ts` | Disable all background services: quota refresh, provider-limits sync, connection recovery, credential health, cloud/model/free-proxy/proxy-health sync, retention cleanup, scheduled VACUUM and the settings hot-reload poll. Useful for CI/test; on a long-running install, connections do not auto-recover and the database keeps growing. | | `OMNIROUTE_HEADLESS` | _(unset)_ | `src/lib/system/headless.ts` | Proxy-engine-only boot: skips optional subsystems, `/dashboard` → 404. Same as `omniroute serve --headless`. See [HEADLESS.md](../guides/HEADLESS.md). | | `OMNIROUTE_ENABLE_RUNTIME_BACKGROUND_TASKS` | _(unset)_ | `src/lib/config/runtimeSettings.ts` | Force background tasks on under automated test detection. Set `1` to override the test heuristic. | | `OMNIROUTE_BUDGET_RESET_JOB_INTERVAL_MS` | `600000` | `src/lib/jobs/budgetResetJob.ts` | Budget reset check cadence (ms). Floor `10000`. | diff --git a/docs/reference/FEATURE_FLAGS.md b/docs/reference/FEATURE_FLAGS.md index 00b1debf7cef..a3ebde4ceb2f 100644 --- a/docs/reference/FEATURE_FLAGS.md +++ b/docs/reference/FEATURE_FLAGS.md @@ -130,7 +130,7 @@ same effective policy. | `OMNIROUTE_MCP_ENFORCE_SCOPES` | boolean | `false` | | Enforce scope restrictions on MCP tool access. | | `OMNIROUTE_MCP_COMPRESS_DESCRIPTIONS` | boolean | `false` | | Compress MCP tool descriptions to reduce token usage. | | `OMNIROUTE_ENABLE_RUNTIME_BACKGROUND_TASKS` | boolean | `false` | | Enable background task processing at runtime. | -| `OMNIROUTE_DISABLE_BACKGROUND_SERVICES` | boolean | `false` | ✓ | Disable all background services (quota refresh, sync, etc). | +| `OMNIROUTE_DISABLE_BACKGROUND_SERVICES` | boolean | `false` | ✓ | Disable all background services: quota refresh, provider-limits sync, connection recovery, credential health, cloud/model/free-proxy/proxy-health sync, retention cleanup, scheduled VACUUM, settings hot-reload poll. On a long-running install, connections do not auto-recover and the database keeps growing. | | `OMNIROUTE_RTK_TRUST_PROJECT_FILTERS` | boolean | `false` | | Trust project-level RTK filters without validation. | | `OMNIROUTE_ENABLE_LIVE_WS` | boolean | `true` | ✓ | Start the real-time dashboard WebSocket server on import (port 20132 by default). | | `OMNIROUTE_CODEX_WS_ENABLED` | boolean | `true` | | Allow Codex to use the Responses-over-WebSocket transport. When off, Codex falls back to HTTP Responses. | diff --git a/src/i18n/request.ts b/src/i18n/request.ts index f70bd9bed77e..ea087aaca86a 100644 --- a/src/i18n/request.ts +++ b/src/i18n/request.ts @@ -153,6 +153,18 @@ const loadLocaleMessages = createLocaleMessagesLoader( async (locale) => (await import(`./messages/${locale}.json`)).default ); +const normalizedCache = new WeakMap, Record>(); + +export function getNormalizedLocaleMessages( + messages: Record +): Record { + const cached = normalizedCache.get(messages); + if (cached) return cached; + const normalized = normalizeComplianceEventTypes(messages); + normalizedCache.set(messages, normalized); + return normalized; +} + export default getRequestConfig(async () => { const cookieStore = await cookies(); let locale: string = cookieStore.get(LOCALE_COOKIE)?.value || ""; @@ -164,17 +176,22 @@ export default getRequestConfig(async () => { locale = resolveRequestedLocale(locale, LOCALES, LOCALE_ALIASES, DEFAULT_LOCALE); - const localeMessages = normalizeComplianceEventTypes(await loadLocaleMessages(locale)); + const rawLocaleMessages = await loadLocaleMessages(locale); + const localeMessages = getNormalizedLocaleMessages(rawLocaleMessages); + + // When the active locale is the fallback (EN), return immediately without cloning or merging + if (locale === FALLBACK_LOCALE) { + return { + locale, + messages: localeMessages, + }; + } // G1: fall back to EN for any missing key. EN is loaded only once per request // and only when the active locale is not EN itself (no-op). - let messages = localeMessages as Record; - if (locale !== FALLBACK_LOCALE) { - const fallbackMessages = normalizeComplianceEventTypes( - await loadLocaleMessages(FALLBACK_LOCALE) - ); - messages = deepMergeFallback({ ...localeMessages }, fallbackMessages); - } + const rawFallbackMessages = await loadLocaleMessages(FALLBACK_LOCALE); + const fallbackMessages = getNormalizedLocaleMessages(rawFallbackMessages); + let messages = deepMergeFallback({ ...localeMessages }, fallbackMessages); // 4. Merge EN as namespace-level fallback for locales that are missing new namespaces. // Only applied when the active locale is not EN (avoids a redundant import). @@ -183,14 +200,14 @@ export default getRequestConfig(async () => { // This ensures new namespaces (e.g. cliCode, cliAgents, acpAgents, cliCommon added // in plan 14 F9) are displayed in English for the 39 non-EN/non-pt-BR locales until // translations are shipped. - let mergedMessages: Record = messages as Record; - if (locale !== DEFAULT_LOCALE) { - const enMessages = normalizeComplianceEventTypes(await loadLocaleMessages(DEFAULT_LOCALE)); - mergedMessages = { ...enMessages, ...mergedMessages }; + if (locale !== DEFAULT_LOCALE && DEFAULT_LOCALE !== FALLBACK_LOCALE) { + const rawEnMessages = await loadLocaleMessages(DEFAULT_LOCALE); + const enMessages = getNormalizedLocaleMessages(rawEnMessages); + messages = { ...enMessages, ...messages }; } return { locale, - messages: mergedMessages, + messages, }; }); diff --git a/src/instrumentation-node.ts b/src/instrumentation-node.ts index b345d3166fd1..e42bf66247e0 100755 --- a/src/instrumentation-node.ts +++ b/src/instrumentation-node.ts @@ -7,6 +7,7 @@ */ import { markServerReady, markServerStarting } from "@/lib/serverLifecycle"; +import { isBackgroundServicesDisabled } from "@/lib/env/backgroundServices"; import { normalizeBootError } from "@/lib/instrumentationBootError"; import { isHeadless, skipInHeadless } from "@/lib/system/headless"; @@ -103,11 +104,7 @@ export async function ensureDbReadyForBoot( } } -export function isBackgroundServicesDisabled(): boolean { - const raw = process.env.OMNIROUTE_DISABLE_BACKGROUND_SERVICES; - if (!raw) return false; - return new Set(["1", "true", "yes", "on"]).has(raw.trim().toLowerCase()); -} +export { isBackgroundServicesDisabled }; async function ensureSecrets(): Promise { let getPersistedSecret = (_key: string): string | null => null; @@ -838,26 +835,30 @@ export async function registerNodejs(): Promise { console.warn("[COMPLIANCE] Could not initialize audit log:", msg); } - // Storage-configured scheduled VACUUM (#4437): registers the timer from - // Settings > System & Storage and persists lastVacuumAt for the UI. - try { - const { initVacuumScheduler } = await import("@/lib/db/vacuumScheduler"); - initVacuumScheduler(); - console.log("[STARTUP] Scheduled VACUUM initialized (#4437)"); - } catch (err: unknown) { - const msg = err instanceof Error ? err.message : String(err); - console.warn("[STARTUP] Could not initialize vacuum scheduler (non-fatal):", msg); - } + if (!isBackgroundServicesDisabled()) { + // Storage-configured scheduled VACUUM (#4437): registers the timer from + // Settings > System & Storage and persists lastVacuumAt for the UI. + try { + const { initVacuumScheduler } = await import("@/lib/db/vacuumScheduler"); + initVacuumScheduler(); + console.log("[STARTUP] Scheduled VACUUM initialized (#4437)"); + } catch (err: unknown) { + const msg = err instanceof Error ? err.message : String(err); + console.warn("[STARTUP] Could not initialize vacuum scheduler (non-fatal):", msg); + } - // Retention cleanup scheduler (#4691/#6988, #9624): runs the general retention - // cleanup once after startup and then every 6 hours. Previously this was only - // wired into the unused src/server-init.ts, so telemetry tables grew unboundedly - // even with retention.autoCleanupEnabled=true. Idempotent (guarded internally). - try { - startCleanupScheduler(); - } catch (err: unknown) { - const msg = err instanceof Error ? err.message : String(err); - console.warn("[STARTUP] Could not start cleanup scheduler (non-fatal):", msg); + // Retention cleanup scheduler (#4691/#6988, #9624): runs the general retention + // cleanup once after startup and then every 6 hours. Previously this was only + // wired into the unused src/server-init.ts, so telemetry tables grew unboundedly + // even with retention.autoCleanupEnabled=true. Idempotent (guarded internally). + try { + startCleanupScheduler(); + } catch (err: unknown) { + const msg = err instanceof Error ? err.message : String(err); + console.warn("[STARTUP] Could not start cleanup scheduler (non-fatal):", msg); + } + } else { + console.log("[STARTUP] Background schedulers disabled (vacuum, cleanup)"); } if (!isBackgroundServicesDisabled()) { diff --git a/src/lib/config/hotReload.ts b/src/lib/config/hotReload.ts index 63218e476450..4b50859fd8e1 100644 --- a/src/lib/config/hotReload.ts +++ b/src/lib/config/hotReload.ts @@ -2,6 +2,7 @@ import fs from "node:fs"; import path from "node:path"; import { SQLITE_FILE } from "@/lib/db/core"; import { getSettings } from "@/lib/db/settings"; +import { isBackgroundServicesDisabled } from "@/lib/env/backgroundServices"; import { applyRuntimeSettings, type RuntimeReloadChange } from "./runtimeSettings"; const DEFAULT_POLL_INTERVAL_MS = 5_000; @@ -11,6 +12,7 @@ let activeCheck: Promise | null = null; let queuedSource: string | null = null; let pollTimer: ReturnType | null = null; let sqliteWatcher: fs.FSWatcher | null = null; +let hotReloadStarted = false; function getPollIntervalMs() { const parsed = Number.parseInt(process.env.OMNIROUTE_CONFIG_HOT_RELOAD_MS || "", 10); @@ -63,39 +65,49 @@ function queueHotReloadCheck(source: string) { }); } -export function startRuntimeConfigHotReload(options: { pollIntervalMs?: number } = {}) { - if (pollTimer || sqliteWatcher) return; - - const pollIntervalMs = Math.max( - options.pollIntervalMs || getPollIntervalMs(), - MIN_POLL_INTERVAL_MS - ); - +function armPoll(pollIntervalMs: number) { pollTimer = setInterval(() => { queueHotReloadCheck("hot-reload:poll"); }, pollIntervalMs); if (typeof pollTimer === "object" && "unref" in pollTimer) { (pollTimer as { unref?: () => void }).unref?.(); } +} - if (SQLITE_FILE) { - try { - sqliteWatcher = fs.watch(path.dirname(SQLITE_FILE), (_eventType, filename) => { - const normalizedFilename = typeof filename === "string" ? filename : filename?.toString(); - if (isRelevantSqliteChange(normalizedFilename || null)) { - queueHotReloadCheck("hot-reload:fs-watch"); - } - }); - } catch (error) { - console.warn( - "[HOT_RELOAD] SQLite file watch unavailable, polling only:", - error instanceof Error ? error.message : error - ); - } +function watchSqliteFile(pollEnabled: boolean) { + if (!SQLITE_FILE) return; + try { + sqliteWatcher = fs.watch(path.dirname(SQLITE_FILE), (_eventType, filename) => { + const normalizedFilename = typeof filename === "string" ? filename : filename?.toString(); + if (isRelevantSqliteChange(normalizedFilename || null)) { + queueHotReloadCheck("hot-reload:fs-watch"); + } + }); + } catch (error) { + console.warn( + pollEnabled + ? "[HOT_RELOAD] SQLite file watch unavailable, polling only:" + : "[HOT_RELOAD] SQLite file watch unavailable and polling is off, settings apply at restart:", + error instanceof Error ? error.message : error + ); } +} + +export function startRuntimeConfigHotReload(options: { pollIntervalMs?: number } = {}) { + if (hotReloadStarted) return; + hotReloadStarted = true; + + const pollEnabled = !isBackgroundServicesDisabled(); + const pollIntervalMs = Math.max( + options.pollIntervalMs || getPollIntervalMs(), + MIN_POLL_INTERVAL_MS + ); + + if (pollEnabled) armPoll(pollIntervalMs); + watchSqliteFile(pollEnabled); console.log( - `[HOT_RELOAD] Runtime config hot-reload started (poll=${pollIntervalMs}ms, fsWatch=${ + `[HOT_RELOAD] Runtime config hot-reload started (poll=${pollEnabled ? `${pollIntervalMs}ms` : "off"}, fsWatch=${ sqliteWatcher ? "on" : "off" })` ); @@ -110,6 +122,7 @@ export function stopRuntimeConfigHotReloadForTests() { } sqliteWatcher?.close(); sqliteWatcher = null; + hotReloadStarted = false; queuedSource = null; activeCheck = null; } diff --git a/src/lib/credentialHealth/scheduler.ts b/src/lib/credentialHealth/scheduler.ts index 9fd81d655122..7146bbc1008a 100644 --- a/src/lib/credentialHealth/scheduler.ts +++ b/src/lib/credentialHealth/scheduler.ts @@ -31,6 +31,7 @@ import { } from "@/lib/credentialHealth/probePolicy"; import { isInRefreshBackoff } from "@/lib/tokenRefreshCircuit"; import { emit } from "@/lib/events/eventBus"; +import { isBackgroundServicesDisabled } from "@/lib/env/backgroundServices"; import { isAutomatedTestProcess } from "@/shared/utils/testProcess"; import { SEARCH_VALIDATOR_CONFIGS } from "@/lib/providers/validation/searchProviders"; @@ -83,6 +84,7 @@ function isBuildProcess(): boolean { function isCredentialHealthCheckDisabled(): boolean { if (isBuildProcess() || isAutomatedTestProcess()) return true; + if (isBackgroundServicesDisabled()) return true; const val = process.env.OMNIROUTE_DISABLE_CREDENTIAL_HEALTH_CHECK; return val ? TRUE_ENV_VALUES.has(val.trim().toLowerCase()) : false; } diff --git a/src/lib/db/cleanup.ts b/src/lib/db/cleanup.ts index 081edcb64a03..edc3122dd817 100644 --- a/src/lib/db/cleanup.ts +++ b/src/lib/db/cleanup.ts @@ -4,6 +4,7 @@ * @module lib/db/cleanup */ +import { isBackgroundServicesDisabled } from "@/lib/env/backgroundServices"; import { rollupUsageHistoryBeforeDate } from "@/lib/usage/aggregateHistory"; import { purgeCallLogArtifactDirectory } from "@/lib/usage/callLogArtifacts"; @@ -1111,6 +1112,13 @@ export async function runScheduledCleanupPass(phase: "startup" | "periodic"): Pr export function startCleanupScheduler(): void { if (_cleanupSchedulerTimer) return; + if (isBackgroundServicesDisabled()) { + console.log( + "[Cleanup] Background cleanup scheduler disabled (OMNIROUTE_DISABLE_BACKGROUND_SERVICES)." + ); + return; + } + // Run cleanup 30s after startup (let the server initialize first). setTimeout(async () => { try { diff --git a/src/lib/db/vacuumScheduler.ts b/src/lib/db/vacuumScheduler.ts index 191c55898559..fe1c1a633910 100644 --- a/src/lib/db/vacuumScheduler.ts +++ b/src/lib/db/vacuumScheduler.ts @@ -1,3 +1,4 @@ +import { isBackgroundServicesDisabled } from "@/lib/env/backgroundServices"; import { DEFAULT_DATABASE_SETTINGS } from "@/types/databaseSettings"; import { MAX_TIMER_TIMEOUT_MS } from "@/shared/utils/runtimeTimeouts"; @@ -237,7 +238,14 @@ function armTimer(): void { clearTimeout(timer); timer = null; } - if (!currentState.enabled || currentState.nextRunAt === null || currentState.isRunning) { + // The flag is checked here because refresh() also runs from the settings API and after a manual + // VACUUM, and either of those would otherwise re-arm the timer. + if ( + !currentState.enabled || + currentState.nextRunAt === null || + currentState.isRunning || + isBackgroundServicesDisabled() + ) { currentState.nextRunAt = null; return; } @@ -432,6 +440,10 @@ export function requestFullVacuum(reason: string): VacuumSchedulerState { export function init(): VacuumSchedulerState { if (timer) return getState(); + if (isBackgroundServicesDisabled()) { + console.log("[STARTUP] Vacuum scheduler disabled (OMNIROUTE_DISABLE_BACKGROUND_SERVICES)"); + } + hydrated = false; // an explicit init() always re-reads the persisted blob hydrateFromPersistedState(); return refresh(); diff --git a/src/lib/env/backgroundServices.ts b/src/lib/env/backgroundServices.ts new file mode 100644 index 000000000000..895d7e7ab40f --- /dev/null +++ b/src/lib/env/backgroundServices.ts @@ -0,0 +1,11 @@ +const TRUTHY_ENV_VALUES = new Set(["1", "true", "yes", "on"]); + +/** + * Whether OMNIROUTE_DISABLE_BACKGROUND_SERVICES is on. One reader for the schedulers that gate on + * this flag, so they agree on which spellings count as "on". + */ +export function isBackgroundServicesDisabled(env: NodeJS.ProcessEnv = process.env): boolean { + const raw = env.OMNIROUTE_DISABLE_BACKGROUND_SERVICES; + if (!raw) return false; + return TRUTHY_ENV_VALUES.has(raw.trim().toLowerCase()); +} diff --git a/src/shared/components/CloudSyncStatus.tsx b/src/shared/components/CloudSyncStatus.tsx index e820ec2845cd..9ad7e83bb2c1 100644 --- a/src/shared/components/CloudSyncStatus.tsx +++ b/src/shared/components/CloudSyncStatus.tsx @@ -13,6 +13,7 @@ import { useState, useEffect, useRef, useCallback } from "react"; import { useRouter } from "next/navigation"; import { useTranslations } from "next-intl"; +import { isTabVisible, startVisiblePolling } from "@/shared/utils/visiblePolling"; import Button from "./Button"; import Icon from "./Icon"; @@ -36,6 +37,7 @@ export default function CloudSyncStatus({ collapsed = false }) { const router = useRouter(); const poll = useCallback(async () => { + if (!isTabVisible()) return; try { const res = await fetch("/api/sync/cloud"); if (!mountedRef.current) return; @@ -60,9 +62,7 @@ export default function CloudSyncStatus({ collapsed = false }) { useEffect(() => { mountedRef.current = true; - // Schedule initial poll outside of effect body to avoid setState-in-effect lint - queueMicrotask(poll); - const interval = setInterval(poll, 30000); + const stopPolling = startVisiblePolling(poll, 30000); // Listen for immediate re-poll events from EndpointPageClient const handleCloudChange = () => { @@ -72,7 +72,7 @@ export default function CloudSyncStatus({ collapsed = false }) { return () => { mountedRef.current = false; - clearInterval(interval); + stopPolling(); globalThis.removeEventListener("cloud-status-changed", handleCloudChange); }; }, [poll]); diff --git a/src/shared/components/DegradationBadge.tsx b/src/shared/components/DegradationBadge.tsx index 877de770028e..6d183a309475 100644 --- a/src/shared/components/DegradationBadge.tsx +++ b/src/shared/components/DegradationBadge.tsx @@ -5,6 +5,7 @@ import Link from "next/link"; import { useTranslations } from "next-intl"; import { Badge as KitBadge } from "@omniroute/ui-kit/badge"; import Icon from "@/shared/components/Icon"; +import { startVisiblePolling } from "@/shared/utils/visiblePolling"; export default function DegradationBadge() { const [isDegraded, setDegraded] = useState(false); @@ -23,9 +24,7 @@ export default function DegradationBadge() { } }; - checkDegradation(); - const interval = setInterval(checkDegradation, 60000); - return () => clearInterval(interval); + return startVisiblePolling(checkDegradation, 60000); }, []); if (!isDegraded) return null; diff --git a/src/shared/components/MaintenanceBanner.tsx b/src/shared/components/MaintenanceBanner.tsx index 5820c26864b4..02f7b270f631 100644 --- a/src/shared/components/MaintenanceBanner.tsx +++ b/src/shared/components/MaintenanceBanner.tsx @@ -13,6 +13,7 @@ import { useRef } from "react"; import { useTranslations } from "next-intl"; import Button from "./Button"; import Icon from "./Icon"; +import { startVisiblePolling } from "@/shared/utils/visiblePolling"; export default function MaintenanceBanner() { const [show, setShow] = useState(false); @@ -58,10 +59,8 @@ export default function MaintenanceBanner() { } }; - // Run immediately on mount, then every 10 seconds - checkHealth(); - const interval = setInterval(checkHealth, 10000); - return () => clearInterval(interval); + // Run on mount, then every 10 seconds while the tab is visible + return startVisiblePolling(checkHealth, 10000); }, [t]); if (!show) return null; diff --git a/src/shared/components/TokenHealthBadge.tsx b/src/shared/components/TokenHealthBadge.tsx index 47e6f7a7024b..c50876dedfae 100644 --- a/src/shared/components/TokenHealthBadge.tsx +++ b/src/shared/components/TokenHealthBadge.tsx @@ -12,6 +12,7 @@ import { useTranslations } from "next-intl"; import { useState, useEffect, useRef } from "react"; import { Popover, PopoverContent, PopoverTrigger } from "@omniroute/ui-kit/popover"; +import { startVisiblePolling } from "@/shared/utils/visiblePolling"; import Badge from "./Badge"; import Button from "./Button"; import Icon from "./Icon"; @@ -56,9 +57,7 @@ export default function TokenHealthBadge() { } }; - fetchHealth(); - const interval = setInterval(fetchHealth, 60000); - return () => clearInterval(interval); + return startVisiblePolling(fetchHealth, 60000); }, []); if (!health || health.total === 0) return null; diff --git a/src/shared/utils/visiblePolling.ts b/src/shared/utils/visiblePolling.ts new file mode 100644 index 000000000000..db73fac34667 --- /dev/null +++ b/src/shared/utils/visiblePolling.ts @@ -0,0 +1,29 @@ +/** True when the tab can show the result of a poll; always true outside a browser. */ +export function isTabVisible(): boolean { + return typeof document === "undefined" || document.visibilityState === "visible"; +} + +/** + * Poll on an interval, skipping ticks while the tab is hidden, and catch up as soon as the tab + * returns so a badge is never stale for up to a full interval. Returns the stop function. + */ +export function startVisiblePolling( + poll: () => void | Promise, + intervalMs: number +): () => void { + let stopped = false; + const tick = () => { + if (stopped || !isTabVisible()) return; + void poll(); + }; + + queueMicrotask(tick); + const interval = setInterval(tick, intervalMs); + if (typeof document !== "undefined") document.addEventListener("visibilitychange", tick); + + return () => { + stopped = true; + clearInterval(interval); + if (typeof document !== "undefined") document.removeEventListener("visibilitychange", tick); + }; +} diff --git a/tests/unit/background-services-disabled-schedulers.test.ts b/tests/unit/background-services-disabled-schedulers.test.ts new file mode 100644 index 000000000000..5a6515041cd8 --- /dev/null +++ b/tests/unit/background-services-disabled-schedulers.test.ts @@ -0,0 +1,180 @@ +import { afterEach, after, before, beforeEach, test } from "node:test"; +import assert from "node:assert/strict"; +import { spawnSync } from "node:child_process"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; + +import { getDbInstance, resetDbInstance } from "../../src/lib/db/core.ts"; +import { startCleanupScheduler, stopCleanupScheduler } from "../../src/lib/db/cleanup.ts"; +import * as vacuumScheduler from "../../src/lib/db/vacuumScheduler.ts"; +import { + startRuntimeConfigHotReload, + stopRuntimeConfigHotReloadForTests, +} from "../../src/lib/config/hotReload.ts"; + +const FLAG = "OMNIROUTE_DISABLE_BACKGROUND_SERVICES"; +const originalFlag = process.env[FLAG]; +const realSetTimeout = globalThis.setTimeout; +const realSetInterval = globalThis.setInterval; + +type TimerCall = { delay: number }; + +/** Records the timers a scheduler arms, without letting them fire. */ +function captureTimers() { + const timeouts: TimerCall[] = []; + const intervals: TimerCall[] = []; + const handle = () => ({ unref() {}, ref() {}, hasRef: () => false }) as unknown as NodeJS.Timeout; + globalThis.setTimeout = ((_fn: unknown, delay = 0) => { + timeouts.push({ delay }); + return handle(); + }) as typeof setTimeout; + globalThis.setInterval = ((_fn: unknown, delay = 0) => { + intervals.push({ delay }); + return handle(); + }) as typeof setInterval; + return { timeouts, intervals }; +} + +function restoreTimers() { + globalThis.setTimeout = realSetTimeout; + globalThis.setInterval = realSetInterval; +} + +function setFlag(value: string | undefined) { + if (value === undefined) delete process.env[FLAG]; + else process.env[FLAG] = value; +} + +before(() => { + getDbInstance(); +}); + +beforeEach(() => { + vacuumScheduler.__resetForTests(); +}); + +afterEach(() => { + restoreTimers(); + stopCleanupScheduler(); + stopRuntimeConfigHotReloadForTests(); + vacuumScheduler.__resetForTests(); + setFlag(originalFlag); +}); + +after(() => { + resetDbInstance(); +}); + +test("cleanup scheduler arms no timer with the flag on", () => { + setFlag("1"); + const timers = captureTimers(); + startCleanupScheduler(); + restoreTimers(); + assert.deepEqual(timers.timeouts, []); + assert.deepEqual(timers.intervals, []); +}); + +test("cleanup scheduler still arms its startup and periodic timers with the flag off", () => { + setFlag(undefined); + const timers = captureTimers(); + startCleanupScheduler(); + restoreTimers(); + assert.equal(timers.timeouts.filter((t) => t.delay === 30_000).length, 1); + assert.equal(timers.intervals.filter((t) => t.delay === 6 * 60 * 60 * 1000).length, 1); +}); + +test("vacuum scheduler arms no timer on init or refresh with the flag on", () => { + setFlag("yes"); + + const afterInit = vacuumScheduler.initVacuumScheduler(); + assert.equal(afterInit.enabled, true, "the schedule itself stays configured"); + assert.equal(afterInit.nextRunAt, null, "init must not schedule a run"); + + // The settings API calls refresh(), so that path must not re-arm the timer behind the flag. + const afterRefresh = vacuumScheduler.refreshVacuumScheduler(); + assert.equal(afterRefresh.nextRunAt, null, "refresh must not schedule a run"); +}); + +test("vacuum scheduler still schedules a run with the flag off", () => { + setFlag(undefined); + const state = vacuumScheduler.initVacuumScheduler(); + assert.equal(typeof state.nextRunAt, "number"); +}); + +test("hot reload arms no poll with the flag on and starts only once", () => { + setFlag("true"); + const timers = captureTimers(); + const logs: string[] = []; + const realLog = console.log; + console.log = (...args: unknown[]) => logs.push(args.join(" ")); + try { + startRuntimeConfigHotReload({ pollIntervalMs: 54_321 }); + startRuntimeConfigHotReload({ pollIntervalMs: 54_321 }); + } finally { + console.log = realLog; + restoreTimers(); + } + assert.deepEqual( + timers.intervals.filter((t) => t.delay === 54_321), + [] + ); + const started = logs.filter((line) => line.includes("hot-reload started")); + assert.equal(started.length, 1, "a second start must be a no-op"); + assert.match(started[0], /poll=off/); +}); + +test("hot reload still arms its poll with the flag off", () => { + setFlag(undefined); + const timers = captureTimers(); + const logs: string[] = []; + const realLog = console.log; + console.log = (...args: unknown[]) => logs.push(args.join(" ")); + try { + startRuntimeConfigHotReload({ pollIntervalMs: 54_321 }); + } finally { + console.log = realLog; + restoreTimers(); + } + assert.equal(timers.intervals.filter((t) => t.delay === 54_321).length, 1); + assert.match(logs.find((line) => line.includes("hot-reload started")) ?? "", /poll=54321ms/); +}); + +/** + * initCredentialHealthCheck() short-circuits on isAutomatedTestProcess(), so the flag branch can + * only be reached from a process that does not look like a test run. No argument of the child may + * contain a "test" token, and NODE_ENV and the runner's own variables must be gone from its env. + */ +function runCredentialHealthInit(flag: string | undefined): string { + const dataDir = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-bg-services-")); + const env: NodeJS.ProcessEnv = { ...process.env, DATA_DIR: dataDir }; + delete env.NODE_ENV; + delete env.VITEST; + delete env.NODE_TEST_CONTEXT; + delete env.NODE_OPTIONS; + delete env[FLAG]; + if (flag !== undefined) env[FLAG] = flag; + const code = [ + 'const m = await import("./src/lib/credentialHealth/scheduler.ts");', + "const started = m.initCredentialHealthCheck();", + "m.stopCredentialHealthCheck();", + "process.stdout.write(`RESULT:${started}`);", + "process.exit(0);", + ].join("\n"); + try { + const child = spawnSync( + process.execPath, + ["--import", "tsx/esm", "--input-type=module", "-e", code], + { cwd: process.cwd(), env, encoding: "utf8", timeout: 180_000 } + ); + assert.equal(child.status, 0, child.stderr); + return /RESULT:(true|false)/.exec(child.stdout)?.[1] ?? `no result: ${child.stdout}`; + } finally { + fs.rmSync(dataDir, { recursive: true, force: true }); + } +} + +test("credential health check honours the flag outside the test-process short-circuit", () => { + assert.equal(runCredentialHealthInit("true"), "false"); + assert.equal(runCredentialHealthInit(undefined), "true"); +}); diff --git a/tests/unit/scheduler-6h-stagger-13973.test.ts b/tests/unit/scheduler-6h-stagger-13973.test.ts index 2c721888e32b..ea894368b300 100644 --- a/tests/unit/scheduler-6h-stagger-13973.test.ts +++ b/tests/unit/scheduler-6h-stagger-13973.test.ts @@ -19,7 +19,13 @@ import { test, before, after } from "node:test"; import assert from "node:assert/strict"; -process.env.OMNIROUTE_DISABLE_BACKGROUND_SERVICES = "1"; +// The stagger contract only exists while the background services run. With +// OMNIROUTE_DISABLE_BACKGROUND_SERVICES on, startCleanupScheduler() arms nothing at all (covered +// by background-services-disabled-schedulers.test.ts), so force the flag off for this file and +// restore it afterwards. +const BACKGROUND_FLAG = "OMNIROUTE_DISABLE_BACKGROUND_SERVICES"; +const originalBackgroundFlag = process.env[BACKGROUND_FLAG]; +delete process.env[BACKGROUND_FLAG]; let startCleanupScheduler: () => void; let stopCleanupScheduler: () => void; @@ -33,6 +39,8 @@ before(async () => { }); after(() => { + if (originalBackgroundFlag === undefined) delete process.env[BACKGROUND_FLAG]; + else process.env[BACKGROUND_FLAG] = originalBackgroundFlag; try { stopCleanupScheduler(); } catch {} diff --git a/tests/unit/ui/visible-polling-badges.test.tsx b/tests/unit/ui/visible-polling-badges.test.tsx new file mode 100644 index 000000000000..e408715f3314 --- /dev/null +++ b/tests/unit/ui/visible-polling-badges.test.tsx @@ -0,0 +1,61 @@ +// @vitest-environment jsdom +/** + * The dashboard's polling badges must not fetch from a hidden tab and must refresh as soon as the + * tab returns, so a tab switch never leaves them stale for a full polling interval. + */ +import React from "react"; +import { act, cleanup, render } from "@testing-library/react"; +import { NextIntlClientProvider } from "next-intl"; +import { afterEach, describe, expect, it, vi } from "vitest"; +import CloudSyncStatus from "@/shared/components/CloudSyncStatus"; +import DegradationBadge from "@/shared/components/DegradationBadge"; +import MaintenanceBanner from "@/shared/components/MaintenanceBanner"; +import TokenHealthBadge from "@/shared/components/TokenHealthBadge"; +import messages from "../../../src/i18n/messages/en.json"; + +vi.mock("next/navigation", () => ({ + useRouter: () => ({ push: vi.fn(), refresh: vi.fn() }), + usePathname: () => "/dashboard", +})); + +function setVisibility(state: "visible" | "hidden") { + Object.defineProperty(document, "visibilityState", { configurable: true, value: state }); +} + +afterEach(() => { + cleanup(); + vi.unstubAllGlobals(); + setVisibility("visible"); +}); + +const cases: Array<[string, React.ReactElement, string]> = [ + ["DegradationBadge", , "/api/health/degradation?summary=true"], + ["MaintenanceBanner", , "/api/health/ping"], + ["TokenHealthBadge", , "/api/token-health"], + ["CloudSyncStatus", , "/api/sync/cloud"], +]; + +describe.each(cases)("%s", (_name, element, url) => { + it("does not fetch while hidden and refreshes when the tab returns", async () => { + const fetchMock = vi.fn(async () => ({ ok: true, json: async () => ({}) })); + vi.stubGlobal("fetch", fetchMock); + setVisibility("hidden"); + + render( + + {element} + + ); + await act(async () => { + await Promise.resolve(); + }); + expect(fetchMock).not.toHaveBeenCalled(); + + setVisibility("visible"); + await act(async () => { + document.dispatchEvent(new Event("visibilitychange")); + await Promise.resolve(); + }); + expect(fetchMock.mock.calls.map((call) => call[0])).toEqual([url]); + }); +}); diff --git a/tests/unit/visible-polling.test.ts b/tests/unit/visible-polling.test.ts new file mode 100644 index 000000000000..a552e244afc2 --- /dev/null +++ b/tests/unit/visible-polling.test.ts @@ -0,0 +1,85 @@ +import { afterEach, test } from "node:test"; +import assert from "node:assert/strict"; + +import { isTabVisible, startVisiblePolling } from "../../src/shared/utils/visiblePolling.ts"; +import { + getNormalizedLocaleMessages, + normalizeComplianceEventTypes, +} from "../../src/i18n/request.ts"; + +type Listener = () => void; + +function fakeDocument(visibilityState: string) { + const listeners = new Set(); + return { + visibilityState, + addEventListener: (_type: string, listener: Listener) => listeners.add(listener), + removeEventListener: (_type: string, listener: Listener) => listeners.delete(listener), + fire: () => listeners.forEach((listener) => listener()), + listeners, + }; +} + +const flush = () => new Promise((resolve) => setImmediate(resolve)); + +afterEach(() => { + delete (globalThis as { document?: unknown }).document; +}); + +test("isTabVisible is true without a document, so server rendering never skips", () => { + assert.equal(isTabVisible(), true); +}); + +test("polls on start while visible, and stops after the returned cleanup", async () => { + const doc = fakeDocument("visible"); + (globalThis as { document?: unknown }).document = doc; + let calls = 0; + const stop = startVisiblePolling(() => { + calls += 1; + }, 60_000); + await flush(); + assert.equal(calls, 1); + + stop(); + assert.equal(doc.listeners.size, 0, "the visibilitychange listener must be removed"); + doc.fire(); + assert.equal(calls, 1); +}); + +test("skips while hidden and catches up when the tab returns", async () => { + const doc = fakeDocument("hidden"); + (globalThis as { document?: unknown }).document = doc; + let calls = 0; + const stop = startVisiblePolling(() => { + calls += 1; + }, 60_000); + await flush(); + assert.equal(calls, 0, "a hidden tab must not poll on mount"); + + doc.fire(); + assert.equal(calls, 0, "a change that leaves the tab hidden must not poll"); + + doc.visibilityState = "visible"; + doc.fire(); + assert.equal(calls, 1, "returning to the tab must refresh at once"); + stop(); +}); + +test("does not poll if stopped before the first tick", async () => { + (globalThis as { document?: unknown }).document = fakeDocument("visible"); + let calls = 0; + const stop = startVisiblePolling(() => { + calls += 1; + }, 60_000); + stop(); + await flush(); + assert.equal(calls, 0); +}); + +test("normalized catalogs are computed once per loaded catalog", () => { + const catalog = { compliance: { eventTypes: { "auth.login": "Login" } } }; + const first = getNormalizedLocaleMessages(catalog); + assert.equal(getNormalizedLocaleMessages(catalog), first, "second read must reuse the result"); + assert.deepEqual(first, normalizeComplianceEventTypes(catalog)); + assert.notEqual(getNormalizedLocaleMessages({ ...catalog }), first); +}); From 542104bb5b0a36058c503c42c0aa36b40236f657 Mon Sep 17 00:00:00 2001 From: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> Date: Wed, 4 Nov 2026 13:44:31 -0300 Subject: [PATCH 20/20] fix(sse): stop prefill 400s from locking models; opt-in user-last for anthropic-compatible (#18332) --- ...-anthropic-compatible-assistant-prefill.md | 1 + open-sse/executors/base.ts | 17 + open-sse/services/accountFallback.ts | 10 +- open-sse/services/assistantPrefill.ts | 89 +++++ open-sse/services/assistantPrefillError.ts | 21 ++ open-sse/services/combo/comboPredicates.ts | 5 +- open-sse/services/modelAccessDenied.ts | 5 + .../components/modals/EditConnectionModal.tsx | 25 ++ src/i18n/messages/am.json | 2 + src/i18n/messages/ar.json | 2 + src/i18n/messages/az.json | 2 + src/i18n/messages/bg.json | 2 + src/i18n/messages/bn.json | 2 + src/i18n/messages/bs.json | 2 + src/i18n/messages/cs.json | 2 + src/i18n/messages/da.json | 2 + src/i18n/messages/de.json | 2 + src/i18n/messages/el.json | 2 + src/i18n/messages/en.json | 2 + src/i18n/messages/es.json | 2 + src/i18n/messages/et.json | 2 + src/i18n/messages/fa.json | 2 + src/i18n/messages/fi.json | 2 + src/i18n/messages/fr.json | 2 + src/i18n/messages/ga.json | 2 + src/i18n/messages/gu.json | 2 + src/i18n/messages/ha.json | 2 + src/i18n/messages/he.json | 2 + src/i18n/messages/hi.json | 2 + src/i18n/messages/hr.json | 2 + src/i18n/messages/hu.json | 2 + src/i18n/messages/hy.json | 2 + src/i18n/messages/id.json | 2 + src/i18n/messages/ig.json | 2 + src/i18n/messages/it.json | 2 + src/i18n/messages/ja.json | 2 + src/i18n/messages/ka.json | 2 + src/i18n/messages/km.json | 2 + src/i18n/messages/kn.json | 2 + src/i18n/messages/ko.json | 2 + src/i18n/messages/lt.json | 2 + src/i18n/messages/lv.json | 2 + src/i18n/messages/ml.json | 2 + src/i18n/messages/mr.json | 2 + src/i18n/messages/ms.json | 2 + src/i18n/messages/mt.json | 2 + src/i18n/messages/my.json | 2 + src/i18n/messages/ne.json | 2 + src/i18n/messages/nl.json | 2 + src/i18n/messages/no.json | 2 + src/i18n/messages/or.json | 2 + src/i18n/messages/pa.json | 2 + src/i18n/messages/phi.json | 2 + src/i18n/messages/pl.json | 2 + src/i18n/messages/pt-BR.json | 2 + src/i18n/messages/pt.json | 2 + src/i18n/messages/ro.json | 2 + src/i18n/messages/ru.json | 2 + src/i18n/messages/si.json | 2 + src/i18n/messages/sk.json | 2 + src/i18n/messages/sl.json | 2 + src/i18n/messages/sr.json | 2 + src/i18n/messages/sv.json | 2 + src/i18n/messages/sw.json | 2 + src/i18n/messages/ta.json | 2 + src/i18n/messages/te.json | 2 + src/i18n/messages/th.json | 2 + src/i18n/messages/tr.json | 2 + src/i18n/messages/uk-UA.json | 2 + src/i18n/messages/ur.json | 2 + src/i18n/messages/uz.json | 2 + src/i18n/messages/vi.json | 2 + src/i18n/messages/yo.json | 2 + src/i18n/messages/zh-CN.json | 2 + src/i18n/messages/zh-TW.json | 2 + src/lib/providers/requestDefaults.ts | 8 + src/shared/validation/providerSpecificData.ts | 9 + stryker.conf.json | 1 + .../unit/anthropic-compatible-prefill.test.ts | 355 ++++++++++++++++++ 79 files changed, 678 insertions(+), 2 deletions(-) create mode 100644 changelog.d/fixes/18332-anthropic-compatible-assistant-prefill.md create mode 100644 open-sse/services/assistantPrefill.ts create mode 100644 open-sse/services/assistantPrefillError.ts create mode 100644 tests/unit/anthropic-compatible-prefill.test.ts diff --git a/changelog.d/fixes/18332-anthropic-compatible-assistant-prefill.md b/changelog.d/fixes/18332-anthropic-compatible-assistant-prefill.md new file mode 100644 index 000000000000..a229ac873554 --- /dev/null +++ b/changelog.d/fixes/18332-anthropic-compatible-assistant-prefill.md @@ -0,0 +1 @@ +- **fix(sse):** an upstream `400 "This model does not support assistant message prefill"` is now classified as a request-shape error instead of "model unavailable", so it no longer locks a healthy model for 1h on every combo target; plain `anthropic-compatible-*` connections gain an opt-in `requireUserLastMessage` setting that drops trailing assistant turns (no structured `tool_use`) before sending diff --git a/open-sse/executors/base.ts b/open-sse/executors/base.ts index 30162698d3f4..c9da98f4a4b5 100644 --- a/open-sse/executors/base.ts +++ b/open-sse/executors/base.ts @@ -83,6 +83,10 @@ import { stripTrailingAssistantOrphanToolUse, stripTrailingAssistantForProvider, } from "../services/contextManager.ts"; +import { + requiresUserLastMessage, + stripTrailingAssistantTurns, +} from "../services/assistantPrefill.ts"; import { randomUUID } from "node:crypto"; import { getClaudeCodeVersion, @@ -1397,6 +1401,19 @@ export class BaseExecutor { // trailing text-only assistant turn with 400. Strip here so combo // failover does not burn the next account on the same body. tb.messages = stripTrailingAssistantForProvider(stripped, this.provider); + // Per-connection opt-in for relays that ban assistant prefill. + if (requiresUserLastMessage(requestCredentials?.providerSpecificData)) { + const userLast = stripTrailingAssistantTurns(tb.messages as JsonRecord[]); + tb.messages = userLast.messages; + if (userLast.removedTurns > 0 || userLast.noUserTurn) { + log?.info?.( + "PREFILL", + userLast.noUserTurn + ? "requireUserLastMessage: no user turn to end on — forwarded unchanged" + : `requireUserLastMessage: dropped ${userLast.removedTurns} trailing assistant turn(s)` + ); + } + } } } diff --git a/open-sse/services/accountFallback.ts b/open-sse/services/accountFallback.ts index 9e1ebd6ff09b..77d5a4adfa78 100644 --- a/open-sse/services/accountFallback.ts +++ b/open-sse/services/accountFallback.ts @@ -39,6 +39,7 @@ import { getCircuitBreaker, } from "../../src/shared/utils/circuitBreaker"; import { MODEL_ACCESS_DENIED_PATTERNS, isModelScoped400 } from "./modelAccessDenied.ts"; +import { isAssistantPrefillUnsupportedError } from "./assistantPrefillError.ts"; import { connectionCircuitBreakerName, failureCircuitBreakerName, @@ -426,6 +427,8 @@ const PROVIDER_MODEL_UNSUPPORTED_PATTERNS = [ export function isProviderModelUnsupported400(status: number, errorText: string): boolean { if (status !== HTTP_STATUS.BAD_REQUEST) return false; if (AUTH_CREDENTIAL_ERROR_PATTERNS.some((p) => p.test(errorText))) return false; + // Prefill rejection ("model does not support assistant message prefill") is request-shape. + if (isAssistantPrefillUnsupportedError(errorText)) return false; return PROVIDER_MODEL_UNSUPPORTED_PATTERNS.some((p) => p.test(errorText)); } @@ -434,6 +437,9 @@ export const MALFORMED_REQUEST_PATTERNS = [ /\binvalid.*message.*format/i, /\bmessages must alternate\b/i, /\bempty (message|content)\b/i, + // Assistant prefill rejected: the body must end with a user turn (request shape). + /\bdoes\s+not\s+support\s+assistant\s+message\s+prefill\b/i, + /\bconversation\s+must\s+end\s+with\s+a\s+user\s+message\b/i, // Tool call function name errors /\bfunction'?s? name (?:can't|can not|is|has) (?:blank|empty|missing)/i, /function.*name.*(?:blank|empty|missing)/i, @@ -2378,7 +2384,9 @@ export function checkFallbackError( p.test(errorStr) ); const matchesModelAccessPattern = - !looksLikeAuthCredentialError && MODEL_ACCESS_DENIED_PATTERNS.some((p) => p.test(errorStr)); + !looksLikeAuthCredentialError && + !isAssistantPrefillUnsupportedError(errorStr) && + MODEL_ACCESS_DENIED_PATTERNS.some((p) => p.test(errorStr)); const isModelAccessDeniedStructured = !!structuredError && diff --git a/open-sse/services/assistantPrefill.ts b/open-sse/services/assistantPrefill.ts new file mode 100644 index 000000000000..0e42ec022a33 --- /dev/null +++ b/open-sse/services/assistantPrefill.ts @@ -0,0 +1,89 @@ +/** + * Assistant-prefill compatibility for upstreams that require a user-last conversation. + * + * Some Anthropic-compatible relays (and newer Claude models behind them) reject a request + * whose last message is an `assistant` turn: + * 400 "This model does not support assistant message prefill. The conversation must end + * with a user message." + * Other relays accept prefill, so the strip is an explicit per-connection opt-in + * (`providerSpecificData.requireUserLastMessage`), not a global rule. + * + * Trade-off: dropping the trailing assistant turn hands the model back the last user turn, + * so it may regenerate an answer the client already has. It is a compatibility shim for the + * prefill ban, not a recovery of whatever the client meant by that trailing turn. + */ + +import { stripTrailingAssistantOrphanToolUse } from "./contextManager.ts"; + +export { + ASSISTANT_PREFILL_UNSUPPORTED_PATTERN, + isAssistantPrefillUnsupported400, + isAssistantPrefillUnsupportedError, +} from "./assistantPrefillError.ts"; + +/** Opt-in flag on the connection's providerSpecificData. */ +export function requiresUserLastMessage(providerSpecificData: unknown): boolean { + return ( + !!providerSpecificData && + typeof providerSpecificData === "object" && + (providerSpecificData as Record).requireUserLastMessage === true + ); +} + +function hasStructuredToolCall(message: Record): boolean { + const toolUse = + Array.isArray(message.content) && + (message.content as Array | null>).some( + (block) => block?.type === "tool_use" + ); + const toolCalls = Array.isArray(message.tool_calls) && message.tool_calls.length > 0; + return toolUse || toolCalls; +} + +export interface UserLastNormalization { + messages: Record[]; + /** Trailing assistant turns dropped (0 when unchanged). */ + removedTurns: number; + /** True when nothing could be stripped because no user turn would remain. */ + noUserTurn: boolean; +} + +/** + * Drop every trailing `assistant` turn so the conversation ends on a user turn. + * + * - Text / thinking-only assistant turns are dropped (XML that merely looks like a tool call + * inside `text` is still text — only structured `tool_use` / `tool_calls` count). + * - A trailing assistant with an unanswered `tool_use` goes through the existing orphan + * cleanup first (its tool_use blocks are removed; nothing after it can reference them). + * - Never returns an empty array and never invents a user turn: if no user turn would remain, + * the input is returned unchanged with `noUserTurn: true` and the upstream decides. + * + * Pure: the input array and its message objects are not mutated. + */ +export function stripTrailingAssistantTurns( + messages: Record[] +): UserLastNormalization { + if (!Array.isArray(messages) || messages.length === 0) { + return { messages, removedTurns: 0, noUserTurn: false }; + } + if (!messages.some((m) => m?.role === "user")) { + const endsOnAssistant = messages[messages.length - 1]?.role === "assistant"; + return { messages, removedTurns: 0, noUserTurn: endsOnAssistant }; + } + + let current = messages; + let removedTurns = 0; + while (current.length > 0 && current[current.length - 1]?.role === "assistant") { + const last = current[current.length - 1]; + if (hasStructuredToolCall(last)) { + const cleaned = stripTrailingAssistantOrphanToolUse(current); + if (cleaned === current) break; + if (cleaned.length < current.length) removedTurns++; + current = cleaned; + continue; + } + current = current.slice(0, -1); + removedTurns++; + } + return { messages: current, removedTurns, noUserTurn: false }; +} diff --git a/open-sse/services/assistantPrefillError.ts b/open-sse/services/assistantPrefillError.ts new file mode 100644 index 000000000000..4bd2f8a3aa45 --- /dev/null +++ b/open-sse/services/assistantPrefillError.ts @@ -0,0 +1,21 @@ +/** + * Upstream rejection of an assistant-prefill request (conversation ends on `assistant`): + * 400 "This model does not support assistant message prefill. The conversation must end + * with a user message." + * It is a request-shape error, not model availability. Kept narrow on purpose: the text + * also contains "model does not support", which the broad model-access patterns would + * otherwise read as "this model is unavailable" and lock a healthy model for an hour. + * + * Leaf module (no imports) so the fallback classifiers can depend on it without cycles. + */ +export const ASSISTANT_PREFILL_UNSUPPORTED_PATTERN = + /\bdoes\s+not\s+support\s+assistant\s+message\s+prefill\b|\bconversation\s+must\s+end\s+with\s+a\s+user\s+message\b/i; + +export function isAssistantPrefillUnsupportedError(errorText: string | null | undefined): boolean { + return ASSISTANT_PREFILL_UNSUPPORTED_PATTERN.test(String(errorText || "")); +} + +/** HTTP-status-aware variant: the prefill rejection is a request-shape 400. */ +export function isAssistantPrefillUnsupported400(status: number, errorText: string): boolean { + return status === 400 && isAssistantPrefillUnsupportedError(errorText); +} diff --git a/open-sse/services/combo/comboPredicates.ts b/open-sse/services/combo/comboPredicates.ts index a4f294ea0e45..b602305b4dd5 100644 --- a/open-sse/services/combo/comboPredicates.ts +++ b/open-sse/services/combo/comboPredicates.ts @@ -31,6 +31,7 @@ import { } from "../accountFallback.ts"; import { isModelNotFound404 } from "../accountFallback/perModelFailureScope.ts"; import { isRequestScoped400 } from "../accountFallback/requestScoped400.ts"; +import { isAssistantPrefillUnsupported400 } from "../assistantPrefillError.ts"; import { isResourceNotFoundResponse } from "../errorClassifier.ts"; import { isOpencodeFreeTierRefusal } from "../../executors/opencodeGeoBlock.ts"; import { isChatGptWebBridgeFailure } from "../../utils/chatgptWebBridgeFailure.ts"; @@ -441,7 +442,9 @@ export function isComboRequestScopedFailure( isRequestScopedUpstreamFailure(error) || (response.status === 404 && isResourceNotFoundResponse(errorText)) || // #14313: body-only free-tier refusals (relayed sentence, no error.type kept). - isOpencodeFreeTierRefusal(response.status, errorText) + isOpencodeFreeTierRefusal(response.status, errorText) || + // Prefill ban: the body's shape, not the model/connection — even without error.type. + isAssistantPrefillUnsupported400(response.status, errorText) ); } diff --git a/open-sse/services/modelAccessDenied.ts b/open-sse/services/modelAccessDenied.ts index cda188a828c7..a8dac89a87e2 100644 --- a/open-sse/services/modelAccessDenied.ts +++ b/open-sse/services/modelAccessDenied.ts @@ -5,6 +5,8 @@ * not on this account" advances. A different account may still serve it. */ +import { isAssistantPrefillUnsupportedError } from "./assistantPrefillError.ts"; + export const MODEL_ACCESS_DENIED_PATTERNS = [ /\binvalid model\b/i, /\bmodel.*not.*(?:available|found|supported|accessible)\b/i, @@ -24,6 +26,9 @@ export const MODEL_ACCESS_DENIED_PATTERNS = [ export function isModelScoped400(errorText: string | null | undefined): boolean { const text = String(errorText || ""); if (!text) return false; + // "This model does not support assistant message prefill" is a request-shape + // rejection, not a missing model — it must never lock the model. + if (isAssistantPrefillUnsupportedError(text)) return false; if (MODEL_ACCESS_DENIED_PATTERNS.some((pattern) => pattern.test(text))) return true; return ( /\bmodel\b[\s\S]{0,80}?\b(?:not\s+supported|unsupported|unknown|unavailable)\b/i.test(text) || diff --git a/src/app/(dashboard)/dashboard/providers/[id]/components/modals/EditConnectionModal.tsx b/src/app/(dashboard)/dashboard/providers/[id]/components/modals/EditConnectionModal.tsx index 69aaab3a2a9a..cce76e043501 100644 --- a/src/app/(dashboard)/dashboard/providers/[id]/components/modals/EditConnectionModal.tsx +++ b/src/app/(dashboard)/dashboard/providers/[id]/components/modals/EditConnectionModal.tsx @@ -179,6 +179,7 @@ export default function EditConnectionModal({ allowPaidCredits: connectionProviderSpecificData?.allowPaidCredits === true, passthroughModels: connectionProviderSpecificData?.passthroughModels === true, disableCooling: connectionProviderSpecificData?.disableCooling === true, + requireUserLastMessage: connectionProviderSpecificData?.requireUserLastMessage === true, importFreeModelsOnly: connectionProviderSpecificData?.importFreeModelsOnly === true, commandCodeZdrEnabled: connectionProviderSpecificData?.zdrEnabled === true, tunnelId: stringField(connectionProviderSpecificData?.tunnelId), @@ -256,6 +257,7 @@ export default function EditConnectionModal({ const apiKeyOptional = providerAllowsOptionalApiKey(provider) || Boolean(isNoAuthWebSessionCredential); const isCcCompatible = isClaudeCodeCompatibleProvider(provider); + const isPlainAnthropicCompatible = isAnthropicCompatibleProvider(provider) && !isCcCompatible; const isCompatible = isOpenAICompatibleProvider(provider) || isAnthropicCompatibleProvider(provider); const vertexCopy = isVertex ? getVertexCredentialCopy(t) : null; @@ -408,6 +410,7 @@ export default function EditConnectionModal({ allowPaidCredits: connection.providerSpecificData?.allowPaidCredits === true, passthroughModels: connection?.providerSpecificData?.passthroughModels === true, disableCooling: connection?.providerSpecificData?.disableCooling === true, + requireUserLastMessage: connection?.providerSpecificData?.requireUserLastMessage === true, importFreeModelsOnly: connection?.providerSpecificData?.importFreeModelsOnly === true, commandCodeZdrEnabled: connection.providerSpecificData?.zdrEnabled === true, tunnelId: stringField(connection.providerSpecificData?.tunnelId), @@ -706,6 +709,11 @@ export default function EditConnectionModal({ } if (updates.providerSpecificData) { updates.providerSpecificData.disableCooling = formData.disableCooling ? true : undefined; + if (isPlainAnthropicCompatible) { + updates.providerSpecificData.requireUserLastMessage = formData.requireUserLastMessage + ? true + : undefined; + } updates.providerSpecificData.peakHourProtection = normalizePeakHourProtectionForSave( formData.peakHourProtection ); @@ -769,6 +777,22 @@ export default function EditConnectionModal({ )} /> ) : null; + const requireUserLastMessageToggle = isPlainAnthropicCompatible ? ( + setFormData({ ...formData, requireUserLastMessage: checked })} + label={providerText( + t, + "requireUserLastMessageLabel", + "Require conversation to end with a user message" + )} + description={providerText( + t, + "requireUserLastMessageDescription", + "For upstreams that reject assistant prefill: drop trailing assistant turns (no tool_use) before sending. The model may regenerate its previous answer." + )} + /> + ) : null; const openaiResponsesStoreToggle = isResponsesConnection ? ( ; +const user = (text: string): Msg => ({ role: "user", content: [{ type: "text", text }] }); +const assistantText = (text: string): Msg => ({ + role: "assistant", + content: [{ type: "text", text }], +}); +// Incident shape: thinking + text whose XML only *looks* like a tool call. +const assistantThinkingXml = (): Msg => ({ + role: "assistant", + content: [ + { type: "thinking", thinking: "plan", signature: "sig" }, + { + type: "text", + text: 'done 1m1', + }, + ], +}); +const toolUse = (id: string): Msg => ({ + role: "assistant", + content: [{ type: "tool_use", id, name: "read", input: {} }], +}); +const toolResult = (id: string): Msg => ({ + role: "user", + content: [ + { type: "tool_result", tool_use_id: id, content: "ok", cache_control: { type: "ephemeral" } }, + ], +}); + +test.after(() => { + clearAllModelLockouts(); + core.resetDbInstance(); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); +}); + +// ── helper ─────────────────────────────────────────────────────────────────── + +test("strips a trailing text assistant", () => { + const r = stripTrailingAssistantTurns([user("q"), assistantText("a")]); + assert.equal(r.removedTurns, 1); + assert.deepEqual( + r.messages.map((m) => m.role), + ["user"] + ); +}); + +test("strips thinking + text with XML tool-look-alike (not a structured tool_use)", () => { + const r = stripTrailingAssistantTurns([user("q"), assistantThinkingXml()]); + assert.equal(r.removedTurns, 1); + assert.equal(r.messages.at(-1)?.role, "user"); +}); + +test("strips several consecutive trailing assistant turns", () => { + const r = stripTrailingAssistantTurns([ + user("q"), + assistantText("a1"), + assistantThinkingXml(), + assistantText("a3"), + ]); + assert.equal(r.removedTurns, 3); + assert.equal(r.messages.length, 1); +}); + +test("user-last history is returned unchanged (same reference)", () => { + const msgs = [user("q"), assistantText("a"), user("q2")]; + const r = stripTrailingAssistantTurns(msgs); + assert.equal(r.messages, msgs); + assert.equal(r.removedTurns, 0); +}); + +test("completed tool pairs and their cache_control survive", () => { + const msgs = [user("q"), toolUse("t1"), toolResult("t1"), assistantText("final")]; + const r = stripTrailingAssistantTurns(msgs); + assert.equal(r.messages.length, 3); + assert.deepEqual(r.messages[2], toolResult("t1")); +}); + +test("unanswered trailing tool_use is removed without leaving orphan tool_result", () => { + const msgs = [user("q"), toolUse("t1"), toolResult("t1"), toolUse("t2")]; + const r = stripTrailingAssistantTurns(msgs); + assert.equal(r.messages.at(-1)?.role, "user"); + assert.equal(r.messages.length, 3); +}); + +test("assistant-only history is never emptied nor given an invented user turn", () => { + const msgs = [assistantText("a")]; + const r = stripTrailingAssistantTurns(msgs); + assert.equal(r.messages, msgs); + assert.equal(r.noUserTurn, true); + assert.equal(r.removedTurns, 0); +}); + +test("does not mutate the input array or messages", () => { + const msgs = [user("q"), assistantText("a")]; + const snapshot = structuredClone(msgs); + stripTrailingAssistantTurns(msgs); + assert.deepEqual(msgs, snapshot); +}); + +test("requiresUserLastMessage is strict-boolean opt-in", () => { + assert.equal(requiresUserLastMessage({ requireUserLastMessage: true }), true); + assert.equal(requiresUserLastMessage({ requireUserLastMessage: "true" }), false); + assert.equal(requiresUserLastMessage({}), false); + assert.equal(requiresUserLastMessage(null), false); +}); + +// ── settings plumbing ─────────────────────────────────────────────────────── + +test("validation accepts a boolean requireUserLastMessage and rejects other types", () => { + const issues: unknown[] = []; + const ctx = { addIssue: (i: unknown) => issues.push(i) } as never; + validateProviderSpecificData({ requireUserLastMessage: true }, ctx); + assert.equal(issues.length, 0); + validateProviderSpecificData({ requireUserLastMessage: "yes" }, ctx); + assert.equal(issues.length, 1); +}); + +test("normalization keeps a boolean flag and drops a non-boolean one", () => { + assert.equal( + ( + normalizeProviderSpecificData("anthropic-compatible-x", { + requireUserLastMessage: true, + }) as Msg + ).requireUserLastMessage, + true + ); + assert.equal( + "requireUserLastMessage" in + (normalizeProviderSpecificData("anthropic-compatible-x", { + requireUserLastMessage: "1", + x: 1, + }) as Msg), + false + ); +}); + +// ── executor wiring (real DefaultExecutor.execute, fetch captured) ───────── + +function captureFetch() { + const bodies: Msg[] = []; + const original = globalThis.fetch; + globalThis.fetch = (async (_url: unknown, init: { body?: unknown } = {}) => { + bodies.push(JSON.parse(String(init.body ?? "{}"))); + return new Response(JSON.stringify({ ok: true }), { + status: 200, + headers: { "Content-Type": "application/json" }, + }); + }) as typeof globalThis.fetch; + return { bodies, restore: () => void (globalThis.fetch = original) }; +} + +async function sendThroughExecutor(flag: boolean | undefined, stream: boolean, messages: Msg[]) { + const { bodies, restore } = captureFetch(); + const body = { model: "claude-sonnet-5-5", max_tokens: 16, messages }; + try { + await new DefaultExecutor("anthropic-compatible-prefilltest").execute({ + model: "claude-sonnet-5-5", + body, + stream, + credentials: { + apiKey: "relay-key", + providerSpecificData: { + baseUrl: "https://relay.example/v1", + ...(flag === undefined ? {} : { requireUserLastMessage: flag }), + }, + }, + } as never); + } finally { + restore(); + } + return { sent: bodies[0], body }; +} + +for (const stream of [false, true]) { + test(`executor (stream=${stream}): flag on → trailing assistant dropped before send`, async () => { + const messages = [user("q"), assistantThinkingXml()]; + const original = structuredClone(messages); + const { sent, body } = await sendThroughExecutor(true, stream, messages); + const sentMsgs = sent.messages as Msg[]; + assert.equal(sentMsgs.at(-1)?.role, "user"); + assert.equal(sentMsgs.length, 1); + // Caller's body (shared by combo targets / logging snapshot) is untouched. + assert.deepEqual(body.messages, original); + }); +} + +test("executor: flag off → legitimate prefill forwarded unchanged", async () => { + const { sent } = await sendThroughExecutor(undefined, false, [user("q"), assistantText("{")]); + assert.equal((sent.messages as Msg[]).at(-1)?.role, "assistant"); +}); + +test("executor: flag on keeps tool pairs and cache_control", async () => { + const { sent } = await sendThroughExecutor(true, false, [ + user("q"), + toolUse("t1"), + toolResult("t1"), + assistantText("final"), + ]); + const sentMsgs = sent.messages as Msg[]; + assert.equal(sentMsgs.length, 3); + assert.deepEqual(sentMsgs[2], toolResult("t1")); +}); + +test("executor: two connections with different flags get independent payloads", async () => { + const messages = [user("q"), assistantText("a")]; + const on = await sendThroughExecutor(true, false, messages); + const off = await sendThroughExecutor(false, false, messages); + assert.equal((on.sent.messages as Msg[]).length, 1); + assert.equal((off.sent.messages as Msg[]).length, 2); + assert.equal(messages.length, 2); +}); + +// ── classification ────────────────────────────────────────────────────────── + +for (const [label, text] of [ + ["plain", PREFILL_MSG], + ["status-prefixed", `[400]: ${PREFILL_MSG}`], + ["JSON envelope", PREFILL_ENVELOPE], +] as const) { + test(`prefill 400 (${label}) is request-shape, not model availability`, () => { + assert.equal(isAssistantPrefillUnsupported400(400, text), true); + assert.equal(isModelScoped400(text), false); + assert.equal(isProviderModelUnsupported400(400, text), false); + assert.equal(isRequestScoped400(400, text), true); + assert.equal(comboTargetDecision(400, text), "advance"); + const fb = checkFallbackError(400, text, 0, "claude-sonnet-5-5", "anthropic-compatible-x"); + assert.equal(fb.cooldownMs, 0); + assert.equal( + shouldSkipConnDisable( + { status: 400, rawMessage: text }, + false, + false, + "anthropic-compatible-x" + ), + true + ); + }); +} + +test("genuine model-unavailable 400s are still recognized", () => { + assert.equal(isModelScoped400("The requested model is not supported."), true); + assert.equal(isProviderModelUnsupported400(400, "model claude-x is not supported"), true); + assert.equal(isModelScoped400("model x does not support Responses API"), true); + assert.equal(isAssistantPrefillUnsupported400(400, "model x does not support tools"), false); + assert.equal(isAssistantPrefillUnsupported400(429, PREFILL_MSG), false); +}); + +// ── combo: no lockout poisoning ───────────────────────────────────────────── + +const log = { info: () => {}, warn: () => {}, error: () => {}, debug: () => {} }; +const lockoutSettings = { + modelLockout: { + enabled: true, + errorCodes: [400, 403, 404, 429, 502, 503, 504], + baseCooldownMs: 120_000, + maxCooldownMs: 300_000, + maxBackoffSteps: 10, + useExponentialBackoff: false, + }, +}; +const TARGETS = [ + { provider: "anthropic-compatible-relay-a", model: "claude-sonnet-5-5" }, + { provider: "anthropic-compatible-relay-b", model: "claude-sonnet-5-5" }, +]; + +for (const [label, errorBody] of [ + ["JSON envelope", PREFILL_ENVELOPE], + ["bare text", PREFILL_MSG], +] as const) { + test(`combo: prefill 400 (${label}) on both targets locks neither model`, async () => { + clearAllModelLockouts(); + const tried: string[] = []; + const run = (failing: boolean) => + handleComboChat({ + body: { messages: [{ role: "user", content: "q" }] }, + combo: { + name: `prefill-${label}`, + strategy: "priority", + models: TARGETS.map((t) => `${t.provider}/${t.model}`), + config: { maxRetries: 0, retryDelayMs: 0, fallbackDelayMs: 0 }, + }, + handleSingleModel: async (_b: unknown, modelStr: string) => { + tried.push(modelStr); + return failing + ? new Response(errorBody, { status: 400 }) + : Response.json({ id: "ok", choices: [{ message: { content: "ok" } }] }); + }, + isModelAvailable: async () => true, + log, + settings: lockoutSettings, + allCombos: null, + } as never); + + const first = await run(true); + assert.equal(first.status, 400); + for (const t of TARGETS) { + assert.equal(getModelLockoutInfo(t.provider, "", t.model), null, `${t.provider} locked`); + } + // The next well-formed request must actually reach an upstream (no 503 skip). + tried.length = 0; + const second = await run(false); + assert.equal(second.status, 200); + assert.equal(tried.length, 1); + }); +}