omniroute 2.8.4 → 2.8.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (267) hide show
  1. package/app/.next/BUILD_ID +1 -1
  2. package/app/.next/build-manifest.json +2 -2
  3. package/app/.next/prerender-manifest.json +3 -3
  4. package/app/.next/server/app/(dashboard)/dashboard/a2a/page_client-reference-manifest.js +1 -1
  5. package/app/.next/server/app/(dashboard)/dashboard/agents/page_client-reference-manifest.js +1 -1
  6. package/app/.next/server/app/(dashboard)/dashboard/analytics/page_client-reference-manifest.js +1 -1
  7. package/app/.next/server/app/(dashboard)/dashboard/api-manager/page_client-reference-manifest.js +1 -1
  8. package/app/.next/server/app/(dashboard)/dashboard/audit-log/page_client-reference-manifest.js +1 -1
  9. package/app/.next/server/app/(dashboard)/dashboard/auto-combo/page_client-reference-manifest.js +1 -1
  10. package/app/.next/server/app/(dashboard)/dashboard/cli-tools/page_client-reference-manifest.js +1 -1
  11. package/app/.next/server/app/(dashboard)/dashboard/combos/page_client-reference-manifest.js +1 -1
  12. package/app/.next/server/app/(dashboard)/dashboard/costs/page_client-reference-manifest.js +1 -1
  13. package/app/.next/server/app/(dashboard)/dashboard/endpoint/page_client-reference-manifest.js +1 -1
  14. package/app/.next/server/app/(dashboard)/dashboard/health/page_client-reference-manifest.js +1 -1
  15. package/app/.next/server/app/(dashboard)/dashboard/limits/page_client-reference-manifest.js +1 -1
  16. package/app/.next/server/app/(dashboard)/dashboard/logs/page_client-reference-manifest.js +1 -1
  17. package/app/.next/server/app/(dashboard)/dashboard/mcp/page_client-reference-manifest.js +1 -1
  18. package/app/.next/server/app/(dashboard)/dashboard/media/page_client-reference-manifest.js +1 -1
  19. package/app/.next/server/app/(dashboard)/dashboard/onboarding/page_client-reference-manifest.js +1 -1
  20. package/app/.next/server/app/(dashboard)/dashboard/page_client-reference-manifest.js +1 -1
  21. package/app/.next/server/app/(dashboard)/dashboard/playground/page_client-reference-manifest.js +1 -1
  22. package/app/.next/server/app/(dashboard)/dashboard/profile/page_client-reference-manifest.js +1 -1
  23. package/app/.next/server/app/(dashboard)/dashboard/providers/[id]/page_client-reference-manifest.js +1 -1
  24. package/app/.next/server/app/(dashboard)/dashboard/providers/new/page_client-reference-manifest.js +1 -1
  25. package/app/.next/server/app/(dashboard)/dashboard/providers/page_client-reference-manifest.js +1 -1
  26. package/app/.next/server/app/(dashboard)/dashboard/search-tools/page_client-reference-manifest.js +1 -1
  27. package/app/.next/server/app/(dashboard)/dashboard/settings/page_client-reference-manifest.js +1 -1
  28. package/app/.next/server/app/(dashboard)/dashboard/settings/pricing/page_client-reference-manifest.js +1 -1
  29. package/app/.next/server/app/(dashboard)/dashboard/translator/page_client-reference-manifest.js +1 -1
  30. package/app/.next/server/app/(dashboard)/dashboard/usage/page_client-reference-manifest.js +1 -1
  31. package/app/.next/server/app/400/page_client-reference-manifest.js +1 -1
  32. package/app/.next/server/app/401/page_client-reference-manifest.js +1 -1
  33. package/app/.next/server/app/403/page_client-reference-manifest.js +1 -1
  34. package/app/.next/server/app/408/page_client-reference-manifest.js +1 -1
  35. package/app/.next/server/app/429/page_client-reference-manifest.js +1 -1
  36. package/app/.next/server/app/500/page_client-reference-manifest.js +1 -1
  37. package/app/.next/server/app/502/page_client-reference-manifest.js +1 -1
  38. package/app/.next/server/app/503/page_client-reference-manifest.js +1 -1
  39. package/app/.next/server/app/_global-error.html +2 -2
  40. package/app/.next/server/app/_global-error.rsc +1 -1
  41. package/app/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  42. package/app/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  43. package/app/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  44. package/app/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  45. package/app/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  46. package/app/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  47. package/app/.next/server/app/api/acp/agents/route.js +1 -1
  48. package/app/.next/server/app/api/acp/agents/route.js.nft.json +1 -1
  49. package/app/.next/server/app/api/auth/login/route.js +1 -1
  50. package/app/.next/server/app/api/auth/login/route.js.nft.json +1 -1
  51. package/app/.next/server/app/api/cli-tools/antigravity-mitm/alias/route.js +1 -1
  52. package/app/.next/server/app/api/cli-tools/antigravity-mitm/alias/route.js.nft.json +1 -1
  53. package/app/.next/server/app/api/cloud/auth/route.js +1 -1
  54. package/app/.next/server/app/api/cloud/auth/route.js.nft.json +1 -1
  55. package/app/.next/server/app/api/cloud/credentials/update/route.js +1 -1
  56. package/app/.next/server/app/api/cloud/credentials/update/route.js.nft.json +1 -1
  57. package/app/.next/server/app/api/cloud/model/resolve/route.js +1 -1
  58. package/app/.next/server/app/api/cloud/model/resolve/route.js.nft.json +1 -1
  59. package/app/.next/server/app/api/cloud/models/alias/route.js +1 -1
  60. package/app/.next/server/app/api/cloud/models/alias/route.js.nft.json +1 -1
  61. package/app/.next/server/app/api/combos/[id]/route.js +1 -1
  62. package/app/.next/server/app/api/combos/[id]/route.js.nft.json +1 -1
  63. package/app/.next/server/app/api/combos/route.js +1 -1
  64. package/app/.next/server/app/api/combos/route.js.nft.json +1 -1
  65. package/app/.next/server/app/api/combos/test/route.js +1 -1
  66. package/app/.next/server/app/api/combos/test/route.js.nft.json +1 -1
  67. package/app/.next/server/app/api/db-backups/export/route.js +1 -1
  68. package/app/.next/server/app/api/db-backups/export/route.js.nft.json +1 -1
  69. package/app/.next/server/app/api/db-backups/import/route.js +1 -1
  70. package/app/.next/server/app/api/db-backups/import/route.js.nft.json +1 -1
  71. package/app/.next/server/app/api/db-backups/route.js +1 -1
  72. package/app/.next/server/app/api/db-backups/route.js.nft.json +1 -1
  73. package/app/.next/server/app/api/keys/[id]/route.js +1 -1
  74. package/app/.next/server/app/api/keys/[id]/route.js.nft.json +1 -1
  75. package/app/.next/server/app/api/keys/route.js +1 -1
  76. package/app/.next/server/app/api/keys/route.js.nft.json +1 -1
  77. package/app/.next/server/app/api/logs/detail/route.js +1 -1
  78. package/app/.next/server/app/api/logs/detail/route.js.nft.json +1 -1
  79. package/app/.next/server/app/api/models/alias/route.js +1 -1
  80. package/app/.next/server/app/api/models/alias/route.js.nft.json +1 -1
  81. package/app/.next/server/app/api/models/openrouter-catalog/route.js +1 -1
  82. package/app/.next/server/app/api/models/openrouter-catalog/route.js.nft.json +1 -1
  83. package/app/.next/server/app/api/models/route.js +1 -1
  84. package/app/.next/server/app/api/models/route.js.nft.json +1 -1
  85. package/app/.next/server/app/api/monitoring/health/route.js +1 -1
  86. package/app/.next/server/app/api/monitoring/health/route.js.nft.json +1 -1
  87. package/app/.next/server/app/api/oauth/[provider]/[action]/route.js +1 -1
  88. package/app/.next/server/app/api/oauth/[provider]/[action]/route.js.nft.json +1 -1
  89. package/app/.next/server/app/api/oauth/cursor/auto-import/route.js +1 -1
  90. package/app/.next/server/app/api/oauth/cursor/auto-import/route.js.nft.json +1 -1
  91. package/app/.next/server/app/api/oauth/cursor/import/route.js +1 -1
  92. package/app/.next/server/app/api/oauth/cursor/import/route.js.nft.json +1 -1
  93. package/app/.next/server/app/api/oauth/kiro/auto-import/route.js +1 -1
  94. package/app/.next/server/app/api/oauth/kiro/auto-import/route.js.nft.json +1 -1
  95. package/app/.next/server/app/api/oauth/kiro/import/route.js +1 -1
  96. package/app/.next/server/app/api/oauth/kiro/import/route.js.nft.json +1 -1
  97. package/app/.next/server/app/api/oauth/kiro/social-exchange/route.js +1 -1
  98. package/app/.next/server/app/api/oauth/kiro/social-exchange/route.js.nft.json +1 -1
  99. package/app/.next/server/app/api/pricing/models/route.js +1 -1
  100. package/app/.next/server/app/api/pricing/models/route.js.nft.json +1 -1
  101. package/app/.next/server/app/api/pricing/route.js +1 -1
  102. package/app/.next/server/app/api/pricing/route.js.nft.json +1 -1
  103. package/app/.next/server/app/api/provider-models/route.js +2 -2
  104. package/app/.next/server/app/api/provider-models/route.js.nft.json +1 -1
  105. package/app/.next/server/app/api/provider-nodes/[id]/route.js +1 -1
  106. package/app/.next/server/app/api/provider-nodes/[id]/route.js.nft.json +1 -1
  107. package/app/.next/server/app/api/provider-nodes/route.js +1 -1
  108. package/app/.next/server/app/api/provider-nodes/route.js.nft.json +1 -1
  109. package/app/.next/server/app/api/providers/[id]/models/route.js +1 -1
  110. package/app/.next/server/app/api/providers/[id]/models/route.js.nft.json +1 -1
  111. package/app/.next/server/app/api/providers/[id]/refresh/route.js +1 -1
  112. package/app/.next/server/app/api/providers/[id]/refresh/route.js.nft.json +1 -1
  113. package/app/.next/server/app/api/providers/[id]/route.js +1 -1
  114. package/app/.next/server/app/api/providers/[id]/route.js.nft.json +1 -1
  115. package/app/.next/server/app/api/providers/[id]/test/route.js +1 -1
  116. package/app/.next/server/app/api/providers/[id]/test/route.js.nft.json +1 -1
  117. package/app/.next/server/app/api/providers/client/route.js +1 -1
  118. package/app/.next/server/app/api/providers/client/route.js.nft.json +1 -1
  119. package/app/.next/server/app/api/providers/route.js +1 -1
  120. package/app/.next/server/app/api/providers/route.js.nft.json +1 -1
  121. package/app/.next/server/app/api/providers/test-batch/route.js +2 -2
  122. package/app/.next/server/app/api/providers/test-batch/route.js.nft.json +1 -1
  123. package/app/.next/server/app/api/providers/validate/route.js +1 -1
  124. package/app/.next/server/app/api/providers/validate/route.js.nft.json +1 -1
  125. package/app/.next/server/app/api/rate-limits/route.js +1 -1
  126. package/app/.next/server/app/api/rate-limits/route.js.nft.json +1 -1
  127. package/app/.next/server/app/api/resilience/route.js +1 -1
  128. package/app/.next/server/app/api/resilience/route.js.nft.json +1 -1
  129. package/app/.next/server/app/api/search/providers/route.js +1 -1
  130. package/app/.next/server/app/api/search/providers/route.js.nft.json +1 -1
  131. package/app/.next/server/app/api/search/stats/route.js +1 -1
  132. package/app/.next/server/app/api/search/stats/route.js.nft.json +1 -1
  133. package/app/.next/server/app/api/settings/codex-service-tier/route.js +1 -1
  134. package/app/.next/server/app/api/settings/codex-service-tier/route.js.nft.json +1 -1
  135. package/app/.next/server/app/api/settings/combo-defaults/route.js +1 -1
  136. package/app/.next/server/app/api/settings/combo-defaults/route.js.nft.json +1 -1
  137. package/app/.next/server/app/api/settings/proxies/assignments/route.js +2 -2
  138. package/app/.next/server/app/api/settings/proxies/bulk-assign/route.js +2 -2
  139. package/app/.next/server/app/api/settings/proxies/route.js +1 -1
  140. package/app/.next/server/app/api/settings/proxy/route.js +2 -2
  141. package/app/.next/server/app/api/settings/require-login/route.js +1 -1
  142. package/app/.next/server/app/api/settings/require-login/route.js.nft.json +1 -1
  143. package/app/.next/server/app/api/settings/route.js +1 -1
  144. package/app/.next/server/app/api/settings/route.js.nft.json +1 -1
  145. package/app/.next/server/app/api/settings/system-prompt/route.js +1 -1
  146. package/app/.next/server/app/api/settings/system-prompt/route.js.nft.json +1 -1
  147. package/app/.next/server/app/api/settings/thinking-budget/route.js +1 -1
  148. package/app/.next/server/app/api/settings/thinking-budget/route.js.nft.json +1 -1
  149. package/app/.next/server/app/api/sync/cloud/route.js +1 -1
  150. package/app/.next/server/app/api/sync/cloud/route.js.nft.json +1 -1
  151. package/app/.next/server/app/api/sync/initialize/route.js +1 -1
  152. package/app/.next/server/app/api/sync/initialize/route.js.nft.json +1 -1
  153. package/app/.next/server/app/api/system/version/route.js +1 -1
  154. package/app/.next/server/app/api/system/version/route.js.nft.json +1 -1
  155. package/app/.next/server/app/api/translator/send/route.js +1 -1
  156. package/app/.next/server/app/api/translator/send/route.js.nft.json +1 -1
  157. package/app/.next/server/app/api/translator/translate/route.js +1 -1
  158. package/app/.next/server/app/api/translator/translate/route.js.nft.json +1 -1
  159. package/app/.next/server/app/api/usage/quota/route.js +1 -1
  160. package/app/.next/server/app/api/usage/quota/route.js.nft.json +1 -1
  161. package/app/.next/server/app/api/v1/management/proxies/assignments/route.js +1 -1
  162. package/app/.next/server/app/api/v1/management/proxies/assignments/route.js.nft.json +1 -1
  163. package/app/.next/server/app/api/v1/management/proxies/bulk-assign/route.js +1 -1
  164. package/app/.next/server/app/api/v1/management/proxies/bulk-assign/route.js.nft.json +1 -1
  165. package/app/.next/server/app/api/v1/management/proxies/health/route.js +1 -1
  166. package/app/.next/server/app/api/v1/management/proxies/health/route.js.nft.json +1 -1
  167. package/app/.next/server/app/api/v1/management/proxies/route.js +1 -1
  168. package/app/.next/server/app/api/v1/management/proxies/route.js.nft.json +1 -1
  169. package/app/.next/server/app/api/v1/models/route.js +1 -1
  170. package/app/.next/server/app/api/v1/models/route.js.nft.json +1 -1
  171. package/app/.next/server/app/api/v1/route.js +1 -1
  172. package/app/.next/server/app/api/v1/route.js.nft.json +1 -1
  173. package/app/.next/server/app/callback/page_client-reference-manifest.js +1 -1
  174. package/app/.next/server/app/docs/page_client-reference-manifest.js +1 -1
  175. package/app/.next/server/app/forbidden/page_client-reference-manifest.js +1 -1
  176. package/app/.next/server/app/forgot-password/page_client-reference-manifest.js +1 -1
  177. package/app/.next/server/app/landing/page_client-reference-manifest.js +1 -1
  178. package/app/.next/server/app/login/page_client-reference-manifest.js +1 -1
  179. package/app/.next/server/app/maintenance/page_client-reference-manifest.js +1 -1
  180. package/app/.next/server/app/offline/page_client-reference-manifest.js +1 -1
  181. package/app/.next/server/app/page_client-reference-manifest.js +1 -1
  182. package/app/.next/server/app/privacy/page_client-reference-manifest.js +1 -1
  183. package/app/.next/server/app/status/page_client-reference-manifest.js +1 -1
  184. package/app/.next/server/app/terms/page_client-reference-manifest.js +1 -1
  185. package/app/.next/server/chunks/[root-of-the-server]__09c944b3._.js +1 -1
  186. package/app/.next/server/chunks/[root-of-the-server]__134baf4c._.js +1 -1
  187. package/app/.next/server/chunks/[root-of-the-server]__179c5303._.js +1 -1
  188. package/app/.next/server/chunks/[root-of-the-server]__237e5042._.js +1 -1
  189. package/app/.next/server/chunks/[root-of-the-server]__46fad57a._.js +1 -1
  190. package/app/.next/server/chunks/[root-of-the-server]__784fb7c5._.js +1 -1
  191. package/app/.next/server/chunks/[root-of-the-server]__7d9b23e7._.js +1 -1
  192. package/app/.next/server/chunks/[root-of-the-server]__80e3bfc3._.js +2 -2
  193. package/app/.next/server/chunks/[root-of-the-server]__84e445b2._.js +1 -1
  194. package/app/.next/server/chunks/[root-of-the-server]__92cb0def._.js +1 -1
  195. package/app/.next/server/chunks/[root-of-the-server]__9bbd49c8._.js +1 -1
  196. package/app/.next/server/chunks/[root-of-the-server]__a630d6ef._.js +5 -5
  197. package/app/.next/server/chunks/[root-of-the-server]__add0a68c._.js +1 -1
  198. package/app/.next/server/chunks/[root-of-the-server]__c393c81f._.js +1 -1
  199. package/app/.next/server/chunks/[root-of-the-server]__c8e3c8a9._.js +50 -0
  200. package/app/.next/server/chunks/[root-of-the-server]__cd42b732._.js +1 -1
  201. package/app/.next/server/chunks/[root-of-the-server]__d4563e10._.js +1 -1
  202. package/app/.next/server/chunks/[root-of-the-server]__db2f9fe0._.js +1 -1
  203. package/app/.next/server/chunks/[root-of-the-server]__e27a89bd._.js +1 -1
  204. package/app/.next/server/chunks/[root-of-the-server]__e56edf04._.js +1 -1
  205. package/app/.next/server/chunks/[root-of-the-server]__e6e94646._.js +1 -1
  206. package/app/.next/server/chunks/[root-of-the-server]__eb98039a._.js +1 -1
  207. package/app/.next/server/chunks/[root-of-the-server]__f31b4656._.js +1 -1
  208. package/app/.next/server/chunks/_05c48915._.js +1 -1
  209. package/app/.next/server/chunks/_1244636c._.js +2 -2
  210. package/app/.next/server/chunks/_14963fed._.js +1 -1
  211. package/app/.next/server/chunks/_1717e651._.js +1 -1
  212. package/app/.next/server/chunks/_2115d8de._.js +1 -1
  213. package/app/.next/server/chunks/_3ac953eb._.js +1 -1
  214. package/app/.next/server/chunks/_4b8fd853._.js +1 -1
  215. package/app/.next/server/chunks/_5bbb2e7a._.js +1 -1
  216. package/app/.next/server/chunks/_68683848._.js +1 -1
  217. package/app/.next/server/chunks/_c05b9de3._.js +1 -1
  218. package/app/.next/server/chunks/_c795fc74._.js +1 -1
  219. package/app/.next/server/chunks/_ee7b7859._.js +1 -1
  220. package/app/.next/server/chunks/_ee9b677b._.js +1 -1
  221. package/app/.next/server/chunks/open-sse_cf4d5692._.js +1 -1
  222. package/app/.next/server/chunks/open-sse_config_constants_ts_9583de19._.js +1 -1
  223. package/app/.next/server/chunks/open-sse_services_826884e1._.js +2 -1
  224. package/app/.next/server/chunks/open-sse_translator_index_ts_f5fd0821._.js +2 -2
  225. package/app/.next/server/chunks/src_lib_localDb_ts_83220848._.js +1 -1
  226. package/app/.next/server/chunks/src_shared_validation_schemas_ts_4e63863a._.js +1 -1
  227. package/app/.next/server/chunks/ssr/[root-of-the-server]__9affb65e._.js +1 -1
  228. package/app/.next/server/chunks/ssr/[root-of-the-server]__a6942102._.js +1 -1
  229. package/app/.next/server/chunks/ssr/src_9197fb9b._.js +2 -2
  230. package/app/.next/server/chunks/ssr/src_d3225e36._.js +1 -1
  231. package/app/.next/server/chunks/ssr/src_i18n_messages_en_json_c3d5c412._.js +1 -1
  232. package/app/.next/server/chunks/ssr/src_i18n_messages_zh-CN_json_f4112d90._.js +1 -1
  233. package/app/.next/server/chunks/ssr/src_lib_initCloudSync_ts_982b9d4d._.js +1 -1
  234. package/app/.next/server/pages/500.html +2 -2
  235. package/app/.next/server/server-reference-manifest.js +1 -1
  236. package/app/.next/server/server-reference-manifest.json +1 -1
  237. package/app/.next/static/chunks/1042694db0c08f1f.css +1 -0
  238. package/app/.next/static/chunks/{0f71d7fbf89bb737.js → 34569b4e9d93c0ad.js} +2 -2
  239. package/app/.next/static/chunks/{1e206030e7793015.js → 353ef826fbae436d.js} +1 -1
  240. package/app/.next/static/chunks/91761ba00c702cff.js +1 -0
  241. package/app/CHANGELOG.md +56 -0
  242. package/app/docs/openapi.yaml +1 -1
  243. package/app/open-sse/config/constants.ts +3 -3
  244. package/app/open-sse/handlers/chatCore.ts +6 -2
  245. package/app/open-sse/services/comboAgentMiddleware.ts +9 -1
  246. package/app/open-sse/services/roleNormalizer.ts +14 -19
  247. package/app/open-sse/translator/index.ts +27 -3
  248. package/app/package-lock.json +2 -2
  249. package/app/package.json +1 -1
  250. package/app/src/app/(dashboard)/dashboard/cli-tools/CLIToolsPageClient.tsx +12 -0
  251. package/app/src/app/(dashboard)/dashboard/cli-tools/components/AntigravityToolCard.tsx +5 -5
  252. package/app/src/app/(dashboard)/dashboard/providers/[id]/page.tsx +381 -62
  253. package/app/src/app/api/provider-models/route.ts +47 -6
  254. package/app/src/i18n/messages/en.json +10 -0
  255. package/app/src/i18n/messages/zh-CN.json +10 -0
  256. package/app/src/lib/db/models.ts +130 -13
  257. package/app/src/lib/localDb.ts +5 -0
  258. package/app/src/shared/constants/cliTools.ts +2 -2
  259. package/app/src/shared/validation/schemas.ts +1 -0
  260. package/app/tests/e2e/providers-bailian-coding-plan.spec.ts +14 -0
  261. package/package.json +1 -1
  262. package/app/.next/server/chunks/[root-of-the-server]__3972de72._.js +0 -50
  263. package/app/.next/static/chunks/af071ff826ecff1b.css +0 -1
  264. package/app/.next/static/chunks/d19ab4efcaddd1db.js +0 -1
  265. /package/app/.next/static/{Ys6bRJXPNt8j-MKOjvZwp → m9BrfW8GQTdaCjvX6OgP2}/_buildManifest.js +0 -0
  266. /package/app/.next/static/{Ys6bRJXPNt8j-MKOjvZwp → m9BrfW8GQTdaCjvX6OgP2}/_clientMiddlewareManifest.json +0 -0
  267. /package/app/.next/static/{Ys6bRJXPNt8j-MKOjvZwp → m9BrfW8GQTdaCjvX6OgP2}/_ssgManifest.js +0 -0
