@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.
- package/Models/DatabaseModels/AIConversation.ts +322 -0
- package/Models/DatabaseModels/AIConversationMessage.ts +495 -0
- package/Models/DatabaseModels/AIRun.ts +584 -0
- package/Models/DatabaseModels/AIRunEvent.ts +443 -0
- package/Models/DatabaseModels/IncidentEpisodeRoleMember.ts +1 -1
- package/Models/DatabaseModels/IncidentMember.ts +1 -1
- package/Models/DatabaseModels/IncomingCallPolicy.ts +13 -0
- package/Models/DatabaseModels/Index.ts +11 -0
- package/Models/DatabaseModels/LlmLog.ts +26 -0
- package/Models/DatabaseModels/LlmProvider.ts +2 -2
- package/Models/DatabaseModels/MigrationFailure.ts +170 -0
- package/Models/DatabaseModels/OnCallDutyPolicyExecutionLog.ts +51 -2
- package/Models/DatabaseModels/OnCallDutyPolicySchedule.ts +44 -0
- package/Models/DatabaseModels/OnCallDutyPolicyScheduleLayer.ts +6 -0
- package/Models/DatabaseModels/OnCallDutyPolicyScheduleLayerUser.ts +6 -0
- package/Models/DatabaseModels/OnCallDutyPolicyTimeLog.ts +1 -1
- package/Models/DatabaseModels/OnCallDutyPolicyUserOverride.ts +2 -2
- package/Server/API/AIChatAPI.ts +693 -0
- package/Server/API/AlertAPI.ts +1 -1
- package/Server/API/IncidentAPI.ts +2 -2
- package/Server/API/IncidentEpisodeAPI.ts +1 -1
- package/Server/API/LlmProviderAPI.ts +170 -0
- package/Server/API/MicrosoftTeamsAPI.ts +5 -0
- package/Server/API/ScheduledMaintenanceAPI.ts +1 -1
- package/Server/API/SlackAPI.ts +663 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1783363279075-AddAIChatModels.ts +245 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1783443471795-AddLlmProviderToAIConversation.ts +36 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1783453297388-AddAIChatWriteActionsAndWidgets.ts +46 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1783461767405-MigrationName.ts +119 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1783470000000-AddIncomingCallPolicyRoutingPhoneNumberUnique.ts +25 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1783510935686-FixNotNullForeignKeysOnDelete.ts +81 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1783515836148-MigrationName.ts +25 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1783523076215-AddMigrationFailureTable.ts +35 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +16 -0
- package/Server/Infrastructure/PostgresDatabase.ts +11 -0
- package/Server/Services/AIConversationMessageService.ts +39 -0
- package/Server/Services/AIConversationService.ts +63 -0
- package/Server/Services/AIRunEventService.ts +39 -0
- package/Server/Services/AIRunService.ts +39 -0
- package/Server/Services/AIService.ts +127 -33
- package/Server/Services/AnalyticsDatabaseService.ts +10 -1
- package/Server/Services/DatabaseService.ts +29 -1
- package/Server/Services/IncidentService.ts +1 -1
- package/Server/Services/IncomingCallPolicyEscalationRuleService.ts +75 -2
- package/Server/Services/IncomingCallPolicyService.ts +42 -1
- package/Server/Services/Index.ts +2 -0
- package/Server/Services/LlmProviderService.ts +110 -0
- package/Server/Services/MigrationFailureService.ts +9 -0
- package/Server/Services/MonitorProbeService.ts +33 -7
- package/Server/Services/MonitorService.ts +19 -7
- package/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.ts +65 -0
- package/Server/Services/OnCallDutyPolicyEscalationRuleService.ts +185 -41
- package/Server/Services/OnCallDutyPolicyEscalationRuleUserService.ts +23 -15
- package/Server/Services/OnCallDutyPolicyExecutionLogService.ts +39 -0
- package/Server/Services/OnCallDutyPolicyScheduleLayerService.ts +148 -0
- package/Server/Services/OnCallDutyPolicyScheduleLayerUserService.ts +11 -2
- package/Server/Services/OnCallDutyPolicyScheduleService.ts +502 -26
- package/Server/Services/OnCallDutyPolicyTimeLogService.ts +49 -7
- package/Server/Services/OnCallDutyPolicyUserOverrideService.ts +218 -4
- package/Server/Services/ProjectCallSMSConfigService.ts +82 -1
- package/Server/Services/ProjectService.ts +24 -0
- package/Server/Services/TeamMemberService.ts +6 -0
- package/Server/Services/UserNotificationRuleService.ts +13 -42
- package/Server/Services/UserOnCallLogService.ts +90 -0
- package/Server/Services/UserOnCallLogTimelineService.ts +43 -1
- package/Server/Types/AnalyticsDatabase/ModelPermission.ts +74 -9
- package/Server/Types/Database/QueryUtil.ts +57 -0
- package/Server/Utils/AI/AIChatPrivacyFilter.ts +28 -0
- package/Server/Utils/AI/AlertAIContextBuilder.ts +6 -1
- package/Server/Utils/AI/Chat/ChatAgentRunner.ts +1066 -0
- package/Server/Utils/AI/Chat/ObservabilityAssistant.ts +239 -0
- package/Server/Utils/AI/Chat/ObservabilityChatPrompt.ts +51 -0
- package/Server/Utils/AI/IncidentAIContextBuilder.ts +18 -3
- package/Server/Utils/AI/IncidentEpisodeAIContextBuilder.ts +6 -1
- package/Server/Utils/AI/ScheduledMaintenanceAIContextBuilder.ts +12 -2
- package/Server/Utils/AI/Toolbox/AlertTools.ts +201 -0
- package/Server/Utils/AI/Toolbox/AlertWriteTools.ts +174 -0
- package/Server/Utils/AI/Toolbox/ContextTools.ts +189 -0
- package/Server/Utils/AI/Toolbox/ExceptionTools.ts +149 -0
- package/Server/Utils/AI/Toolbox/IncidentTools.ts +386 -0
- package/Server/Utils/AI/Toolbox/IncidentWriteTools.ts +350 -0
- package/Server/Utils/AI/Toolbox/Index.ts +231 -0
- package/Server/Utils/AI/Toolbox/LogTools.ts +339 -0
- package/Server/Utils/AI/Toolbox/MetricTools.ts +179 -0
- package/Server/Utils/AI/Toolbox/MonitorTools.ts +193 -0
- package/Server/Utils/AI/Toolbox/RecentChangesTools.ts +208 -0
- package/Server/Utils/AI/Toolbox/Serializer.ts +299 -0
- package/Server/Utils/AI/Toolbox/ToolTypes.ts +249 -0
- package/Server/Utils/AI/Toolbox/TraceTools.ts +402 -0
- package/Server/Utils/AI/Toolbox/WidgetBuilder.ts +189 -0
- package/Server/Utils/Database/MigrationFailureLog.ts +240 -0
- package/Server/Utils/IncomingCallPhoneNumber.ts +41 -0
- package/Server/Utils/LLM/LLMService.ts +605 -96
- package/Server/Utils/Telemetry/IoTSnapshotScan.ts +12 -9
- package/Server/Utils/Telemetry/ProxmoxCephSnapshotScan.ts +12 -9
- package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +512 -19
- package/Server/Utils/Workspace/Slack/Slack.ts +80 -0
- package/Server/Utils/Workspace/Slack/app-manifest.json +16 -1
- package/Tests/Server/Services/AnalyticsDatabaseService.test.ts +0 -8
- package/Tests/Server/Services/OnCallDutyPolicyScheduleLayerUserReorder.test.ts +233 -0
- package/Tests/Server/Services/OnCallDutyPolicyTimeLogServiceScoping.test.ts +242 -0
- package/Tests/Server/Services/OnCallDutyPolicyUserOverrideEdit.test.ts +198 -0
- package/Tests/Server/Services/UserOnCallLogClaimNotificationRule.test.ts +495 -0
- package/Tests/Server/Types/Database/QueryUtil.test.ts +74 -0
- package/Tests/Server/Utils/AI/AIChatModelACL.test.ts +42 -0
- package/Tests/Server/Utils/AI/ChatAgentHelpers.test.ts +89 -0
- package/Tests/Server/Utils/AI/LLMServiceBaseUrl.test.ts +139 -0
- package/Tests/Server/Utils/AI/LLMServiceToolCalling.test.ts +386 -0
- package/Tests/Server/Utils/AI/ToolArgsGetTimeRange.test.ts +62 -0
- package/Tests/Server/Utils/AI/ToolArgsScopeServiceIds.test.ts +79 -0
- package/Tests/Server/Utils/AI/ToolResultSerializer.test.ts +223 -0
- package/Tests/Types/Billing/SubscriptionStatus.test.ts +127 -0
- package/Tests/Types/DateExhaustiveTimezone.test.ts +700 -0
- package/Tests/Types/DateTimezoneWallClock.test.ts +99 -0
- package/Tests/Types/Events/Recurring.test.ts +22 -9
- package/Tests/Types/Metrics/RecordingRuleDefinition.test.ts +213 -0
- package/Tests/Types/OnCallDutyPolicy/LayerUtilAuditFixes.test.ts +291 -0
- package/Tests/Types/OnCallDutyPolicy/LayerUtilAuditFixesRound2.test.ts +398 -0
- package/Tests/Types/OnCallDutyPolicy/LayerUtilDSTWeekendGap.test.ts +114 -0
- package/Tests/Types/OnCallDutyPolicy/LayerUtilDSTWeeklyDayShift.test.ts +76 -0
- package/Tests/Types/OnCallDutyPolicy/LayerUtilDailyMutation.test.ts +128 -0
- package/Tests/Types/OnCallDutyPolicy/LayerUtilDailyProbe.test.ts +203 -0
- package/Tests/Types/OnCallDutyPolicy/LayerUtilDiffFuzz.test.ts +322 -0
- package/Tests/Types/OnCallDutyPolicy/LayerUtilEdgeCases.test.ts +280 -0
- package/Tests/Types/OnCallDutyPolicy/LayerUtilExhaustiveCurrentUser.test.ts +863 -0
- package/Tests/Types/OnCallDutyPolicy/LayerUtilExhaustiveDaily.test.ts +900 -0
- package/Tests/Types/OnCallDutyPolicy/LayerUtilExhaustiveMultiLayer.test.ts +1104 -0
- package/Tests/Types/OnCallDutyPolicy/LayerUtilExhaustiveRotation.test.ts +957 -0
- package/Tests/Types/OnCallDutyPolicy/LayerUtilExhaustiveWeekly.test.ts +1095 -0
- package/Tests/Types/OnCallDutyPolicy/LayerUtilInvariants.test.ts +232 -0
- package/Tests/Types/OnCallDutyPolicy/LayerUtilMergeAudit.test.ts +227 -0
- package/Tests/Types/OnCallDutyPolicy/LayerUtilMonthYearAudit.test.ts +255 -0
- package/Tests/Types/OnCallDutyPolicy/LayerUtilMultiLayerAudit.test.ts +388 -0
- package/Tests/Types/OnCallDutyPolicy/LayerUtilMultiLayerEdge.test.ts +300 -0
- package/Tests/Types/OnCallDutyPolicy/LayerUtilOvernightDSTAudit.test.ts +158 -0
- package/Tests/Types/OnCallDutyPolicy/LayerUtilRestrictedGapFix.test.ts +253 -0
- package/Tests/Types/OnCallDutyPolicy/LayerUtilRestrictedGapRepro.test.ts +157 -0
- package/Tests/Types/OnCallDutyPolicy/LayerUtilRotationFixes.test.ts +414 -0
- package/Tests/Types/OnCallDutyPolicy/LayerUtilTimezone.test.ts +243 -0
- package/Tests/Types/OnCallDutyPolicy/LayerUtilWeekendGapRepro.test.ts +112 -0
- package/Tests/Types/OnCallDutyPolicy/OverrideLensAudit.test.ts +117 -0
- package/Tests/Types/OnCallDutyPolicy/RestrictionTimes.test.ts +8 -6
- package/Tests/Types/OnCallDutyPolicy/RestrictionTimesDefaultDayAudit.test.ts +112 -0
- package/Tests/Types/OnCallDutyPolicy/RestrictionTimesExhaustive.test.ts +821 -0
- package/Tests/Types/OnCallDutyPolicy/RestrictionTimesMutationInvariant.test.ts +886 -0
- package/Tests/Types/OnCallDutyPolicy/UserOverrideUtil.test.ts +255 -0
- package/Tests/Types/OnCallDutyPolicy/UserOverrideUtilExhaustive.test.ts +1126 -0
- package/Types/AI/AIChatMessageRole.ts +6 -0
- package/Types/AI/AIChatMessageStatus.ts +32 -0
- package/Types/AI/AIChatPermissionMode.ts +69 -0
- package/Types/AI/AIChatTypes.ts +231 -0
- package/Types/AI/AIRunEventType.ts +17 -0
- package/Types/AI/AIRunStatus.ts +28 -0
- package/Types/AI/AIRunType.ts +6 -0
- package/Types/Date.ts +200 -15
- package/Types/LLM/LlmType.ts +6 -0
- package/Types/OnCallDutyPolicy/Layer.ts +790 -260
- package/Types/OnCallDutyPolicy/RestrictionTimes.ts +39 -13
- package/Types/OnCallDutyPolicy/UserOverrideUtil.ts +21 -5
- package/UI/Components/Events/RecurringFieldElement.tsx +16 -0
- package/UI/Components/Header/HeaderIconDropdownButton.tsx +22 -12
- package/UI/Components/Markdown.tsx/MarkdownViewer.tsx +35 -3
- package/UI/Components/ModelFormModal/ModelFormModal.tsx +8 -0
- package/UI/Components/Page/Page.tsx +14 -0
- package/UI/Components/RadioButtons/BasicRadioButtons.tsx +1 -1
- package/UI/Utils/LlmTypeDropdownOptions.ts +40 -0
- package/UI/Utils/TestLLMProvider.ts +59 -0
- package/build/dist/Models/DatabaseModels/AIConversation.js +345 -0
- package/build/dist/Models/DatabaseModels/AIConversation.js.map +1 -0
- package/build/dist/Models/DatabaseModels/AIConversationMessage.js +521 -0
- package/build/dist/Models/DatabaseModels/AIConversationMessage.js.map +1 -0
- package/build/dist/Models/DatabaseModels/AIRun.js +619 -0
- package/build/dist/Models/DatabaseModels/AIRun.js.map +1 -0
- package/build/dist/Models/DatabaseModels/AIRunEvent.js +469 -0
- package/build/dist/Models/DatabaseModels/AIRunEvent.js.map +1 -0
- package/build/dist/Models/DatabaseModels/IncidentEpisodeRoleMember.js +1 -1
- package/build/dist/Models/DatabaseModels/IncidentEpisodeRoleMember.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncidentMember.js +1 -1
- package/build/dist/Models/DatabaseModels/IncidentMember.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncomingCallPolicy.js +10 -0
- package/build/dist/Models/DatabaseModels/IncomingCallPolicy.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Index.js +10 -0
- package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
- package/build/dist/Models/DatabaseModels/LlmLog.js +28 -0
- package/build/dist/Models/DatabaseModels/LlmLog.js.map +1 -1
- package/build/dist/Models/DatabaseModels/LlmProvider.js +2 -2
- package/build/dist/Models/DatabaseModels/LlmProvider.js.map +1 -1
- package/build/dist/Models/DatabaseModels/MigrationFailure.js +196 -0
- package/build/dist/Models/DatabaseModels/MigrationFailure.js.map +1 -0
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyExecutionLog.js +52 -2
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyExecutionLog.js.map +1 -1
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicySchedule.js +45 -0
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicySchedule.js.map +1 -1
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyScheduleLayer.js +6 -0
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyScheduleLayer.js.map +1 -1
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyScheduleLayerUser.js +6 -0
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyScheduleLayerUser.js.map +1 -1
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyTimeLog.js +1 -1
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyTimeLog.js.map +1 -1
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyUserOverride.js +2 -2
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyUserOverride.js.map +1 -1
- package/build/dist/Server/API/AIChatAPI.js +498 -0
- package/build/dist/Server/API/AIChatAPI.js.map +1 -0
- package/build/dist/Server/API/AlertAPI.js +1 -1
- package/build/dist/Server/API/IncidentAPI.js +2 -2
- package/build/dist/Server/API/IncidentEpisodeAPI.js +1 -1
- package/build/dist/Server/API/LlmProviderAPI.js +108 -1
- package/build/dist/Server/API/LlmProviderAPI.js.map +1 -1
- package/build/dist/Server/API/MicrosoftTeamsAPI.js +4 -0
- package/build/dist/Server/API/MicrosoftTeamsAPI.js.map +1 -1
- package/build/dist/Server/API/ScheduledMaintenanceAPI.js +1 -1
- package/build/dist/Server/API/SlackAPI.js +442 -0
- package/build/dist/Server/API/SlackAPI.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783363279075-AddAIChatModels.js +96 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783363279075-AddAIChatModels.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783443471795-AddLlmProviderToAIConversation.js +25 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783443471795-AddLlmProviderToAIConversation.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783453297388-AddAIChatWriteActionsAndWidgets.js +31 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783453297388-AddAIChatWriteActionsAndWidgets.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783461767405-MigrationName.js +46 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783461767405-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783470000000-AddIncomingCallPolicyRoutingPhoneNumberUnique.js +18 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783470000000-AddIncomingCallPolicyRoutingPhoneNumberUnique.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783510935686-FixNotNullForeignKeysOnDelete.js +38 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783510935686-FixNotNullForeignKeysOnDelete.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783515836148-MigrationName.js +16 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783515836148-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783523076215-AddMigrationFailureTable.js +18 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783523076215-AddMigrationFailureTable.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +16 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Infrastructure/PostgresDatabase.js +9 -0
- package/build/dist/Server/Infrastructure/PostgresDatabase.js.map +1 -1
- package/build/dist/Server/Services/AIConversationMessageService.js +34 -0
- package/build/dist/Server/Services/AIConversationMessageService.js.map +1 -0
- package/build/dist/Server/Services/AIConversationService.js +42 -0
- package/build/dist/Server/Services/AIConversationService.js.map +1 -0
- package/build/dist/Server/Services/AIRunEventService.js +34 -0
- package/build/dist/Server/Services/AIRunEventService.js.map +1 -0
- package/build/dist/Server/Services/AIRunService.js +34 -0
- package/build/dist/Server/Services/AIRunService.js.map +1 -0
- package/build/dist/Server/Services/AIService.js +84 -25
- package/build/dist/Server/Services/AIService.js.map +1 -1
- package/build/dist/Server/Services/AnalyticsDatabaseService.js +10 -1
- package/build/dist/Server/Services/AnalyticsDatabaseService.js.map +1 -1
- package/build/dist/Server/Services/DatabaseService.js +20 -1
- package/build/dist/Server/Services/DatabaseService.js.map +1 -1
- package/build/dist/Server/Services/IncidentService.js +1 -1
- package/build/dist/Server/Services/IncomingCallPolicyEscalationRuleService.js +55 -2
- package/build/dist/Server/Services/IncomingCallPolicyEscalationRuleService.js.map +1 -1
- package/build/dist/Server/Services/IncomingCallPolicyService.js +40 -0
- package/build/dist/Server/Services/IncomingCallPolicyService.js.map +1 -1
- package/build/dist/Server/Services/Index.js +2 -0
- package/build/dist/Server/Services/Index.js.map +1 -1
- package/build/dist/Server/Services/LlmProviderService.js +108 -0
- package/build/dist/Server/Services/LlmProviderService.js.map +1 -1
- package/build/dist/Server/Services/MigrationFailureService.js +9 -0
- package/build/dist/Server/Services/MigrationFailureService.js.map +1 -0
- package/build/dist/Server/Services/MonitorProbeService.js +33 -7
- package/build/dist/Server/Services/MonitorProbeService.js.map +1 -1
- package/build/dist/Server/Services/MonitorService.js +12 -5
- package/build/dist/Server/Services/MonitorService.js.map +1 -1
- package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.js +81 -22
- package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.js.map +1 -1
- package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleService.js +134 -31
- package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleService.js.map +1 -1
- package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleUserService.js +21 -13
- package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleUserService.js.map +1 -1
- package/build/dist/Server/Services/OnCallDutyPolicyExecutionLogService.js +36 -0
- package/build/dist/Server/Services/OnCallDutyPolicyExecutionLogService.js.map +1 -1
- package/build/dist/Server/Services/OnCallDutyPolicyScheduleLayerService.js +121 -0
- package/build/dist/Server/Services/OnCallDutyPolicyScheduleLayerService.js.map +1 -1
- package/build/dist/Server/Services/OnCallDutyPolicyScheduleLayerUserService.js +11 -2
- package/build/dist/Server/Services/OnCallDutyPolicyScheduleLayerUserService.js.map +1 -1
- package/build/dist/Server/Services/OnCallDutyPolicyScheduleService.js +409 -36
- package/build/dist/Server/Services/OnCallDutyPolicyScheduleService.js.map +1 -1
- package/build/dist/Server/Services/OnCallDutyPolicyTimeLogService.js +52 -8
- package/build/dist/Server/Services/OnCallDutyPolicyTimeLogService.js.map +1 -1
- package/build/dist/Server/Services/OnCallDutyPolicyUserOverrideService.js +170 -3
- package/build/dist/Server/Services/OnCallDutyPolicyUserOverrideService.js.map +1 -1
- package/build/dist/Server/Services/ProjectCallSMSConfigService.js +72 -0
- package/build/dist/Server/Services/ProjectCallSMSConfigService.js.map +1 -1
- package/build/dist/Server/Services/ProjectService.js +25 -0
- package/build/dist/Server/Services/ProjectService.js.map +1 -1
- package/build/dist/Server/Services/TeamMemberService.js +6 -0
- package/build/dist/Server/Services/TeamMemberService.js.map +1 -1
- package/build/dist/Server/Services/UserNotificationRuleService.js +12 -30
- package/build/dist/Server/Services/UserNotificationRuleService.js.map +1 -1
- package/build/dist/Server/Services/UserOnCallLogService.js +75 -0
- package/build/dist/Server/Services/UserOnCallLogService.js.map +1 -1
- package/build/dist/Server/Services/UserOnCallLogTimelineService.js +31 -1
- package/build/dist/Server/Services/UserOnCallLogTimelineService.js.map +1 -1
- package/build/dist/Server/Types/AnalyticsDatabase/ModelPermission.js +50 -7
- package/build/dist/Server/Types/AnalyticsDatabase/ModelPermission.js.map +1 -1
- package/build/dist/Server/Types/Database/QueryUtil.js +45 -0
- package/build/dist/Server/Types/Database/QueryUtil.js.map +1 -1
- package/build/dist/Server/Utils/AI/AIChatPrivacyFilter.js +18 -0
- package/build/dist/Server/Utils/AI/AIChatPrivacyFilter.js.map +1 -0
- package/build/dist/Server/Utils/AI/AlertAIContextBuilder.js +6 -1
- package/build/dist/Server/Utils/AI/AlertAIContextBuilder.js.map +1 -1
- package/build/dist/Server/Utils/AI/Chat/ChatAgentRunner.js +756 -0
- package/build/dist/Server/Utils/AI/Chat/ChatAgentRunner.js.map +1 -0
- package/build/dist/Server/Utils/AI/Chat/ObservabilityAssistant.js +165 -0
- package/build/dist/Server/Utils/AI/Chat/ObservabilityAssistant.js.map +1 -0
- package/build/dist/Server/Utils/AI/Chat/ObservabilityChatPrompt.js +44 -0
- package/build/dist/Server/Utils/AI/Chat/ObservabilityChatPrompt.js.map +1 -0
- package/build/dist/Server/Utils/AI/IncidentAIContextBuilder.js +18 -3
- package/build/dist/Server/Utils/AI/IncidentAIContextBuilder.js.map +1 -1
- package/build/dist/Server/Utils/AI/IncidentEpisodeAIContextBuilder.js +6 -1
- package/build/dist/Server/Utils/AI/IncidentEpisodeAIContextBuilder.js.map +1 -1
- package/build/dist/Server/Utils/AI/ScheduledMaintenanceAIContextBuilder.js +12 -2
- package/build/dist/Server/Utils/AI/ScheduledMaintenanceAIContextBuilder.js.map +1 -1
- package/build/dist/Server/Utils/AI/Toolbox/AlertTools.js +167 -0
- package/build/dist/Server/Utils/AI/Toolbox/AlertTools.js.map +1 -0
- package/build/dist/Server/Utils/AI/Toolbox/AlertWriteTools.js +136 -0
- package/build/dist/Server/Utils/AI/Toolbox/AlertWriteTools.js.map +1 -0
- package/build/dist/Server/Utils/AI/Toolbox/ContextTools.js +141 -0
- package/build/dist/Server/Utils/AI/Toolbox/ContextTools.js.map +1 -0
- package/build/dist/Server/Utils/AI/Toolbox/ExceptionTools.js +117 -0
- package/build/dist/Server/Utils/AI/Toolbox/ExceptionTools.js.map +1 -0
- package/build/dist/Server/Utils/AI/Toolbox/IncidentTools.js +318 -0
- package/build/dist/Server/Utils/AI/Toolbox/IncidentTools.js.map +1 -0
- package/build/dist/Server/Utils/AI/Toolbox/IncidentWriteTools.js +280 -0
- package/build/dist/Server/Utils/AI/Toolbox/IncidentWriteTools.js.map +1 -0
- package/build/dist/Server/Utils/AI/Toolbox/Index.js +153 -0
- package/build/dist/Server/Utils/AI/Toolbox/Index.js.map +1 -0
- package/build/dist/Server/Utils/AI/Toolbox/LogTools.js +246 -0
- package/build/dist/Server/Utils/AI/Toolbox/LogTools.js.map +1 -0
- package/build/dist/Server/Utils/AI/Toolbox/MetricTools.js +120 -0
- package/build/dist/Server/Utils/AI/Toolbox/MetricTools.js.map +1 -0
- package/build/dist/Server/Utils/AI/Toolbox/MonitorTools.js +158 -0
- package/build/dist/Server/Utils/AI/Toolbox/MonitorTools.js.map +1 -0
- package/build/dist/Server/Utils/AI/Toolbox/RecentChangesTools.js +165 -0
- package/build/dist/Server/Utils/AI/Toolbox/RecentChangesTools.js.map +1 -0
- package/build/dist/Server/Utils/AI/Toolbox/Serializer.js +228 -0
- package/build/dist/Server/Utils/AI/Toolbox/Serializer.js.map +1 -0
- package/build/dist/Server/Utils/AI/Toolbox/ToolTypes.js +142 -0
- package/build/dist/Server/Utils/AI/Toolbox/ToolTypes.js.map +1 -0
- package/build/dist/Server/Utils/AI/Toolbox/TraceTools.js +309 -0
- package/build/dist/Server/Utils/AI/Toolbox/TraceTools.js.map +1 -0
- package/build/dist/Server/Utils/AI/Toolbox/WidgetBuilder.js +120 -0
- package/build/dist/Server/Utils/AI/Toolbox/WidgetBuilder.js.map +1 -0
- package/build/dist/Server/Utils/Database/MigrationFailureLog.js +174 -0
- package/build/dist/Server/Utils/Database/MigrationFailureLog.js.map +1 -0
- package/build/dist/Server/Utils/IncomingCallPhoneNumber.js +30 -0
- package/build/dist/Server/Utils/IncomingCallPhoneNumber.js.map +1 -0
- package/build/dist/Server/Utils/LLM/LLMService.js +452 -92
- package/build/dist/Server/Utils/LLM/LLMService.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/IoTSnapshotScan.js +11 -8
- package/build/dist/Server/Utils/Telemetry/IoTSnapshotScan.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/ProxmoxCephSnapshotScan.js +11 -8
- package/build/dist/Server/Utils/Telemetry/ProxmoxCephSnapshotScan.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +378 -15
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/Slack/Slack.js +59 -0
- package/build/dist/Server/Utils/Workspace/Slack/Slack.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/Slack/app-manifest.json +16 -1
- package/build/dist/Types/AI/AIChatMessageRole.js +7 -0
- package/build/dist/Types/AI/AIChatMessageRole.js.map +1 -0
- package/build/dist/Types/AI/AIChatMessageStatus.js +27 -0
- package/build/dist/Types/AI/AIChatMessageStatus.js.map +1 -0
- package/build/dist/Types/AI/AIChatPermissionMode.js +53 -0
- package/build/dist/Types/AI/AIChatPermissionMode.js.map +1 -0
- package/build/dist/Types/AI/AIChatTypes.js +74 -0
- package/build/dist/Types/AI/AIChatTypes.js.map +1 -0
- package/build/dist/Types/AI/AIRunEventType.js +18 -0
- package/build/dist/Types/AI/AIRunEventType.js.map +1 -0
- package/build/dist/Types/AI/AIRunStatus.js +26 -0
- package/build/dist/Types/AI/AIRunStatus.js.map +1 -0
- package/build/dist/Types/AI/AIRunType.js +7 -0
- package/build/dist/Types/AI/AIRunType.js.map +1 -0
- package/build/dist/Types/Date.js +153 -15
- package/build/dist/Types/Date.js.map +1 -1
- package/build/dist/Types/LLM/LlmType.js +6 -0
- package/build/dist/Types/LLM/LlmType.js.map +1 -1
- package/build/dist/Types/OnCallDutyPolicy/Layer.js +602 -171
- package/build/dist/Types/OnCallDutyPolicy/Layer.js.map +1 -1
- package/build/dist/Types/OnCallDutyPolicy/RestrictionTimes.js +27 -13
- package/build/dist/Types/OnCallDutyPolicy/RestrictionTimes.js.map +1 -1
- package/build/dist/Types/OnCallDutyPolicy/UserOverrideUtil.js +20 -5
- package/build/dist/Types/OnCallDutyPolicy/UserOverrideUtil.js.map +1 -1
- package/build/dist/UI/Components/Events/RecurringFieldElement.js +13 -0
- package/build/dist/UI/Components/Events/RecurringFieldElement.js.map +1 -1
- package/build/dist/UI/Components/Header/HeaderIconDropdownButton.js +10 -5
- package/build/dist/UI/Components/Header/HeaderIconDropdownButton.js.map +1 -1
- package/build/dist/UI/Components/Markdown.tsx/MarkdownViewer.js +18 -4
- package/build/dist/UI/Components/Markdown.tsx/MarkdownViewer.js.map +1 -1
- package/build/dist/UI/Components/ModelFormModal/ModelFormModal.js.map +1 -1
- package/build/dist/UI/Components/Page/Page.js +3 -1
- package/build/dist/UI/Components/Page/Page.js.map +1 -1
- package/build/dist/UI/Components/RadioButtons/BasicRadioButtons.js +1 -1
- package/build/dist/UI/Components/RadioButtons/BasicRadioButtons.js.map +1 -1
- package/build/dist/UI/Utils/LlmTypeDropdownOptions.js +38 -0
- package/build/dist/UI/Utils/LlmTypeDropdownOptions.js.map +1 -0
- package/build/dist/UI/Utils/TestLLMProvider.js +37 -0
- package/build/dist/UI/Utils/TestLLMProvider.js.map +1 -0
- 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
|
-
|
|
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(
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
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
|
-
|
|
230
|
-
|
|
514
|
+
return this.parseOpenAIResponse(
|
|
515
|
+
response.jsonData as JSONObject,
|
|
516
|
+
"Azure OpenAI",
|
|
517
|
+
);
|
|
518
|
+
}
|
|
231
519
|
|
|
232
|
-
|
|
233
|
-
|
|
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
|
-
|
|
237
|
-
const usage: JSONObject = jsonData["usage"] as JSONObject;
|
|
526
|
+
private static readonly ANTHROPIC_DEFAULT_MAX_TOKENS: number = 4096;
|
|
238
527
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
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:
|
|
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"] =
|
|
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:
|
|
325
|
-
(
|
|
326
|
-
return
|
|
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
|
|
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
|
-
|
|
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
|
}
|