@oneuptime/common 11.3.24 → 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
package/Server/API/SlackAPI.ts
CHANGED
|
@@ -48,6 +48,10 @@ import {
|
|
|
48
48
|
PrivateNoteEmojis,
|
|
49
49
|
PublicNoteEmojis,
|
|
50
50
|
} from "../Utils/Workspace/Slack/Actions/ActionTypes";
|
|
51
|
+
import WorkspaceUserAuthToken from "../../Models/DatabaseModels/WorkspaceUserAuthToken";
|
|
52
|
+
import AccessTokenService from "../Services/AccessTokenService";
|
|
53
|
+
import ObservabilityAssistant from "../Utils/AI/Chat/ObservabilityAssistant";
|
|
54
|
+
import { AIChatCitation } from "../../Types/AI/AIChatTypes";
|
|
51
55
|
|
|
52
56
|
export default class SlackAPI {
|
|
53
57
|
public getRouter(): ExpressRouter {
|
|
@@ -944,6 +948,322 @@ export default class SlackAPI {
|
|
|
944
948
|
|
|
945
949
|
return;
|
|
946
950
|
}
|
|
951
|
+
|
|
952
|
+
/*
|
|
953
|
+
* Handle "AI Ops" conversational events. There are three classes:
|
|
954
|
+
* - app_mention: the bot was @mentioned in a channel/group.
|
|
955
|
+
* - message with channel_type "im": a direct message to the bot.
|
|
956
|
+
* - message with channel_type "channel" | "group" | "mpim": a plain
|
|
957
|
+
* thread reply. We only treat these as follow-ups when they land
|
|
958
|
+
* in a thread the bot already participates in (see below), so we
|
|
959
|
+
* never answer arbitrary channel chatter.
|
|
960
|
+
*/
|
|
961
|
+
const eventChannelType: string | undefined = event["channel_type"] as
|
|
962
|
+
| string
|
|
963
|
+
| undefined;
|
|
964
|
+
const isAppMention: boolean = event["type"] === "app_mention";
|
|
965
|
+
const isDirectMessage: boolean =
|
|
966
|
+
event["type"] === "message" && eventChannelType === "im";
|
|
967
|
+
const isChannelThreadMessage: boolean =
|
|
968
|
+
event["type"] === "message" &&
|
|
969
|
+
(eventChannelType === "channel" ||
|
|
970
|
+
eventChannelType === "group" ||
|
|
971
|
+
eventChannelType === "mpim");
|
|
972
|
+
|
|
973
|
+
if (isAppMention || isDirectMessage || isChannelThreadMessage) {
|
|
974
|
+
/*
|
|
975
|
+
* Slack retries an event if it does not receive a 200 within 3
|
|
976
|
+
* seconds. Acknowledge immediately and do the (slow) assistant
|
|
977
|
+
* work detached. Also skip entirely on Slack retries so we never
|
|
978
|
+
* answer the same question twice.
|
|
979
|
+
*/
|
|
980
|
+
const isSlackRetry: boolean = Boolean(
|
|
981
|
+
req.headers["x-slack-retry-num"],
|
|
982
|
+
);
|
|
983
|
+
|
|
984
|
+
Response.sendTextResponse(req, res, "ok");
|
|
985
|
+
|
|
986
|
+
if (isSlackRetry) {
|
|
987
|
+
logger.debug(
|
|
988
|
+
"Skipping Slack AI Ops event because it is a Slack retry.",
|
|
989
|
+
getLogAttributesFromRequest(req as any),
|
|
990
|
+
);
|
|
991
|
+
return;
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
/*
|
|
995
|
+
* CRITICAL loop-guard: never react to messages produced by a bot
|
|
996
|
+
* (including ourselves) or to message subtypes such as
|
|
997
|
+
* message_changed / bot_message. Without this the bot would answer
|
|
998
|
+
* its own replies forever.
|
|
999
|
+
*/
|
|
1000
|
+
const eventSubtype: string | undefined = event["subtype"] as
|
|
1001
|
+
| string
|
|
1002
|
+
| undefined;
|
|
1003
|
+
const eventBotId: string | undefined = event["bot_id"] as
|
|
1004
|
+
| string
|
|
1005
|
+
| undefined;
|
|
1006
|
+
const eventUserId: string | undefined = event["user"] as
|
|
1007
|
+
| string
|
|
1008
|
+
| undefined;
|
|
1009
|
+
|
|
1010
|
+
if (eventBotId || eventSubtype) {
|
|
1011
|
+
logger.debug(
|
|
1012
|
+
"Skipping Slack AI Ops event from a bot or with a subtype.",
|
|
1013
|
+
getLogAttributesFromRequest(req as any),
|
|
1014
|
+
);
|
|
1015
|
+
return;
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
const eventText: string = (event["text"] as string) || "";
|
|
1019
|
+
|
|
1020
|
+
/*
|
|
1021
|
+
* A mention fires BOTH app_mention and message.* events. Let the
|
|
1022
|
+
* app_mention path own mentions and skip mention-bearing plain
|
|
1023
|
+
* message.* events to avoid answering twice.
|
|
1024
|
+
*/
|
|
1025
|
+
const botMentionRegex: RegExp = /<@[A-Z0-9]+>/;
|
|
1026
|
+
if (isChannelThreadMessage && botMentionRegex.test(eventText)) {
|
|
1027
|
+
logger.debug(
|
|
1028
|
+
"Skipping channel message that mentions the bot (handled by app_mention).",
|
|
1029
|
+
getLogAttributesFromRequest(req as any),
|
|
1030
|
+
);
|
|
1031
|
+
return;
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
const slackTeamId: string = payload["team_id"] as string;
|
|
1035
|
+
const slackChannelId: string = event["channel"] as string;
|
|
1036
|
+
const eventTs: string | undefined = event["ts"] as
|
|
1037
|
+
| string
|
|
1038
|
+
| undefined;
|
|
1039
|
+
const eventThreadTs: string | undefined = event["thread_ts"] as
|
|
1040
|
+
| string
|
|
1041
|
+
| undefined;
|
|
1042
|
+
/*
|
|
1043
|
+
* Reply in the same thread. For app_mention the mention may be in a
|
|
1044
|
+
* thread (thread_ts) or a top-level message (ts). For DMs we thread
|
|
1045
|
+
* off the message ts (or its thread if present).
|
|
1046
|
+
*/
|
|
1047
|
+
const threadTs: string = eventThreadTs || eventTs || "";
|
|
1048
|
+
|
|
1049
|
+
/*
|
|
1050
|
+
* Detach the assistant work (fire-and-forget). Everything below is
|
|
1051
|
+
* best-effort; the Slack ack has already been sent.
|
|
1052
|
+
*/
|
|
1053
|
+
(async (): Promise<void> => {
|
|
1054
|
+
const context:
|
|
1055
|
+
| {
|
|
1056
|
+
projectId: ObjectID;
|
|
1057
|
+
projectAuthToken: string;
|
|
1058
|
+
botUserId: string | undefined;
|
|
1059
|
+
userId: ObjectID | undefined;
|
|
1060
|
+
}
|
|
1061
|
+
| undefined = await SlackAPI.resolveSlackAiOpsContext({
|
|
1062
|
+
slackTeamId: slackTeamId,
|
|
1063
|
+
slackUserId: eventUserId || "",
|
|
1064
|
+
});
|
|
1065
|
+
|
|
1066
|
+
// Workspace not connected to any project — nothing we can do.
|
|
1067
|
+
if (!context) {
|
|
1068
|
+
logger.debug(
|
|
1069
|
+
"Slack AI Ops event: workspace not connected to any project.",
|
|
1070
|
+
getLogAttributesFromRequest(req as any),
|
|
1071
|
+
);
|
|
1072
|
+
return;
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
/*
|
|
1076
|
+
* Loop-guard: ignore messages authored by our own bot user id.
|
|
1077
|
+
*/
|
|
1078
|
+
if (
|
|
1079
|
+
context.botUserId &&
|
|
1080
|
+
eventUserId &&
|
|
1081
|
+
context.botUserId === eventUserId
|
|
1082
|
+
) {
|
|
1083
|
+
logger.debug(
|
|
1084
|
+
"Skipping Slack AI Ops event authored by our bot user.",
|
|
1085
|
+
getLogAttributesFromRequest(req as any),
|
|
1086
|
+
);
|
|
1087
|
+
return;
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
/*
|
|
1091
|
+
* Extract the question text. Strip mention tokens (for
|
|
1092
|
+
* app_mention the leading <@BOTID>) and trim.
|
|
1093
|
+
*/
|
|
1094
|
+
const questionText: string =
|
|
1095
|
+
SlackAPI.cleanSlackMessageText(eventText);
|
|
1096
|
+
|
|
1097
|
+
if (!questionText) {
|
|
1098
|
+
logger.debug(
|
|
1099
|
+
"Slack AI Ops event has no question text. Skipping.",
|
|
1100
|
+
getLogAttributesFromRequest(req as any),
|
|
1101
|
+
);
|
|
1102
|
+
return;
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
/*
|
|
1106
|
+
* Build conversation history so follow-ups are context-aware.
|
|
1107
|
+
* - app_mention: history only when inside a thread.
|
|
1108
|
+
* - channel/group/mpim: MUST be inside a thread AND the bot must
|
|
1109
|
+
* already participate in that thread (it started/answered it).
|
|
1110
|
+
* Otherwise we ignore the message entirely.
|
|
1111
|
+
* - im: history from recent DM messages.
|
|
1112
|
+
*/
|
|
1113
|
+
let history:
|
|
1114
|
+
| Array<{ role: "user" | "assistant"; content: string }>
|
|
1115
|
+
| undefined = undefined;
|
|
1116
|
+
|
|
1117
|
+
if (isChannelThreadMessage) {
|
|
1118
|
+
if (!eventThreadTs) {
|
|
1119
|
+
logger.debug(
|
|
1120
|
+
"Skipping channel message not in a thread.",
|
|
1121
|
+
getLogAttributesFromRequest(req as any),
|
|
1122
|
+
);
|
|
1123
|
+
return;
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
const threadReplies: Array<{
|
|
1127
|
+
user?: string | undefined;
|
|
1128
|
+
bot_id?: string | undefined;
|
|
1129
|
+
text?: string | undefined;
|
|
1130
|
+
ts?: string | undefined;
|
|
1131
|
+
subtype?: string | undefined;
|
|
1132
|
+
}> = await SlackUtil.getThreadReplies({
|
|
1133
|
+
authToken: context.projectAuthToken,
|
|
1134
|
+
channelId: slackChannelId,
|
|
1135
|
+
threadTs: eventThreadTs,
|
|
1136
|
+
});
|
|
1137
|
+
|
|
1138
|
+
const botParticipatesInThread: boolean = threadReplies.some(
|
|
1139
|
+
(reply: {
|
|
1140
|
+
user?: string | undefined;
|
|
1141
|
+
bot_id?: string | undefined;
|
|
1142
|
+
}) => {
|
|
1143
|
+
return Boolean(
|
|
1144
|
+
reply.bot_id ||
|
|
1145
|
+
(context.botUserId && reply.user === context.botUserId),
|
|
1146
|
+
);
|
|
1147
|
+
},
|
|
1148
|
+
);
|
|
1149
|
+
|
|
1150
|
+
if (!botParticipatesInThread) {
|
|
1151
|
+
logger.debug(
|
|
1152
|
+
"Skipping channel thread the bot does not participate in.",
|
|
1153
|
+
getLogAttributesFromRequest(req as any),
|
|
1154
|
+
);
|
|
1155
|
+
return;
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
history = SlackAPI.buildHistoryFromSlackMessages({
|
|
1159
|
+
messages: threadReplies,
|
|
1160
|
+
botUserId: context.botUserId,
|
|
1161
|
+
currentMessageTs: eventTs,
|
|
1162
|
+
});
|
|
1163
|
+
} else if (isAppMention && eventThreadTs) {
|
|
1164
|
+
const threadReplies: Array<{
|
|
1165
|
+
user?: string | undefined;
|
|
1166
|
+
bot_id?: string | undefined;
|
|
1167
|
+
text?: string | undefined;
|
|
1168
|
+
ts?: string | undefined;
|
|
1169
|
+
subtype?: string | undefined;
|
|
1170
|
+
}> = await SlackUtil.getThreadReplies({
|
|
1171
|
+
authToken: context.projectAuthToken,
|
|
1172
|
+
channelId: slackChannelId,
|
|
1173
|
+
threadTs: eventThreadTs,
|
|
1174
|
+
});
|
|
1175
|
+
|
|
1176
|
+
history = SlackAPI.buildHistoryFromSlackMessages({
|
|
1177
|
+
messages: threadReplies,
|
|
1178
|
+
botUserId: context.botUserId,
|
|
1179
|
+
currentMessageTs: eventTs,
|
|
1180
|
+
});
|
|
1181
|
+
} else if (isDirectMessage) {
|
|
1182
|
+
const dmMessages: Array<{
|
|
1183
|
+
messageId: string;
|
|
1184
|
+
text: string;
|
|
1185
|
+
userId?: string;
|
|
1186
|
+
username?: string;
|
|
1187
|
+
timestamp: Date;
|
|
1188
|
+
isBot: boolean;
|
|
1189
|
+
}> = await SlackUtil.getChannelMessages({
|
|
1190
|
+
channelId: slackChannelId,
|
|
1191
|
+
authToken: context.projectAuthToken,
|
|
1192
|
+
limit: 30,
|
|
1193
|
+
});
|
|
1194
|
+
|
|
1195
|
+
history = SlackAPI.buildHistoryFromSlackMessages({
|
|
1196
|
+
messages: dmMessages.map(
|
|
1197
|
+
(message: {
|
|
1198
|
+
messageId: string;
|
|
1199
|
+
text: string;
|
|
1200
|
+
userId?: string;
|
|
1201
|
+
isBot: boolean;
|
|
1202
|
+
}) => {
|
|
1203
|
+
return {
|
|
1204
|
+
user: message.userId,
|
|
1205
|
+
bot_id: message.isBot ? "bot" : undefined,
|
|
1206
|
+
text: message.text,
|
|
1207
|
+
ts: message.messageId,
|
|
1208
|
+
};
|
|
1209
|
+
},
|
|
1210
|
+
),
|
|
1211
|
+
botUserId: context.botUserId,
|
|
1212
|
+
currentMessageTs: eventTs,
|
|
1213
|
+
});
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1216
|
+
// If the user's Slack account is not connected to OneUptime.
|
|
1217
|
+
if (!context.userId) {
|
|
1218
|
+
await SlackUtil.sendMessageToThread({
|
|
1219
|
+
authToken: context.projectAuthToken,
|
|
1220
|
+
channelId: slackChannelId,
|
|
1221
|
+
threadTs: threadTs,
|
|
1222
|
+
text: SlackAPI.getSlackAccountNotConnectedMessage(),
|
|
1223
|
+
});
|
|
1224
|
+
return;
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1227
|
+
// Immediate acknowledgement so the user sees we are working.
|
|
1228
|
+
try {
|
|
1229
|
+
await SlackUtil.sendMessageToThread({
|
|
1230
|
+
authToken: context.projectAuthToken,
|
|
1231
|
+
channelId: slackChannelId,
|
|
1232
|
+
threadTs: threadTs,
|
|
1233
|
+
text: "Looking into it… :hourglass_flowing_sand:",
|
|
1234
|
+
});
|
|
1235
|
+
} catch (err) {
|
|
1236
|
+
logger.error(
|
|
1237
|
+
"Error sending Slack AI Ops acknowledgement:",
|
|
1238
|
+
getLogAttributesFromRequest(req as any),
|
|
1239
|
+
);
|
|
1240
|
+
logger.error(err, getLogAttributesFromRequest(req as any));
|
|
1241
|
+
}
|
|
1242
|
+
|
|
1243
|
+
const answerMarkdown: string =
|
|
1244
|
+
await SlackAPI.getAiOpsAnswerMarkdown({
|
|
1245
|
+
projectId: context.projectId,
|
|
1246
|
+
userId: context.userId,
|
|
1247
|
+
question: questionText,
|
|
1248
|
+
history: history,
|
|
1249
|
+
});
|
|
1250
|
+
|
|
1251
|
+
await SlackUtil.sendMessageToThread({
|
|
1252
|
+
authToken: context.projectAuthToken,
|
|
1253
|
+
channelId: slackChannelId,
|
|
1254
|
+
threadTs: threadTs,
|
|
1255
|
+
text: SlackUtil.convertMarkdownToSlackRichText(answerMarkdown),
|
|
1256
|
+
});
|
|
1257
|
+
})().catch((err: Error) => {
|
|
1258
|
+
logger.error(
|
|
1259
|
+
"Error handling Slack AI Ops event:",
|
|
1260
|
+
getLogAttributesFromRequest(req as any),
|
|
1261
|
+
);
|
|
1262
|
+
logger.error(err, getLogAttributesFromRequest(req as any));
|
|
1263
|
+
});
|
|
1264
|
+
|
|
1265
|
+
return;
|
|
1266
|
+
}
|
|
947
1267
|
}
|
|
948
1268
|
|
|
949
1269
|
// For any other event types, just acknowledge
|
|
@@ -951,6 +1271,127 @@ export default class SlackAPI {
|
|
|
951
1271
|
},
|
|
952
1272
|
);
|
|
953
1273
|
|
|
1274
|
+
/*
|
|
1275
|
+
* Slash command endpoint: `/oneuptime ask <question>` (or bare
|
|
1276
|
+
* `/oneuptime <question>`). Slack posts an application/x-www-form-urlencoded
|
|
1277
|
+
* body and expects a 200 within 3 seconds. We acknowledge instantly with an
|
|
1278
|
+
* ephemeral message, then run the assistant detached and POST the final
|
|
1279
|
+
* answer back to the command's response_url.
|
|
1280
|
+
*/
|
|
1281
|
+
router.post(
|
|
1282
|
+
"/slack/command",
|
|
1283
|
+
SlackAuthorization.isAuthorizedSlackRequest,
|
|
1284
|
+
async (req: ExpressRequest, res: ExpressResponse) => {
|
|
1285
|
+
logger.debug(
|
|
1286
|
+
"Slack slash command received",
|
|
1287
|
+
getLogAttributesFromRequest(req as any),
|
|
1288
|
+
);
|
|
1289
|
+
|
|
1290
|
+
const body: JSONObject = req.body;
|
|
1291
|
+
|
|
1292
|
+
const slackTeamId: string = (body["team_id"] as string) || "";
|
|
1293
|
+
const slackUserId: string = (body["user_id"] as string) || "";
|
|
1294
|
+
const responseUrl: string = (body["response_url"] as string) || "";
|
|
1295
|
+
|
|
1296
|
+
/*
|
|
1297
|
+
* Slack sends everything after the command word in `text`. We also
|
|
1298
|
+
* accept a leading "ask" keyword (e.g. `/oneuptime ask <question>`).
|
|
1299
|
+
*/
|
|
1300
|
+
const askPrefixRegex: RegExp = /^ask\s+/i;
|
|
1301
|
+
let questionText: string = ((body["text"] as string) || "").trim();
|
|
1302
|
+
if (askPrefixRegex.test(questionText)) {
|
|
1303
|
+
questionText = questionText.replace(askPrefixRegex, "").trim();
|
|
1304
|
+
}
|
|
1305
|
+
|
|
1306
|
+
// Empty command -> ephemeral usage help.
|
|
1307
|
+
if (!questionText) {
|
|
1308
|
+
return Response.sendJsonObjectResponse(req, res, {
|
|
1309
|
+
response_type: "ephemeral",
|
|
1310
|
+
text: "Usage: `/oneuptime ask which monitors are down?` — ask OneUptime AI about your logs, traces, metrics, incidents and monitors.",
|
|
1311
|
+
});
|
|
1312
|
+
}
|
|
1313
|
+
|
|
1314
|
+
/*
|
|
1315
|
+
* Acknowledge instantly so Slack does not time out. The real answer is
|
|
1316
|
+
* delivered to response_url below.
|
|
1317
|
+
*/
|
|
1318
|
+
Response.sendJsonObjectResponse(req, res, {
|
|
1319
|
+
response_type: "ephemeral",
|
|
1320
|
+
text: "On it… :hourglass_flowing_sand:",
|
|
1321
|
+
});
|
|
1322
|
+
|
|
1323
|
+
// Detached work (fire-and-forget). Ack has already been sent.
|
|
1324
|
+
(async (): Promise<void> => {
|
|
1325
|
+
const context:
|
|
1326
|
+
| {
|
|
1327
|
+
projectId: ObjectID;
|
|
1328
|
+
projectAuthToken: string;
|
|
1329
|
+
botUserId: string | undefined;
|
|
1330
|
+
userId: ObjectID | undefined;
|
|
1331
|
+
}
|
|
1332
|
+
| undefined = await SlackAPI.resolveSlackAiOpsContext({
|
|
1333
|
+
slackTeamId: slackTeamId,
|
|
1334
|
+
slackUserId: slackUserId,
|
|
1335
|
+
});
|
|
1336
|
+
|
|
1337
|
+
if (!context) {
|
|
1338
|
+
// Workspace not connected — tell the user ephemerally.
|
|
1339
|
+
await API.post({
|
|
1340
|
+
url: URL.fromString(responseUrl),
|
|
1341
|
+
data: {
|
|
1342
|
+
response_type: "ephemeral",
|
|
1343
|
+
text: "This Slack workspace is not connected to any OneUptime project.",
|
|
1344
|
+
},
|
|
1345
|
+
headers: {
|
|
1346
|
+
["Content-Type"]: "application/json",
|
|
1347
|
+
},
|
|
1348
|
+
});
|
|
1349
|
+
return;
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1352
|
+
if (!context.userId) {
|
|
1353
|
+
// User's Slack account is not connected to OneUptime.
|
|
1354
|
+
await API.post({
|
|
1355
|
+
url: URL.fromString(responseUrl),
|
|
1356
|
+
data: {
|
|
1357
|
+
response_type: "ephemeral",
|
|
1358
|
+
text: SlackAPI.getSlackAccountNotConnectedMessage(),
|
|
1359
|
+
},
|
|
1360
|
+
headers: {
|
|
1361
|
+
["Content-Type"]: "application/json",
|
|
1362
|
+
},
|
|
1363
|
+
});
|
|
1364
|
+
return;
|
|
1365
|
+
}
|
|
1366
|
+
|
|
1367
|
+
const answerMarkdown: string = await SlackAPI.getAiOpsAnswerMarkdown({
|
|
1368
|
+
projectId: context.projectId,
|
|
1369
|
+
userId: context.userId,
|
|
1370
|
+
question: questionText,
|
|
1371
|
+
});
|
|
1372
|
+
|
|
1373
|
+
await API.post({
|
|
1374
|
+
url: URL.fromString(responseUrl),
|
|
1375
|
+
data: {
|
|
1376
|
+
response_type: "in_channel",
|
|
1377
|
+
text: SlackUtil.convertMarkdownToSlackRichText(answerMarkdown),
|
|
1378
|
+
},
|
|
1379
|
+
headers: {
|
|
1380
|
+
["Content-Type"]: "application/json",
|
|
1381
|
+
},
|
|
1382
|
+
});
|
|
1383
|
+
})().catch((err: Error) => {
|
|
1384
|
+
logger.error(
|
|
1385
|
+
"Error handling Slack slash command:",
|
|
1386
|
+
getLogAttributesFromRequest(req as any),
|
|
1387
|
+
);
|
|
1388
|
+
logger.error(err, getLogAttributesFromRequest(req as any));
|
|
1389
|
+
});
|
|
1390
|
+
|
|
1391
|
+
return;
|
|
1392
|
+
},
|
|
1393
|
+
);
|
|
1394
|
+
|
|
954
1395
|
// options load endpoint.
|
|
955
1396
|
|
|
956
1397
|
router.post(
|
|
@@ -965,4 +1406,226 @@ export default class SlackAPI {
|
|
|
965
1406
|
|
|
966
1407
|
return router;
|
|
967
1408
|
}
|
|
1409
|
+
|
|
1410
|
+
/*
|
|
1411
|
+
* Shared "AI Ops" resolution used by both @mentions / DMs (Events API) and
|
|
1412
|
+
* the /oneuptime slash command. Resolves the Slack team_id -> OneUptime
|
|
1413
|
+
* project and the Slack user id -> OneUptime user using the same
|
|
1414
|
+
* WorkspaceProjectAuthToken / WorkspaceUserAuthToken pattern as
|
|
1415
|
+
* SlackAuthAction.isAuthorized. Returns undefined when the workspace is not
|
|
1416
|
+
* connected to any OneUptime project at all.
|
|
1417
|
+
*/
|
|
1418
|
+
private static async resolveSlackAiOpsContext(data: {
|
|
1419
|
+
slackTeamId: string;
|
|
1420
|
+
slackUserId: string;
|
|
1421
|
+
}): Promise<
|
|
1422
|
+
| {
|
|
1423
|
+
projectId: ObjectID;
|
|
1424
|
+
projectAuthToken: string;
|
|
1425
|
+
botUserId: string | undefined;
|
|
1426
|
+
userId: ObjectID | undefined;
|
|
1427
|
+
}
|
|
1428
|
+
| undefined
|
|
1429
|
+
> {
|
|
1430
|
+
const { slackTeamId, slackUserId } = data;
|
|
1431
|
+
|
|
1432
|
+
if (!slackTeamId) {
|
|
1433
|
+
return undefined;
|
|
1434
|
+
}
|
|
1435
|
+
|
|
1436
|
+
const projectAuth: WorkspaceProjectAuthToken | null =
|
|
1437
|
+
await WorkspaceProjectAuthTokenService.findOneBy({
|
|
1438
|
+
query: {
|
|
1439
|
+
workspaceProjectId: slackTeamId,
|
|
1440
|
+
},
|
|
1441
|
+
select: {
|
|
1442
|
+
projectId: true,
|
|
1443
|
+
authToken: true,
|
|
1444
|
+
miscData: true,
|
|
1445
|
+
},
|
|
1446
|
+
props: {
|
|
1447
|
+
isRoot: true,
|
|
1448
|
+
},
|
|
1449
|
+
});
|
|
1450
|
+
|
|
1451
|
+
if (!projectAuth || !projectAuth.projectId || !projectAuth.authToken) {
|
|
1452
|
+
return undefined;
|
|
1453
|
+
}
|
|
1454
|
+
|
|
1455
|
+
const projectId: ObjectID = projectAuth.projectId;
|
|
1456
|
+
|
|
1457
|
+
const userAuth: WorkspaceUserAuthToken | null =
|
|
1458
|
+
await WorkspaceUserAuthTokenService.findOneBy({
|
|
1459
|
+
query: {
|
|
1460
|
+
workspaceUserId: slackUserId,
|
|
1461
|
+
projectId: projectId,
|
|
1462
|
+
},
|
|
1463
|
+
select: {
|
|
1464
|
+
userId: true,
|
|
1465
|
+
},
|
|
1466
|
+
props: {
|
|
1467
|
+
isRoot: true,
|
|
1468
|
+
},
|
|
1469
|
+
});
|
|
1470
|
+
|
|
1471
|
+
return {
|
|
1472
|
+
projectId: projectId,
|
|
1473
|
+
projectAuthToken: projectAuth.authToken,
|
|
1474
|
+
botUserId: (projectAuth.miscData as SlackMiscData)?.botUserId,
|
|
1475
|
+
userId: userAuth?.userId,
|
|
1476
|
+
};
|
|
1477
|
+
}
|
|
1478
|
+
|
|
1479
|
+
/*
|
|
1480
|
+
* Runs the observability assistant for a resolved user + project and returns
|
|
1481
|
+
* the finished markdown answer with a compact "Sources" footer appended.
|
|
1482
|
+
* `history` (oldest-first prior turns, excluding the current question) makes
|
|
1483
|
+
* threaded / DM follow-ups context-aware.
|
|
1484
|
+
*/
|
|
1485
|
+
private static async getAiOpsAnswerMarkdown(data: {
|
|
1486
|
+
projectId: ObjectID;
|
|
1487
|
+
userId: ObjectID;
|
|
1488
|
+
question: string;
|
|
1489
|
+
history?:
|
|
1490
|
+
| Array<{ role: "user" | "assistant"; content: string }>
|
|
1491
|
+
| undefined;
|
|
1492
|
+
}): Promise<string> {
|
|
1493
|
+
const props: DatabaseCommonInteractionProps =
|
|
1494
|
+
await AccessTokenService.getDatabaseCommonInteractionPropsByUserAndProject(
|
|
1495
|
+
{
|
|
1496
|
+
userId: data.userId,
|
|
1497
|
+
projectId: data.projectId,
|
|
1498
|
+
},
|
|
1499
|
+
);
|
|
1500
|
+
|
|
1501
|
+
const result: {
|
|
1502
|
+
contentInMarkdown: string;
|
|
1503
|
+
citations: Array<AIChatCitation>;
|
|
1504
|
+
totalTokens: number;
|
|
1505
|
+
llmCallCount: number;
|
|
1506
|
+
toolCallCount: number;
|
|
1507
|
+
providerName?: string | undefined;
|
|
1508
|
+
modelName?: string | undefined;
|
|
1509
|
+
} = await ObservabilityAssistant.answerQuestion({
|
|
1510
|
+
projectId: data.projectId,
|
|
1511
|
+
userId: data.userId,
|
|
1512
|
+
props: props,
|
|
1513
|
+
question: data.question,
|
|
1514
|
+
history: data.history,
|
|
1515
|
+
feature: "Slack ChatOps",
|
|
1516
|
+
});
|
|
1517
|
+
|
|
1518
|
+
let answerMarkdown: string = result.contentInMarkdown || "";
|
|
1519
|
+
|
|
1520
|
+
// Append a compact "Sources" footer from the server-minted citations.
|
|
1521
|
+
if (result.citations && result.citations.length > 0) {
|
|
1522
|
+
const sourceLines: Array<string> = result.citations.map(
|
|
1523
|
+
(citation: AIChatCitation) => {
|
|
1524
|
+
return `• ${citation.label} (${citation.rowCount} rows)`;
|
|
1525
|
+
},
|
|
1526
|
+
);
|
|
1527
|
+
|
|
1528
|
+
answerMarkdown =
|
|
1529
|
+
answerMarkdown + "\n\n*Sources*\n" + sourceLines.join("\n");
|
|
1530
|
+
}
|
|
1531
|
+
|
|
1532
|
+
return answerMarkdown;
|
|
1533
|
+
}
|
|
1534
|
+
|
|
1535
|
+
/*
|
|
1536
|
+
* Removes Slack `<@USER>` / `<@USER|name>` mention tokens and trims the text.
|
|
1537
|
+
* Used both to extract the question and to clean history turns.
|
|
1538
|
+
*/
|
|
1539
|
+
private static cleanSlackMessageText(text: string | undefined): string {
|
|
1540
|
+
return (text || "")
|
|
1541
|
+
.replace(/<@[^>]+>/g, " ")
|
|
1542
|
+
.replace(/\s+/g, " ")
|
|
1543
|
+
.trim();
|
|
1544
|
+
}
|
|
1545
|
+
|
|
1546
|
+
/*
|
|
1547
|
+
* The bot posts transient acknowledgements ("Looking into it…", "On it…")
|
|
1548
|
+
* before the real answer. These must never enter the assistant's history or
|
|
1549
|
+
* they pollute context.
|
|
1550
|
+
*/
|
|
1551
|
+
private static isTransientAckMessage(text: string): boolean {
|
|
1552
|
+
const normalized: string = text.trim().toLowerCase();
|
|
1553
|
+
return (
|
|
1554
|
+
normalized.startsWith("looking into it") || normalized.startsWith("on it")
|
|
1555
|
+
);
|
|
1556
|
+
}
|
|
1557
|
+
|
|
1558
|
+
/*
|
|
1559
|
+
* Builds oldest-first conversation history for the observability assistant
|
|
1560
|
+
* from a list of Slack messages (thread replies or recent DM messages).
|
|
1561
|
+
*
|
|
1562
|
+
* - Each message becomes a turn: authored by the bot -> "assistant",
|
|
1563
|
+
* otherwise -> "user".
|
|
1564
|
+
* - Text is stripped of mention tokens; empty messages are skipped.
|
|
1565
|
+
* - The bot's transient acknowledgements are filtered out.
|
|
1566
|
+
* - The current triggering message (matched by its ts) is excluded because it
|
|
1567
|
+
* is passed separately as the `question`.
|
|
1568
|
+
* - Capped to the most recent ~12 turns.
|
|
1569
|
+
*/
|
|
1570
|
+
private static buildHistoryFromSlackMessages(data: {
|
|
1571
|
+
messages: Array<{
|
|
1572
|
+
user?: string | undefined;
|
|
1573
|
+
bot_id?: string | undefined;
|
|
1574
|
+
text?: string | undefined;
|
|
1575
|
+
ts?: string | undefined;
|
|
1576
|
+
subtype?: string | undefined;
|
|
1577
|
+
}>;
|
|
1578
|
+
botUserId: string | undefined;
|
|
1579
|
+
currentMessageTs: string | undefined;
|
|
1580
|
+
}): Array<{ role: "user" | "assistant"; content: string }> {
|
|
1581
|
+
const MAX_HISTORY_TURNS: number = 12;
|
|
1582
|
+
|
|
1583
|
+
const history: Array<{ role: "user" | "assistant"; content: string }> = [];
|
|
1584
|
+
|
|
1585
|
+
for (const message of data.messages) {
|
|
1586
|
+
// Exclude the message that triggered this run.
|
|
1587
|
+
if (
|
|
1588
|
+
data.currentMessageTs &&
|
|
1589
|
+
message.ts &&
|
|
1590
|
+
message.ts === data.currentMessageTs
|
|
1591
|
+
) {
|
|
1592
|
+
continue;
|
|
1593
|
+
}
|
|
1594
|
+
|
|
1595
|
+
const content: string = SlackAPI.cleanSlackMessageText(message.text);
|
|
1596
|
+
|
|
1597
|
+
if (!content) {
|
|
1598
|
+
continue;
|
|
1599
|
+
}
|
|
1600
|
+
|
|
1601
|
+
const isAssistant: boolean = Boolean(
|
|
1602
|
+
message.bot_id || (data.botUserId && message.user === data.botUserId),
|
|
1603
|
+
);
|
|
1604
|
+
|
|
1605
|
+
// Drop the bot's own transient acknowledgements.
|
|
1606
|
+
if (isAssistant && SlackAPI.isTransientAckMessage(content)) {
|
|
1607
|
+
continue;
|
|
1608
|
+
}
|
|
1609
|
+
|
|
1610
|
+
history.push({
|
|
1611
|
+
role: isAssistant ? "assistant" : "user",
|
|
1612
|
+
content: content,
|
|
1613
|
+
});
|
|
1614
|
+
}
|
|
1615
|
+
|
|
1616
|
+
// Keep only the most recent turns.
|
|
1617
|
+
if (history.length > MAX_HISTORY_TURNS) {
|
|
1618
|
+
return history.slice(history.length - MAX_HISTORY_TURNS);
|
|
1619
|
+
}
|
|
1620
|
+
|
|
1621
|
+
return history;
|
|
1622
|
+
}
|
|
1623
|
+
|
|
1624
|
+
/*
|
|
1625
|
+
* Message shown to a Slack user whose Slack account is not connected to
|
|
1626
|
+
* OneUptime. Mirrors the copy used in SlackAuthAction.isAuthorized.
|
|
1627
|
+
*/
|
|
1628
|
+
private static getSlackAccountNotConnectedMessage(): string {
|
|
1629
|
+
return "Unfortunately your Slack account is not connected to OneUptime. Please log into your OneUptime account, click on User Settings and then connect your Slack account.";
|
|
1630
|
+
}
|
|
968
1631
|
}
|