@oneuptime/common 12.0.9 → 12.0.11

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 (262) hide show
  1. package/Models/DatabaseModels/NetworkDevice.ts +44 -0
  2. package/Models/DatabaseModels/NetworkDeviceLink.ts +116 -0
  3. package/Models/DatabaseModels/RunnerJob.ts +17 -2
  4. package/Server/API/LlmProviderAPI.ts +8 -0
  5. package/Server/Infrastructure/Postgres/SchemaMigrations/1787400000000-AddDeviceRoleAndDeclaredLinkParent.ts +52 -0
  6. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +2 -0
  7. package/Server/Services/NetworkDeviceLabelRuleEngineService.ts +240 -0
  8. package/Server/Services/NetworkDeviceLinkService.ts +338 -2
  9. package/Server/Services/NetworkDeviceService.ts +265 -12
  10. package/Server/Services/RunnerJobService.ts +24 -1
  11. package/Server/Types/Billing/MeteredPlan/AllMeteredPlans.ts +19 -5
  12. package/Server/Types/Database/JSONColumnQuery.ts +42 -4
  13. package/Server/Utils/LLM/LLMService.ts +108 -19
  14. package/Server/Utils/Monitor/MonitorAlert.ts +22 -115
  15. package/Server/Utils/Monitor/MonitorIncident.ts +7 -337
  16. package/Server/Utils/Monitor/MonitorMaintenanceSuppression.ts +27 -2
  17. package/Server/Utils/Monitor/SeriesResourceLabels.ts +32 -0
  18. package/Server/Utils/Monitor/SeriesResourceLinker.ts +469 -0
  19. package/Server/Utils/SSRFProtection.ts +5 -4
  20. package/Server/Utils/Telemetry/InventoryEntityRegistry.ts +186 -38
  21. package/Tests/App/AdminDashboard/AdminUsersBulkAddToProject.test.tsx +955 -0
  22. package/Tests/App/AdminDashboard/BulkAddUsersToProject.test.ts +778 -0
  23. package/Tests/App/AdminDashboard/BulkAddUsersToProjectModal.test.tsx +1051 -0
  24. package/Tests/App/Dashboard/AdminNotificationRulesPage.test.tsx +420 -112
  25. package/Tests/App/Dashboard/OnCallReadinessSurfaces.test.tsx +81 -6
  26. package/Tests/App/Dashboard/PayAsYouGoConsentGate.test.tsx +543 -0
  27. package/Tests/App/Dashboard/PayAsYouGoFreePlan.test.ts +125 -0
  28. package/Tests/App/Dashboard/PayAsYouGoNotices.test.tsx +612 -0
  29. package/Tests/App/Dashboard/SloWidgetChartRendering.test.tsx +536 -0
  30. package/Tests/App/Dashboard/UserElementEmail.test.tsx +447 -0
  31. package/Tests/App/Dashboard/UserEmailCallSites.test.tsx +284 -0
  32. package/Tests/Models/AnalyticsModels/CommonModel.test.ts +312 -0
  33. package/Tests/Server/Infrastructure/Postgres/DeviceRoleAndDeclaredLinkParentMigration.test.ts +250 -0
  34. package/Tests/Server/Infrastructure/Postgres/EpisodeMemberNotifyIndexesMigration.test.ts +46 -18
  35. package/Tests/Server/Services/NetworkDeviceLabelRuleRun.test.ts +618 -0
  36. package/Tests/Server/Services/NetworkDeviceLinkParentValidation.test.ts +1043 -0
  37. package/Tests/Server/Services/NetworkSiteAssignmentRuleRun.test.ts +706 -0
  38. package/Tests/Server/Services/RunnerJobEnqueue.test.ts +412 -0
  39. package/Tests/Server/Types/Billing/PayAsYouGoMeteredPricing.test.ts +125 -0
  40. package/Tests/Server/Types/Database/JSONColumnQuery.test.ts +508 -0
  41. package/Tests/Server/Utils/AI/LLMServiceRequestPolicy.test.ts +2 -2
  42. package/Tests/Server/Utils/AI/LLMServiceRetryPolicy.test.ts +450 -0
  43. package/Tests/Server/Utils/Monitor/MonitorAlertResourceLinking.test.ts +556 -0
  44. package/Tests/Server/Utils/Monitor/MonitorIncidentResourceLinking.test.ts +547 -0
  45. package/Tests/Server/Utils/Monitor/MonitorMaintenanceSuppression.test.ts +30 -0
  46. package/Tests/Server/Utils/Monitor/SeriesResourceLinker.test.ts +699 -0
  47. package/Tests/Server/Utils/SeriesResourceLabels.test.ts +55 -0
  48. package/Tests/Server/Utils/Telemetry/InventoryEntityRegistry.test.ts +668 -0
  49. package/Tests/Types/API/URLMalformedTolerance.test.ts +301 -0
  50. package/Tests/Types/API/URLTelScheme.test.ts +261 -0
  51. package/Tests/Types/API/URLUnsupportedScheme.test.ts +148 -0
  52. package/Tests/Types/Billing/PayAsYouGoPricing.test.ts +64 -0
  53. package/Tests/Types/CustomField/CustomFieldTypeCorrespondence.test.ts +107 -0
  54. package/Tests/Types/Monitor/MonitorType.test.ts +62 -0
  55. package/Tests/Types/NetworkAutomation/RuleRunResult.test.ts +184 -0
  56. package/Tests/Types/Runbook/RunbookStepType.test.ts +124 -0
  57. package/Tests/Types/Workflow/BaseModelComponents.test.ts +17 -0
  58. package/Tests/UI/Components/Button.test.tsx +279 -0
  59. package/Tests/UI/Components/Charts/ChartXAxisDataKey.test.tsx +256 -0
  60. package/Tests/UI/Components/Charts/DataPointUtil.test.ts +323 -0
  61. package/Tests/UI/Components/DictionaryValuePlaceholderAndSuggestions.test.tsx +595 -0
  62. package/Tests/UI/Components/Icon.test.tsx +57 -0
  63. package/Tests/UI/Components/List.test.tsx +20 -3
  64. package/Tests/UI/Components/LogsSavedViewsDropdown.test.tsx +304 -0
  65. package/Tests/UI/Components/LogsViewerClearSavedView.test.tsx +179 -0
  66. package/Tests/UI/Components/ModelTable/CustomFieldColumns.test.tsx +349 -1
  67. package/Tests/UI/Components/Pagination.test.tsx +721 -146
  68. package/Tests/UI/Components/PaginationIntegration.test.tsx +386 -0
  69. package/Tests/UI/Components/PaginationUtil.test.ts +417 -0
  70. package/Tests/UI/Components/TelemetrySavedViewsDropdown.test.tsx +343 -0
  71. package/Tests/UI/Components/Workflow/ColumnEditor/ColumnControl.test.ts +289 -0
  72. package/Tests/UI/Components/Workflow/ColumnEditor/ColumnOperators.test.ts +271 -0
  73. package/Tests/UI/Components/Workflow/ColumnEditor/ColumnRow.test.ts +119 -0
  74. package/Tests/UI/Components/Workflow/ColumnEditor/ColumnRowSerialization.test.ts +968 -0
  75. package/Tests/UI/Components/Workflow/ModelColumnEditor.render.test.tsx +766 -0
  76. package/Tests/UI/Utils/RunNetworkRule.test.ts +320 -0
  77. package/Tests/Utils/APIRetryPolicy.test.ts +823 -0
  78. package/Tests/Utils/Monitor/NetworkDeviceRoleUtil.test.ts +502 -0
  79. package/Tests/Utils/Monitor/NetworkTopologyDeclaredHierarchy.test.ts +759 -0
  80. package/Tests/Utils/NetworkAutomation/RuleRunSummary.test.ts +286 -0
  81. package/Tests/Utils/Schema/BaseSchema.test.ts +651 -0
  82. package/Types/API/Protocol.ts +9 -0
  83. package/Types/API/URL.ts +286 -20
  84. package/Types/Billing/PayAsYouGoPricing.ts +47 -0
  85. package/Types/CustomField/CustomFieldType.ts +28 -0
  86. package/Types/Monitor/MonitorType.ts +11 -0
  87. package/Types/Monitor/SnmpMonitor/NetworkTopology.ts +13 -0
  88. package/Types/NetworkAutomation/RuleRunResult.ts +121 -0
  89. package/Types/Runbook/RunbookStepType.ts +23 -0
  90. package/UI/Components/AutocompleteTextInput/AutocompleteTextInput.tsx +7 -0
  91. package/UI/Components/Button/Button.tsx +14 -22
  92. package/UI/Components/Charts/Area/AreaChart.tsx +4 -2
  93. package/UI/Components/Charts/Bar/BarChart.tsx +4 -2
  94. package/UI/Components/Charts/ChartLibrary/Types/ChartDataPoint.ts +13 -0
  95. package/UI/Components/Charts/Line/LineChart.tsx +4 -2
  96. package/UI/Components/Charts/Types/XAxis/XAxis.ts +6 -0
  97. package/UI/Components/Charts/Utils/DataPoint.ts +15 -12
  98. package/UI/Components/Dictionary/Dictionary.tsx +4 -39
  99. package/UI/Components/Icon/Icon.tsx +1 -1
  100. package/UI/Components/LogsViewer/LogsViewer.tsx +5 -1
  101. package/UI/Components/LogsViewer/components/LogsFacetSidebar.tsx +8 -0
  102. package/UI/Components/LogsViewer/components/LogsPagination.tsx +29 -91
  103. package/UI/Components/LogsViewer/components/LogsViewerToolbar.tsx +2 -0
  104. package/UI/Components/LogsViewer/components/SavedViewsDropdown.tsx +66 -1
  105. package/UI/Components/ModelTable/CustomFieldColumns.tsx +55 -0
  106. package/UI/Components/Pagination/Pagination.tsx +373 -328
  107. package/UI/Components/Pagination/PaginationUtil.ts +263 -0
  108. package/UI/Components/Pagination/Screenshots/README.md +15 -0
  109. package/UI/Components/Pagination/Screenshots/pagination-before-after.png +0 -0
  110. package/UI/Components/Pagination/Screenshots/pagination-go-to-page-modal.png +0 -0
  111. package/UI/Components/Pagination/Screenshots/pagination-long-list.png +0 -0
  112. package/UI/Components/Pagination/Screenshots/pagination-mobile.png +0 -0
  113. package/UI/Components/Pagination/Screenshots/pagination-telemetry.png +0 -0
  114. package/UI/Components/TelemetryViewer/components/SavedViewsDropdown.tsx +66 -1
  115. package/UI/Components/TelemetryViewer/components/TelemetryPagination.tsx +38 -92
  116. package/UI/Components/TextArea/TextArea.tsx +3 -0
  117. package/UI/Components/Workflow/ArgumentsForm.tsx +18 -1
  118. package/UI/Components/Workflow/ColumnEditor/AddColumnDropdown.tsx +177 -0
  119. package/UI/Components/Workflow/ColumnEditor/ColumnConditionRow.tsx +243 -0
  120. package/UI/Components/Workflow/ColumnEditor/ColumnControl.ts +255 -0
  121. package/UI/Components/Workflow/ColumnEditor/ColumnFieldRow.tsx +178 -0
  122. package/UI/Components/Workflow/ColumnEditor/ColumnOperators.ts +206 -0
  123. package/UI/Components/Workflow/ColumnEditor/ColumnRow.ts +157 -0
  124. package/UI/Components/Workflow/ColumnEditor/ColumnRowSerialization.ts +517 -0
  125. package/UI/Components/Workflow/ColumnEditor/ColumnValueInput.tsx +482 -0
  126. package/UI/Components/Workflow/ColumnEditor/ModelQueryBuilder.tsx +131 -0
  127. package/UI/Components/Workflow/ColumnEditor/ModelRecordForm.tsx +163 -0
  128. package/UI/Components/Workflow/ModelColumnEditor.tsx +284 -100
  129. package/UI/Utils/NetworkAutomation/RunNetworkRule.ts +122 -0
  130. package/Utils/API.ts +243 -36
  131. package/Utils/Monitor/NetworkDeviceRoleUtil.ts +45 -0
  132. package/Utils/Monitor/NetworkTopologyUtil.ts +42 -2
  133. package/Utils/NetworkAutomation/RuleRunSummary.ts +147 -0
  134. package/build/dist/Models/DatabaseModels/NetworkDevice.js +45 -0
  135. package/build/dist/Models/DatabaseModels/NetworkDevice.js.map +1 -1
  136. package/build/dist/Models/DatabaseModels/NetworkDeviceLink.js +115 -0
  137. package/build/dist/Models/DatabaseModels/NetworkDeviceLink.js.map +1 -1
  138. package/build/dist/Models/DatabaseModels/RunnerJob.js +17 -2
  139. package/build/dist/Models/DatabaseModels/RunnerJob.js.map +1 -1
  140. package/build/dist/Server/API/LlmProviderAPI.js +9 -1
  141. package/build/dist/Server/API/LlmProviderAPI.js.map +1 -1
  142. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787400000000-AddDeviceRoleAndDeclaredLinkParent.js +33 -0
  143. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787400000000-AddDeviceRoleAndDeclaredLinkParent.js.map +1 -0
  144. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +2 -0
  145. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  146. package/build/dist/Server/Services/NetworkDeviceLabelRuleEngineService.js +202 -0
  147. package/build/dist/Server/Services/NetworkDeviceLabelRuleEngineService.js.map +1 -1
  148. package/build/dist/Server/Services/NetworkDeviceLinkService.js +253 -1
  149. package/build/dist/Server/Services/NetworkDeviceLinkService.js.map +1 -1
  150. package/build/dist/Server/Services/NetworkDeviceService.js +223 -12
  151. package/build/dist/Server/Services/NetworkDeviceService.js.map +1 -1
  152. package/build/dist/Server/Services/RunnerJobService.js +20 -2
  153. package/build/dist/Server/Services/RunnerJobService.js.map +1 -1
  154. package/build/dist/Server/Types/Billing/MeteredPlan/AllMeteredPlans.js +12 -5
  155. package/build/dist/Server/Types/Billing/MeteredPlan/AllMeteredPlans.js.map +1 -1
  156. package/build/dist/Server/Types/Database/JSONColumnQuery.js +40 -4
  157. package/build/dist/Server/Types/Database/JSONColumnQuery.js.map +1 -1
  158. package/build/dist/Server/Utils/LLM/LLMService.js +78 -22
  159. package/build/dist/Server/Utils/LLM/LLMService.js.map +1 -1
  160. package/build/dist/Server/Utils/Monitor/MonitorAlert.js +22 -98
  161. package/build/dist/Server/Utils/Monitor/MonitorAlert.js.map +1 -1
  162. package/build/dist/Server/Utils/Monitor/MonitorIncident.js +7 -254
  163. package/build/dist/Server/Utils/Monitor/MonitorIncident.js.map +1 -1
  164. package/build/dist/Server/Utils/Monitor/MonitorMaintenanceSuppression.js +14 -2
  165. package/build/dist/Server/Utils/Monitor/MonitorMaintenanceSuppression.js.map +1 -1
  166. package/build/dist/Server/Utils/Monitor/SeriesResourceLabels.js +27 -0
  167. package/build/dist/Server/Utils/Monitor/SeriesResourceLabels.js.map +1 -1
  168. package/build/dist/Server/Utils/Monitor/SeriesResourceLinker.js +285 -0
  169. package/build/dist/Server/Utils/Monitor/SeriesResourceLinker.js.map +1 -0
  170. package/build/dist/Server/Utils/SSRFProtection.js +5 -4
  171. package/build/dist/Server/Utils/SSRFProtection.js.map +1 -1
  172. package/build/dist/Server/Utils/Telemetry/InventoryEntityRegistry.js +128 -35
  173. package/build/dist/Server/Utils/Telemetry/InventoryEntityRegistry.js.map +1 -1
  174. package/build/dist/Types/API/Protocol.js +9 -0
  175. package/build/dist/Types/API/Protocol.js.map +1 -1
  176. package/build/dist/Types/API/URL.js +241 -18
  177. package/build/dist/Types/API/URL.js.map +1 -1
  178. package/build/dist/Types/Billing/PayAsYouGoPricing.js +41 -0
  179. package/build/dist/Types/Billing/PayAsYouGoPricing.js.map +1 -0
  180. package/build/dist/Types/CustomField/CustomFieldType.js +26 -0
  181. package/build/dist/Types/CustomField/CustomFieldType.js.map +1 -1
  182. package/build/dist/Types/Monitor/MonitorType.js +10 -0
  183. package/build/dist/Types/Monitor/MonitorType.js.map +1 -1
  184. package/build/dist/Types/NetworkAutomation/RuleRunResult.js +39 -0
  185. package/build/dist/Types/NetworkAutomation/RuleRunResult.js.map +1 -0
  186. package/build/dist/Types/Runbook/RunbookStepType.js +21 -0
  187. package/build/dist/Types/Runbook/RunbookStepType.js.map +1 -1
  188. package/build/dist/UI/Components/AutocompleteTextInput/AutocompleteTextInput.js +1 -1
  189. package/build/dist/UI/Components/AutocompleteTextInput/AutocompleteTextInput.js.map +1 -1
  190. package/build/dist/UI/Components/Button/Button.js +10 -16
  191. package/build/dist/UI/Components/Button/Button.js.map +1 -1
  192. package/build/dist/UI/Components/Charts/Area/AreaChart.js +2 -1
  193. package/build/dist/UI/Components/Charts/Area/AreaChart.js.map +1 -1
  194. package/build/dist/UI/Components/Charts/Bar/BarChart.js +2 -1
  195. package/build/dist/UI/Components/Charts/Bar/BarChart.js.map +1 -1
  196. package/build/dist/UI/Components/Charts/ChartLibrary/Types/ChartDataPoint.js +12 -0
  197. package/build/dist/UI/Components/Charts/ChartLibrary/Types/ChartDataPoint.js.map +1 -1
  198. package/build/dist/UI/Components/Charts/Line/LineChart.js +2 -1
  199. package/build/dist/UI/Components/Charts/Line/LineChart.js.map +1 -1
  200. package/build/dist/UI/Components/Charts/Utils/DataPoint.js +17 -10
  201. package/build/dist/UI/Components/Charts/Utils/DataPoint.js.map +1 -1
  202. package/build/dist/UI/Components/Dictionary/Dictionary.js +4 -18
  203. package/build/dist/UI/Components/Dictionary/Dictionary.js.map +1 -1
  204. package/build/dist/UI/Components/Icon/Icon.js +1 -1
  205. package/build/dist/UI/Components/Icon/Icon.js.map +1 -1
  206. package/build/dist/UI/Components/LogsViewer/LogsViewer.js +3 -3
  207. package/build/dist/UI/Components/LogsViewer/LogsViewer.js.map +1 -1
  208. package/build/dist/UI/Components/LogsViewer/components/LogsFacetSidebar.js +5 -1
  209. package/build/dist/UI/Components/LogsViewer/components/LogsFacetSidebar.js.map +1 -1
  210. package/build/dist/UI/Components/LogsViewer/components/LogsPagination.js +14 -44
  211. package/build/dist/UI/Components/LogsViewer/components/LogsPagination.js.map +1 -1
  212. package/build/dist/UI/Components/LogsViewer/components/LogsViewerToolbar.js +1 -1
  213. package/build/dist/UI/Components/LogsViewer/components/LogsViewerToolbar.js.map +1 -1
  214. package/build/dist/UI/Components/LogsViewer/components/SavedViewsDropdown.js +20 -2
  215. package/build/dist/UI/Components/LogsViewer/components/SavedViewsDropdown.js.map +1 -1
  216. package/build/dist/UI/Components/ModelTable/CustomFieldColumns.js +42 -0
  217. package/build/dist/UI/Components/ModelTable/CustomFieldColumns.js.map +1 -1
  218. package/build/dist/UI/Components/Pagination/Pagination.js +174 -199
  219. package/build/dist/UI/Components/Pagination/Pagination.js.map +1 -1
  220. package/build/dist/UI/Components/Pagination/PaginationUtil.js +160 -0
  221. package/build/dist/UI/Components/Pagination/PaginationUtil.js.map +1 -0
  222. package/build/dist/UI/Components/TelemetryViewer/components/SavedViewsDropdown.js +20 -2
  223. package/build/dist/UI/Components/TelemetryViewer/components/SavedViewsDropdown.js.map +1 -1
  224. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryPagination.js +23 -47
  225. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryPagination.js.map +1 -1
  226. package/build/dist/UI/Components/TextArea/TextArea.js +1 -1
  227. package/build/dist/UI/Components/TextArea/TextArea.js.map +1 -1
  228. package/build/dist/UI/Components/Workflow/ArgumentsForm.js +14 -2
  229. package/build/dist/UI/Components/Workflow/ArgumentsForm.js.map +1 -1
  230. package/build/dist/UI/Components/Workflow/ColumnEditor/AddColumnDropdown.js +86 -0
  231. package/build/dist/UI/Components/Workflow/ColumnEditor/AddColumnDropdown.js.map +1 -0
  232. package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnConditionRow.js +119 -0
  233. package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnConditionRow.js.map +1 -0
  234. package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnControl.js +206 -0
  235. package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnControl.js.map +1 -0
  236. package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnFieldRow.js +57 -0
  237. package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnFieldRow.js.map +1 -0
  238. package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnOperators.js +150 -0
  239. package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnOperators.js.map +1 -0
  240. package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnRow.js +67 -0
  241. package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnRow.js.map +1 -0
  242. package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnRowSerialization.js +309 -0
  243. package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnRowSerialization.js.map +1 -0
  244. package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnValueInput.js +204 -0
  245. package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnValueInput.js.map +1 -0
  246. package/build/dist/UI/Components/Workflow/ColumnEditor/ModelQueryBuilder.js +66 -0
  247. package/build/dist/UI/Components/Workflow/ColumnEditor/ModelQueryBuilder.js.map +1 -0
  248. package/build/dist/UI/Components/Workflow/ColumnEditor/ModelRecordForm.js +83 -0
  249. package/build/dist/UI/Components/Workflow/ColumnEditor/ModelRecordForm.js.map +1 -0
  250. package/build/dist/UI/Components/Workflow/ModelColumnEditor.js +187 -68
  251. package/build/dist/UI/Components/Workflow/ModelColumnEditor.js.map +1 -1
  252. package/build/dist/UI/Utils/NetworkAutomation/RunNetworkRule.js +75 -0
  253. package/build/dist/UI/Utils/NetworkAutomation/RunNetworkRule.js.map +1 -0
  254. package/build/dist/Utils/API.js +175 -34
  255. package/build/dist/Utils/API.js.map +1 -1
  256. package/build/dist/Utils/Monitor/NetworkDeviceRoleUtil.js +37 -0
  257. package/build/dist/Utils/Monitor/NetworkDeviceRoleUtil.js.map +1 -1
  258. package/build/dist/Utils/Monitor/NetworkTopologyUtil.js +25 -3
  259. package/build/dist/Utils/Monitor/NetworkTopologyUtil.js.map +1 -1
  260. package/build/dist/Utils/NetworkAutomation/RuleRunSummary.js +78 -0
  261. package/build/dist/Utils/NetworkAutomation/RuleRunSummary.js.map +1 -0
  262. package/package.json +1 -1
