@oneuptime/common 11.3.25 → 11.3.28

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 (397) hide show
  1. package/Models/DatabaseModels/AIConversation.ts +322 -0
  2. package/Models/DatabaseModels/AIConversationMessage.ts +495 -0
  3. package/Models/DatabaseModels/AIRun.ts +584 -0
  4. package/Models/DatabaseModels/AIRunEvent.ts +443 -0
  5. package/Models/DatabaseModels/IncidentEpisodeRoleMember.ts +1 -1
  6. package/Models/DatabaseModels/IncidentMember.ts +1 -1
  7. package/Models/DatabaseModels/IncomingCallPolicy.ts +13 -0
  8. package/Models/DatabaseModels/Index.ts +11 -0
  9. package/Models/DatabaseModels/LlmLog.ts +26 -0
  10. package/Models/DatabaseModels/LlmProvider.ts +2 -2
  11. package/Models/DatabaseModels/MigrationFailure.ts +170 -0
  12. package/Models/DatabaseModels/OnCallDutyPolicyExecutionLog.ts +51 -2
  13. package/Models/DatabaseModels/OnCallDutyPolicySchedule.ts +44 -0
  14. package/Models/DatabaseModels/OnCallDutyPolicyScheduleLayer.ts +6 -0
  15. package/Models/DatabaseModels/OnCallDutyPolicyScheduleLayerUser.ts +6 -0
  16. package/Models/DatabaseModels/OnCallDutyPolicyTimeLog.ts +1 -1
  17. package/Models/DatabaseModels/OnCallDutyPolicyUserOverride.ts +2 -2
  18. package/Server/API/AIChatAPI.ts +693 -0
  19. package/Server/API/AlertAPI.ts +1 -1
  20. package/Server/API/IncidentAPI.ts +2 -2
  21. package/Server/API/IncidentEpisodeAPI.ts +1 -1
  22. package/Server/API/LlmProviderAPI.ts +170 -0
  23. package/Server/API/MicrosoftTeamsAPI.ts +5 -0
  24. package/Server/API/ScheduledMaintenanceAPI.ts +1 -1
  25. package/Server/API/SlackAPI.ts +663 -0
  26. package/Server/Infrastructure/Postgres/SchemaMigrations/1783363279075-AddAIChatModels.ts +245 -0
  27. package/Server/Infrastructure/Postgres/SchemaMigrations/1783443471795-AddLlmProviderToAIConversation.ts +36 -0
  28. package/Server/Infrastructure/Postgres/SchemaMigrations/1783453297388-AddAIChatWriteActionsAndWidgets.ts +46 -0
  29. package/Server/Infrastructure/Postgres/SchemaMigrations/1783461767405-MigrationName.ts +119 -0
  30. package/Server/Infrastructure/Postgres/SchemaMigrations/1783470000000-AddIncomingCallPolicyRoutingPhoneNumberUnique.ts +25 -0
  31. package/Server/Infrastructure/Postgres/SchemaMigrations/1783510935686-FixNotNullForeignKeysOnDelete.ts +81 -0
  32. package/Server/Infrastructure/Postgres/SchemaMigrations/1783515836148-MigrationName.ts +25 -0
  33. package/Server/Infrastructure/Postgres/SchemaMigrations/1783523076215-AddMigrationFailureTable.ts +35 -0
  34. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +16 -0
  35. package/Server/Infrastructure/PostgresDatabase.ts +11 -0
  36. package/Server/Services/AIConversationMessageService.ts +39 -0
  37. package/Server/Services/AIConversationService.ts +63 -0
  38. package/Server/Services/AIRunEventService.ts +39 -0
  39. package/Server/Services/AIRunService.ts +39 -0
  40. package/Server/Services/AIService.ts +127 -33
  41. package/Server/Services/AnalyticsDatabaseService.ts +10 -1
  42. package/Server/Services/DatabaseService.ts +29 -1
  43. package/Server/Services/IncidentService.ts +1 -1
  44. package/Server/Services/IncomingCallPolicyEscalationRuleService.ts +75 -2
  45. package/Server/Services/IncomingCallPolicyService.ts +42 -1
  46. package/Server/Services/Index.ts +2 -0
  47. package/Server/Services/LlmProviderService.ts +110 -0
  48. package/Server/Services/MigrationFailureService.ts +9 -0
  49. package/Server/Services/MonitorProbeService.ts +33 -7
  50. package/Server/Services/MonitorService.ts +19 -7
  51. package/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.ts +65 -0
  52. package/Server/Services/OnCallDutyPolicyEscalationRuleService.ts +185 -41
  53. package/Server/Services/OnCallDutyPolicyEscalationRuleUserService.ts +23 -15
  54. package/Server/Services/OnCallDutyPolicyExecutionLogService.ts +39 -0
  55. package/Server/Services/OnCallDutyPolicyScheduleLayerService.ts +148 -0
  56. package/Server/Services/OnCallDutyPolicyScheduleLayerUserService.ts +11 -2
  57. package/Server/Services/OnCallDutyPolicyScheduleService.ts +502 -26
  58. package/Server/Services/OnCallDutyPolicyTimeLogService.ts +49 -7
  59. package/Server/Services/OnCallDutyPolicyUserOverrideService.ts +218 -4
  60. package/Server/Services/ProjectCallSMSConfigService.ts +82 -1
  61. package/Server/Services/ProjectService.ts +24 -0
  62. package/Server/Services/TeamMemberService.ts +6 -0
  63. package/Server/Services/UserNotificationRuleService.ts +13 -42
  64. package/Server/Services/UserOnCallLogService.ts +90 -0
  65. package/Server/Services/UserOnCallLogTimelineService.ts +43 -1
  66. package/Server/Types/AnalyticsDatabase/ModelPermission.ts +74 -9
  67. package/Server/Types/Database/QueryUtil.ts +57 -0
  68. package/Server/Utils/AI/AIChatPrivacyFilter.ts +28 -0
  69. package/Server/Utils/AI/AlertAIContextBuilder.ts +6 -1
  70. package/Server/Utils/AI/Chat/ChatAgentRunner.ts +1066 -0
  71. package/Server/Utils/AI/Chat/ObservabilityAssistant.ts +239 -0
  72. package/Server/Utils/AI/Chat/ObservabilityChatPrompt.ts +51 -0
  73. package/Server/Utils/AI/IncidentAIContextBuilder.ts +18 -3
  74. package/Server/Utils/AI/IncidentEpisodeAIContextBuilder.ts +6 -1
  75. package/Server/Utils/AI/ScheduledMaintenanceAIContextBuilder.ts +12 -2
  76. package/Server/Utils/AI/Toolbox/AlertTools.ts +201 -0
  77. package/Server/Utils/AI/Toolbox/AlertWriteTools.ts +174 -0
  78. package/Server/Utils/AI/Toolbox/ContextTools.ts +189 -0
  79. package/Server/Utils/AI/Toolbox/ExceptionTools.ts +149 -0
  80. package/Server/Utils/AI/Toolbox/IncidentTools.ts +386 -0
  81. package/Server/Utils/AI/Toolbox/IncidentWriteTools.ts +350 -0
  82. package/Server/Utils/AI/Toolbox/Index.ts +231 -0
  83. package/Server/Utils/AI/Toolbox/LogTools.ts +339 -0
  84. package/Server/Utils/AI/Toolbox/MetricTools.ts +179 -0
  85. package/Server/Utils/AI/Toolbox/MonitorTools.ts +193 -0
  86. package/Server/Utils/AI/Toolbox/RecentChangesTools.ts +208 -0
  87. package/Server/Utils/AI/Toolbox/Serializer.ts +299 -0
  88. package/Server/Utils/AI/Toolbox/ToolTypes.ts +249 -0
  89. package/Server/Utils/AI/Toolbox/TraceTools.ts +402 -0
  90. package/Server/Utils/AI/Toolbox/WidgetBuilder.ts +189 -0
  91. package/Server/Utils/Database/MigrationFailureLog.ts +240 -0
  92. package/Server/Utils/IncomingCallPhoneNumber.ts +41 -0
  93. package/Server/Utils/LLM/LLMService.ts +605 -96
  94. package/Server/Utils/Telemetry/IoTSnapshotScan.ts +12 -9
  95. package/Server/Utils/Telemetry/ProxmoxCephSnapshotScan.ts +12 -9
  96. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +512 -19
  97. package/Server/Utils/Workspace/Slack/Slack.ts +80 -0
  98. package/Server/Utils/Workspace/Slack/app-manifest.json +16 -1
  99. package/Tests/Server/Services/AnalyticsDatabaseService.test.ts +0 -8
  100. package/Tests/Server/Services/OnCallDutyPolicyScheduleLayerUserReorder.test.ts +233 -0
  101. package/Tests/Server/Services/OnCallDutyPolicyTimeLogServiceScoping.test.ts +242 -0
  102. package/Tests/Server/Services/OnCallDutyPolicyUserOverrideEdit.test.ts +198 -0
  103. package/Tests/Server/Services/UserOnCallLogClaimNotificationRule.test.ts +495 -0
  104. package/Tests/Server/Types/Database/QueryUtil.test.ts +74 -0
  105. package/Tests/Server/Utils/AI/AIChatModelACL.test.ts +42 -0
  106. package/Tests/Server/Utils/AI/ChatAgentHelpers.test.ts +89 -0
  107. package/Tests/Server/Utils/AI/LLMServiceBaseUrl.test.ts +139 -0
  108. package/Tests/Server/Utils/AI/LLMServiceToolCalling.test.ts +386 -0
  109. package/Tests/Server/Utils/AI/ToolArgsGetTimeRange.test.ts +62 -0
  110. package/Tests/Server/Utils/AI/ToolArgsScopeServiceIds.test.ts +79 -0
  111. package/Tests/Server/Utils/AI/ToolResultSerializer.test.ts +223 -0
  112. package/Tests/Types/Billing/SubscriptionStatus.test.ts +127 -0
  113. package/Tests/Types/DateExhaustiveTimezone.test.ts +700 -0
  114. package/Tests/Types/DateTimezoneWallClock.test.ts +99 -0
  115. package/Tests/Types/Events/Recurring.test.ts +22 -9
  116. package/Tests/Types/Metrics/RecordingRuleDefinition.test.ts +213 -0
  117. package/Tests/Types/OnCallDutyPolicy/LayerUtilAuditFixes.test.ts +291 -0
  118. package/Tests/Types/OnCallDutyPolicy/LayerUtilAuditFixesRound2.test.ts +398 -0
  119. package/Tests/Types/OnCallDutyPolicy/LayerUtilDSTWeekendGap.test.ts +114 -0
  120. package/Tests/Types/OnCallDutyPolicy/LayerUtilDSTWeeklyDayShift.test.ts +76 -0
  121. package/Tests/Types/OnCallDutyPolicy/LayerUtilDailyMutation.test.ts +128 -0
  122. package/Tests/Types/OnCallDutyPolicy/LayerUtilDailyProbe.test.ts +203 -0
  123. package/Tests/Types/OnCallDutyPolicy/LayerUtilDiffFuzz.test.ts +322 -0
  124. package/Tests/Types/OnCallDutyPolicy/LayerUtilEdgeCases.test.ts +280 -0
  125. package/Tests/Types/OnCallDutyPolicy/LayerUtilExhaustiveCurrentUser.test.ts +863 -0
  126. package/Tests/Types/OnCallDutyPolicy/LayerUtilExhaustiveDaily.test.ts +900 -0
  127. package/Tests/Types/OnCallDutyPolicy/LayerUtilExhaustiveMultiLayer.test.ts +1104 -0
  128. package/Tests/Types/OnCallDutyPolicy/LayerUtilExhaustiveRotation.test.ts +957 -0
  129. package/Tests/Types/OnCallDutyPolicy/LayerUtilExhaustiveWeekly.test.ts +1095 -0
  130. package/Tests/Types/OnCallDutyPolicy/LayerUtilInvariants.test.ts +232 -0
  131. package/Tests/Types/OnCallDutyPolicy/LayerUtilMergeAudit.test.ts +227 -0
  132. package/Tests/Types/OnCallDutyPolicy/LayerUtilMonthYearAudit.test.ts +255 -0
  133. package/Tests/Types/OnCallDutyPolicy/LayerUtilMultiLayerAudit.test.ts +388 -0
  134. package/Tests/Types/OnCallDutyPolicy/LayerUtilMultiLayerEdge.test.ts +300 -0
  135. package/Tests/Types/OnCallDutyPolicy/LayerUtilOvernightDSTAudit.test.ts +158 -0
  136. package/Tests/Types/OnCallDutyPolicy/LayerUtilRestrictedGapFix.test.ts +253 -0
  137. package/Tests/Types/OnCallDutyPolicy/LayerUtilRestrictedGapRepro.test.ts +157 -0
  138. package/Tests/Types/OnCallDutyPolicy/LayerUtilRotationFixes.test.ts +414 -0
  139. package/Tests/Types/OnCallDutyPolicy/LayerUtilTimezone.test.ts +243 -0
  140. package/Tests/Types/OnCallDutyPolicy/LayerUtilWeekendGapRepro.test.ts +112 -0
  141. package/Tests/Types/OnCallDutyPolicy/OverrideLensAudit.test.ts +117 -0
  142. package/Tests/Types/OnCallDutyPolicy/RestrictionTimes.test.ts +8 -6
  143. package/Tests/Types/OnCallDutyPolicy/RestrictionTimesDefaultDayAudit.test.ts +112 -0
  144. package/Tests/Types/OnCallDutyPolicy/RestrictionTimesExhaustive.test.ts +821 -0
  145. package/Tests/Types/OnCallDutyPolicy/RestrictionTimesMutationInvariant.test.ts +886 -0
  146. package/Tests/Types/OnCallDutyPolicy/UserOverrideUtil.test.ts +255 -0
  147. package/Tests/Types/OnCallDutyPolicy/UserOverrideUtilExhaustive.test.ts +1126 -0
  148. package/Types/AI/AIChatMessageRole.ts +6 -0
  149. package/Types/AI/AIChatMessageStatus.ts +32 -0
  150. package/Types/AI/AIChatPermissionMode.ts +69 -0
  151. package/Types/AI/AIChatTypes.ts +231 -0
  152. package/Types/AI/AIRunEventType.ts +17 -0
  153. package/Types/AI/AIRunStatus.ts +28 -0
  154. package/Types/AI/AIRunType.ts +6 -0
  155. package/Types/Date.ts +200 -15
  156. package/Types/LLM/LlmType.ts +6 -0
  157. package/Types/OnCallDutyPolicy/Layer.ts +790 -260
  158. package/Types/OnCallDutyPolicy/RestrictionTimes.ts +39 -13
  159. package/Types/OnCallDutyPolicy/UserOverrideUtil.ts +21 -5
  160. package/UI/Components/Events/RecurringFieldElement.tsx +16 -0
  161. package/UI/Components/Header/HeaderIconDropdownButton.tsx +22 -12
  162. package/UI/Components/Markdown.tsx/MarkdownViewer.tsx +35 -3
  163. package/UI/Components/ModelFormModal/ModelFormModal.tsx +8 -0
  164. package/UI/Components/Page/Page.tsx +14 -0
  165. package/UI/Components/RadioButtons/BasicRadioButtons.tsx +1 -1
  166. package/UI/Utils/LlmTypeDropdownOptions.ts +40 -0
  167. package/UI/Utils/TestLLMProvider.ts +59 -0
  168. package/build/dist/Models/DatabaseModels/AIConversation.js +345 -0
  169. package/build/dist/Models/DatabaseModels/AIConversation.js.map +1 -0
  170. package/build/dist/Models/DatabaseModels/AIConversationMessage.js +521 -0
  171. package/build/dist/Models/DatabaseModels/AIConversationMessage.js.map +1 -0
  172. package/build/dist/Models/DatabaseModels/AIRun.js +619 -0
  173. package/build/dist/Models/DatabaseModels/AIRun.js.map +1 -0
  174. package/build/dist/Models/DatabaseModels/AIRunEvent.js +469 -0
  175. package/build/dist/Models/DatabaseModels/AIRunEvent.js.map +1 -0
  176. package/build/dist/Models/DatabaseModels/IncidentEpisodeRoleMember.js +1 -1
  177. package/build/dist/Models/DatabaseModels/IncidentEpisodeRoleMember.js.map +1 -1
  178. package/build/dist/Models/DatabaseModels/IncidentMember.js +1 -1
  179. package/build/dist/Models/DatabaseModels/IncidentMember.js.map +1 -1
  180. package/build/dist/Models/DatabaseModels/IncomingCallPolicy.js +10 -0
  181. package/build/dist/Models/DatabaseModels/IncomingCallPolicy.js.map +1 -1
  182. package/build/dist/Models/DatabaseModels/Index.js +10 -0
  183. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  184. package/build/dist/Models/DatabaseModels/LlmLog.js +28 -0
  185. package/build/dist/Models/DatabaseModels/LlmLog.js.map +1 -1
  186. package/build/dist/Models/DatabaseModels/LlmProvider.js +2 -2
  187. package/build/dist/Models/DatabaseModels/LlmProvider.js.map +1 -1
  188. package/build/dist/Models/DatabaseModels/MigrationFailure.js +196 -0
  189. package/build/dist/Models/DatabaseModels/MigrationFailure.js.map +1 -0
  190. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyExecutionLog.js +52 -2
  191. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyExecutionLog.js.map +1 -1
  192. package/build/dist/Models/DatabaseModels/OnCallDutyPolicySchedule.js +45 -0
  193. package/build/dist/Models/DatabaseModels/OnCallDutyPolicySchedule.js.map +1 -1
  194. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyScheduleLayer.js +6 -0
  195. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyScheduleLayer.js.map +1 -1
  196. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyScheduleLayerUser.js +6 -0
  197. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyScheduleLayerUser.js.map +1 -1
  198. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyTimeLog.js +1 -1
  199. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyTimeLog.js.map +1 -1
  200. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyUserOverride.js +2 -2
  201. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyUserOverride.js.map +1 -1
  202. package/build/dist/Server/API/AIChatAPI.js +498 -0
  203. package/build/dist/Server/API/AIChatAPI.js.map +1 -0
  204. package/build/dist/Server/API/AlertAPI.js +1 -1
  205. package/build/dist/Server/API/IncidentAPI.js +2 -2
  206. package/build/dist/Server/API/IncidentEpisodeAPI.js +1 -1
  207. package/build/dist/Server/API/LlmProviderAPI.js +108 -1
  208. package/build/dist/Server/API/LlmProviderAPI.js.map +1 -1
  209. package/build/dist/Server/API/MicrosoftTeamsAPI.js +4 -0
  210. package/build/dist/Server/API/MicrosoftTeamsAPI.js.map +1 -1
  211. package/build/dist/Server/API/ScheduledMaintenanceAPI.js +1 -1
  212. package/build/dist/Server/API/SlackAPI.js +442 -0
  213. package/build/dist/Server/API/SlackAPI.js.map +1 -1
  214. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783363279075-AddAIChatModels.js +96 -0
  215. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783363279075-AddAIChatModels.js.map +1 -0
  216. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783443471795-AddLlmProviderToAIConversation.js +25 -0
  217. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783443471795-AddLlmProviderToAIConversation.js.map +1 -0
  218. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783453297388-AddAIChatWriteActionsAndWidgets.js +31 -0
  219. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783453297388-AddAIChatWriteActionsAndWidgets.js.map +1 -0
  220. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783461767405-MigrationName.js +46 -0
  221. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783461767405-MigrationName.js.map +1 -0
  222. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783470000000-AddIncomingCallPolicyRoutingPhoneNumberUnique.js +18 -0
  223. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783470000000-AddIncomingCallPolicyRoutingPhoneNumberUnique.js.map +1 -0
  224. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783510935686-FixNotNullForeignKeysOnDelete.js +38 -0
  225. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783510935686-FixNotNullForeignKeysOnDelete.js.map +1 -0
  226. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783515836148-MigrationName.js +16 -0
  227. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783515836148-MigrationName.js.map +1 -0
  228. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783523076215-AddMigrationFailureTable.js +18 -0
  229. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783523076215-AddMigrationFailureTable.js.map +1 -0
  230. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +16 -0
  231. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  232. package/build/dist/Server/Infrastructure/PostgresDatabase.js +9 -0
  233. package/build/dist/Server/Infrastructure/PostgresDatabase.js.map +1 -1
  234. package/build/dist/Server/Services/AIConversationMessageService.js +34 -0
  235. package/build/dist/Server/Services/AIConversationMessageService.js.map +1 -0
  236. package/build/dist/Server/Services/AIConversationService.js +42 -0
  237. package/build/dist/Server/Services/AIConversationService.js.map +1 -0
  238. package/build/dist/Server/Services/AIRunEventService.js +34 -0
  239. package/build/dist/Server/Services/AIRunEventService.js.map +1 -0
  240. package/build/dist/Server/Services/AIRunService.js +34 -0
  241. package/build/dist/Server/Services/AIRunService.js.map +1 -0
  242. package/build/dist/Server/Services/AIService.js +84 -25
  243. package/build/dist/Server/Services/AIService.js.map +1 -1
  244. package/build/dist/Server/Services/AnalyticsDatabaseService.js +10 -1
  245. package/build/dist/Server/Services/AnalyticsDatabaseService.js.map +1 -1
  246. package/build/dist/Server/Services/DatabaseService.js +20 -1
  247. package/build/dist/Server/Services/DatabaseService.js.map +1 -1
  248. package/build/dist/Server/Services/IncidentService.js +1 -1
  249. package/build/dist/Server/Services/IncomingCallPolicyEscalationRuleService.js +55 -2
  250. package/build/dist/Server/Services/IncomingCallPolicyEscalationRuleService.js.map +1 -1
  251. package/build/dist/Server/Services/IncomingCallPolicyService.js +40 -0
  252. package/build/dist/Server/Services/IncomingCallPolicyService.js.map +1 -1
  253. package/build/dist/Server/Services/Index.js +2 -0
  254. package/build/dist/Server/Services/Index.js.map +1 -1
  255. package/build/dist/Server/Services/LlmProviderService.js +108 -0
  256. package/build/dist/Server/Services/LlmProviderService.js.map +1 -1
  257. package/build/dist/Server/Services/MigrationFailureService.js +9 -0
  258. package/build/dist/Server/Services/MigrationFailureService.js.map +1 -0
  259. package/build/dist/Server/Services/MonitorProbeService.js +33 -7
  260. package/build/dist/Server/Services/MonitorProbeService.js.map +1 -1
  261. package/build/dist/Server/Services/MonitorService.js +12 -5
  262. package/build/dist/Server/Services/MonitorService.js.map +1 -1
  263. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.js +81 -22
  264. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.js.map +1 -1
  265. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleService.js +134 -31
  266. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleService.js.map +1 -1
  267. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleUserService.js +21 -13
  268. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleUserService.js.map +1 -1
  269. package/build/dist/Server/Services/OnCallDutyPolicyExecutionLogService.js +36 -0
  270. package/build/dist/Server/Services/OnCallDutyPolicyExecutionLogService.js.map +1 -1
  271. package/build/dist/Server/Services/OnCallDutyPolicyScheduleLayerService.js +121 -0
  272. package/build/dist/Server/Services/OnCallDutyPolicyScheduleLayerService.js.map +1 -1
  273. package/build/dist/Server/Services/OnCallDutyPolicyScheduleLayerUserService.js +11 -2
  274. package/build/dist/Server/Services/OnCallDutyPolicyScheduleLayerUserService.js.map +1 -1
  275. package/build/dist/Server/Services/OnCallDutyPolicyScheduleService.js +409 -36
  276. package/build/dist/Server/Services/OnCallDutyPolicyScheduleService.js.map +1 -1
  277. package/build/dist/Server/Services/OnCallDutyPolicyTimeLogService.js +52 -8
  278. package/build/dist/Server/Services/OnCallDutyPolicyTimeLogService.js.map +1 -1
  279. package/build/dist/Server/Services/OnCallDutyPolicyUserOverrideService.js +170 -3
  280. package/build/dist/Server/Services/OnCallDutyPolicyUserOverrideService.js.map +1 -1
  281. package/build/dist/Server/Services/ProjectCallSMSConfigService.js +72 -0
  282. package/build/dist/Server/Services/ProjectCallSMSConfigService.js.map +1 -1
  283. package/build/dist/Server/Services/ProjectService.js +25 -0
  284. package/build/dist/Server/Services/ProjectService.js.map +1 -1
  285. package/build/dist/Server/Services/TeamMemberService.js +6 -0
  286. package/build/dist/Server/Services/TeamMemberService.js.map +1 -1
  287. package/build/dist/Server/Services/UserNotificationRuleService.js +12 -30
  288. package/build/dist/Server/Services/UserNotificationRuleService.js.map +1 -1
  289. package/build/dist/Server/Services/UserOnCallLogService.js +75 -0
  290. package/build/dist/Server/Services/UserOnCallLogService.js.map +1 -1
  291. package/build/dist/Server/Services/UserOnCallLogTimelineService.js +31 -1
  292. package/build/dist/Server/Services/UserOnCallLogTimelineService.js.map +1 -1
  293. package/build/dist/Server/Types/AnalyticsDatabase/ModelPermission.js +50 -7
  294. package/build/dist/Server/Types/AnalyticsDatabase/ModelPermission.js.map +1 -1
  295. package/build/dist/Server/Types/Database/QueryUtil.js +45 -0
  296. package/build/dist/Server/Types/Database/QueryUtil.js.map +1 -1
  297. package/build/dist/Server/Utils/AI/AIChatPrivacyFilter.js +18 -0
  298. package/build/dist/Server/Utils/AI/AIChatPrivacyFilter.js.map +1 -0
  299. package/build/dist/Server/Utils/AI/AlertAIContextBuilder.js +6 -1
  300. package/build/dist/Server/Utils/AI/AlertAIContextBuilder.js.map +1 -1
  301. package/build/dist/Server/Utils/AI/Chat/ChatAgentRunner.js +756 -0
  302. package/build/dist/Server/Utils/AI/Chat/ChatAgentRunner.js.map +1 -0
  303. package/build/dist/Server/Utils/AI/Chat/ObservabilityAssistant.js +165 -0
  304. package/build/dist/Server/Utils/AI/Chat/ObservabilityAssistant.js.map +1 -0
  305. package/build/dist/Server/Utils/AI/Chat/ObservabilityChatPrompt.js +44 -0
  306. package/build/dist/Server/Utils/AI/Chat/ObservabilityChatPrompt.js.map +1 -0
  307. package/build/dist/Server/Utils/AI/IncidentAIContextBuilder.js +18 -3
  308. package/build/dist/Server/Utils/AI/IncidentAIContextBuilder.js.map +1 -1
  309. package/build/dist/Server/Utils/AI/IncidentEpisodeAIContextBuilder.js +6 -1
  310. package/build/dist/Server/Utils/AI/IncidentEpisodeAIContextBuilder.js.map +1 -1
  311. package/build/dist/Server/Utils/AI/ScheduledMaintenanceAIContextBuilder.js +12 -2
  312. package/build/dist/Server/Utils/AI/ScheduledMaintenanceAIContextBuilder.js.map +1 -1
  313. package/build/dist/Server/Utils/AI/Toolbox/AlertTools.js +167 -0
  314. package/build/dist/Server/Utils/AI/Toolbox/AlertTools.js.map +1 -0
  315. package/build/dist/Server/Utils/AI/Toolbox/AlertWriteTools.js +136 -0
  316. package/build/dist/Server/Utils/AI/Toolbox/AlertWriteTools.js.map +1 -0
  317. package/build/dist/Server/Utils/AI/Toolbox/ContextTools.js +141 -0
  318. package/build/dist/Server/Utils/AI/Toolbox/ContextTools.js.map +1 -0
  319. package/build/dist/Server/Utils/AI/Toolbox/ExceptionTools.js +117 -0
  320. package/build/dist/Server/Utils/AI/Toolbox/ExceptionTools.js.map +1 -0
  321. package/build/dist/Server/Utils/AI/Toolbox/IncidentTools.js +318 -0
  322. package/build/dist/Server/Utils/AI/Toolbox/IncidentTools.js.map +1 -0
  323. package/build/dist/Server/Utils/AI/Toolbox/IncidentWriteTools.js +280 -0
  324. package/build/dist/Server/Utils/AI/Toolbox/IncidentWriteTools.js.map +1 -0
  325. package/build/dist/Server/Utils/AI/Toolbox/Index.js +153 -0
  326. package/build/dist/Server/Utils/AI/Toolbox/Index.js.map +1 -0
  327. package/build/dist/Server/Utils/AI/Toolbox/LogTools.js +246 -0
  328. package/build/dist/Server/Utils/AI/Toolbox/LogTools.js.map +1 -0
  329. package/build/dist/Server/Utils/AI/Toolbox/MetricTools.js +120 -0
  330. package/build/dist/Server/Utils/AI/Toolbox/MetricTools.js.map +1 -0
  331. package/build/dist/Server/Utils/AI/Toolbox/MonitorTools.js +158 -0
  332. package/build/dist/Server/Utils/AI/Toolbox/MonitorTools.js.map +1 -0
  333. package/build/dist/Server/Utils/AI/Toolbox/RecentChangesTools.js +165 -0
  334. package/build/dist/Server/Utils/AI/Toolbox/RecentChangesTools.js.map +1 -0
  335. package/build/dist/Server/Utils/AI/Toolbox/Serializer.js +228 -0
  336. package/build/dist/Server/Utils/AI/Toolbox/Serializer.js.map +1 -0
  337. package/build/dist/Server/Utils/AI/Toolbox/ToolTypes.js +142 -0
  338. package/build/dist/Server/Utils/AI/Toolbox/ToolTypes.js.map +1 -0
  339. package/build/dist/Server/Utils/AI/Toolbox/TraceTools.js +309 -0
  340. package/build/dist/Server/Utils/AI/Toolbox/TraceTools.js.map +1 -0
  341. package/build/dist/Server/Utils/AI/Toolbox/WidgetBuilder.js +120 -0
  342. package/build/dist/Server/Utils/AI/Toolbox/WidgetBuilder.js.map +1 -0
  343. package/build/dist/Server/Utils/Database/MigrationFailureLog.js +174 -0
  344. package/build/dist/Server/Utils/Database/MigrationFailureLog.js.map +1 -0
  345. package/build/dist/Server/Utils/IncomingCallPhoneNumber.js +30 -0
  346. package/build/dist/Server/Utils/IncomingCallPhoneNumber.js.map +1 -0
  347. package/build/dist/Server/Utils/LLM/LLMService.js +452 -92
  348. package/build/dist/Server/Utils/LLM/LLMService.js.map +1 -1
  349. package/build/dist/Server/Utils/Telemetry/IoTSnapshotScan.js +11 -8
  350. package/build/dist/Server/Utils/Telemetry/IoTSnapshotScan.js.map +1 -1
  351. package/build/dist/Server/Utils/Telemetry/ProxmoxCephSnapshotScan.js +11 -8
  352. package/build/dist/Server/Utils/Telemetry/ProxmoxCephSnapshotScan.js.map +1 -1
  353. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +378 -15
  354. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
  355. package/build/dist/Server/Utils/Workspace/Slack/Slack.js +59 -0
  356. package/build/dist/Server/Utils/Workspace/Slack/Slack.js.map +1 -1
  357. package/build/dist/Server/Utils/Workspace/Slack/app-manifest.json +16 -1
  358. package/build/dist/Types/AI/AIChatMessageRole.js +7 -0
  359. package/build/dist/Types/AI/AIChatMessageRole.js.map +1 -0
  360. package/build/dist/Types/AI/AIChatMessageStatus.js +27 -0
  361. package/build/dist/Types/AI/AIChatMessageStatus.js.map +1 -0
  362. package/build/dist/Types/AI/AIChatPermissionMode.js +53 -0
  363. package/build/dist/Types/AI/AIChatPermissionMode.js.map +1 -0
  364. package/build/dist/Types/AI/AIChatTypes.js +74 -0
  365. package/build/dist/Types/AI/AIChatTypes.js.map +1 -0
  366. package/build/dist/Types/AI/AIRunEventType.js +18 -0
  367. package/build/dist/Types/AI/AIRunEventType.js.map +1 -0
  368. package/build/dist/Types/AI/AIRunStatus.js +26 -0
  369. package/build/dist/Types/AI/AIRunStatus.js.map +1 -0
  370. package/build/dist/Types/AI/AIRunType.js +7 -0
  371. package/build/dist/Types/AI/AIRunType.js.map +1 -0
  372. package/build/dist/Types/Date.js +153 -15
  373. package/build/dist/Types/Date.js.map +1 -1
  374. package/build/dist/Types/LLM/LlmType.js +6 -0
  375. package/build/dist/Types/LLM/LlmType.js.map +1 -1
  376. package/build/dist/Types/OnCallDutyPolicy/Layer.js +602 -171
  377. package/build/dist/Types/OnCallDutyPolicy/Layer.js.map +1 -1
  378. package/build/dist/Types/OnCallDutyPolicy/RestrictionTimes.js +27 -13
  379. package/build/dist/Types/OnCallDutyPolicy/RestrictionTimes.js.map +1 -1
  380. package/build/dist/Types/OnCallDutyPolicy/UserOverrideUtil.js +20 -5
  381. package/build/dist/Types/OnCallDutyPolicy/UserOverrideUtil.js.map +1 -1
  382. package/build/dist/UI/Components/Events/RecurringFieldElement.js +13 -0
  383. package/build/dist/UI/Components/Events/RecurringFieldElement.js.map +1 -1
  384. package/build/dist/UI/Components/Header/HeaderIconDropdownButton.js +10 -5
  385. package/build/dist/UI/Components/Header/HeaderIconDropdownButton.js.map +1 -1
  386. package/build/dist/UI/Components/Markdown.tsx/MarkdownViewer.js +18 -4
  387. package/build/dist/UI/Components/Markdown.tsx/MarkdownViewer.js.map +1 -1
  388. package/build/dist/UI/Components/ModelFormModal/ModelFormModal.js.map +1 -1
  389. package/build/dist/UI/Components/Page/Page.js +3 -1
  390. package/build/dist/UI/Components/Page/Page.js.map +1 -1
  391. package/build/dist/UI/Components/RadioButtons/BasicRadioButtons.js +1 -1
  392. package/build/dist/UI/Components/RadioButtons/BasicRadioButtons.js.map +1 -1
  393. package/build/dist/UI/Utils/LlmTypeDropdownOptions.js +38 -0
  394. package/build/dist/UI/Utils/LlmTypeDropdownOptions.js.map +1 -0
  395. package/build/dist/UI/Utils/TestLLMProvider.js +37 -0
  396. package/build/dist/UI/Utils/TestLLMProvider.js.map +1 -0
  397. package/package.json +1 -1
