@oneuptime/common 11.3.25 → 11.3.26
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/Index.ts +8 -0
- package/Models/DatabaseModels/LlmLog.ts +26 -0
- package/Models/DatabaseModels/LlmProvider.ts +2 -2
- package/Server/API/AIChatAPI.ts +693 -0
- package/Server/API/LlmProviderAPI.ts +169 -0
- package/Server/API/MicrosoftTeamsAPI.ts +5 -0
- 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/Index.ts +8 -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 +53 -31
- package/Server/Services/DatabaseService.ts +19 -0
- package/Server/Services/LlmProviderService.ts +110 -0
- package/Server/Services/ProjectService.ts +24 -0
- package/Server/Types/AnalyticsDatabase/ModelPermission.ts +74 -9
- package/Server/Utils/AI/AIChatPrivacyFilter.ts +28 -0
- package/Server/Utils/AI/Chat/ChatAgentRunner.ts +1054 -0
- package/Server/Utils/AI/Chat/ObservabilityAssistant.ts +239 -0
- package/Server/Utils/AI/Chat/ObservabilityChatPrompt.ts +51 -0
- 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 +204 -0
- package/Server/Utils/AI/Toolbox/IncidentWriteTools.ts +350 -0
- package/Server/Utils/AI/Toolbox/Index.ts +228 -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/Serializer.ts +257 -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/LLM/LLMService.ts +502 -92
- 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/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 +308 -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 +155 -0
- package/Tests/Types/Billing/SubscriptionStatus.test.ts +127 -0
- package/Tests/Types/Metrics/RecordingRuleDefinition.test.ts +213 -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/LLM/LlmType.ts +6 -0
- package/UI/Components/Header/HeaderIconDropdownButton.tsx +22 -12
- package/UI/Components/Markdown.tsx/MarkdownViewer.tsx +35 -3
- package/UI/Components/Page/Page.tsx +14 -0
- 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/Index.js +8 -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/Server/API/AIChatAPI.js +498 -0
- package/build/dist/Server/API/AIChatAPI.js.map +1 -0
- package/build/dist/Server/API/LlmProviderAPI.js +107 -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/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/Index.js +8 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.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 +31 -23
- package/build/dist/Server/Services/AIService.js.map +1 -1
- package/build/dist/Server/Services/DatabaseService.js +10 -0
- package/build/dist/Server/Services/DatabaseService.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/ProjectService.js +25 -0
- package/build/dist/Server/Services/ProjectService.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/Utils/AI/AIChatPrivacyFilter.js +18 -0
- package/build/dist/Server/Utils/AI/AIChatPrivacyFilter.js.map +1 -0
- package/build/dist/Server/Utils/AI/Chat/ChatAgentRunner.js +745 -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/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 +167 -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 +150 -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/Serializer.js +188 -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/LLM/LLMService.js +372 -88
- package/build/dist/Server/Utils/LLM/LLMService.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/LLM/LlmType.js +6 -0
- package/build/dist/Types/LLM/LlmType.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/Page/Page.js +3 -1
- package/build/dist/UI/Components/Page/Page.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
|
@@ -91,6 +91,16 @@ import MicrosoftTeamsMonitorActions from "./Actions/Monitor";
|
|
|
91
91
|
import MicrosoftTeamsScheduledMaintenanceActions from "./Actions/ScheduledMaintenance";
|
|
92
92
|
import MicrosoftTeamsOnCallDutyActions from "./Actions/OnCallDutyPolicy";
|
|
93
93
|
|
|
94
|
+
/*
|
|
95
|
+
* AI Ops - observability assistant imports. These power the natural-language
|
|
96
|
+
* "ask" experience where a Teams user can question the OneUptime AI about
|
|
97
|
+
* their logs, traces, metrics, incidents and monitors.
|
|
98
|
+
*/
|
|
99
|
+
import type { ObservabilityAssistantResult } from "../../AI/Chat/ObservabilityAssistant";
|
|
100
|
+
import AccessTokenService from "../../../Services/AccessTokenService";
|
|
101
|
+
import DatabaseCommonInteractionProps from "../../../../Types/BaseDatabase/DatabaseCommonInteractionProps";
|
|
102
|
+
import { AIChatCitation } from "../../../../Types/AI/AIChatTypes";
|
|
103
|
+
|
|
94
104
|
// Microsoft Teams apps should always be single-tenant
|
|
95
105
|
const MICROSOFT_TEAMS_APP_TYPE: string = "SingleTenant";
|
|
96
106
|
|
|
@@ -1890,6 +1900,27 @@ export default class MicrosoftTeamsUtil extends WorkspaceBase {
|
|
|
1890
1900
|
logger.debug(`Channel data: ${JSON.stringify(channelData)}`);
|
|
1891
1901
|
logger.debug(`Entities: ${JSON.stringify(entities)}`);
|
|
1892
1902
|
|
|
1903
|
+
/*
|
|
1904
|
+
* Loop-guard: never process the bot's own messages. Teams generally does not
|
|
1905
|
+
* echo the bot to itself, but if the sender is the bot recipient (same id) or
|
|
1906
|
+
* is flagged with the "bot" role, ignore the activity to avoid a self-reply
|
|
1907
|
+
* loop when routing free-form text to the AI assistant.
|
|
1908
|
+
*/
|
|
1909
|
+
const senderId: string = (from["id"] as string) || "";
|
|
1910
|
+
const botRecipientId: string = (data.activity["recipient"] as JSONObject)?.[
|
|
1911
|
+
"id"
|
|
1912
|
+
] as string;
|
|
1913
|
+
const senderRole: string = (from["role"] as string) || "";
|
|
1914
|
+
if (
|
|
1915
|
+
senderRole === "bot" ||
|
|
1916
|
+
(senderId && botRecipientId && senderId === botRecipientId)
|
|
1917
|
+
) {
|
|
1918
|
+
logger.debug(
|
|
1919
|
+
"Message activity originates from the bot itself; ignoring to prevent a loop",
|
|
1920
|
+
);
|
|
1921
|
+
return;
|
|
1922
|
+
}
|
|
1923
|
+
|
|
1893
1924
|
// If this is actually an Adaptive Card submit wrapped as a message, route to invoke handler
|
|
1894
1925
|
if (
|
|
1895
1926
|
(possibleActionValue["action"] as string) ||
|
|
@@ -1974,6 +2005,15 @@ export default class MicrosoftTeamsUtil extends WorkspaceBase {
|
|
|
1974
2005
|
.trim()
|
|
1975
2006
|
.toLowerCase();
|
|
1976
2007
|
|
|
2008
|
+
/*
|
|
2009
|
+
* Preserve the original casing of the user's question. The AI assistant
|
|
2010
|
+
* should receive the free-form text exactly as the user typed it (case,
|
|
2011
|
+
* punctuation and spacing all matter), with only the bot @mention stripped.
|
|
2012
|
+
*/
|
|
2013
|
+
const originalQuestionText: string = messageText
|
|
2014
|
+
.replace(/<at[^>]*>.*?<\/at>/g, "")
|
|
2015
|
+
.trim();
|
|
2016
|
+
|
|
1977
2017
|
let responseText: string = "";
|
|
1978
2018
|
|
|
1979
2019
|
try {
|
|
@@ -1985,7 +2025,38 @@ export default class MicrosoftTeamsUtil extends WorkspaceBase {
|
|
|
1985
2025
|
cleanText === "create maintenance" ||
|
|
1986
2026
|
cleanText.startsWith("create maintenance ");
|
|
1987
2027
|
|
|
1988
|
-
|
|
2028
|
+
/*
|
|
2029
|
+
* Explicit commands are matched precisely so that natural-language
|
|
2030
|
+
* questions (which may incidentally contain words like "help" or
|
|
2031
|
+
* "alerts") fall through to the AI assistant instead of a canned command.
|
|
2032
|
+
*/
|
|
2033
|
+
const isHelpCommand: boolean =
|
|
2034
|
+
cleanText === "help" || cleanText === "" || cleanText === "?";
|
|
2035
|
+
|
|
2036
|
+
const isShowActiveIncidentsCommand: boolean =
|
|
2037
|
+
cleanText === "show active incidents" ||
|
|
2038
|
+
cleanText === "active incidents";
|
|
2039
|
+
|
|
2040
|
+
const isShowScheduledMaintenanceCommand: boolean =
|
|
2041
|
+
cleanText === "show scheduled maintenance" ||
|
|
2042
|
+
cleanText === "scheduled maintenance";
|
|
2043
|
+
|
|
2044
|
+
const isShowOngoingMaintenanceCommand: boolean =
|
|
2045
|
+
cleanText === "show ongoing maintenance" ||
|
|
2046
|
+
cleanText === "ongoing maintenance";
|
|
2047
|
+
|
|
2048
|
+
const isShowActiveAlertsCommand: boolean =
|
|
2049
|
+
cleanText === "show active alerts" || cleanText === "active alerts";
|
|
2050
|
+
|
|
2051
|
+
/*
|
|
2052
|
+
* "ask <question>" is an explicit prefix that always routes to the AI
|
|
2053
|
+
* assistant. When present, strip the prefix and use the remainder as the
|
|
2054
|
+
* question.
|
|
2055
|
+
*/
|
|
2056
|
+
const isAskCommand: boolean =
|
|
2057
|
+
cleanText === "ask" || cleanText.startsWith("ask ");
|
|
2058
|
+
|
|
2059
|
+
if (isHelpCommand) {
|
|
1989
2060
|
responseText = this.getHelpMessage();
|
|
1990
2061
|
} else if (isCreateIncidentCommand) {
|
|
1991
2062
|
// Handle create incident command (legacy slash command supported)
|
|
@@ -2019,28 +2090,38 @@ export default class MicrosoftTeamsUtil extends WorkspaceBase {
|
|
|
2019
2090
|
});
|
|
2020
2091
|
logger.debug("New scheduled maintenance card sent successfully");
|
|
2021
2092
|
return;
|
|
2022
|
-
} else if (
|
|
2023
|
-
cleanText.includes("show active incidents") ||
|
|
2024
|
-
cleanText.includes("active incidents")
|
|
2025
|
-
) {
|
|
2093
|
+
} else if (isShowActiveIncidentsCommand) {
|
|
2026
2094
|
responseText = await this.getActiveIncidentsMessage(projectId);
|
|
2027
|
-
} else if (
|
|
2028
|
-
cleanText.includes("show scheduled maintenance") ||
|
|
2029
|
-
cleanText.includes("scheduled maintenance")
|
|
2030
|
-
) {
|
|
2095
|
+
} else if (isShowScheduledMaintenanceCommand) {
|
|
2031
2096
|
responseText = await this.getScheduledMaintenanceMessage(projectId);
|
|
2032
|
-
} else if (
|
|
2033
|
-
cleanText.includes("show ongoing maintenance") ||
|
|
2034
|
-
cleanText.includes("ongoing maintenance")
|
|
2035
|
-
) {
|
|
2097
|
+
} else if (isShowOngoingMaintenanceCommand) {
|
|
2036
2098
|
responseText = await this.getOngoingMaintenanceMessage(projectId);
|
|
2037
|
-
} else if (
|
|
2038
|
-
cleanText.includes("show active alerts") ||
|
|
2039
|
-
cleanText.includes("active alerts")
|
|
2040
|
-
) {
|
|
2099
|
+
} else if (isShowActiveAlertsCommand) {
|
|
2041
2100
|
responseText = await this.getActiveAlertsMessage(projectId);
|
|
2042
2101
|
} else {
|
|
2043
|
-
|
|
2102
|
+
/*
|
|
2103
|
+
* AI Ops: any message that is not one of the explicit commands above is
|
|
2104
|
+
* treated as a natural-language question for the observability
|
|
2105
|
+
* assistant. This also handles the explicit "ask <question>" prefix.
|
|
2106
|
+
*/
|
|
2107
|
+
const question: string = isAskCommand
|
|
2108
|
+
? originalQuestionText.replace(/^ask\s*/i, "").trim()
|
|
2109
|
+
: originalQuestionText;
|
|
2110
|
+
|
|
2111
|
+
if (!question) {
|
|
2112
|
+
// Bare "ask" with no question - point the user at help.
|
|
2113
|
+
responseText = this.getHelpMessage();
|
|
2114
|
+
await data.turnContext.sendActivity(responseText);
|
|
2115
|
+
return;
|
|
2116
|
+
}
|
|
2117
|
+
|
|
2118
|
+
await this.answerObservabilityQuestion({
|
|
2119
|
+
activity: data.activity,
|
|
2120
|
+
turnContext: data.turnContext,
|
|
2121
|
+
projectId: projectId,
|
|
2122
|
+
question: question,
|
|
2123
|
+
});
|
|
2124
|
+
return;
|
|
2044
2125
|
}
|
|
2045
2126
|
|
|
2046
2127
|
// Send response directly using TurnContext - this is the recommended Bot Framework pattern
|
|
@@ -2059,12 +2140,419 @@ export default class MicrosoftTeamsUtil extends WorkspaceBase {
|
|
|
2059
2140
|
}
|
|
2060
2141
|
}
|
|
2061
2142
|
|
|
2143
|
+
/*
|
|
2144
|
+
* AI Ops: transient acknowledgement text the bot posts before answering. We
|
|
2145
|
+
* must skip these when reconstructing conversation history so the assistant
|
|
2146
|
+
* never treats its own "please wait" filler as a real prior turn.
|
|
2147
|
+
*/
|
|
2148
|
+
private static readonly AI_OPS_ACK_TEXT: string = "Looking into it…";
|
|
2149
|
+
|
|
2150
|
+
/*
|
|
2151
|
+
* AI Ops: cap on how many prior turns we feed the assistant as history. The
|
|
2152
|
+
* engine also clamps history internally, but we keep the payload small.
|
|
2153
|
+
*/
|
|
2154
|
+
private static readonly AI_OPS_MAX_HISTORY_TURNS: number = 12;
|
|
2155
|
+
|
|
2156
|
+
/*
|
|
2157
|
+
* AI Ops: strip a Teams message body down to plain text - remove <at> bot
|
|
2158
|
+
* mentions and any remaining HTML tags, collapse whitespace, and trim. Teams
|
|
2159
|
+
* channel/chat message bodies are typically HTML (body.contentType "html").
|
|
2160
|
+
*/
|
|
2161
|
+
private static toPlainTextFromTeamsMessageBody(rawContent: string): string {
|
|
2162
|
+
return rawContent
|
|
2163
|
+
.replace(/<at[^>]*>.*?<\/at>/g, "")
|
|
2164
|
+
.replace(/<[^>]*>/g, "")
|
|
2165
|
+
.replace(/ /g, " ")
|
|
2166
|
+
.replace(/\s+/g, " ")
|
|
2167
|
+
.trim();
|
|
2168
|
+
}
|
|
2169
|
+
|
|
2170
|
+
/*
|
|
2171
|
+
* AI Ops: gather the prior turns of the current Teams conversation/thread and
|
|
2172
|
+
* map them to the assistant's history shape ({ role, content }, oldest-first,
|
|
2173
|
+
* excluding the current triggering message).
|
|
2174
|
+
*
|
|
2175
|
+
* Teams limitation: in channels a bot only receives messages that @mention it
|
|
2176
|
+
* (unless RSC / ChannelMessage.Read.Group is granted at install time), so
|
|
2177
|
+
* channel follow-ups generally require re-@mentioning the bot. 1:1 (personal)
|
|
2178
|
+
* chat follow-ups do not require a mention. If ids can't be parsed or the
|
|
2179
|
+
* Graph call fails, we degrade gracefully to no history - the caller wraps
|
|
2180
|
+
* this in a try/catch and never lets a history failure break the reply.
|
|
2181
|
+
*/
|
|
2182
|
+
@CaptureSpan()
|
|
2183
|
+
private static async getConversationHistoryTurns(data: {
|
|
2184
|
+
activity: JSONObject;
|
|
2185
|
+
projectId: ObjectID;
|
|
2186
|
+
}): Promise<Array<{ role: "user" | "assistant"; content: string }>> {
|
|
2187
|
+
const { activity, projectId } = data;
|
|
2188
|
+
|
|
2189
|
+
const conversation: JSONObject =
|
|
2190
|
+
(activity["conversation"] as JSONObject) || {};
|
|
2191
|
+
const channelData: JSONObject =
|
|
2192
|
+
(activity["channelData"] as JSONObject) || {};
|
|
2193
|
+
const conversationType: string =
|
|
2194
|
+
(conversation["conversationType"] as string) || "";
|
|
2195
|
+
const conversationId: string = (conversation["id"] as string) || "";
|
|
2196
|
+
const currentMessageId: string = (activity["id"] as string) || "";
|
|
2197
|
+
|
|
2198
|
+
/*
|
|
2199
|
+
* The bot's id - used to classify each message as "assistant" (from the
|
|
2200
|
+
* bot) vs "user". This is the same id the loop-guard compares against.
|
|
2201
|
+
*/
|
|
2202
|
+
const botId: string =
|
|
2203
|
+
((activity["recipient"] as JSONObject)?.["id"] as string) ||
|
|
2204
|
+
MicrosoftTeamsAppClientId ||
|
|
2205
|
+
"";
|
|
2206
|
+
|
|
2207
|
+
if (!conversationId) {
|
|
2208
|
+
return [];
|
|
2209
|
+
}
|
|
2210
|
+
|
|
2211
|
+
// Acquire a Graph app token using the same mechanism the rest of this file uses.
|
|
2212
|
+
const accessToken: string = await this.getValidAccessToken({
|
|
2213
|
+
authToken: "",
|
|
2214
|
+
projectId: projectId,
|
|
2215
|
+
});
|
|
2216
|
+
|
|
2217
|
+
// Collect raw Graph message objects (unordered; we sort at the end).
|
|
2218
|
+
let rawMessages: Array<JSONObject> = [];
|
|
2219
|
+
|
|
2220
|
+
if (conversationType === "personal") {
|
|
2221
|
+
/*
|
|
2222
|
+
* 1:1 chat: conversation.id is the chat id. Fetch recent chat messages.
|
|
2223
|
+
*/
|
|
2224
|
+
const chatId: string = conversationId;
|
|
2225
|
+
const response: HTTPErrorResponse | HTTPResponse<JSONObject> =
|
|
2226
|
+
await API.get<JSONObject>({
|
|
2227
|
+
url: URL.fromString(
|
|
2228
|
+
`https://graph.microsoft.com/v1.0/chats/${encodeURIComponent(
|
|
2229
|
+
chatId,
|
|
2230
|
+
)}/messages?$top=20`,
|
|
2231
|
+
),
|
|
2232
|
+
headers: {
|
|
2233
|
+
Authorization: `Bearer ${accessToken}`,
|
|
2234
|
+
"Content-Type": "application/json",
|
|
2235
|
+
},
|
|
2236
|
+
});
|
|
2237
|
+
|
|
2238
|
+
if (response instanceof HTTPErrorResponse) {
|
|
2239
|
+
logger.debug("Failed to fetch 1:1 chat history for AI Ops context");
|
|
2240
|
+
logger.debug(response);
|
|
2241
|
+
return [];
|
|
2242
|
+
}
|
|
2243
|
+
|
|
2244
|
+
rawMessages = (response.data["value"] as Array<JSONObject>) || [];
|
|
2245
|
+
} else {
|
|
2246
|
+
/*
|
|
2247
|
+
* Channel thread: conversation.id encodes the root message id as
|
|
2248
|
+
* "<channelId>;messageid=<rootId>". Team & channel ids come from
|
|
2249
|
+
* channelData. Fetch the root message plus its replies.
|
|
2250
|
+
*/
|
|
2251
|
+
const teamId: string | undefined = (channelData["team"] as JSONObject)?.[
|
|
2252
|
+
"id"
|
|
2253
|
+
] as string;
|
|
2254
|
+
const channelId: string | undefined = (
|
|
2255
|
+
channelData["channel"] as JSONObject
|
|
2256
|
+
)?.["id"] as string;
|
|
2257
|
+
|
|
2258
|
+
const messageIdMatch: RegExpMatchArray | null =
|
|
2259
|
+
conversationId.match(/messageid=(\d+)/);
|
|
2260
|
+
const rootMessageId: string | undefined = messageIdMatch?.[1];
|
|
2261
|
+
|
|
2262
|
+
if (!teamId || !channelId || !rootMessageId) {
|
|
2263
|
+
logger.debug(
|
|
2264
|
+
"Could not parse team/channel/root message ids for AI Ops channel history; proceeding without history",
|
|
2265
|
+
);
|
|
2266
|
+
return [];
|
|
2267
|
+
}
|
|
2268
|
+
|
|
2269
|
+
// Fetch replies in the thread.
|
|
2270
|
+
const repliesResponse: HTTPErrorResponse | HTTPResponse<JSONObject> =
|
|
2271
|
+
await API.get<JSONObject>({
|
|
2272
|
+
url: URL.fromString(
|
|
2273
|
+
`https://graph.microsoft.com/v1.0/teams/${teamId}/channels/${channelId}/messages/${rootMessageId}/replies?$top=20`,
|
|
2274
|
+
),
|
|
2275
|
+
headers: {
|
|
2276
|
+
Authorization: `Bearer ${accessToken}`,
|
|
2277
|
+
"Content-Type": "application/json",
|
|
2278
|
+
},
|
|
2279
|
+
});
|
|
2280
|
+
|
|
2281
|
+
if (repliesResponse instanceof HTTPErrorResponse) {
|
|
2282
|
+
logger.debug(
|
|
2283
|
+
"Failed to fetch channel thread replies for AI Ops context",
|
|
2284
|
+
);
|
|
2285
|
+
logger.debug(repliesResponse);
|
|
2286
|
+
} else {
|
|
2287
|
+
rawMessages =
|
|
2288
|
+
(repliesResponse.data["value"] as Array<JSONObject>) || [];
|
|
2289
|
+
}
|
|
2290
|
+
|
|
2291
|
+
// Also fetch the root message so the original question is part of context.
|
|
2292
|
+
const rootResponse: HTTPErrorResponse | HTTPResponse<JSONObject> =
|
|
2293
|
+
await API.get<JSONObject>({
|
|
2294
|
+
url: URL.fromString(
|
|
2295
|
+
`https://graph.microsoft.com/v1.0/teams/${teamId}/channels/${channelId}/messages/${rootMessageId}`,
|
|
2296
|
+
),
|
|
2297
|
+
headers: {
|
|
2298
|
+
Authorization: `Bearer ${accessToken}`,
|
|
2299
|
+
"Content-Type": "application/json",
|
|
2300
|
+
},
|
|
2301
|
+
});
|
|
2302
|
+
|
|
2303
|
+
if (!(rootResponse instanceof HTTPErrorResponse) && rootResponse.data) {
|
|
2304
|
+
rawMessages.push(rootResponse.data);
|
|
2305
|
+
}
|
|
2306
|
+
}
|
|
2307
|
+
|
|
2308
|
+
// Map each Graph message to a { role, content, createdAt, id } tuple.
|
|
2309
|
+
const mappedTurns: Array<{
|
|
2310
|
+
role: "user" | "assistant";
|
|
2311
|
+
content: string;
|
|
2312
|
+
createdAtMs: number;
|
|
2313
|
+
id: string;
|
|
2314
|
+
}> = [];
|
|
2315
|
+
|
|
2316
|
+
for (const message of rawMessages) {
|
|
2317
|
+
const messageId: string = (message["id"] as string) || "";
|
|
2318
|
+
|
|
2319
|
+
// Exclude the current triggering message - that is the live question.
|
|
2320
|
+
if (messageId && currentMessageId && messageId === currentMessageId) {
|
|
2321
|
+
continue;
|
|
2322
|
+
}
|
|
2323
|
+
|
|
2324
|
+
const body: JSONObject = (message["body"] as JSONObject) || {};
|
|
2325
|
+
const rawContent: string = (body["content"] as string) || "";
|
|
2326
|
+
const content: string = this.toPlainTextFromTeamsMessageBody(rawContent);
|
|
2327
|
+
|
|
2328
|
+
if (!content) {
|
|
2329
|
+
continue;
|
|
2330
|
+
}
|
|
2331
|
+
|
|
2332
|
+
// Skip the bot's transient "Looking into it…" acknowledgements.
|
|
2333
|
+
if (content === this.AI_OPS_ACK_TEXT) {
|
|
2334
|
+
continue;
|
|
2335
|
+
}
|
|
2336
|
+
|
|
2337
|
+
/*
|
|
2338
|
+
* Classify sender. Graph marks bot/app messages via from.application; a
|
|
2339
|
+
* matching application/user id to the bot id also means it is the bot.
|
|
2340
|
+
*/
|
|
2341
|
+
const fromObj: JSONObject = (message["from"] as JSONObject) || {};
|
|
2342
|
+
const fromApplication: JSONObject =
|
|
2343
|
+
(fromObj["application"] as JSONObject) || {};
|
|
2344
|
+
const fromUser: JSONObject = (fromObj["user"] as JSONObject) || {};
|
|
2345
|
+
const fromApplicationId: string = (fromApplication["id"] as string) || "";
|
|
2346
|
+
const fromUserId: string = (fromUser["id"] as string) || "";
|
|
2347
|
+
|
|
2348
|
+
const isFromBot: boolean =
|
|
2349
|
+
Boolean(fromApplication["id"]) ||
|
|
2350
|
+
(botId !== "" && (fromApplicationId === botId || fromUserId === botId));
|
|
2351
|
+
|
|
2352
|
+
const role: "user" | "assistant" = isFromBot ? "assistant" : "user";
|
|
2353
|
+
|
|
2354
|
+
const createdAtRaw: string = (message["createdDateTime"] as string) || "";
|
|
2355
|
+
const createdAtMs: number = createdAtRaw
|
|
2356
|
+
? new Date(createdAtRaw).getTime()
|
|
2357
|
+
: 0;
|
|
2358
|
+
|
|
2359
|
+
mappedTurns.push({
|
|
2360
|
+
role: role,
|
|
2361
|
+
content: content,
|
|
2362
|
+
createdAtMs: createdAtMs,
|
|
2363
|
+
id: messageId,
|
|
2364
|
+
});
|
|
2365
|
+
}
|
|
2366
|
+
|
|
2367
|
+
// Order oldest -> newest so the assistant reads the conversation in order.
|
|
2368
|
+
mappedTurns.sort(
|
|
2369
|
+
(a: { createdAtMs: number }, b: { createdAtMs: number }): number => {
|
|
2370
|
+
return a.createdAtMs - b.createdAtMs;
|
|
2371
|
+
},
|
|
2372
|
+
);
|
|
2373
|
+
|
|
2374
|
+
// Cap to the most recent turns.
|
|
2375
|
+
const cappedTurns: Array<{
|
|
2376
|
+
role: "user" | "assistant";
|
|
2377
|
+
content: string;
|
|
2378
|
+
}> = mappedTurns
|
|
2379
|
+
.slice(-this.AI_OPS_MAX_HISTORY_TURNS)
|
|
2380
|
+
.map((turn: { role: "user" | "assistant"; content: string }) => {
|
|
2381
|
+
return { role: turn.role, content: turn.content };
|
|
2382
|
+
});
|
|
2383
|
+
|
|
2384
|
+
return cappedTurns;
|
|
2385
|
+
}
|
|
2386
|
+
|
|
2387
|
+
/*
|
|
2388
|
+
* AI Ops: resolve the OneUptime user for the Teams sender, build their real
|
|
2389
|
+
* permission props, ask the observability assistant, and reply in the same
|
|
2390
|
+
* conversation with the markdown answer plus a compact "Sources" footer.
|
|
2391
|
+
*/
|
|
2392
|
+
@CaptureSpan()
|
|
2393
|
+
private static async answerObservabilityQuestion(data: {
|
|
2394
|
+
activity: JSONObject;
|
|
2395
|
+
turnContext: TurnContext;
|
|
2396
|
+
projectId: ObjectID;
|
|
2397
|
+
question: string;
|
|
2398
|
+
}): Promise<void> {
|
|
2399
|
+
const { activity, turnContext, projectId, question } = data;
|
|
2400
|
+
|
|
2401
|
+
/*
|
|
2402
|
+
* Resolve the Teams user. Teams identifies the sender by their Azure AD
|
|
2403
|
+
* object id (aadObjectId), which is what WorkspaceUserAuthToken stores as
|
|
2404
|
+
* the workspaceUserId. This mirrors how handleBotInvokeActivity resolves
|
|
2405
|
+
* the acting user.
|
|
2406
|
+
*/
|
|
2407
|
+
const fromObj: JSONObject = (activity["from"] as JSONObject) || {};
|
|
2408
|
+
const teamsUserId: string | undefined =
|
|
2409
|
+
(fromObj["aadObjectId"] as string) || undefined;
|
|
2410
|
+
|
|
2411
|
+
if (!teamsUserId) {
|
|
2412
|
+
logger.error(
|
|
2413
|
+
"AAD Object ID (teamsUserId) not found in message activity from object",
|
|
2414
|
+
{
|
|
2415
|
+
projectId: projectId.toString(),
|
|
2416
|
+
},
|
|
2417
|
+
);
|
|
2418
|
+
await turnContext.sendActivity(
|
|
2419
|
+
"Sorry, I couldn't identify you. Please try again later.",
|
|
2420
|
+
);
|
|
2421
|
+
return;
|
|
2422
|
+
}
|
|
2423
|
+
|
|
2424
|
+
// Resolve the OneUptime user linked to this Teams user.
|
|
2425
|
+
let oneUptimeUserId: ObjectID;
|
|
2426
|
+
try {
|
|
2427
|
+
oneUptimeUserId =
|
|
2428
|
+
await MicrosoftTeamsAuthAction.getOneUptimeUserIdFromTeamsUserId({
|
|
2429
|
+
teamsUserId: teamsUserId,
|
|
2430
|
+
projectId: projectId,
|
|
2431
|
+
});
|
|
2432
|
+
} catch (error) {
|
|
2433
|
+
logger.debug(
|
|
2434
|
+
"No OneUptime user linked to Teams user; prompting to connect account",
|
|
2435
|
+
{
|
|
2436
|
+
projectId: projectId.toString(),
|
|
2437
|
+
workspaceUserId: teamsUserId,
|
|
2438
|
+
},
|
|
2439
|
+
);
|
|
2440
|
+
logger.debug(error);
|
|
2441
|
+
await turnContext.sendActivity(
|
|
2442
|
+
"I couldn't find your OneUptime account. Please connect your Microsoft Teams account in OneUptime User Settings before asking me questions.",
|
|
2443
|
+
);
|
|
2444
|
+
return;
|
|
2445
|
+
}
|
|
2446
|
+
|
|
2447
|
+
/*
|
|
2448
|
+
* Let the user know we're working on it. The assistant runs a bounded,
|
|
2449
|
+
* tool-grounded agent loop and can take several seconds, so send a quick
|
|
2450
|
+
* acknowledgement first.
|
|
2451
|
+
*/
|
|
2452
|
+
try {
|
|
2453
|
+
await turnContext.sendActivity(this.AI_OPS_ACK_TEXT);
|
|
2454
|
+
} catch (ackError) {
|
|
2455
|
+
// A failed acknowledgement should not stop us from answering.
|
|
2456
|
+
logger.debug("Failed to send acknowledgement activity");
|
|
2457
|
+
logger.debug(ackError);
|
|
2458
|
+
}
|
|
2459
|
+
|
|
2460
|
+
/*
|
|
2461
|
+
* Make the assistant context-aware: gather the prior turns of this
|
|
2462
|
+
* conversation/thread and pass them as history (oldest-first, excluding the
|
|
2463
|
+
* current question). Any failure here is non-fatal - we simply proceed
|
|
2464
|
+
* statelessly so a history problem never breaks the reply.
|
|
2465
|
+
*/
|
|
2466
|
+
let history: Array<{ role: "user" | "assistant"; content: string }> = [];
|
|
2467
|
+
try {
|
|
2468
|
+
history = await this.getConversationHistoryTurns({
|
|
2469
|
+
activity: activity,
|
|
2470
|
+
projectId: projectId,
|
|
2471
|
+
});
|
|
2472
|
+
logger.debug(
|
|
2473
|
+
`AI Ops gathered ${history.length} prior conversation turn(s) as history`,
|
|
2474
|
+
{
|
|
2475
|
+
projectId: projectId.toString(),
|
|
2476
|
+
},
|
|
2477
|
+
);
|
|
2478
|
+
} catch (historyError) {
|
|
2479
|
+
logger.debug(
|
|
2480
|
+
"Failed to gather AI Ops conversation history; proceeding statelessly",
|
|
2481
|
+
);
|
|
2482
|
+
logger.debug(historyError);
|
|
2483
|
+
history = [];
|
|
2484
|
+
}
|
|
2485
|
+
|
|
2486
|
+
try {
|
|
2487
|
+
// Build the user's real permission props - the assistant's tools run under these.
|
|
2488
|
+
const props: DatabaseCommonInteractionProps =
|
|
2489
|
+
await AccessTokenService.getDatabaseCommonInteractionPropsByUserAndProject(
|
|
2490
|
+
{
|
|
2491
|
+
userId: oneUptimeUserId,
|
|
2492
|
+
projectId: projectId,
|
|
2493
|
+
},
|
|
2494
|
+
);
|
|
2495
|
+
|
|
2496
|
+
/*
|
|
2497
|
+
* Loaded on demand via require(): importing the AI toolbox at module top
|
|
2498
|
+
* pulls the entire observability tool graph — and its database
|
|
2499
|
+
* infrastructure — into the core API module graph at import time, which
|
|
2500
|
+
* trips circular-dependency init-order crashes. Teams ChatOps is the only
|
|
2501
|
+
* caller, so it is resolved lazily here. A value-position dynamic
|
|
2502
|
+
* import() is avoided because it fails TS1323 under the consuming
|
|
2503
|
+
* projects' module configuration.
|
|
2504
|
+
*/
|
|
2505
|
+
/* eslint-disable @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires */
|
|
2506
|
+
const ObservabilityAssistant: typeof import("../../AI/Chat/ObservabilityAssistant").default =
|
|
2507
|
+
require("../../AI/Chat/ObservabilityAssistant").default;
|
|
2508
|
+
/* eslint-enable @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires */
|
|
2509
|
+
|
|
2510
|
+
const result: ObservabilityAssistantResult =
|
|
2511
|
+
await ObservabilityAssistant.answerQuestion({
|
|
2512
|
+
projectId: projectId,
|
|
2513
|
+
userId: oneUptimeUserId,
|
|
2514
|
+
props: props,
|
|
2515
|
+
question: question,
|
|
2516
|
+
...(history.length > 0 && { history: history }),
|
|
2517
|
+
feature: "Microsoft Teams ChatOps",
|
|
2518
|
+
});
|
|
2519
|
+
|
|
2520
|
+
// Build a compact "Sources" footer from the server-minted citations.
|
|
2521
|
+
let replyText: string = result.contentInMarkdown;
|
|
2522
|
+
|
|
2523
|
+
if (result.citations && result.citations.length > 0) {
|
|
2524
|
+
const sourceLines: Array<string> = result.citations.map(
|
|
2525
|
+
(citation: AIChatCitation) => {
|
|
2526
|
+
return `• ${citation.label} (${citation.rowCount} rows)`;
|
|
2527
|
+
},
|
|
2528
|
+
);
|
|
2529
|
+
replyText += `\n\n**Sources**\n${sourceLines.join("\n")}`;
|
|
2530
|
+
}
|
|
2531
|
+
|
|
2532
|
+
await turnContext.sendActivity(replyText);
|
|
2533
|
+
logger.debug("AI Ops answer sent successfully using TurnContext", {
|
|
2534
|
+
projectId: projectId.toString(),
|
|
2535
|
+
});
|
|
2536
|
+
} catch (error) {
|
|
2537
|
+
logger.error(
|
|
2538
|
+
"Error answering observability question via AI Ops: " + error,
|
|
2539
|
+
{
|
|
2540
|
+
projectId: projectId.toString(),
|
|
2541
|
+
},
|
|
2542
|
+
);
|
|
2543
|
+
await turnContext.sendActivity(
|
|
2544
|
+
"Sorry, I ran into a problem answering that question. Please try again later.",
|
|
2545
|
+
);
|
|
2546
|
+
}
|
|
2547
|
+
}
|
|
2548
|
+
|
|
2062
2549
|
// Helper methods for bot commands
|
|
2063
2550
|
private static getHelpMessage(): string {
|
|
2064
2551
|
return `Hello! I'm the OneUptime bot. I can help you with the following commands:
|
|
2065
2552
|
|
|
2066
2553
|
**Available Commands:**
|
|
2067
2554
|
- **help** - Show this help message
|
|
2555
|
+
- **ask <question>** - Ask OneUptime AI about your logs, traces, metrics, incidents and monitors
|
|
2068
2556
|
- **create incident** - Create a new incident
|
|
2069
2557
|
- **create maintenance** - Create a new scheduled maintenance event
|
|
2070
2558
|
- **show active incidents** - Display all currently active incidents
|
|
@@ -2072,7 +2560,7 @@ export default class MicrosoftTeamsUtil extends WorkspaceBase {
|
|
|
2072
2560
|
- **show ongoing maintenance** - Display currently ongoing maintenance events
|
|
2073
2561
|
- **show active alerts** - Display all active alerts
|
|
2074
2562
|
|
|
2075
|
-
|
|
2563
|
+
You can also just ask me a question in plain language - for example, "which monitors are down right now?" - and I'll look into your observability data for you.`;
|
|
2076
2564
|
}
|
|
2077
2565
|
|
|
2078
2566
|
private static async getActiveIncidentsMessage(
|
|
@@ -2949,6 +3437,11 @@ All monitoring checks are passing normally.`;
|
|
|
2949
3437
|
title: "help",
|
|
2950
3438
|
value: "Show quick help and useful links",
|
|
2951
3439
|
},
|
|
3440
|
+
{
|
|
3441
|
+
title: "ask",
|
|
3442
|
+
value:
|
|
3443
|
+
"Ask OneUptime AI about your logs, traces, metrics, incidents and monitors",
|
|
3444
|
+
},
|
|
2952
3445
|
{
|
|
2953
3446
|
title: "create incident",
|
|
2954
3447
|
value: "Create a new incident without leaving Teams",
|
|
@@ -1530,6 +1530,86 @@ export default class SlackUtil extends WorkspaceBase {
|
|
|
1530
1530
|
return messageText || null;
|
|
1531
1531
|
}
|
|
1532
1532
|
|
|
1533
|
+
/*
|
|
1534
|
+
* Fetches the replies of a Slack thread (oldest-first, as Slack returns them)
|
|
1535
|
+
* using the bot token. Used by the AI Ops threaded follow-up feature to build
|
|
1536
|
+
* conversation history for the observability assistant.
|
|
1537
|
+
*/
|
|
1538
|
+
@CaptureSpan()
|
|
1539
|
+
public static async getThreadReplies(data: {
|
|
1540
|
+
authToken: string;
|
|
1541
|
+
channelId: string;
|
|
1542
|
+
threadTs: string;
|
|
1543
|
+
}): Promise<
|
|
1544
|
+
Array<{
|
|
1545
|
+
user?: string | undefined;
|
|
1546
|
+
bot_id?: string | undefined;
|
|
1547
|
+
text?: string | undefined;
|
|
1548
|
+
ts?: string | undefined;
|
|
1549
|
+
subtype?: string | undefined;
|
|
1550
|
+
}>
|
|
1551
|
+
> {
|
|
1552
|
+
const getRepliesLogAttributes: LogAttributes = {
|
|
1553
|
+
channelId: data.channelId,
|
|
1554
|
+
};
|
|
1555
|
+
|
|
1556
|
+
logger.debug("Getting thread replies with data:", getRepliesLogAttributes);
|
|
1557
|
+
logger.debug(data, getRepliesLogAttributes);
|
|
1558
|
+
|
|
1559
|
+
const response: HTTPErrorResponse | HTTPResponse<JSONObject> =
|
|
1560
|
+
await API.post({
|
|
1561
|
+
url: URL.fromString("https://slack.com/api/conversations.replies"),
|
|
1562
|
+
data: {
|
|
1563
|
+
channel: data.channelId,
|
|
1564
|
+
ts: data.threadTs,
|
|
1565
|
+
limit: 50,
|
|
1566
|
+
},
|
|
1567
|
+
headers: {
|
|
1568
|
+
Authorization: `Bearer ${data.authToken}`,
|
|
1569
|
+
["Content-Type"]: "application/x-www-form-urlencoded",
|
|
1570
|
+
},
|
|
1571
|
+
options: {
|
|
1572
|
+
retries: 3,
|
|
1573
|
+
exponentialBackoff: true,
|
|
1574
|
+
},
|
|
1575
|
+
});
|
|
1576
|
+
|
|
1577
|
+
logger.debug(
|
|
1578
|
+
"Response from Slack API for getting thread replies:",
|
|
1579
|
+
getRepliesLogAttributes,
|
|
1580
|
+
);
|
|
1581
|
+
logger.debug(response, getRepliesLogAttributes);
|
|
1582
|
+
|
|
1583
|
+
if (response instanceof HTTPErrorResponse) {
|
|
1584
|
+
logger.error("Error response from Slack API:", getRepliesLogAttributes);
|
|
1585
|
+
logger.error(response, getRepliesLogAttributes);
|
|
1586
|
+
throw response;
|
|
1587
|
+
}
|
|
1588
|
+
|
|
1589
|
+
if ((response.jsonData as JSONObject)?.["ok"] !== true) {
|
|
1590
|
+
logger.error("Invalid response from Slack API:", getRepliesLogAttributes);
|
|
1591
|
+
logger.error(response.jsonData, getRepliesLogAttributes);
|
|
1592
|
+
const messageFromSlack: string = (response.jsonData as JSONObject)?.[
|
|
1593
|
+
"error"
|
|
1594
|
+
] as string;
|
|
1595
|
+
throw new BadRequestException("Error from Slack " + messageFromSlack);
|
|
1596
|
+
}
|
|
1597
|
+
|
|
1598
|
+
const messages: Array<JSONObject> =
|
|
1599
|
+
((response.jsonData as JSONObject)?.["messages"] as Array<JSONObject>) ||
|
|
1600
|
+
[];
|
|
1601
|
+
|
|
1602
|
+
return messages.map((message: JSONObject) => {
|
|
1603
|
+
return {
|
|
1604
|
+
user: message["user"] as string | undefined,
|
|
1605
|
+
bot_id: message["bot_id"] as string | undefined,
|
|
1606
|
+
text: message["text"] as string | undefined,
|
|
1607
|
+
ts: message["ts"] as string | undefined,
|
|
1608
|
+
subtype: message["subtype"] as string | undefined,
|
|
1609
|
+
};
|
|
1610
|
+
});
|
|
1611
|
+
}
|
|
1612
|
+
|
|
1533
1613
|
@CaptureSpan()
|
|
1534
1614
|
public static override getButtonsBlock(data: {
|
|
1535
1615
|
payloadButtonsBlock: WorkspacePayloadButtons;
|