@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
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
import Incident from "../../../../Models/DatabaseModels/Incident";
|
|
2
|
+
import IncidentSeverity from "../../../../Models/DatabaseModels/IncidentSeverity";
|
|
3
|
+
import { JSONObject } from "../../../../Types/JSON";
|
|
4
|
+
import ObjectID from "../../../../Types/ObjectID";
|
|
5
|
+
import Permission from "../../../../Types/Permission";
|
|
6
|
+
import BadDataException from "../../../../Types/Exception/BadDataException";
|
|
7
|
+
import SortOrder from "../../../../Types/BaseDatabase/SortOrder";
|
|
8
|
+
import { AIChatCitationTargetType } from "../../../../Types/AI/AIChatTypes";
|
|
9
|
+
import IncidentService from "../../../Services/IncidentService";
|
|
10
|
+
import IncidentSeverityService from "../../../Services/IncidentSeverityService";
|
|
11
|
+
import ToolResultSerializer, { SerializedResult } from "./Serializer";
|
|
12
|
+
import WidgetBuilder from "./WidgetBuilder";
|
|
13
|
+
import {
|
|
14
|
+
ObservabilityTool,
|
|
15
|
+
ToolArgs,
|
|
16
|
+
ToolContext,
|
|
17
|
+
ToolExecutionResult,
|
|
18
|
+
} from "./ToolTypes";
|
|
19
|
+
|
|
20
|
+
/*
|
|
21
|
+
* Derived from the model ACL so the tool gate can never drift from RBAC.
|
|
22
|
+
* Resolved lazily rather than at module load: this module is pulled in through
|
|
23
|
+
* the service import graph before the Incident model class is fully wired up,
|
|
24
|
+
* so calling a model method at import time throws a circular-dependency
|
|
25
|
+
* TypeError. By the time a tool actually executes, every module is loaded.
|
|
26
|
+
*/
|
|
27
|
+
let cachedCreatePermissions: Array<Permission> | null = null;
|
|
28
|
+
const resolveCreatePermissions: () => Array<Permission> =
|
|
29
|
+
(): Array<Permission> => {
|
|
30
|
+
if (!cachedCreatePermissions) {
|
|
31
|
+
cachedCreatePermissions = new Incident().getCreatePermissions();
|
|
32
|
+
}
|
|
33
|
+
return cachedCreatePermissions;
|
|
34
|
+
};
|
|
35
|
+
let cachedUpdatePermissions: Array<Permission> | null = null;
|
|
36
|
+
const resolveUpdatePermissions: () => Array<Permission> =
|
|
37
|
+
(): Array<Permission> => {
|
|
38
|
+
if (!cachedUpdatePermissions) {
|
|
39
|
+
cachedUpdatePermissions = new Incident().getUpdatePermissions();
|
|
40
|
+
}
|
|
41
|
+
return cachedUpdatePermissions;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
/*
|
|
45
|
+
* Write tools mutate the project. They are gated twice: once by the tool's
|
|
46
|
+
* requiredPermissions (RBAC) and once by the conversation's permission mode
|
|
47
|
+
* (approval / auto-run / hidden in read-only). The user id used for the write
|
|
48
|
+
* is the requesting user's — taken from ctx.props, never a tool argument.
|
|
49
|
+
*/
|
|
50
|
+
|
|
51
|
+
export const CreateIncidentTool: ObservabilityTool = {
|
|
52
|
+
name: "create_incident",
|
|
53
|
+
description:
|
|
54
|
+
"Declare (create) a new incident in this project. Provide a clear title and a description of the problem. Optionally set the severity by name (must match an existing incident severity); otherwise the project's first-defined severity is used. Use this when the user asks you to open, declare or create an incident.",
|
|
55
|
+
inputSchema: {
|
|
56
|
+
type: "object",
|
|
57
|
+
properties: {
|
|
58
|
+
title: {
|
|
59
|
+
type: "string",
|
|
60
|
+
description: "Short, specific incident title (required).",
|
|
61
|
+
},
|
|
62
|
+
description: {
|
|
63
|
+
type: "string",
|
|
64
|
+
description:
|
|
65
|
+
"Markdown description of what is happening, impact and any known cause.",
|
|
66
|
+
},
|
|
67
|
+
severityName: {
|
|
68
|
+
type: "string",
|
|
69
|
+
description:
|
|
70
|
+
"Name of an existing incident severity (e.g. 'SEV1', 'Critical'). Defaults to the project's first-defined severity.",
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
required: ["title"],
|
|
74
|
+
},
|
|
75
|
+
get requiredPermissions(): Array<Permission> {
|
|
76
|
+
return resolveCreatePermissions();
|
|
77
|
+
},
|
|
78
|
+
isMutation: true,
|
|
79
|
+
buildActionTitle: (args: JSONObject): string => {
|
|
80
|
+
return `Create incident: ${ToolArgs.getString(args, "title") || "Untitled"}`;
|
|
81
|
+
},
|
|
82
|
+
execute: async (
|
|
83
|
+
args: JSONObject,
|
|
84
|
+
ctx: ToolContext,
|
|
85
|
+
): Promise<ToolExecutionResult> => {
|
|
86
|
+
const title: string | undefined = ToolArgs.getString(args, "title");
|
|
87
|
+
if (!title) {
|
|
88
|
+
throw new BadDataException("title is required to create an incident.");
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const description: string = ToolArgs.getString(args, "description") || "";
|
|
92
|
+
|
|
93
|
+
const userId: ObjectID | undefined = ctx.props.userId;
|
|
94
|
+
if (!userId) {
|
|
95
|
+
throw new BadDataException(
|
|
96
|
+
"No authenticated user in context; cannot create an incident.",
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const severities: Array<IncidentSeverity> =
|
|
101
|
+
await IncidentSeverityService.findBy({
|
|
102
|
+
query: {},
|
|
103
|
+
select: {
|
|
104
|
+
_id: true,
|
|
105
|
+
name: true,
|
|
106
|
+
order: true,
|
|
107
|
+
},
|
|
108
|
+
sort: {
|
|
109
|
+
order: SortOrder.Ascending,
|
|
110
|
+
},
|
|
111
|
+
limit: 50,
|
|
112
|
+
skip: 0,
|
|
113
|
+
props: ctx.props,
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
if (severities.length === 0) {
|
|
117
|
+
throw new BadDataException(
|
|
118
|
+
"No incident severities are configured for this project. Add one in Project Settings > Incidents > Severities first.",
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
let severity: IncidentSeverity = severities[0]!;
|
|
123
|
+
const severityName: string | undefined = ToolArgs.getString(
|
|
124
|
+
args,
|
|
125
|
+
"severityName",
|
|
126
|
+
);
|
|
127
|
+
if (severityName) {
|
|
128
|
+
const match: IncidentSeverity | undefined = severities.find(
|
|
129
|
+
(item: IncidentSeverity) => {
|
|
130
|
+
return item.name?.toLowerCase() === severityName.toLowerCase();
|
|
131
|
+
},
|
|
132
|
+
);
|
|
133
|
+
if (match) {
|
|
134
|
+
severity = match;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const incident: Incident = new Incident();
|
|
139
|
+
incident.projectId = ctx.projectId;
|
|
140
|
+
incident.title = title;
|
|
141
|
+
incident.description = description;
|
|
142
|
+
incident.incidentSeverityId = severity.id!;
|
|
143
|
+
incident.createdByUserId = userId;
|
|
144
|
+
incident.rootCause = "Incident created via the OneUptime AI copilot.";
|
|
145
|
+
|
|
146
|
+
// Created under the user's props so the model-layer RBAC applies too.
|
|
147
|
+
const created: Incident = await IncidentService.create({
|
|
148
|
+
data: incident,
|
|
149
|
+
props: ctx.props,
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
const incidentIdString: string = created.id!.toString();
|
|
153
|
+
|
|
154
|
+
const serialized: SerializedResult = ToolResultSerializer.serializeRows([
|
|
155
|
+
{
|
|
156
|
+
id: incidentIdString,
|
|
157
|
+
incidentNumber: created.incidentNumber,
|
|
158
|
+
title: title,
|
|
159
|
+
severity: severity.name,
|
|
160
|
+
},
|
|
161
|
+
]);
|
|
162
|
+
|
|
163
|
+
return {
|
|
164
|
+
dataForLlm: `Successfully created incident #${created.incidentNumber} ("${title}", severity ${severity.name}).\n${serialized.text}`,
|
|
165
|
+
rowCount: 1,
|
|
166
|
+
citationLabel: `Created incident #${created.incidentNumber}`,
|
|
167
|
+
citationTarget: {
|
|
168
|
+
type: AIChatCitationTargetType.IncidentView,
|
|
169
|
+
params: { incidentId: incidentIdString },
|
|
170
|
+
},
|
|
171
|
+
redactionCount: serialized.redactionCount,
|
|
172
|
+
isTruncated: false,
|
|
173
|
+
widget: WidgetBuilder.resourceCard({
|
|
174
|
+
title: "Incident created",
|
|
175
|
+
resourceType: "Incident",
|
|
176
|
+
heading: `#${created.incidentNumber} · ${title}`,
|
|
177
|
+
subheading: severity.name ? `Severity: ${severity.name}` : undefined,
|
|
178
|
+
fields: [
|
|
179
|
+
{ label: "Number", value: `#${created.incidentNumber ?? ""}` },
|
|
180
|
+
{ label: "Severity", value: severity.name || "—" },
|
|
181
|
+
...(description
|
|
182
|
+
? [
|
|
183
|
+
{
|
|
184
|
+
label: "Description",
|
|
185
|
+
value:
|
|
186
|
+
description.length > 200
|
|
187
|
+
? `${description.substring(0, 200)}…`
|
|
188
|
+
: description,
|
|
189
|
+
},
|
|
190
|
+
]
|
|
191
|
+
: []),
|
|
192
|
+
],
|
|
193
|
+
link: {
|
|
194
|
+
type: AIChatCitationTargetType.IncidentView,
|
|
195
|
+
params: { incidentId: incidentIdString },
|
|
196
|
+
},
|
|
197
|
+
}),
|
|
198
|
+
};
|
|
199
|
+
},
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
async function changeIncidentStateTool(data: {
|
|
203
|
+
args: JSONObject;
|
|
204
|
+
ctx: ToolContext;
|
|
205
|
+
verb: "acknowledge" | "resolve";
|
|
206
|
+
}): Promise<ToolExecutionResult> {
|
|
207
|
+
const { args, ctx, verb } = data;
|
|
208
|
+
|
|
209
|
+
const incidentId: ObjectID | undefined = ToolArgs.getObjectID(
|
|
210
|
+
args,
|
|
211
|
+
"incidentId",
|
|
212
|
+
);
|
|
213
|
+
if (!incidentId) {
|
|
214
|
+
throw new BadDataException(
|
|
215
|
+
"incidentId is required. Use query_incidents to find it first.",
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const userId: ObjectID | undefined = ctx.props.userId;
|
|
220
|
+
if (!userId) {
|
|
221
|
+
throw new BadDataException(
|
|
222
|
+
"No authenticated user in context; cannot change the incident.",
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/*
|
|
227
|
+
* Confirm the incident is visible to this user under their RBAC (the state
|
|
228
|
+
* change itself runs as root inside the service). This stops the tool from
|
|
229
|
+
* acting on incidents the user cannot see.
|
|
230
|
+
*/
|
|
231
|
+
const incident: Incident | null = await IncidentService.findOneById({
|
|
232
|
+
id: incidentId,
|
|
233
|
+
select: {
|
|
234
|
+
_id: true,
|
|
235
|
+
incidentNumber: true,
|
|
236
|
+
title: true,
|
|
237
|
+
},
|
|
238
|
+
props: ctx.props,
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
if (!incident) {
|
|
242
|
+
throw new BadDataException(
|
|
243
|
+
"Incident not found (or you do not have access to it).",
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
if (verb === "acknowledge") {
|
|
248
|
+
await IncidentService.acknowledgeIncident(incidentId, userId);
|
|
249
|
+
} else {
|
|
250
|
+
await IncidentService.resolveIncident(incidentId, userId);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const newStateName: string =
|
|
254
|
+
verb === "acknowledge" ? "Acknowledged" : "Resolved";
|
|
255
|
+
|
|
256
|
+
const incidentIdString: string = incidentId.toString();
|
|
257
|
+
|
|
258
|
+
const serialized: SerializedResult = ToolResultSerializer.serializeRows([
|
|
259
|
+
{
|
|
260
|
+
id: incidentIdString,
|
|
261
|
+
incidentNumber: incident.incidentNumber,
|
|
262
|
+
title: incident.title,
|
|
263
|
+
newState: newStateName,
|
|
264
|
+
},
|
|
265
|
+
]);
|
|
266
|
+
|
|
267
|
+
return {
|
|
268
|
+
dataForLlm: `Incident #${incident.incidentNumber} ("${incident.title}") is now ${newStateName}.\n${serialized.text}`,
|
|
269
|
+
rowCount: 1,
|
|
270
|
+
citationLabel: `${newStateName} incident #${incident.incidentNumber}`,
|
|
271
|
+
citationTarget: {
|
|
272
|
+
type: AIChatCitationTargetType.IncidentView,
|
|
273
|
+
params: { incidentId: incidentIdString },
|
|
274
|
+
},
|
|
275
|
+
redactionCount: serialized.redactionCount,
|
|
276
|
+
isTruncated: false,
|
|
277
|
+
widget: WidgetBuilder.resourceCard({
|
|
278
|
+
title: `Incident ${newStateName.toLowerCase()}`,
|
|
279
|
+
resourceType: "Incident",
|
|
280
|
+
heading: `#${incident.incidentNumber} · ${incident.title}`,
|
|
281
|
+
subheading: `Now ${newStateName}`,
|
|
282
|
+
fields: [
|
|
283
|
+
{ label: "Number", value: `#${incident.incidentNumber ?? ""}` },
|
|
284
|
+
{ label: "State", value: newStateName },
|
|
285
|
+
],
|
|
286
|
+
link: {
|
|
287
|
+
type: AIChatCitationTargetType.IncidentView,
|
|
288
|
+
params: { incidentId: incidentIdString },
|
|
289
|
+
},
|
|
290
|
+
}),
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
export const AcknowledgeIncidentTool: ObservabilityTool = {
|
|
295
|
+
name: "acknowledge_incident",
|
|
296
|
+
description:
|
|
297
|
+
"Acknowledge an incident (move it to the acknowledged state) by its incidentId. Find the incidentId with query_incidents first.",
|
|
298
|
+
inputSchema: {
|
|
299
|
+
type: "object",
|
|
300
|
+
properties: {
|
|
301
|
+
incidentId: {
|
|
302
|
+
type: "string",
|
|
303
|
+
description: "The incident's ID (required).",
|
|
304
|
+
},
|
|
305
|
+
},
|
|
306
|
+
required: ["incidentId"],
|
|
307
|
+
},
|
|
308
|
+
get requiredPermissions(): Array<Permission> {
|
|
309
|
+
return resolveUpdatePermissions();
|
|
310
|
+
},
|
|
311
|
+
isMutation: true,
|
|
312
|
+
buildActionTitle: (args: JSONObject): string => {
|
|
313
|
+
return `Acknowledge incident ${ToolArgs.getString(args, "incidentId") || ""}`.trim();
|
|
314
|
+
},
|
|
315
|
+
execute: async (
|
|
316
|
+
args: JSONObject,
|
|
317
|
+
ctx: ToolContext,
|
|
318
|
+
): Promise<ToolExecutionResult> => {
|
|
319
|
+
return changeIncidentStateTool({ args, ctx, verb: "acknowledge" });
|
|
320
|
+
},
|
|
321
|
+
};
|
|
322
|
+
|
|
323
|
+
export const ResolveIncidentTool: ObservabilityTool = {
|
|
324
|
+
name: "resolve_incident",
|
|
325
|
+
description:
|
|
326
|
+
"Resolve an incident (move it to the resolved state) by its incidentId. Find the incidentId with query_incidents first.",
|
|
327
|
+
inputSchema: {
|
|
328
|
+
type: "object",
|
|
329
|
+
properties: {
|
|
330
|
+
incidentId: {
|
|
331
|
+
type: "string",
|
|
332
|
+
description: "The incident's ID (required).",
|
|
333
|
+
},
|
|
334
|
+
},
|
|
335
|
+
required: ["incidentId"],
|
|
336
|
+
},
|
|
337
|
+
get requiredPermissions(): Array<Permission> {
|
|
338
|
+
return resolveUpdatePermissions();
|
|
339
|
+
},
|
|
340
|
+
isMutation: true,
|
|
341
|
+
buildActionTitle: (args: JSONObject): string => {
|
|
342
|
+
return `Resolve incident ${ToolArgs.getString(args, "incidentId") || ""}`.trim();
|
|
343
|
+
},
|
|
344
|
+
execute: async (
|
|
345
|
+
args: JSONObject,
|
|
346
|
+
ctx: ToolContext,
|
|
347
|
+
): Promise<ToolExecutionResult> => {
|
|
348
|
+
return changeIncidentStateTool({ args, ctx, verb: "resolve" });
|
|
349
|
+
},
|
|
350
|
+
};
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
import { JSONObject } from "../../../../Types/JSON";
|
|
2
|
+
import Permission, {
|
|
3
|
+
PermissionHelper,
|
|
4
|
+
UserPermission,
|
|
5
|
+
} from "../../../../Types/Permission";
|
|
6
|
+
import DatabaseCommonInteractionPropsUtil, {
|
|
7
|
+
PermissionType,
|
|
8
|
+
} from "../../../../Types/BaseDatabase/DatabaseCommonInteractionPropsUtil";
|
|
9
|
+
import { LLMToolDefinition } from "../../LLM/LLMService";
|
|
10
|
+
import logger from "../../Logger";
|
|
11
|
+
import {
|
|
12
|
+
ObservabilityTool,
|
|
13
|
+
ToolContext,
|
|
14
|
+
ToolExecutionResult,
|
|
15
|
+
} from "./ToolTypes";
|
|
16
|
+
import { QueryIncidentsTool } from "./IncidentTools";
|
|
17
|
+
import { QueryAlertsTool } from "./AlertTools";
|
|
18
|
+
import { QueryMonitorsTool } from "./MonitorTools";
|
|
19
|
+
import { TopExceptionsTool } from "./ExceptionTools";
|
|
20
|
+
import { LogHistogramTool, SearchLogsTool } from "./LogTools";
|
|
21
|
+
import { QueryMetricsTool } from "./MetricTools";
|
|
22
|
+
import { GetTraceTool, QueryTracesTool } from "./TraceTools";
|
|
23
|
+
import { LookupContextTool } from "./ContextTools";
|
|
24
|
+
import {
|
|
25
|
+
AcknowledgeIncidentTool,
|
|
26
|
+
CreateIncidentTool,
|
|
27
|
+
ResolveIncidentTool,
|
|
28
|
+
} from "./IncidentWriteTools";
|
|
29
|
+
import { AcknowledgeAlertTool, ResolveAlertTool } from "./AlertWriteTools";
|
|
30
|
+
import AIChatPermissionMode from "../../../../Types/AI/AIChatPermissionMode";
|
|
31
|
+
|
|
32
|
+
export interface ToolCallOutcome {
|
|
33
|
+
success: boolean;
|
|
34
|
+
/*
|
|
35
|
+
* What goes back to the LLM: serialized data, or an error envelope the
|
|
36
|
+
* model can self-correct from.
|
|
37
|
+
*/
|
|
38
|
+
textForLlm: string;
|
|
39
|
+
result?: ToolExecutionResult | undefined;
|
|
40
|
+
errorMessage?: string | undefined;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const TOOL_EXECUTION_TIMEOUT_MS: number = 45 * 1000;
|
|
44
|
+
|
|
45
|
+
/*
|
|
46
|
+
* The curated tool belt for AI features (chat today, the Investigation Engine
|
|
47
|
+
* later). Read tools wrap an existing deterministic query; write tools mutate
|
|
48
|
+
* the project (create/acknowledge/resolve). Every tool executes under the
|
|
49
|
+
* requesting user's permission props, and write tools are additionally gated by
|
|
50
|
+
* the conversation's permission mode (see ChatAgentRunner).
|
|
51
|
+
*/
|
|
52
|
+
export default class AIToolbox {
|
|
53
|
+
private static readonly tools: Array<ObservabilityTool> = [
|
|
54
|
+
LookupContextTool,
|
|
55
|
+
QueryIncidentsTool,
|
|
56
|
+
QueryAlertsTool,
|
|
57
|
+
QueryMonitorsTool,
|
|
58
|
+
TopExceptionsTool,
|
|
59
|
+
SearchLogsTool,
|
|
60
|
+
LogHistogramTool,
|
|
61
|
+
QueryMetricsTool,
|
|
62
|
+
QueryTracesTool,
|
|
63
|
+
GetTraceTool,
|
|
64
|
+
// Write tools (mutations). Gated by conversation permission mode.
|
|
65
|
+
CreateIncidentTool,
|
|
66
|
+
AcknowledgeIncidentTool,
|
|
67
|
+
ResolveIncidentTool,
|
|
68
|
+
AcknowledgeAlertTool,
|
|
69
|
+
ResolveAlertTool,
|
|
70
|
+
];
|
|
71
|
+
|
|
72
|
+
public static getTools(): Array<ObservabilityTool> {
|
|
73
|
+
return this.tools;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// LLM tool definitions cached per permission mode (ReadOnly hides mutations).
|
|
77
|
+
private static llmToolDefinitionsByMode: Map<
|
|
78
|
+
AIChatPermissionMode,
|
|
79
|
+
Array<LLMToolDefinition>
|
|
80
|
+
> = new Map();
|
|
81
|
+
|
|
82
|
+
public static getToolByName(name: string): ObservabilityTool | undefined {
|
|
83
|
+
return this.tools.find((tool: ObservabilityTool) => {
|
|
84
|
+
return tool.name === name;
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
public static isMutationTool(name: string): boolean {
|
|
89
|
+
const tool: ObservabilityTool | undefined = this.getToolByName(name);
|
|
90
|
+
return Boolean(tool?.isMutation);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/*
|
|
94
|
+
* The tool definitions offered to the model for a given permission mode. In
|
|
95
|
+
* ReadOnly mode the mutating tools are withheld entirely, so the model never
|
|
96
|
+
* even proposes an action it isn't allowed to take.
|
|
97
|
+
*/
|
|
98
|
+
public static getLlmToolDefinitions(
|
|
99
|
+
mode: AIChatPermissionMode,
|
|
100
|
+
): Array<LLMToolDefinition> {
|
|
101
|
+
const cached: Array<LLMToolDefinition> | undefined =
|
|
102
|
+
this.llmToolDefinitionsByMode.get(mode);
|
|
103
|
+
if (cached) {
|
|
104
|
+
return cached;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const includeMutations: boolean = mode !== AIChatPermissionMode.ReadOnly;
|
|
108
|
+
|
|
109
|
+
const definitions: Array<LLMToolDefinition> = this.tools
|
|
110
|
+
.filter((tool: ObservabilityTool) => {
|
|
111
|
+
return includeMutations || !tool.isMutation;
|
|
112
|
+
})
|
|
113
|
+
.map((tool: ObservabilityTool) => {
|
|
114
|
+
return {
|
|
115
|
+
name: tool.name,
|
|
116
|
+
description: tool.description,
|
|
117
|
+
inputSchema: tool.inputSchema,
|
|
118
|
+
};
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
this.llmToolDefinitionsByMode.set(mode, definitions);
|
|
122
|
+
return definitions;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
public static hasPermissionForTool(
|
|
126
|
+
tool: ObservabilityTool,
|
|
127
|
+
ctx: ToolContext,
|
|
128
|
+
): boolean {
|
|
129
|
+
if (ctx.props.isRoot || ctx.props.isMasterAdmin) {
|
|
130
|
+
return true;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/*
|
|
134
|
+
* Fail closed on block permissions: if any of the tool's permissions is
|
|
135
|
+
* block-listed for this user, deny the tool outright. This is coarser
|
|
136
|
+
* than the label-scoped block filtering the model layer applies, but the
|
|
137
|
+
* raw-SQL aggregation tools have no model layer — this gate is their
|
|
138
|
+
* only authorization.
|
|
139
|
+
*/
|
|
140
|
+
const blockedPermissions: Array<Permission> =
|
|
141
|
+
DatabaseCommonInteractionPropsUtil.getUserPermissions(
|
|
142
|
+
ctx.props,
|
|
143
|
+
PermissionType.Block,
|
|
144
|
+
).map((userPermission: UserPermission) => {
|
|
145
|
+
return userPermission.permission;
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
if (
|
|
149
|
+
PermissionHelper.doesPermissionsIntersect(
|
|
150
|
+
blockedPermissions,
|
|
151
|
+
tool.requiredPermissions,
|
|
152
|
+
)
|
|
153
|
+
) {
|
|
154
|
+
return false;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const userPermissions: Array<Permission> =
|
|
158
|
+
DatabaseCommonInteractionPropsUtil.getUserPermissions(
|
|
159
|
+
ctx.props,
|
|
160
|
+
PermissionType.Allow,
|
|
161
|
+
).map((userPermission: UserPermission) => {
|
|
162
|
+
return userPermission.permission;
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
return PermissionHelper.doesPermissionsIntersect(
|
|
166
|
+
userPermissions,
|
|
167
|
+
tool.requiredPermissions,
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
public static async executeTool(data: {
|
|
172
|
+
name: string;
|
|
173
|
+
args: JSONObject;
|
|
174
|
+
ctx: ToolContext;
|
|
175
|
+
}): Promise<ToolCallOutcome> {
|
|
176
|
+
const tool: ObservabilityTool | undefined = this.getToolByName(data.name);
|
|
177
|
+
|
|
178
|
+
if (!tool) {
|
|
179
|
+
return {
|
|
180
|
+
success: false,
|
|
181
|
+
textForLlm: `Error: unknown tool "${data.name}". Available tools: ${this.tools
|
|
182
|
+
.map((availableTool: ObservabilityTool) => {
|
|
183
|
+
return availableTool.name;
|
|
184
|
+
})
|
|
185
|
+
.join(", ")}.`,
|
|
186
|
+
errorMessage: `Unknown tool: ${data.name}`,
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (!this.hasPermissionForTool(tool, data.ctx)) {
|
|
191
|
+
return {
|
|
192
|
+
success: false,
|
|
193
|
+
textForLlm: `Error: the current user does not have permission to use ${data.name}. Answer with the data you already have, and tell the user which permission is missing.`,
|
|
194
|
+
errorMessage: `Permission denied for tool: ${data.name}`,
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
try {
|
|
199
|
+
const result: ToolExecutionResult = await Promise.race([
|
|
200
|
+
tool.execute(data.args, data.ctx),
|
|
201
|
+
new Promise<never>(
|
|
202
|
+
(_resolve: unknown, reject: (err: Error) => void) => {
|
|
203
|
+
setTimeout(() => {
|
|
204
|
+
reject(new Error("Tool execution timed out."));
|
|
205
|
+
}, TOOL_EXECUTION_TIMEOUT_MS);
|
|
206
|
+
},
|
|
207
|
+
),
|
|
208
|
+
]);
|
|
209
|
+
|
|
210
|
+
return {
|
|
211
|
+
success: true,
|
|
212
|
+
textForLlm: result.dataForLlm,
|
|
213
|
+
result: result,
|
|
214
|
+
};
|
|
215
|
+
} catch (error) {
|
|
216
|
+
const message: string =
|
|
217
|
+
error instanceof Error ? error.message : String(error);
|
|
218
|
+
|
|
219
|
+
logger.error(`AI toolbox tool ${data.name} failed: ${message}`);
|
|
220
|
+
|
|
221
|
+
return {
|
|
222
|
+
success: false,
|
|
223
|
+
textForLlm: `Error executing ${data.name}: ${message}. Adjust the arguments and try again, or answer with the data you already have.`,
|
|
224
|
+
errorMessage: message,
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|