@@ -1,21 +1,47 @@
1
1
  import HTTPErrorResponse from "../../../Types/API/HTTPErrorResponse";
2
2
  import HTTPResponse from "../../../Types/API/HTTPResponse";
3
3
  import URL from "../../../Types/API/URL";
4
- import { JSONObject } from "../../../Types/JSON";
4
+ import { JSONArray, JSONObject } from "../../../Types/JSON";
5
+ import JSONFunctions from "../../../Types/JSONFunctions";
5
6
  import API from "../../../Utils/API";
6
7
  import LlmType from "../../../Types/LLM/LlmType";
7
8
  import BadDataException from "../../../Types/Exception/BadDataException";
8
9
  import logger, { LogAttributes } from "../Logger";
9
10
  import CaptureSpan from "../Telemetry/CaptureSpan";
10
11
 
12
+ export interface LLMToolDefinition {
13
+ name: string;
14
+ description: string;
15
+ // JSON Schema for the tool's arguments.
16
+ inputSchema: JSONObject;
17
+ }
18
+
19
+ export interface LLMToolCall {
20
+ id: string;
21
+ name: string;
22
+ arguments: JSONObject;
23
+ /*
24
+ * Set when the provider returned malformed argument JSON that could not be
25
+ * parsed. Callers must NOT execute the tool with the empty arguments —
26
+ * surface the error to the model so it can retry.
27
+ */
28
+ argumentsParseError?: string | undefined;
29
+ }
30
+
11
31
  export interface LLMMessage {
12
- role: "system" | "user" | "assistant";
32
+ role: "system" | "user" | "assistant" | "tool";
13
33
  content: string;
34
+ // Set on assistant messages that requested tool calls.
35
+ toolCalls?: Array<LLMToolCall> | undefined;
36
+ // Set on tool messages: which tool call this result answers.
37
+ toolCallId?: string | undefined;
14
38
  }