@@ -43,6 +43,78 @@ function normalizeCodexLimitPolicy(policy: unknown): { use5h: boolean; useWeekly
43
43
  };
44
44
  }
45
45
 
46
+ function ModelCompatPopover({
47
+ t,
48
+ normalizeToolCallId,
49
+ preserveDeveloperRole,
50
+ showDeveloperToggle = true,
51
+ onNormalizeChange,
52
+ onPreserveChange,
53
+ disabled,
54
+ }: {
55
+ t: (key: string) => string;
56
+ normalizeToolCallId: boolean;
57
+ preserveDeveloperRole?: boolean;
58
+ showDeveloperToggle?: boolean;
59
+ onNormalizeChange: (v: boolean) => void;
60
+ onPreserveChange: (v: boolean) => void;
61
+ disabled?: boolean;
62
+ }) {
63
+ const [open, setOpen] = useState(false);
64
+ const ref = useRef<HTMLDivElement>(null);
65
+
66
+ useEffect(() => {
67
+ if (!open) return;
68
+ const onDocClick = (e: MouseEvent) => {
69
+ if (ref.current && !ref.current.contains(e.target as Node)) setOpen(false);
70
+ };
71
+ document.addEventListener("mousedown", onDocClick);
72
+ return () => document.removeEventListener("mousedown", onDocClick);
73
+ }, [open]);
74
+
75
+ return (
76
+ <div className="relative inline-block" ref={ref}>
77
+ <button
78
+ type="button"
79
+ onClick={() => setOpen((v) => !v)}
80
+ disabled={disabled}
81
+ className="inline-flex items-center gap-1 px-2 py-1 text-xs rounded-md border border-border bg-sidebar/50 hover:bg-sidebar text-text-muted hover:text-text-main disabled:opacity-50"
82
+ title={t("compatAdjustmentsTitle")}
83
+ >
84
+ <span className="material-symbols-outlined text-sm">tune</span>
85
+ {t("compatButtonLabel")}
86
+ </button>
87
+ {open && (
88
+ <div className="absolute left-0 top-full mt-1 z-50 min-w-[200px] p-3 rounded-lg border border-border bg-white dark:bg-zinc-900 shadow-xl ring-1 ring-black/5 dark:ring-white/10">
89
+ <p className="text-[10px] font-semibold uppercase tracking-wide text-text-muted mb-2">
90
+ {t("compatAdjustmentsTitle")}
91
+ </p>
92
+ <div className="flex flex-col gap-3">
93
+ <Toggle
94
+ size="sm"
95
+ label={t("compatToolIdShort")}
96
+ title={t("normalizeToolCallIdLabel")}
97
+ checked={normalizeToolCallId}
98
+ onChange={onNormalizeChange}
99
+ disabled={disabled}
100
+ />
101
+ {showDeveloperToggle && (
102
+ <Toggle
103
+ size="sm"
104
+ label={t("compatDoNotPreserveDeveloper")}
105
+ title={t("preserveDeveloperRoleLabel")}
106
+ checked={preserveDeveloperRole === false}
107
+ onChange={(checked) => onPreserveChange(!checked)}
108
+ disabled={disabled}
109
+ />
110
+ )}
111
+ </div>
112
+ </div>
113
+ )}
114
+ </div>
115
+ );
116
+ }
117
+
46
118
  export default function ProviderDetailPage() {
47
119
  const params = useParams();
48
120
  const router = useRouter();
@@ -76,6 +148,15 @@ export default function ProviderDetailPage() {
76
148
  error: "",
77
149
  importedCount: 0,
78
150
  });