@@ -0,0 +1,450 @@
1
+ import HTTPResponse from "../../../../Types/API/HTTPResponse";
2
+ import { JSONObject } from "../../../../Types/JSON";
3
+ import LlmType from "../../../../Types/LLM/LlmType";
4
+ import Sleep from "../../../../Types/Sleep";
5
+ import API, { RequestOptions } from "../../../../Utils/API";
6
+ import LLMService, {
7
+ LLMCompletionResponse,
8
+ LLMProviderConfig,
9
+ } from "../../../../Server/Utils/LLM/LLMService";
10
+ import logger from "../../../../Server/Utils/Logger";
11
+ import stubLLMEgressGuard from "./StubLLMEgressGuard";
12
+ import {
13
+ afterEach,
14
+ beforeEach,
15
+ describe,
16
+ expect,
17
+ jest,
18
+ test,
19
+ } from "@jest/globals";
20
+ import axios, {
21
+ AxiosError,
22
+ AxiosHeaders,
23
+ AxiosResponse,
24
+ AxiosStatic,
25
+ } from "axios";
26
+
27
+ jest.mock("axios", () => {
28
+ return Object.assign(jest.fn(), jest.requireActual("axios"));
29
+ });
30
+
31
+ const mockedAxios: jest.MockedFunction<AxiosStatic> =
32
+ axios as unknown as jest.MockedFunction<AxiosStatic>;
33
+
34
+ interface ProviderCase {
35
+ name: string;
36
+ config: LLMProviderConfig;
37
+ response: JSONObject;
38
+ defaultTimeoutInMs: number;
39
+ expectedRetryDeadlineInMs: number;
40
+ }
41
+
42
+ const providerCases: Array<ProviderCase> = [
43
+ {
44
+ name: "OpenAI-compatible",
45
+ config: { llmType: LlmType.OpenAI, apiKey: "test-key", modelName: "gpt-x" },
46
+ response: {
47
+ choices: [{ message: { content: "ok" } }],
48
+ usage: { prompt_tokens: 1, completion_tokens: 1, total_tokens: 2 },
49
+ },
50
+ defaultTimeoutInMs: 120_000,
51
+ // max(5 minutes, 3 x 120s)
52
+ expectedRetryDeadlineInMs: 360_000,
53
+ },
54
+ {
55
+ name: "Azure OpenAI",
56
+ config: {
57
+ llmType: LlmType.AzureOpenAI,
58
+ apiKey: "test-key",
59
+ baseUrl:
60
+ "https://example.openai.azure.com/openai/deployments/test-deployment",
61
+ modelName: "gpt-x",
62
+ },
63
+ response: {
64
+ choices: [{ message: { content: "ok" } }],
65
+ usage: { prompt_tokens: 1, completion_tokens: 1, total_tokens: 2 },
66
+ },
67
+ defaultTimeoutInMs: 120_000,
68
+ expectedRetryDeadlineInMs: 360_000,
69
+ },
70
+ {
71
+ name: "Anthropic",
72
+ config: {
73
+ llmType: LlmType.Anthropic,
74
+ apiKey: "test-key",
75
+ modelName: "claude-x",
76
+ },
77
+ response: {
78
+ content: [{ type: "text", text: "ok" }],
79
+ stop_reason: "end_turn",
80
+ usage: { input_tokens: 1, output_tokens: 1 },
81
+ },
82
+ defaultTimeoutInMs: 120_000,
83
+ expectedRetryDeadlineInMs: 360_000,
84
+ },
85
+ {
86
+ name: "Ollama",
87
+ config: {
88
+ llmType: LlmType.Ollama,
89
+ baseUrl: "http://localhost:11434",
90
+ modelName: "llama-x",
91
+ },
92
+ response: {
93
+ message: { role: "assistant", content: "ok" },
94
+ prompt_eval_count: 1,
95
+ eval_count: 1,
96
+ },
97
+ defaultTimeoutInMs: 300_000,
98
+ /*
99
+ * A slow provider keeps room for three full-timeout attempts — the same
100
+ * as the two-retry policy this replaced, so nothing regressed for it.
101
+ */
102
+ expectedRetryDeadlineInMs: 900_000,
103
+ },
104
+ ];
105
+
106
+ type PostSpy = ReturnType<typeof jest.spyOn>;
107
+
108
+ function mockPost(response: JSONObject): PostSpy {
109
+ return jest.spyOn(API, "post").mockResolvedValue({
110
+ jsonData: response,
111
+ } as unknown as HTTPResponse<JSONObject>) as PostSpy;
112
+ }
113
+
114
+ function getPolicy(postSpy: PostSpy): RequestOptions {
115
+ return (postSpy.mock.calls[0]![0] as { options: RequestOptions }).options;
116
+ }
117
+
118
+ function createAxiosError(data: {
119
+ status?: number | undefined;
120
+ code?: string | undefined;
121
+ message?: string | undefined;
122
+ }): AxiosError {
123
+ const error: AxiosError = new Error(
124
+ data.message ?? "request failed",
125
+ ) as AxiosError;
126
+
127
+ error.isAxiosError = true;
128
+ error.name = "AxiosError";
129
+ error.config = { headers: new AxiosHeaders() };
130
+ error.toJSON = () => {
131
+ return {};
132
+ };
133
+
134
+ if (data.code) {
135
+ error.code = data.code;
136
+ }
137
+
138
+ if (data.status !== undefined) {
139
+ error.response = {
140
+ status: data.status,
141
+ statusText: "",
142
+ data: { error: { message: "provider said no" } },
143
+ headers: {},
144
+ config: { headers: new AxiosHeaders() },
145
+ } as unknown as AxiosResponse;
146
+ }
147
+
148
+ return error;
149
+ }
150
+
151
+ function createAxiosSuccess(data: JSONObject): AxiosResponse {
152
+ return {
153
+ data: data,
154
+ status: 200,
155
+ statusText: "OK",
156
+ headers: {},
157
+ config: { headers: new AxiosHeaders() },
158
+ } as unknown as AxiosResponse;
159
+ }
160
+
161
+ beforeEach(() => {
162
+ stubLLMEgressGuard();
163
+ LLMService.clearRequestAdaptationCache();
164
+
165
+ // Never actually sleep through a backoff ladder in a unit test.
166
+ jest.spyOn(Sleep, "sleep").mockImplementation(async () => {});
167
+ });
168
+
169
+ afterEach(() => {
170
+ mockedAxios.mockReset();
171
+ jest.restoreAllMocks();
172
+ });
173
+
174
+ describe("LLMService default retry policy", () => {
175
+ test.each(providerCases)(
176
+ "$name attempts a provider call ten times with capped, jittered backoff",
177
+ async ({ config, response, expectedRetryDeadlineInMs }: ProviderCase) => {
178
+ const postSpy: PostSpy = mockPost(response);
179
+
180
+ await LLMService.getCompletion({
181
+ llmProviderConfig: config,
182
+ messages: [{ role: "user", content: "hello" }],
183
+ });
184
+
185
+ expect(getPolicy(postSpy)).toEqual(
186
+ expect.objectContaining({
187
+ retries: 9,
188
+ exponentialBackoff: true,
189
+ maxBackoffInMs: LLMService.MAX_BACKOFF_IN_MS,
190
+ retryOnlyOnRetryableErrors: true,
191
+ totalTimeoutInMs: expectedRetryDeadlineInMs,
192
+ // The SSRF guard's settings must survive the policy merge.
193
+ doNotFollowRedirects: true,
194
+ }),
195
+ );
196
+ },
197
+ );
198
+
199
+ test("ten attempts means nine retries after the initial request", () => {
200
+ expect(LLMService.DEFAULT_REQUEST_ATTEMPTS).toBe(10);
201
+ });
202
+
203
+ test.each(providerCases)(
204
+ "$name still honours a caller that opts out of retries entirely",
205
+ async ({ config, response }: ProviderCase) => {
206
+ const postSpy: PostSpy = mockPost(response);
207
+
208
+ await LLMService.getCompletion({
209
+ llmProviderConfig: config,
210
+ messages: [{ role: "user", content: "hello" }],
211
+ requestRetries: 0,
212
+ });
213
+
214
+ expect(getPolicy(postSpy).retries).toBe(0);
215
+ // The rest of the policy is unchanged by the opt-out.
216
+ expect(getPolicy(postSpy).retryOnlyOnRetryableErrors).toBe(true);
217
+ expect(getPolicy(postSpy).exponentialBackoff).toBe(true);
218
+ },
219
+ );
220
+
221
+ test.each(providerCases)(
222
+ "$name honours an explicit retry count between the extremes",
223
+ async ({ config, response }: ProviderCase) => {
224
+ const postSpy: PostSpy = mockPost(response);
225
+
226
+ await LLMService.getCompletion({
227
+ llmProviderConfig: config,
228
+ messages: [{ role: "user", content: "hello" }],
229
+ requestRetries: 4,
230
+ });
231
+
232
+ expect(getPolicy(postSpy).retries).toBe(4);
233
+ },
234
+ );
235
+
236
+ test("the capped ladder costs about a minute of sleep, not seventeen", () => {
237
+ let totalInMs: number = 0;
238
+
239
+ for (let attempt: number = 0; attempt < 9; attempt++) {
240
+ totalInMs += Math.min(
241
+ 2 ** (attempt + 1) * 1000,
242
+ LLMService.MAX_BACKOFF_IN_MS,
243
+ );
244
+ }
245
+
246
+ expect(totalInMs).toBeLessThan(90_000);
247
+ });
248
+ });
249
+
250
+ describe("LLMService retry deadline", () => {
251
+ test.each(providerCases)(
252
+ "$name keeps room for three full-timeout attempts",
253
+ async ({
254
+ config,
255
+ response,
256
+ defaultTimeoutInMs,
257
+ expectedRetryDeadlineInMs,
258
+ }: ProviderCase) => {
259
+ const postSpy: PostSpy = mockPost(response);
260
+
261
+ await LLMService.getCompletion({
262
+ llmProviderConfig: config,
263
+ messages: [{ role: "user", content: "hello" }],
264
+ });
265
+
266
+ expect(getPolicy(postSpy).totalTimeoutInMs).toBe(
267
+ expectedRetryDeadlineInMs,
268
+ );
269
+ expect(getPolicy(postSpy).totalTimeoutInMs).toBeGreaterThanOrEqual(
270
+ defaultTimeoutInMs * 3,
271
+ );
272
+ },
273
+ );
274
+
275
+ test("a caller that lowers its per-attempt timeout keeps the five-minute floor", async () => {
276
+ const postSpy: PostSpy = mockPost(providerCases[0]!.response);
277
+
278
+ await LLMService.getCompletion({
279
+ llmProviderConfig: providerCases[0]!.config,
280
+ messages: [{ role: "user", content: "hello" }],
281
+ requestTimeoutInMs: 12_345,
282
+ });
283
+
284
+ expect(getPolicy(postSpy).timeout).toBe(12_345);
285
+ // 3 x 12.345s is well under the floor, so the floor wins.
286
+ expect(getPolicy(postSpy).totalTimeoutInMs).toBe(5 * 60 * 1000);
287
+ });
288
+
289
+ test("a caller that raises its per-attempt timeout raises the deadline with it", async () => {
290
+ const postSpy: PostSpy = mockPost(providerCases[0]!.response);
291
+
292
+ await LLMService.getCompletion({
293
+ llmProviderConfig: providerCases[0]!.config,
294
+ messages: [{ role: "user", content: "hello" }],
295
+ requestTimeoutInMs: 600_000,
296
+ });
297
+
298
+ expect(getPolicy(postSpy).totalTimeoutInMs).toBe(1_800_000);
299
+ });
300
+
301
+ test.each(providerCases)(
302
+ "$name lets a caller set the deadline outright",
303
+ async ({ config, response }: ProviderCase) => {
304
+ const postSpy: PostSpy = mockPost(response);
305
+
306
+ await LLMService.getCompletion({
307
+ llmProviderConfig: config,
308
+ messages: [{ role: "user", content: "hello" }],
309
+ requestRetryDeadlineInMs: 42_000,
310
+ });
311
+
312
+ expect(getPolicy(postSpy).totalTimeoutInMs).toBe(42_000);
313
+ },
314
+ );
315
+ });
316
+
317
+ /*
318
+ * The suites above check the policy LLMService hands to API. These drive the
319
+ * whole stack — LLMService through API through a mocked transport — so the
320
+ * policy is proven to actually produce the retries, not just to be requested.
321
+ */
322
+ describe("LLMService end to end over a failing transport", () => {
323
+ test("a chat completion survives a rate limit, a reset socket and a 502", async () => {
324
+ mockedAxios
325
+ .mockRejectedValueOnce(createAxiosError({ status: 429 }))
326
+ .mockRejectedValueOnce(
327
+ createAxiosError({ code: "ECONNRESET", message: "socket hang up" }),
328
+ )
329
+ .mockRejectedValueOnce(createAxiosError({ status: 502 }))
330
+ .mockResolvedValueOnce(
331
+ createAxiosSuccess({
332
+ choices: [{ message: { content: "recovered answer" } }],
333
+ usage: { prompt_tokens: 3, completion_tokens: 4, total_tokens: 7 },
334
+ }),
335
+ );
336
+
337
+ const completion: LLMCompletionResponse = await LLMService.getCompletion({
338
+ llmProviderConfig: providerCases[0]!.config,
339
+ messages: [{ role: "user", content: "how do we optimize oneuptime?" }],
340
+ });
341
+
342
+ expect(mockedAxios).toHaveBeenCalledTimes(4);
343
+ expect(completion.content).toBe("recovered answer");
344
+ expect(completion.usage?.totalTokens).toBe(7);
345
+ });
346
+
347
+ test("the timeout that used to end a chat turn now recovers on a later attempt", async () => {
348
+ const timeout: AxiosError = createAxiosError({
349
+ code: "ECONNABORTED",
350
+ message: "timeout of 120000ms exceeded",
351
+ });
352
+
353
+ for (let attempt: number = 0; attempt < 8; attempt++) {
354
+ mockedAxios.mockRejectedValueOnce(timeout);
355
+ }
356
+
357
+ mockedAxios.mockResolvedValueOnce(
358
+ createAxiosSuccess({
359
+ choices: [{ message: { content: "answered on the ninth attempt" } }],
360
+ usage: { prompt_tokens: 1, completion_tokens: 1, total_tokens: 2 },
361
+ }),
362
+ );
363
+
364
+ const completion: LLMCompletionResponse = await LLMService.getCompletion({
365
+ llmProviderConfig: providerCases[0]!.config,
366
+ messages: [{ role: "user", content: "hello" }],
367
+ // Fast attempts, so the wall-clock budget is not what stops the ladder.
368
+ requestTimeoutInMs: 100,
369
+ });
370
+
371
+ expect(mockedAxios).toHaveBeenCalledTimes(9);
372
+ expect(completion.content).toBe("answered on the ninth attempt");
373
+ });
374
+
375
+ test("a misconfigured provider fails on the first attempt instead of after ten", async () => {
376
+ jest.spyOn(logger, "error").mockImplementation((): void => {});
377
+
378
+ mockedAxios.mockRejectedValue(createAxiosError({ status: 401 }));
379
+
380
+ await expect(
381
+ LLMService.getCompletion({
382
+ llmProviderConfig: providerCases[0]!.config,
383
+ messages: [{ role: "user", content: "hello" }],
384
+ }),
385
+ ).rejects.toThrow();
386
+
387
+ expect(mockedAxios).toHaveBeenCalledTimes(1);
388
+ });
389
+
390
+ test("an unrecoverable provider is reported after the full budget, not silently", async () => {
391
+ jest.spyOn(logger, "error").mockImplementation((): void => {});
392
+
393
+ mockedAxios.mockRejectedValue(createAxiosError({ status: 503 }));
394
+
395
+ await expect(
396
+ LLMService.getCompletion({
397
+ llmProviderConfig: providerCases[0]!.config,
398
+ messages: [{ role: "user", content: "hello" }],
399
+ requestTimeoutInMs: 100,
400
+ }),
401
+ ).rejects.toThrow();
402
+
403
+ expect(mockedAxios).toHaveBeenCalledTimes(10);
404
+ });
405
+
406
+ test("parameter adaptation costs one attempt per reshape, not a whole ladder", async () => {
407
+ /*
408
+ * A reasoning model rejecting max_tokens is a 400: not retryable, so the
409
+ * adaptation loop pays a single upload to learn it rather than ten.
410
+ */
411
+ mockedAxios.mockImplementation(async (config: unknown) => {
412
+ const body: JSONObject = (config as { data: JSONObject }).data;
413
+
414
+ if (body["max_tokens"] !== undefined) {
415
+ throw createAxiosError({ status: 400 });
416
+ }
417
+
418
+ return createAxiosSuccess({
419
+ choices: [{ message: { content: "adapted" } }],
420
+ usage: { prompt_tokens: 1, completion_tokens: 1, total_tokens: 2 },
421
+ });
422
+ });
423
+
424
+ /*
425
+ * axios errors carry the provider payload on error.response.data, which is
426
+ * where getRequestAdaptationForError reads the offending parameter from.
427
+ */
428
+ mockedAxios.mockImplementationOnce(async () => {
429
+ const error: AxiosError = createAxiosError({ status: 400 });
430
+ (error.response as AxiosResponse).data = {
431
+ error: {
432
+ param: "max_tokens",
433
+ code: "unsupported_parameter",
434
+ message: "Use 'max_completion_tokens' instead.",
435
+ },
436
+ };
437
+ throw error;
438
+ });
439
+
440
+ const completion: LLMCompletionResponse = await LLMService.getCompletion({
441
+ llmProviderConfig: providerCases[0]!.config,
442
+ messages: [{ role: "user", content: "hello" }],
443
+ maxTokens: 256,
444
+ });
445
+
446
+ expect(completion.content).toBe("adapted");
447
+ // One rejected attempt plus the reshaped one that succeeded.
448
+ expect(mockedAxios).toHaveBeenCalledTimes(2);
449
+ });
450
+ });