15
39
 
16
40
  export interface LLMCompletionRequest {
17
41
  messages: Array<LLMMessage>;
18
- temperature?: number;
42
+ temperature?: number | undefined;
43
+ maxTokens?: number | undefined;
44
+ tools?: Array<LLMToolDefinition> | undefined;
19
45
  llmProviderConfig: LLMProviderConfig;
20
46
  }
21
47
 
@@ -23,10 +49,19 @@ export interface LLMUsage {
23
49
  promptTokens: number;
24
50
  completionTokens: number;
25
51
  totalTokens: number;
52
+ /*
53
+ * Prompt-caching breakdown, when the provider reports it. cachedInputTokens
54
+ * are input tokens served from cache (billed at a large discount);
55
+ * cacheCreationTokens are input tokens written to the cache on this call.
56
+ */
57
+ cachedInputTokens?: number | undefined;
58
+ cacheCreationTokens?: number | undefined;
26
59
  }
27
60
 
28
61
  export interface LLMCompletionResponse {
29
62
  content: string;
63
+ toolCalls?: Array<LLMToolCall> | undefined;
64
+ stopReason?: "stop" | "tool_use" | undefined;
30
65
  usage: LLMUsage | undefined;
31
66
  }
32
67
 
@@ -48,6 +83,7 @@ export default class LLMService {
48
83
  case LlmType.OpenAI:
49
84
  case LlmType.Groq:
50
85
  case LlmType.Mistral:
86
+ case LlmType.OpenAICompatible:
51
87
  return await this.getOpenAICompatibleCompletion(config, request);
52
88
  case LlmType.AzureOpenAI:
53
89
  return await this.getAzureOpenAICompletion(config, request);
@@ -60,15 +96,289 @@ export default class LLMService {
60
96
  }
61
97
  }