151
+ const [modelMeta, setModelMeta] = useState<{
152
+ customModels: Record<string, unknown>[];
153
+ modelCompatOverrides: {
154
+ id: string;
155
+ normalizeToolCallId?: boolean;
156
+ preserveOpenAIDeveloperRole?: boolean;
157
+ }[];
158
+ }>({ customModels: [], modelCompatOverrides: [] });
159
+ const [compatSavingModelId, setCompatSavingModelId] = useState<string | null>(null);
79
160
 
80
161
  const providerInfo = providerNode
81
162
  ? {
@@ -119,6 +200,23 @@ export default function ProviderDetailPage() {
119
200
  }
120
201
  }, []);
121
202
 
203
+ const fetchProviderModelMeta = useCallback(async () => {
204
+ if (isSearchProvider) return;
205
+ try {
206
+ const res = await fetch(`/api/provider-models?provider=${encodeURIComponent(providerId)}`, {
207
+ cache: "no-store",
208
+ });
209
+ if (!res.ok) return;
210
+ const data = await res.json();
211
+ setModelMeta({
212
+ customModels: data.models || [],
213
+ modelCompatOverrides: data.modelCompatOverrides || [],
214
+ });
215
+ } catch (e) {
216
+ console.error("fetchProviderModelMeta", e);
217
+ }
218
+ }, [providerId, isSearchProvider]);
219
+
122
220
  const fetchConnections = useCallback(async () => {
123
221
  try {
124
222
  const [connectionsRes, nodesRes] = await Promise.all([
@@ -186,6 +284,11 @@ export default function ProviderDetailPage() {
186
284
  .catch(() => {});
187
285
  }, [fetchConnections, fetchAliases]);
188
286
 
287
+ useEffect(() => {
288
+ if (loading || isSearchProvider) return;
289
+ fetchProviderModelMeta();
290
+ }, [loading, isSearchProvider, fetchProviderModelMeta]);
291
+
189
292
  // Auto-open Add Connection modal when no connections exist (better UX)
190
293
  // Only fires once on initial load, not on HMR remounts or after user dismissal
191
294
  useEffect(() => {
@@ -670,6 +773,79 @@ export default function ProviderDetailPage() {
670
773
 
671
774
  const canImportModels = connections.some((conn) => conn.isActive !== false);
672
775
 
776
+ const effectiveModelNormalize = (modelId: string) => {
777
+ const c = modelMeta.customModels.find((m: { id?: string }) => m.id === modelId) as
778
+ | { normalizeToolCallId?: boolean }
779
+ | undefined;
780
+ if (c) return Boolean(c.normalizeToolCallId);
781
+ const o = modelMeta.modelCompatOverrides.find((e) => e.id === modelId);
782
+ return Boolean(o?.normalizeToolCallId);
783
+ };
784
+
785
+ const effectiveModelPreserveDeveloper = (modelId: string) => {
786
+ const c = modelMeta.customModels.find((m: { id?: string }) => m.id === modelId) as
787
+ | Record<string, unknown>
788
+ | undefined;
789
+ if (c && Object.prototype.hasOwnProperty.call(c, "preserveOpenAIDeveloperRole")) {
790
+ return Boolean(c.preserveOpenAIDeveloperRole);
791
+ }
792
+ const o = modelMeta.modelCompatOverrides.find((e) => e.id === modelId);
793
+ if (o && Object.prototype.hasOwnProperty.call(o, "preserveOpenAIDeveloperRole")) {
794
+ return Boolean(o.preserveOpenAIDeveloperRole);
795
+ }
796
+ return true;
797
+ };
798
+
799
+ const saveModelCompatFlags = async (
800
+ modelId: string,
801
+ patch: { normalizeToolCallId?: boolean; preserveOpenAIDeveloperRole?: boolean }
802
+ ) => {
803
+ setCompatSavingModelId(modelId);
804
+ try {
805
+ const c = modelMeta.customModels.find((m: { id?: string }) => m.id === modelId) as Record<
806
+ string,
807
+ unknown
808
+ > | null;
809
+ let body: Record<string, unknown>;
810
+ if (c) {
811
+ body = {
812
+ provider: providerId,
813
+ modelId,
814
+ modelName: (c.name as string) || modelId,
815
+ source: (c.source as string) || "manual",
816
+ apiFormat: (c.apiFormat as string) || "chat-completions",
817
+ supportedEndpoints:
818
+ Array.isArray(c.supportedEndpoints) && (c.supportedEndpoints as unknown[]).length
819
+ ? c.supportedEndpoints
820
+ : ["chat"],
821
+ normalizeToolCallId:
822
+ patch.normalizeToolCallId !== undefined
823
+ ? patch.normalizeToolCallId
824
+ : Boolean(c.normalizeToolCallId),
825
+ preserveOpenAIDeveloperRole:
826
+ patch.preserveOpenAIDeveloperRole !== undefined
827
+ ? patch.preserveOpenAIDeveloperRole
828
+ : Object.prototype.hasOwnProperty.call(c, "preserveOpenAIDeveloperRole")
829
+ ? Boolean(c.preserveOpenAIDeveloperRole)
830
+ : true,
831
+ };
832
+ } else {
833
+ body = { provider: providerId, modelId, ...patch };
834
+ }
835
+ const res = await fetch("/api/provider-models", {
836
+ method: "PUT",
837
+ headers: { "Content-Type": "application/json" },
838
+ body: JSON.stringify(body),
839
+ });
840
+ if (res.ok) await fetchProviderModelMeta();
841
+ else notify.error(t("failedSaveCustomModel"));
842
+ } catch {
843
+ notify.error(t("failedSaveCustomModel"));
844
+ } finally {
845
+ setCompatSavingModelId(null);
846
+ }
847
+ };
848
+
673
849
  const renderModelsSection = () => {
674
850
  if (isCompatible) {
675
851
  return (
@@ -684,6 +860,12 @@ export default function ProviderDetailPage() {
684
860
  connections={connections}
685
861
  isAnthropic={isAnthropicCompatible}
686
862
  onImportWithProgress={handleCompatibleImportWithProgress}
863
+ t={t}
864
+ effectiveModelNormalize={effectiveModelNormalize}
865
+ effectiveModelPreserveDeveloper={effectiveModelPreserveDeveloper}
866
+ saveModelCompatFlags={saveModelCompatFlags}
867
+ compatSavingModelId={compatSavingModelId}
868
+ onModelsChanged={fetchProviderModelMeta}
687
869
  />
688
870
  );
689
871
  }
@@ -711,6 +893,11 @@ export default function ProviderDetailPage() {
711
893
  onCopy={copy}
712
894
  onSetAlias={handleSetAlias}
713
895
  onDeleteAlias={handleDeleteAlias}
896
+ t={t}
897
+ effectiveModelNormalize={effectiveModelNormalize}
898
+ effectiveModelPreserveDeveloper={effectiveModelPreserveDeveloper}
899
+ saveModelCompatFlags={saveModelCompatFlags}
900
+ compatSavingModelId={compatSavingModelId}
714
901
  />
715
902
  </div>
716
903
  );
@@ -759,8 +946,17 @@ export default function ProviderDetailPage() {
759
946
  alias={existingAlias}
760
947
  copied={copied}
761
948
  onCopy={copy}
762
- onSetAlias={(alias) => handleSetAlias(model.id, alias, providerStorageAlias)}
763
- onDeleteAlias={() => handleDeleteAlias(existingAlias)}
949
+ t={t}
950
+ showDeveloperToggle
951
+ normalizeToolCallId={effectiveModelNormalize(model.id)}
952
+ preserveDeveloperRole={effectiveModelPreserveDeveloper(model.id)}
953
+ onNormalizeChange={(v) =>
954
+ saveModelCompatFlags(model.id, { normalizeToolCallId: v })
955
+ }
956
+ onPreserveChange={(v) =>
957
+ saveModelCompatFlags(model.id, { preserveOpenAIDeveloperRole: v })
958
+ }
959
+ compatDisabled={compatSavingModelId === model.id}
764
960
  />
765
961
  );
766
962
  })}
@@ -1078,6 +1274,7 @@ export default function ProviderDetailPage() {
1078
1274
  providerAlias={providerDisplayAlias}
1079
1275
  copied={copied}
1080
1276
  onCopy={copy}
1277
+ onModelsChanged={fetchProviderModelMeta}
1081
1278
  />
1082
1279
  )}
1083
1280
  </Card>
@@ -1282,23 +1479,48 @@ export default function ProviderDetailPage() {
1282
1479
  );
1283
1480
  }
1284
1481
 
1285
- function ModelRow({ model, fullModel, alias, copied, onCopy, onSetAlias, onDeleteAlias }: any) {
1286
- const t = useTranslations("providers");
1482
+ function ModelRow({
1483
+ model,
1484
+ fullModel,
1485
+ alias,
1486
+ copied,
1487
+ onCopy,
1488
+ t,
1489
+ showDeveloperToggle = true,
1490
+ normalizeToolCallId,
1491
+ preserveDeveloperRole,
1492
+ onNormalizeChange,
1493
+ onPreserveChange,
1494
+ compatDisabled,
1495
+ }: any) {
1287
1496
  return (
1288
- <div className="flex items-center gap-2 px-3 py-2 rounded-lg border border-border hover:bg-sidebar/50">
1289
- <span className="material-symbols-outlined text-base text-text-muted">smart_toy</span>
1290
- <code className="text-xs text-text-muted font-mono bg-sidebar px-1.5 py-0.5 rounded">
1291
- {fullModel}
1292
- </code>
1293
- <button
1294
- onClick={() => onCopy(fullModel, `model-${model.id}`)}
1295
- className="p-0.5 hover:bg-sidebar rounded text-text-muted hover:text-primary"
1296
- title={t("copyModel")}
1297
- >
1298
- <span className="material-symbols-outlined text-sm">
1299
- {copied === `model-${model.id}` ? "check" : "content_copy"}
1497
+ <div className="flex flex-col px-3 py-2 rounded-lg border border-border hover:bg-sidebar/50 min-w-[220px] max-w-md">
1498
+ <div className="flex items-center gap-2 flex-wrap">
1499
+ <span className="material-symbols-outlined text-base text-text-muted shrink-0">
1500
+ smart_toy
1300
1501
  </span>
1301
- </button>
1502
+ <code className="text-xs text-text-muted font-mono bg-sidebar px-1.5 py-0.5 rounded">
1503
+ {fullModel}
1504
+ </code>
1505
+ <button
1506
+ onClick={() => onCopy(fullModel, `model-${model.id}`)}
1507
+ className="p-0.5 hover:bg-sidebar rounded text-text-muted hover:text-primary"
1508
+ title={t("copyModel")}
1509
+ >
1510
+ <span className="material-symbols-outlined text-sm">
1511
+ {copied === `model-${model.id}` ? "check" : "content_copy"}
1512
+ </span>
1513
+ </button>
1514
+ </div>
1515
+ <ModelCompatPopover
1516
+ t={t}
1517
+ normalizeToolCallId={Boolean(normalizeToolCallId)}
1518
+ preserveDeveloperRole={preserveDeveloperRole}
1519
+ showDeveloperToggle={showDeveloperToggle}
1520
+ onNormalizeChange={onNormalizeChange}
1521
+ onPreserveChange={onPreserveChange}
1522
+ disabled={compatDisabled}
1523
+ />
1302
1524
  </div>
1303
1525
  );
1304
1526
  }
@@ -1311,6 +1533,13 @@ ModelRow.propTypes = {
1311
1533
  alias: PropTypes.string,
1312
1534
  copied: PropTypes.string,
1313
1535
  onCopy: PropTypes.func.isRequired,
1536
+ t: PropTypes.func,
1537
+ showDeveloperToggle: PropTypes.bool,
1538
+ normalizeToolCallId: PropTypes.bool,
1539
+ preserveDeveloperRole: PropTypes.bool,
1540
+ onNormalizeChange: PropTypes.func,
1541
+ onPreserveChange: PropTypes.func,
1542
+ compatDisabled: PropTypes.bool,
1314
1543
  };
1315
1544
 
1316
1545
  function PassthroughModelsSection({
@@ -1320,12 +1549,15 @@ function PassthroughModelsSection({
1320
1549
  onCopy,
1321
1550
  onSetAlias,
1322
1551
  onDeleteAlias,
1552
+ t,
1553
+ effectiveModelNormalize,
1554
+ effectiveModelPreserveDeveloper,
1555
+ saveModelCompatFlags,
1556
+ compatSavingModelId,
1323
1557
  }) {
1324
- const t = useTranslations("providers");
1325
1558
  const [newModel, setNewModel] = useState("");
1326
1559
  const [adding, setAdding] = useState(false);
1327
1560
 
1328
- // Filter aliases for this provider - models are persisted via alias
1329
1561
  const providerAliases = Object.entries(modelAliases).filter(([, model]: [string, any]) =>
1330
1562
  (model as string).startsWith(`${providerAlias}/`)
1331
1563
  );
@@ -1400,6 +1632,15 @@ function PassthroughModelsSection({
1400
1632
  copied={copied}
1401
1633
  onCopy={onCopy}
1402
1634
  onDeleteAlias={() => onDeleteAlias(alias)}
1635
+ t={t}
1636
+ showDeveloperToggle
1637
+ normalizeToolCallId={effectiveModelNormalize(modelId)}
1638
+ preserveDeveloperRole={effectiveModelPreserveDeveloper(modelId)}
1639
+ onNormalizeChange={(v) => saveModelCompatFlags(modelId, { normalizeToolCallId: v })}
1640
+ onPreserveChange={(v) =>
1641
+ saveModelCompatFlags(modelId, { preserveOpenAIDeveloperRole: v })
1642
+ }
1643
+ compatDisabled={compatSavingModelId === modelId}
1403
1644
  />
1404
1645
  ))}
1405
1646
  </div>
@@ -1415,41 +1656,69 @@ PassthroughModelsSection.propTypes = {
1415
1656
  onCopy: PropTypes.func.isRequired,
1416
1657
  onSetAlias: PropTypes.func.isRequired,
1417
1658
  onDeleteAlias: PropTypes.func.isRequired,
1659
+ t: PropTypes.func.isRequired,
1660
+ effectiveModelNormalize: PropTypes.func.isRequired,
1661
+ effectiveModelPreserveDeveloper: PropTypes.func.isRequired,
1662
+ saveModelCompatFlags: PropTypes.func.isRequired,
1663
+ compatSavingModelId: PropTypes.string,
1418
1664
  };
1419
1665
 
1420
- function PassthroughModelRow({ modelId, fullModel, copied, onCopy, onDeleteAlias }) {
1421
- const t = useTranslations("providers");
1666
+ function PassthroughModelRow({
1667
+ modelId,
1668
+ fullModel,
1669
+ copied,
1670
+ onCopy,
1671
+ onDeleteAlias,
1672
+ t,
1673
+ showDeveloperToggle = true,
1674
+ normalizeToolCallId,
1675
+ preserveDeveloperRole,
1676
+ onNormalizeChange,
1677
+ onPreserveChange,
1678
+ compatDisabled,
1679
+ }: any) {
1422
1680
  return (
1423
- <div className="flex items-center gap-3 p-3 rounded-lg border border-border hover:bg-sidebar/50">
1424
- <span className="material-symbols-outlined text-base text-text-muted">smart_toy</span>
1425
-
1426
- <div className="flex-1 min-w-0">
1427
- <p className="text-sm font-medium truncate">{modelId}</p>
1428
-
1429
- <div className="flex items-center gap-1 mt-1">
1430
- <code className="text-xs text-text-muted font-mono bg-sidebar px-1.5 py-0.5 rounded">
1431
- {fullModel}
1432
- </code>
1433
- <button
1434
- onClick={() => onCopy(fullModel, `model-${modelId}`)}
1435
- className="p-0.5 hover:bg-sidebar rounded text-text-muted hover:text-primary"
1436
- title={t("copyModel")}
1437
- >
1438
- <span className="material-symbols-outlined text-sm">
1439
- {copied === `model-${modelId}` ? "check" : "content_copy"}
1440
- </span>
1441
- </button>
1681
+ <div className="flex flex-col gap-0 p-3 rounded-lg border border-border hover:bg-sidebar/50">
1682
+ <div className="flex items-start gap-3">
1683
+ <span className="material-symbols-outlined text-base text-text-muted shrink-0">
1684
+ smart_toy
1685
+ </span>
1686
+ <div className="flex-1 min-w-0">
1687
+ <p className="text-sm font-medium truncate">{modelId}</p>
1688
+ <div className="flex items-center gap-1 mt-1 flex-wrap">
1689
+ <code className="text-xs text-text-muted font-mono bg-sidebar px-1.5 py-0.5 rounded">
1690
+ {fullModel}
1691
+ </code>
1692
+ <button
1693
+ onClick={() => onCopy(fullModel, `model-${modelId}`)}
1694
+ className="p-0.5 hover:bg-sidebar rounded text-text-muted hover:text-primary"
1695
+ title={t("copyModel")}
1696
+ >
1697
+ <span className="material-symbols-outlined text-sm">
1698
+ {copied === `model-${modelId}` ? "check" : "content_copy"}
1699
+ </span>
1700
+ </button>
1701
+ </div>
1442
1702
  </div>
1703
+ <button
1704
+ onClick={onDeleteAlias}
1705
+ className="p-1 hover:bg-red-50 rounded text-red-500 shrink-0"
1706
+ title={t("removeModel")}
1707
+ >
1708
+ <span className="material-symbols-outlined text-sm">delete</span>
1709
+ </button>
1710
+ </div>
1711
+ <div className="pl-9">
1712
+ <ModelCompatPopover
1713
+ t={t}
1714
+ normalizeToolCallId={Boolean(normalizeToolCallId)}
1715
+ preserveDeveloperRole={preserveDeveloperRole}
1716
+ showDeveloperToggle={showDeveloperToggle}
1717
+ onNormalizeChange={onNormalizeChange}
1718
+ onPreserveChange={onPreserveChange}
1719
+ disabled={compatDisabled}
1720
+ />
1443
1721
  </div>
1444
-
1445
- {/* Delete button */}
1446
- <button
1447
- onClick={onDeleteAlias}
1448
- className="p-1 hover:bg-red-50 rounded text-red-500"
1449
- title={t("removeModel")}
1450
- >
1451
- <span className="material-symbols-outlined text-sm">delete</span>
1452
- </button>
1453
1722
  </div>
1454
1723
  );
1455
1724
  }
@@ -1460,11 +1729,18 @@ PassthroughModelRow.propTypes = {
1460
1729
  copied: PropTypes.string,
1461
1730
  onCopy: PropTypes.func.isRequired,
1462
1731
  onDeleteAlias: PropTypes.func.isRequired,
1732
+ t: PropTypes.func,
1733
+ showDeveloperToggle: PropTypes.bool,
1734
+ normalizeToolCallId: PropTypes.bool,
1735
+ preserveDeveloperRole: PropTypes.bool,
1736
+ onNormalizeChange: PropTypes.func,
1737
+ onPreserveChange: PropTypes.func,
1738
+ compatDisabled: PropTypes.bool,
1463
1739
  };
1464
1740
 
1465
1741
  // ============ Custom Models Section (for ALL providers) ============
1466
1742
 
1467
- function CustomModelsSection({ providerId, providerAlias, copied, onCopy }) {
1743
+ function CustomModelsSection({ providerId, providerAlias, copied, onCopy, onModelsChanged }) {
1468
1744
  const t = useTranslations("providers");
1469
1745
  const notify = useNotificationStore();
1470
1746
  const [customModels, setCustomModels] = useState([]);
@@ -1478,6 +1754,7 @@ function CustomModelsSection({ providerId, providerAlias, copied, onCopy }) {
1478
1754
  const [editingApiFormat, setEditingApiFormat] = useState("chat-completions");
1479
1755
  const [editingEndpoints, setEditingEndpoints] = useState<string[]>(["chat"]);
1480
1756
  const [editingNormalizeToolCallId, setEditingNormalizeToolCallId] = useState(false);
1757
+ const [editingPreserveDeveloperRole, setEditingPreserveDeveloperRole] = useState(false);
1481
1758
  const [savingModelId, setSavingModelId] = useState<string | null>(null);
1482
1759
 
1483
1760
  const fetchCustomModels = useCallback(async () => {
@@ -1519,6 +1796,7 @@ function CustomModelsSection({ providerId, providerAlias, copied, onCopy }) {
1519
1796
  setNewApiFormat("chat-completions");
1520
1797
  setNewEndpoints(["chat"]);
1521
1798
  await fetchCustomModels();
1799
+ onModelsChanged?.();
1522
1800
  }
1523
1801
  } catch (e) {
1524
1802
  console.error("Failed to add custom model:", e);
@@ -1536,6 +1814,7 @@ function CustomModelsSection({ providerId, providerAlias, copied, onCopy }) {
1536
1814
  }
1537
1815
  );
1538
1816
  await fetchCustomModels();
1817
+ onModelsChanged?.();
1539
1818
  } catch (e) {
1540
1819
  console.error("Failed to remove custom model:", e);
1541
1820
  }
@@ -1550,6 +1829,11 @@ function CustomModelsSection({ providerId, providerAlias, copied, onCopy }) {
1550
1829
  : ["chat"]
1551
1830
  );
1552
1831
  setEditingNormalizeToolCallId(Boolean(model.normalizeToolCallId));
1832
+ setEditingPreserveDeveloperRole(
1833
+ Object.prototype.hasOwnProperty.call(model, "preserveOpenAIDeveloperRole")
1834
+ ? Boolean(model.preserveOpenAIDeveloperRole)
1835
+ : true
1836
+ );
1553
1837
  };
1554
1838
 
1555
1839
  const cancelEdit = () => {
@@ -1557,6 +1841,7 @@ function CustomModelsSection({ providerId, providerAlias, copied, onCopy }) {
1557
1841
  setEditingApiFormat("chat-completions");
1558
1842
  setEditingEndpoints(["chat"]);
1559
1843
  setEditingNormalizeToolCallId(false);
1844
+ setEditingPreserveDeveloperRole(true);
1560
1845
  setSavingModelId(null);
1561
1846
  };
1562
1847
 
@@ -1581,6 +1866,7 @@ function CustomModelsSection({ providerId, providerAlias, copied, onCopy }) {
1581
1866
  apiFormat: editingApiFormat,
1582
1867
  supportedEndpoints: editingEndpoints,
1583
1868
  normalizeToolCallId: editingNormalizeToolCallId,
1869
+ preserveOpenAIDeveloperRole: editingPreserveDeveloperRole,
1584
1870
  }),
1585
1871
  });
1586
1872
 
@@ -1589,6 +1875,7 @@ function CustomModelsSection({ providerId, providerAlias, copied, onCopy }) {
1589
1875
  }
1590
1876
 
1591
1877
  await fetchCustomModels();
1878
+ onModelsChanged?.();
1592
1879
  notify.success("Saved model endpoint settings");
1593
1880
  cancelEdit();
1594
1881
  } catch (e) {
@@ -1745,11 +2032,19 @@ function CustomModelsSection({ providerId, providerAlias, copied, onCopy }) {
1745
2032
  {model.normalizeToolCallId && (
1746
2033
  <span
1747
2034
  className="text-[10px] px-1.5 py-0.5 rounded-full bg-slate-500/15 text-slate-400 font-medium"
1748
- title="9-char tool call ID (Mistral)"
2035
+ title={t("normalizeToolCallIdLabel")}
1749
2036
  >
1750
2037
  ID×9
1751
2038
  </span>
1752
2039
  )}
2040
+ {model.preserveOpenAIDeveloperRole === false && (
2041
+ <span
2042
+ className="text-[10px] px-1.5 py-0.5 rounded-full bg-cyan-500/15 text-cyan-400 font-medium"
2043
+ title={t("compatDoNotPreserveDeveloper")}
2044
+ >
2045
+ {t("compatBadgeNoPreserve")}
2046
+ </span>
2047
+ )}
1753
2048
  </div>
1754
2049
 
1755
2050
  {editingModelId === model.id && (
@@ -1802,16 +2097,17 @@ function CustomModelsSection({ providerId, providerAlias, copied, onCopy }) {
1802
2097
  ))}
1803
2098
  </div>
1804
2099
  </div>
1805
-
1806
- <label className="flex items-center gap-2 text-xs text-text-main cursor-pointer">
1807
- <input
1808
- type="checkbox"
1809
- checked={editingNormalizeToolCallId}
1810
- onChange={(e) => setEditingNormalizeToolCallId(e.target.checked)}
1811
- className="rounded border-border"
1812
- />
1813
- Normalize Tool Call ID (9 chars, Mistral)
1814
- </label>
2100
+ </div>
2101
+ <div className="mt-3 pt-3 border-t border-border/80 w-full">
2102
+ <ModelCompatPopover
2103
+ t={t}
2104
+ normalizeToolCallId={editingNormalizeToolCallId}
2105
+ preserveDeveloperRole={editingPreserveDeveloperRole}
2106
+ showDeveloperToggle
2107
+ onNormalizeChange={setEditingNormalizeToolCallId}
2108
+ onPreserveChange={setEditingPreserveDeveloperRole}
2109
+ disabled={savingModelId === model.id}
2110
+ />
1815
2111
  </div>
1816
2112
  <div className="mt-3 flex items-center gap-2">
1817
2113
  <Button
@@ -1860,6 +2156,7 @@ CustomModelsSection.propTypes = {
1860
2156
  providerAlias: PropTypes.string.isRequired,
1861
2157
  copied: PropTypes.string,
1862
2158
  onCopy: PropTypes.func.isRequired,
2159
+ onModelsChanged: PropTypes.func,
1863
2160
  };
1864
2161
 
1865
2162
  function CompatibleModelsSection({
@@ -1873,8 +2170,13 @@ function CompatibleModelsSection({
1873
2170
  connections,
1874
2171
  isAnthropic,
1875
2172
  onImportWithProgress,
2173
+ t,
2174
+ effectiveModelNormalize,
2175
+ effectiveModelPreserveDeveloper,
2176
+ saveModelCompatFlags,
2177
+ compatSavingModelId,
2178
+ onModelsChanged,
1876
2179
  }) {
1877
- const t = useTranslations("providers");
1878
2180
  const [newModel, setNewModel] = useState("");
1879
2181
  const [adding, setAdding] = useState(false);
1880
2182
  const [importing, setImporting] = useState(false);
@@ -1940,6 +2242,7 @@ function CompatibleModelsSection({
1940
2242
  await onSetAlias(modelId, resolvedAlias, providerStorageAlias);
1941
2243
  setNewModel("");
1942
2244
  notify.success(t("modelAddedSuccess", { modelId }));
2245
+ onModelsChanged?.();
1943
2246
  } catch (error) {
1944
2247
  console.error("Error adding model:", error);
1945
2248
  notify.error(error instanceof Error ? error.message : t("failedAddModelTryAgain"));
@@ -2016,6 +2319,7 @@ function CompatibleModelsSection({
2016
2319
  // Also delete the alias
2017
2320
  await onDeleteAlias(alias);
2018
2321
  notify.success(t("modelRemovedSuccess"));
2322
+ onModelsChanged?.();
2019
2323
  } catch (error) {
2020
2324
  console.error("Error deleting model:", error);
2021
2325
  notify.error(error instanceof Error ? error.message : t("failedDeleteModelTryAgain"));
@@ -2078,6 +2382,15 @@ function CompatibleModelsSection({
2078
2382
  copied={copied}
2079
2383
  onCopy={onCopy}
2080
2384
  onDeleteAlias={() => handleDeleteModel(modelId, alias)}
2385
+ t={t}
2386
+ showDeveloperToggle={!isAnthropic}
2387
+ normalizeToolCallId={effectiveModelNormalize(modelId)}
2388
+ preserveDeveloperRole={effectiveModelPreserveDeveloper(modelId)}
2389
+ onNormalizeChange={(v) => saveModelCompatFlags(modelId, { normalizeToolCallId: v })}
2390
+ onPreserveChange={(v) =>
2391
+ saveModelCompatFlags(modelId, { preserveOpenAIDeveloperRole: v })
2392
+ }
2393
+ compatDisabled={compatSavingModelId === modelId}
2081
2394
  />
2082
2395
  ))}
2083
2396
  </div>
@@ -2102,6 +2415,12 @@ CompatibleModelsSection.propTypes = {
2102
2415
  ).isRequired,
2103
2416
  isAnthropic: PropTypes.bool,
2104
2417
  onImportWithProgress: PropTypes.func.isRequired,
2418
+ t: PropTypes.func.isRequired,
2419
+ effectiveModelNormalize: PropTypes.func.isRequired,
2420
+ effectiveModelPreserveDeveloper: PropTypes.func.isRequired,
2421
+ saveModelCompatFlags: PropTypes.func.isRequired,
2422
+ compatSavingModelId: PropTypes.string,
2423
+ onModelsChanged: PropTypes.func,
2105
2424
  };
2106
2425
 
2107
2426
  function CooldownTimer({ until }) {