62
98
 
99
+ /*
100
+ * OpenAI-compatible wire format (OpenAI, Groq, Mistral, Azure OpenAI).
101
+ */
102
+
103
+ private static toOpenAIMessages(
104
+ messages: Array<LLMMessage>,
105
+ ): Array<JSONObject> {
106
+ return messages.map((msg: LLMMessage) => {
107
+ if (msg.role === "assistant" && msg.toolCalls && msg.toolCalls.length) {
108
+ return {
109
+ role: "assistant",
110
+ content: msg.content || null,
111
+ tool_calls: msg.toolCalls.map((toolCall: LLMToolCall) => {
112
+ return {
113
+ id: toolCall.id,
114
+ type: "function",
115
+ function: {
116
+ name: toolCall.name,
117
+ arguments: JSON.stringify(toolCall.arguments),
118
+ },
119
+ };
120
+ }),
121
+ };
122
+ }
123
+
124
+ if (msg.role === "tool") {
125
+ return {
126
+ role: "tool",
127
+ tool_call_id: msg.toolCallId || "",
128
+ content: msg.content,
129
+ };
130
+ }
131
+
132
+ return {
133
+ role: msg.role,
134
+ content: msg.content,
135
+ };
136
+ });
137
+ }
138
+
139
+ private static toOpenAITools(
140
+ tools: Array<LLMToolDefinition>,
141
+ ): Array<JSONObject> {
142
+ return tools.map((tool: LLMToolDefinition) => {
143
+ return {
144
+ type: "function",
145
+ function: {
146
+ name: tool.name,
147
+ description: tool.description,
148
+ parameters: tool.inputSchema,
149
+ },
150
+ };
151
+ });
152
+ }
153
+
154
+ private static parseOpenAIToolCalls(
155
+ message: JSONObject,
156
+ ): Array<LLMToolCall> | undefined {
157
+ const rawToolCalls: JSONArray | undefined = message["tool_calls"] as
158
+ | JSONArray
159
+ | undefined;
160
+
161
+ if (!rawToolCalls || rawToolCalls.length === 0) {
162
+ return undefined;
163
+ }
164
+
165
+ return rawToolCalls.map((rawToolCall: JSONObject, index: number) => {
166
+ const fn: JSONObject = (rawToolCall["function"] as JSONObject) || {};
167
+ const parsed: { arguments: JSONObject; error?: string | undefined } =
168
+ this.parseToolCallArguments((fn["arguments"] as string) || "{}");
169
+
170
+ return {
171
+ id: (rawToolCall["id"] as string) || `tool_call_${index}`,
172
+ name: (fn["name"] as string) || "",
173
+ arguments: parsed.arguments,
174
+ argumentsParseError: parsed.error,
175
+ };
176
+ });
177
+ }
178
+
179
+ /*
180
+ * Models sometimes emit slightly malformed argument JSON (trailing commas,
181
+ * single quotes). Try strict JSON first, then tolerant JSON5, and report a
182
+ * parse error instead of silently executing with empty arguments.
183
+ */
184
+ private static parseToolCallArguments(rawArguments: string): {
185
+ arguments: JSONObject;
186
+ error?: string | undefined;
187
+ } {
188
+ try {
189
+ return { arguments: JSON.parse(rawArguments) };
190
+ } catch {
191
+ // fall through to tolerant parsing
192
+ }
193
+
194
+ try {
195
+ const parsed: JSONObject | unknown = JSONFunctions.parse(rawArguments);
196
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
197
+ return { arguments: parsed as JSONObject };
198
+ }
199
+ return {
200
+ arguments: {},
201
+ error: "Tool arguments were not a JSON object.",
202
+ };
203
+ } catch {
204
+ return {
205
+ arguments: {},
206
+ error: "Tool arguments were malformed JSON and could not be parsed.",
207
+ };
208
+ }
209
+ }
210
+
211
+ private static buildOpenAIRequestBody(
212
+ modelName: string,
213
+ request: LLMCompletionRequest,
214
+ ): JSONObject {
215
+ const data: JSONObject = {
216
+ model: modelName,
217
+ messages: this.toOpenAIMessages(request.messages),
218
+ temperature: request.temperature ?? 0.7,
219
+ };
220
+
221
+ if (request.maxTokens) {
222
+ data["max_tokens"] = request.maxTokens;
223
+ }
224
+
225
+ if (request.tools && request.tools.length > 0) {
226
+ data["tools"] = this.toOpenAITools(request.tools);
227
+ }
228
+
229
+ return data;
230
+ }
231
+
232
+ private static parseOpenAIResponse(
233
+ jsonData: JSONObject,
234
+ providerName: string,
235
+ ): LLMCompletionResponse {
236
+ const choices: Array<JSONObject> = jsonData["choices"] as Array<JSONObject>;
237
+
238
+ if (!choices || choices.length === 0) {
239
+ throw new BadDataException(`No response from ${providerName}`);
240
+ }
241
+
242
+ const message: JSONObject = choices[0]!["message"] as JSONObject;
243
+ const usage: JSONObject = jsonData["usage"] as JSONObject;
244
+ const toolCalls: Array<LLMToolCall> | undefined =
245
+ this.parseOpenAIToolCalls(message);
246
+
247
+ /*
248
+ * OpenAI (and Azure OpenAI) automatically cache a stable prompt prefix
249
+ * once it is long enough and report the cache hit under
250
+ * prompt_tokens_details.cached_tokens — surface it for cost visibility.
251
+ */
252
+ const cachedTokens: number | undefined = usage
253
+ ? ((usage["prompt_tokens_details"] as JSONObject | undefined)?.[
254
+ "cached_tokens"
255
+ ] as number | undefined)
256
+ : undefined;
257
+
258
+ return {
259
+ content: (message["content"] as string) || "",
260
+ toolCalls: toolCalls,
261
+ stopReason: toolCalls && toolCalls.length > 0 ? "tool_use" : "stop",
262
+ usage: usage
263
+ ? {
264
+ promptTokens: usage["prompt_tokens"] as number,
265
+ completionTokens: usage["completion_tokens"] as number,
266
+ totalTokens: usage["total_tokens"] as number,
267
+ cachedInputTokens: cachedTokens || undefined,
268
+ }
269
+ : undefined,
270
+ };
271
+ }
272
+
273
+ /*
274
+ * Build the chat completions URL for an OpenAI-compatible server from the
275
+ * configured base URL. Users enter the base URL in a few different ways, and
276
+ * we normalize all of them onto the right endpoint instead of 404-ing:
277
+ *
278
+ * - Already a full endpoint (".../chat/completions") -> used as-is.
279
+ * - Includes a path (".../v1", ".../openai/v1") -> append "/chat/completions".
280
+ * - Bare server root ("http://host:8000") -> append "/v1/chat/completions",
281
+ * since vLLM, LocalAI and similar servers expose the OpenAI-compatible API
282
+ * under /v1 by default. This is the most common self-hosted setup and the
283
+ * easiest one for users to get wrong (omitting /v1 returns FastAPI's
284
+ * {"detail":"Not Found"}).
285
+ *
286
+ * The endpoint segment is only ever appended to the PATH portion: any
287
+ * ?query or #fragment on the base URL is split off first and re-attached
288
+ * afterwards (otherwise "/chat/completions" would land inside the query or
289
+ * fragment). Trailing slashes are stripped so we never emit
290
+ * ".../v1//chat/completions", and the URL scheme is lower-cased because URL
291
+ * schemes are case-insensitive but downstream URL parsing only recognizes
292
+ * lowercase http/https.
293
+ */
294
+ private static buildOpenAICompatibleChatCompletionsUrl(
295
+ baseUrl: string,
296
+ ): string {
297
+ const raw: string = baseUrl.trim();
298
+
299
+ // Split off #fragment, then ?query, so we can operate on the path alone.
300
+ const fragmentIndex: number = raw.indexOf("#");
301
+ const fragment: string =
302
+ fragmentIndex >= 0 ? raw.substring(fragmentIndex) : "";
303
+ const withoutFragment: string =
304
+ fragmentIndex >= 0 ? raw.substring(0, fragmentIndex) : raw;
305
+
306
+ const queryIndex: number = withoutFragment.indexOf("?");
307
+ const query: string =
308
+ queryIndex >= 0 ? withoutFragment.substring(queryIndex) : "";
309
+
310
+ let base: string =
311
+ queryIndex >= 0
312
+ ? withoutFragment.substring(0, queryIndex)
313
+ : withoutFragment;
314
+
315
+ /*
316
+ * Lower-case the scheme (e.g. "HTTP://" -> "http://") and strip trailing
317
+ * slashes.
318
+ */
319
+ const schemeRegex: RegExp = /^[a-z][a-z0-9+.-]*:\/\//i;
320
+ base = base
321
+ .replace(schemeRegex, (scheme: string) => {
322
+ return scheme.toLowerCase();
323
+ })
324
+ .replace(/\/+$/, "");
325
+
326
+ let path: string;
327
+ const fullEndpointRegex: RegExp = /\/chat\/completions$/i;
328
+ const hasPathAfterHostRegex: RegExp = /^[a-z][a-z0-9+.-]*:\/\/[^/]+\/.+/i;
329
+
330
+ if (fullEndpointRegex.test(base)) {
331
+ // The user already pointed us at the full endpoint.
332
+ path = base;
333
+ } else if (hasPathAfterHostRegex.test(base)) {
334
+ /*
335
+ * The base URL carries a path after the host[:port] (e.g.
336
+ * "http://host:8000/v1" or ".../openai/v1") — trust it and only append
337
+ * the endpoint.
338
+ */
339
+ path = `${base}/chat/completions`;
340
+ } else {
341
+ /*
342
+ * Bare server root ("http://host:8000") — add the conventional /v1
343
+ * prefix that OpenAI-compatible servers (vLLM, LocalAI, ...) use.
344
+ */
345
+ path = `${base}/v1/chat/completions`;
346
+ }
347
+
348
+ return `${path}${query}${fragment}`;
349
+ }
350
+
63
351
  @CaptureSpan()
64
352
  private static async getOpenAICompatibleCompletion(
65
353
  config: LLMProviderConfig,
66
354
  request: LLMCompletionRequest,
67
355
  ): Promise<LLMCompletionResponse> {
68
- if (!config.apiKey) {
356
+ /*
357
+ * Generic OpenAI-compatible servers (vLLM, LocalAI, etc.) are usually
358
+ * self-hosted, frequently keyless, and have no canonical endpoint or
359
+ * default model — so the API key is optional but the base URL and model
360
+ * name must be provided. The hosted providers (OpenAI, Groq, Mistral)
361
+ * keep requiring a key and fall back to sensible defaults.
362
+ */
363
+ const isGenericOpenAICompatible: boolean =
364
+ config.llmType === LlmType.OpenAICompatible;
365
+
366
+ if (!isGenericOpenAICompatible && !config.apiKey) {
69
367
  throw new BadDataException(`${config.llmType} API key is required`);
70
368
  }
71
369
 
370
+ if (isGenericOpenAICompatible && !config.baseUrl) {
371
+ throw new BadDataException(
372
+ "Base URL is required for OpenAI-compatible providers (e.g. http://your-vllm-server:8000/v1)",
373
+ );
374
+ }
375
+
376
+ if (isGenericOpenAICompatible && !config.modelName) {
377
+ throw new BadDataException(
378
+ "Model Name is required for OpenAI-compatible providers. It must match a model your server exposes.",
379
+ );
380
+ }
381
+
72
382
  const defaultBaseUrls: Record<string, string> = {
73
383
  [LlmType.OpenAI]: "https://api.openai.com/v1",
74
384
  [LlmType.Groq]: "https://api.groq.com/openai/v1",
@@ -89,20 +399,20 @@ export default class LLMService {
89
399
  config.modelName || defaultModels[config.llmType] || "gpt-4o";
90
400
  const response: HTTPErrorResponse | HTTPResponse<JSONObject> =
91
401
  await API.post<JSONObject>({
92
- url: URL.fromString(`${baseUrl}/chat/completions`),
93
- data: {
94
- model: modelName,
95
- messages: request.messages.map((msg: LLMMessage) => {
96
- return {
97
- role: msg.role,
98
- content: msg.content,
99
- };
100
- }),
101
- temperature: request.temperature ?? 0.7,
102
- },
402
+ url: URL.fromString(
403
+ this.buildOpenAICompatibleChatCompletionsUrl(baseUrl),
404
+ ),
405
+ data: this.buildOpenAIRequestBody(modelName, request),
103
406
  headers: {
104
- Authorization: `Bearer ${config.apiKey}`,
105
407
  "Content-Type": "application/json",
408
+ /*
409
+ * Only send Authorization when a key is configured — a keyless
410
+ * server (e.g. vLLM started without --api-key) rejects an empty
411
+ * bearer token.
412
+ */
413
+ ...(config.apiKey
414
+ ? { Authorization: `Bearer ${config.apiKey}` }
415
+ : {}),
106
416
  },
107
417
  options: {
108
418
  retries: 2,
@@ -124,26 +434,10 @@ export default class LLMService {
124
434
  );
125
435
  }
126
436
 
127
- const jsonData: JSONObject = response.jsonData as JSONObject;
128
- const choices: Array<JSONObject> = jsonData["choices"] as Array<JSONObject>;
129
-
130
- if (!choices || choices.length === 0) {
131
- throw new BadDataException(`No response from ${config.llmType}`);
132
- }
133
-
134
- const message: JSONObject = choices[0]!["message"] as JSONObject;
135
- const usage: JSONObject = jsonData["usage"] as JSONObject;
136
-
137
- return {
138
- content: message["content"] as string,
139
- usage: usage
140
- ? {
141
- promptTokens: usage["prompt_tokens"] as number,
142
- completionTokens: usage["completion_tokens"] as number,
143
- totalTokens: usage["total_tokens"] as number,
144
- }
145
- : undefined,
146
- };
437
+ return this.parseOpenAIResponse(
438
+ response.jsonData as JSONObject,
439
+ config.llmType,
440
+ );
147
441
  }
148
442
 
149
443
  /*
@@ -192,16 +486,7 @@ export default class LLMService {
192
486
  const response: HTTPErrorResponse | HTTPResponse<JSONObject> =
193
487
  await API.post<JSONObject>({
194
488
  url: URL.fromString(requestUrl),
195
- data: {
196
- model: modelName,
197
- messages: request.messages.map((msg: LLMMessage) => {
198
- return {
199
- role: msg.role,
200
- content: msg.content,
201
- };
202
- }),
203
- temperature: request.temperature ?? 0.7,
204
- },
489
+ data: this.buildOpenAIRequestBody(modelName, request),
205
490
  headers: {
206
491
  "api-key": config.apiKey,
207
492
  "Content-Type": "application/json",
@@ -226,26 +511,112 @@ export default class LLMService {
226
511
  );
227
512
  }
228
513
 
229
- const jsonData: JSONObject = response.jsonData as JSONObject;
230
- const choices: Array<JSONObject> = jsonData["choices"] as Array<JSONObject>;
514
+ return this.parseOpenAIResponse(
515
+ response.jsonData as JSONObject,
516
+ "Azure OpenAI",
517
+ );
518
+ }
231
519
 
232
- if (!choices || choices.length === 0) {
233
- throw new BadDataException("No response from Azure OpenAI");
234
- }
520
+ /*
521
+ * Anthropic wire format. System message is hoisted, tool results ride in
522
+ * user messages as tool_result blocks, and max_tokens is required by the
523
+ * API.
524
+ */
235
525
 
236
- const message: JSONObject = choices[0]!["message"] as JSONObject;
237
- const usage: JSONObject = jsonData["usage"] as JSONObject;
526
+ private static readonly ANTHROPIC_DEFAULT_MAX_TOKENS: number = 4096;
238
527
 
239
- return {
240
- content: message["content"] as string,
241
- usage: usage
242
- ? {
243
- promptTokens: usage["prompt_tokens"] as number,
244
- completionTokens: usage["completion_tokens"] as number,
245
- totalTokens: usage["total_tokens"] as number,
246
- }
247
- : undefined,
248
- };
528
+ private static toAnthropicMessages(
529
+ messages: Array<LLMMessage>,
530
+ ): Array<JSONObject> {
531
+ const anthropicMessages: Array<JSONObject> = [];
532
+
533
+ for (const msg of messages) {
534
+ if (msg.role === "system") {
535
+ continue; // hoisted separately
536
+ }
537
+
538
+ if (msg.role === "assistant" && msg.toolCalls && msg.toolCalls.length) {
539
+ const contentBlocks: Array<JSONObject> = [];
540
+
541
+ if (msg.content) {
542
+ contentBlocks.push({ type: "text", text: msg.content });
543
+ }
544
+
545
+ for (const toolCall of msg.toolCalls) {
546
+ contentBlocks.push({
547
+ type: "tool_use",
548
+ id: toolCall.id,
549
+ name: toolCall.name,
550
+ input: toolCall.arguments,
551
+ });
552
+ }
553
+
554
+ anthropicMessages.push({ role: "assistant", content: contentBlocks });
555
+ continue;
556
+ }
557
+
558
+ if (msg.role === "tool") {
559
+ const toolResultBlock: JSONObject = {
560
+ type: "tool_result",
561
+ tool_use_id: msg.toolCallId || "",
562
+ content: msg.content,
563
+ };
564
+
565
+ /*
566
+ * Tool results must be user messages. Merge consecutive tool
567
+ * results into one user message so roles keep alternating.
568
+ */
569
+ const lastMessage: JSONObject | undefined =
570
+ anthropicMessages[anthropicMessages.length - 1];
571
+
572
+ if (
573
+ lastMessage &&
574
+ lastMessage["role"] === "user" &&
575
+ Array.isArray(lastMessage["content"])
576
+ ) {
577
+ (lastMessage["content"] as Array<JSONObject>).push(toolResultBlock);
578
+ } else {
579
+ anthropicMessages.push({
580
+ role: "user",
581
+ content: [toolResultBlock],
582
+ });
583
+ }
584
+ continue;
585
+ }
586
+
587
+ /*
588
+ * Anthropic requires strictly alternating user/assistant turns and
589
+ * returns a 400 on two consecutive same-role messages. The agent loop
590
+ * can legitimately emit back-to-back user turns (e.g. a tool_result
591
+ * user message immediately followed by the "budget exhausted, answer
592
+ * now" nudge), so coalesce a run of same-role messages into one instead
593
+ * of failing the whole request.
594
+ */
595
+ const previousMessage: JSONObject | undefined =
596
+ anthropicMessages[anthropicMessages.length - 1];
597
+
598
+ if (previousMessage && previousMessage["role"] === msg.role) {
599
+ if (typeof previousMessage["content"] === "string") {
600
+ previousMessage["content"] =
601
+ `${previousMessage["content"] as string}\n\n${msg.content}`;
602
+ continue;
603
+ }
604
+ if (Array.isArray(previousMessage["content"])) {
605
+ (previousMessage["content"] as Array<JSONObject>).push({
606
+ type: "text",
607
+ text: msg.content,
608
+ });
609
+ continue;
610
+ }
611
+ }
612
+
613
+ anthropicMessages.push({
614
+ role: msg.role,
615
+ content: msg.content,
616
+ });
617
+ }
618
+
619
+ return anthropicMessages;
249
620
  }
250
621
 
251
622
  @CaptureSpan()
@@ -260,29 +631,59 @@ export default class LLMService {
260
631
  const baseUrl: string = config.baseUrl || "https://api.anthropic.com/v1";
261
632
  const modelName: string = config.modelName || "claude-sonnet-4-20250514";
262
633
 
263
- // Anthropic requires system message to be separate
264
634
  let systemMessage: string = "";
265
- const userMessages: Array<{ role: string; content: string }> = [];
266
635
 
267
636
  for (const msg of request.messages) {
268
637
  if (msg.role === "system") {
269
638
  systemMessage = msg.content;
270
- } else {
271
- userMessages.push({
272
- role: msg.role,
273
- content: msg.content,
274
- });
275
639
  }
276
640
  }
277
641
 
278
642
  const requestData: JSONObject = {
279
643
  model: modelName,
280
- messages: userMessages,
644
+ messages: this.toAnthropicMessages(request.messages),
281
645
  temperature: request.temperature ?? 0.7,
646
+ // Anthropic requires max_tokens on every request.
647
+ max_tokens: request.maxTokens || LLMService.ANTHROPIC_DEFAULT_MAX_TOKENS,
282
648
  };
283
649
 
650
+ /*
651
+ * Prompt caching. The system prompt and tool definitions are the large,
652
+ * stable prefix re-sent on every turn of the agent loop, so caching them is
653
+ * the biggest single cost + latency lever (cached input is billed at ~10%).
654
+ * An ephemeral cache_control breakpoint on the system block and on the LAST
655
+ * tool caches the whole system + tools prefix. cache_control is GA under
656
+ * anthropic-version 2023-06-01, so no beta header is required.
657
+ */
284
658
  if (systemMessage) {
285
- requestData["system"] = systemMessage;
659
+ requestData["system"] = [
660
+ {
661
+ type: "text",
662
+ text: systemMessage,
663
+ cache_control: { type: "ephemeral" },
664
+ },
665
+ ];
666
+ }
667
+
668
+ if (request.tools && request.tools.length > 0) {
669
+ const anthropicTools: Array<JSONObject> = request.tools.map(
670
+ (tool: LLMToolDefinition) => {
671
+ return {
672
+ name: tool.name,
673
+ description: tool.description,
674
+ input_schema: tool.inputSchema,
675
+ };
676
+ },
677
+ );
678
+
679
+ // A breakpoint on the last tool caches the entire tools block before it.
680
+ const lastTool: JSONObject | undefined =
681
+ anthropicTools[anthropicTools.length - 1];
682
+ if (lastTool) {
683
+ lastTool["cache_control"] = { type: "ephemeral" };
684
+ }
685
+
686
+ requestData["tools"] = anthropicTools;
286
687
  }
287
688
 
288
689
  const response: HTTPErrorResponse | HTTPResponse<JSONObject> =
@@ -321,32 +722,65 @@ export default class LLMService {
321
722
  throw new BadDataException("No response from Anthropic");
322
723
  }
323
724
 
324
- const textContent: JSONObject | undefined = content.find(
325
- (c: JSONObject) => {
326
- return c["type"] === "text";
327
- },
328
- );
725
+ const textContent: string = content
726
+ .filter((block: JSONObject) => {
727
+ return block["type"] === "text";
728
+ })
729
+ .map((block: JSONObject) => {
730
+ return block["text"] as string;
731
+ })
732
+ .join("");
733
+
734
+ const toolCalls: Array<LLMToolCall> = content
735
+ .filter((block: JSONObject) => {
736
+ return block["type"] === "tool_use";
737
+ })
738
+ .map((block: JSONObject) => {
739
+ return {
740
+ id: (block["id"] as string) || "",
741
+ name: (block["name"] as string) || "",
742
+ arguments: (block["input"] as JSONObject) || {},
743
+ };
744
+ });
329
745
 
330
- if (!textContent) {
746
+ if (!textContent && toolCalls.length === 0) {
331
747
  throw new BadDataException("No text content in Anthropic response");
332
748
  }
333
749
 
334
750
  const usage: JSONObject = jsonData["usage"] as JSONObject;
335
751
 
336
752
  return {
337
- content: textContent["text"] as string,
753
+ content: textContent,
754
+ toolCalls: toolCalls.length > 0 ? toolCalls : undefined,
755
+ stopReason: jsonData["stop_reason"] === "tool_use" ? "tool_use" : "stop",
338
756
  usage: usage
339
757
  ? {
340
- promptTokens: usage["input_tokens"] as number,
341
- completionTokens: usage["output_tokens"] as number,
758
+ promptTokens: (usage["input_tokens"] as number) || 0,
759
+ completionTokens: (usage["output_tokens"] as number) || 0,
760
+ /*
761
+ * Anthropic reports cache-read and cache-creation input tokens
762
+ * separately from input_tokens. Surface them and fold them into
763
+ * totalTokens so the logged total reflects the full billable input.
764
+ */
765
+ cachedInputTokens:
766
+ (usage["cache_read_input_tokens"] as number) || undefined,
767
+ cacheCreationTokens:
768
+ (usage["cache_creation_input_tokens"] as number) || undefined,
342
769
  totalTokens:
343
770
  ((usage["input_tokens"] as number) || 0) +
771
+ ((usage["cache_read_input_tokens"] as number) || 0) +
772
+ ((usage["cache_creation_input_tokens"] as number) || 0) +
344
773
  ((usage["output_tokens"] as number) || 0),
345
774
  }
346
775
  : undefined,
347
776
  };
348
777
  }
349
778
 
779
+ /*
780
+ * Ollama native /api/chat. Deliberately NOT routed through the
781
+ * OpenAI-compatible branch: Ollama deployments are keyless by design and
782
+ * the OpenAI branch requires an API key.
783
+ */
350
784
  @CaptureSpan()
351
785
  private static async getOllamaCompletion(
352
786
  config: LLMProviderConfig,
@@ -358,22 +792,44 @@ export default class LLMService {
358
792
 
359
793
  const modelName: string = config.modelName || "llama2";
360
794
 
795
+ const requestData: JSONObject = {
796
+ model: modelName,
797
+ messages: request.messages.map((msg: LLMMessage) => {
798
+ if (msg.role === "assistant" && msg.toolCalls && msg.toolCalls.length) {
799
+ return {
800
+ role: "assistant",
801
+ content: msg.content || "",
802
+ tool_calls: msg.toolCalls.map((toolCall: LLMToolCall) => {
803
+ return {
804
+ function: {
805
+ name: toolCall.name,
806
+ arguments: toolCall.arguments,
807
+ },
808
+ };
809
+ }),
810
+ };
811
+ }
812
+
813
+ return {
814
+ role: msg.role,
815
+ content: msg.content,
816
+ };
817
+ }),
818
+ stream: false,
819
+ options: {
820
+ temperature: request.temperature ?? 0.7,
821
+ ...(request.maxTokens ? { num_predict: request.maxTokens } : {}),
822
+ },
823
+ };
824
+
825
+ if (request.tools && request.tools.length > 0) {
826
+ requestData["tools"] = this.toOpenAITools(request.tools);
827
+ }
828
+
361
829
  const response: HTTPErrorResponse | HTTPResponse<JSONObject> =
362
830
  await API.post<JSONObject>({
363
831
  url: URL.fromString(`${config.baseUrl}/api/chat`),
364
- data: {
365
- model: modelName,
366
- messages: request.messages.map((msg: LLMMessage) => {
367
- return {
368
- role: msg.role,
369
- content: msg.content,
370
- };
371
- }),
372
- stream: false,
373
- options: {
374
- temperature: request.temperature ?? 0.7,
375
- },
376
- },
832
+ data: requestData,
377
833
  headers: {
378
834
  "Content-Type": "application/json",
379
835
  },
@@ -404,9 +860,62 @@ export default class LLMService {
404
860
  throw new BadDataException("No response from Ollama");
405
861
  }
406
862
 
863
+ const rawToolCalls: JSONArray | undefined = message["tool_calls"] as
864
+ | JSONArray
865
+ | undefined;
866
+
867
+ let toolCalls: Array<LLMToolCall> | undefined = undefined;
868
+
869
+ if (rawToolCalls && rawToolCalls.length > 0) {
870
+ toolCalls = rawToolCalls.map((rawToolCall: JSONObject, index: number) => {
871
+ const fn: JSONObject = (rawToolCall["function"] as JSONObject) || {};
872
+
873
+ let parsedArguments: JSONObject = {};
874
+ let parseError: string | undefined = undefined;
875
+ const rawArguments: unknown = fn["arguments"];
876
+
877
+ if (typeof rawArguments === "string") {
878
+ const parsed: { arguments: JSONObject; error?: string | undefined } =
879
+ this.parseToolCallArguments(rawArguments);
880
+ parsedArguments = parsed.arguments;
881
+ parseError = parsed.error;
882
+ } else if (rawArguments && typeof rawArguments === "object") {
883
+ parsedArguments = rawArguments as JSONObject;
884
+ }
885
+
886
+ // Ollama does not return tool-call ids; synthesize stable ones.
887
+ return {
888
+ id: `tool_call_${index}`,
889
+ name: (fn["name"] as string) || "",
890
+ arguments: parsedArguments,
891
+ argumentsParseError: parseError,
892
+ };
893
+ });
894
+ }
895
+
896
+ /*
897
+ * Ollama reports token counts on the final /api/chat response as
898
+ * prompt_eval_count (input) and eval_count (output). Populate usage from
899
+ * them so LlmLog, the AI dashboards and (costed self-hosted Ollama)
900
+ * billing are not silently blind to token spend.
901
+ */
902
+ const ollamaPromptTokens: number =
903
+ (jsonData["prompt_eval_count"] as number) || 0;
904
+ const ollamaCompletionTokens: number =
905
+ (jsonData["eval_count"] as number) || 0;
906
+
407
907
  return {
408
- content: message["content"] as string,
409
- usage: undefined, // Ollama doesn't provide token usage in the same way
908
+ content: (message["content"] as string) || "",
909
+ toolCalls: toolCalls,
910
+ stopReason: toolCalls && toolCalls.length > 0 ? "tool_use" : "stop",
911
+ usage:
912
+ ollamaPromptTokens || ollamaCompletionTokens
913
+ ? {
914
+ promptTokens: ollamaPromptTokens,
915
+ completionTokens: ollamaCompletionTokens,
916
+ totalTokens: ollamaPromptTokens + ollamaCompletionTokens,
917
+ }
918
+ : undefined,
410
919
  };
411
920
  }
412
921
  }