@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.
Files changed (189) hide show
  1. package/Models/DatabaseModels/AIConversation.ts +322 -0
  2. package/Models/DatabaseModels/AIConversationMessage.ts +495 -0
  3. package/Models/DatabaseModels/AIRun.ts +584 -0
  4. package/Models/DatabaseModels/AIRunEvent.ts +443 -0
  5. package/Models/DatabaseModels/Index.ts +8 -0
  6. package/Models/DatabaseModels/LlmLog.ts +26 -0
  7. package/Models/DatabaseModels/LlmProvider.ts +2 -2
  8. package/Server/API/AIChatAPI.ts +693 -0
  9. package/Server/API/LlmProviderAPI.ts +169 -0
  10. package/Server/API/MicrosoftTeamsAPI.ts +5 -0
  11. package/Server/API/SlackAPI.ts +663 -0
  12. package/Server/Infrastructure/Postgres/SchemaMigrations/1783363279075-AddAIChatModels.ts +245 -0
  13. package/Server/Infrastructure/Postgres/SchemaMigrations/1783443471795-AddLlmProviderToAIConversation.ts +36 -0
  14. package/Server/Infrastructure/Postgres/SchemaMigrations/1783453297388-AddAIChatWriteActionsAndWidgets.ts +46 -0
  15. package/Server/Infrastructure/Postgres/SchemaMigrations/1783461767405-MigrationName.ts +119 -0
  16. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +8 -0
  17. package/Server/Services/AIConversationMessageService.ts +39 -0
  18. package/Server/Services/AIConversationService.ts +63 -0
  19. package/Server/Services/AIRunEventService.ts +39 -0
  20. package/Server/Services/AIRunService.ts +39 -0
  21. package/Server/Services/AIService.ts +53 -31
  22. package/Server/Services/DatabaseService.ts +19 -0
  23. package/Server/Services/LlmProviderService.ts +110 -0
  24. package/Server/Services/ProjectService.ts +24 -0
  25. package/Server/Types/AnalyticsDatabase/ModelPermission.ts +74 -9
  26. package/Server/Utils/AI/AIChatPrivacyFilter.ts +28 -0
  27. package/Server/Utils/AI/Chat/ChatAgentRunner.ts +1054 -0
  28. package/Server/Utils/AI/Chat/ObservabilityAssistant.ts +239 -0
  29. package/Server/Utils/AI/Chat/ObservabilityChatPrompt.ts +51 -0
  30. package/Server/Utils/AI/Toolbox/AlertTools.ts +201 -0
  31. package/Server/Utils/AI/Toolbox/AlertWriteTools.ts +174 -0
  32. package/Server/Utils/AI/Toolbox/ContextTools.ts +189 -0
  33. package/Server/Utils/AI/Toolbox/ExceptionTools.ts +149 -0
  34. package/Server/Utils/AI/Toolbox/IncidentTools.ts +204 -0
  35. package/Server/Utils/AI/Toolbox/IncidentWriteTools.ts +350 -0
  36. package/Server/Utils/AI/Toolbox/Index.ts +228 -0
  37. package/Server/Utils/AI/Toolbox/LogTools.ts +339 -0
  38. package/Server/Utils/AI/Toolbox/MetricTools.ts +179 -0
  39. package/Server/Utils/AI/Toolbox/MonitorTools.ts +193 -0
  40. package/Server/Utils/AI/Toolbox/Serializer.ts +257 -0
  41. package/Server/Utils/AI/Toolbox/ToolTypes.ts +249 -0
  42. package/Server/Utils/AI/Toolbox/TraceTools.ts +402 -0
  43. package/Server/Utils/AI/Toolbox/WidgetBuilder.ts +189 -0
  44. package/Server/Utils/LLM/LLMService.ts +502 -92
  45. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +512 -19
  46. package/Server/Utils/Workspace/Slack/Slack.ts +80 -0
  47. package/Server/Utils/Workspace/Slack/app-manifest.json +16 -1
  48. package/Tests/Server/Utils/AI/AIChatModelACL.test.ts +42 -0
  49. package/Tests/Server/Utils/AI/ChatAgentHelpers.test.ts +89 -0
  50. package/Tests/Server/Utils/AI/LLMServiceBaseUrl.test.ts +139 -0
  51. package/Tests/Server/Utils/AI/LLMServiceToolCalling.test.ts +308 -0
  52. package/Tests/Server/Utils/AI/ToolArgsGetTimeRange.test.ts +62 -0
  53. package/Tests/Server/Utils/AI/ToolArgsScopeServiceIds.test.ts +79 -0
  54. package/Tests/Server/Utils/AI/ToolResultSerializer.test.ts +155 -0
  55. package/Tests/Types/Billing/SubscriptionStatus.test.ts +127 -0
  56. package/Tests/Types/Metrics/RecordingRuleDefinition.test.ts +213 -0
  57. package/Types/AI/AIChatMessageRole.ts +6 -0
  58. package/Types/AI/AIChatMessageStatus.ts +32 -0
  59. package/Types/AI/AIChatPermissionMode.ts +69 -0
  60. package/Types/AI/AIChatTypes.ts +231 -0
  61. package/Types/AI/AIRunEventType.ts +17 -0
  62. package/Types/AI/AIRunStatus.ts +28 -0
  63. package/Types/AI/AIRunType.ts +6 -0
  64. package/Types/LLM/LlmType.ts +6 -0
  65. package/UI/Components/Header/HeaderIconDropdownButton.tsx +22 -12
  66. package/UI/Components/Markdown.tsx/MarkdownViewer.tsx +35 -3
  67. package/UI/Components/Page/Page.tsx +14 -0
  68. package/UI/Utils/LlmTypeDropdownOptions.ts +40 -0
  69. package/UI/Utils/TestLLMProvider.ts +59 -0
  70. package/build/dist/Models/DatabaseModels/AIConversation.js +345 -0
  71. package/build/dist/Models/DatabaseModels/AIConversation.js.map +1 -0
  72. package/build/dist/Models/DatabaseModels/AIConversationMessage.js +521 -0
  73. package/build/dist/Models/DatabaseModels/AIConversationMessage.js.map +1 -0
  74. package/build/dist/Models/DatabaseModels/AIRun.js +619 -0
  75. package/build/dist/Models/DatabaseModels/AIRun.js.map +1 -0
  76. package/build/dist/Models/DatabaseModels/AIRunEvent.js +469 -0
  77. package/build/dist/Models/DatabaseModels/AIRunEvent.js.map +1 -0
  78. package/build/dist/Models/DatabaseModels/Index.js +8 -0
  79. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  80. package/build/dist/Models/DatabaseModels/LlmLog.js +28 -0
  81. package/build/dist/Models/DatabaseModels/LlmLog.js.map +1 -1
  82. package/build/dist/Models/DatabaseModels/LlmProvider.js +2 -2
  83. package/build/dist/Models/DatabaseModels/LlmProvider.js.map +1 -1
  84. package/build/dist/Server/API/AIChatAPI.js +498 -0
  85. package/build/dist/Server/API/AIChatAPI.js.map +1 -0
  86. package/build/dist/Server/API/LlmProviderAPI.js +107 -1
  87. package/build/dist/Server/API/LlmProviderAPI.js.map +1 -1
  88. package/build/dist/Server/API/MicrosoftTeamsAPI.js +4 -0
  89. package/build/dist/Server/API/MicrosoftTeamsAPI.js.map +1 -1
  90. package/build/dist/Server/API/SlackAPI.js +442 -0
  91. package/build/dist/Server/API/SlackAPI.js.map +1 -1
  92. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783363279075-AddAIChatModels.js +96 -0
  93. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783363279075-AddAIChatModels.js.map +1 -0
  94. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783443471795-AddLlmProviderToAIConversation.js +25 -0
  95. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783443471795-AddLlmProviderToAIConversation.js.map +1 -0
  96. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783453297388-AddAIChatWriteActionsAndWidgets.js +31 -0
  97. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783453297388-AddAIChatWriteActionsAndWidgets.js.map +1 -0
  98. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783461767405-MigrationName.js +46 -0
  99. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783461767405-MigrationName.js.map +1 -0
  100. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +8 -0
  101. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  102. package/build/dist/Server/Services/AIConversationMessageService.js +34 -0
  103. package/build/dist/Server/Services/AIConversationMessageService.js.map +1 -0
  104. package/build/dist/Server/Services/AIConversationService.js +42 -0
  105. package/build/dist/Server/Services/AIConversationService.js.map +1 -0
  106. package/build/dist/Server/Services/AIRunEventService.js +34 -0
  107. package/build/dist/Server/Services/AIRunEventService.js.map +1 -0
  108. package/build/dist/Server/Services/AIRunService.js +34 -0
  109. package/build/dist/Server/Services/AIRunService.js.map +1 -0
  110. package/build/dist/Server/Services/AIService.js +31 -23
  111. package/build/dist/Server/Services/AIService.js.map +1 -1
  112. package/build/dist/Server/Services/DatabaseService.js +10 -0
  113. package/build/dist/Server/Services/DatabaseService.js.map +1 -1
  114. package/build/dist/Server/Services/LlmProviderService.js +108 -0
  115. package/build/dist/Server/Services/LlmProviderService.js.map +1 -1
  116. package/build/dist/Server/Services/ProjectService.js +25 -0
  117. package/build/dist/Server/Services/ProjectService.js.map +1 -1
  118. package/build/dist/Server/Types/AnalyticsDatabase/ModelPermission.js +50 -7
  119. package/build/dist/Server/Types/AnalyticsDatabase/ModelPermission.js.map +1 -1
  120. package/build/dist/Server/Utils/AI/AIChatPrivacyFilter.js +18 -0
  121. package/build/dist/Server/Utils/AI/AIChatPrivacyFilter.js.map +1 -0
  122. package/build/dist/Server/Utils/AI/Chat/ChatAgentRunner.js +745 -0
  123. package/build/dist/Server/Utils/AI/Chat/ChatAgentRunner.js.map +1 -0
  124. package/build/dist/Server/Utils/AI/Chat/ObservabilityAssistant.js +165 -0
  125. package/build/dist/Server/Utils/AI/Chat/ObservabilityAssistant.js.map +1 -0
  126. package/build/dist/Server/Utils/AI/Chat/ObservabilityChatPrompt.js +44 -0
  127. package/build/dist/Server/Utils/AI/Chat/ObservabilityChatPrompt.js.map +1 -0
  128. package/build/dist/Server/Utils/AI/Toolbox/AlertTools.js +167 -0
  129. package/build/dist/Server/Utils/AI/Toolbox/AlertTools.js.map +1 -0
  130. package/build/dist/Server/Utils/AI/Toolbox/AlertWriteTools.js +136 -0
  131. package/build/dist/Server/Utils/AI/Toolbox/AlertWriteTools.js.map +1 -0
  132. package/build/dist/Server/Utils/AI/Toolbox/ContextTools.js +141 -0
  133. package/build/dist/Server/Utils/AI/Toolbox/ContextTools.js.map +1 -0
  134. package/build/dist/Server/Utils/AI/Toolbox/ExceptionTools.js +117 -0
  135. package/build/dist/Server/Utils/AI/Toolbox/ExceptionTools.js.map +1 -0
  136. package/build/dist/Server/Utils/AI/Toolbox/IncidentTools.js +167 -0
  137. package/build/dist/Server/Utils/AI/Toolbox/IncidentTools.js.map +1 -0
  138. package/build/dist/Server/Utils/AI/Toolbox/IncidentWriteTools.js +280 -0
  139. package/build/dist/Server/Utils/AI/Toolbox/IncidentWriteTools.js.map +1 -0
  140. package/build/dist/Server/Utils/AI/Toolbox/Index.js +150 -0
  141. package/build/dist/Server/Utils/AI/Toolbox/Index.js.map +1 -0
  142. package/build/dist/Server/Utils/AI/Toolbox/LogTools.js +246 -0
  143. package/build/dist/Server/Utils/AI/Toolbox/LogTools.js.map +1 -0
  144. package/build/dist/Server/Utils/AI/Toolbox/MetricTools.js +120 -0
  145. package/build/dist/Server/Utils/AI/Toolbox/MetricTools.js.map +1 -0
  146. package/build/dist/Server/Utils/AI/Toolbox/MonitorTools.js +158 -0
  147. package/build/dist/Server/Utils/AI/Toolbox/MonitorTools.js.map +1 -0
  148. package/build/dist/Server/Utils/AI/Toolbox/Serializer.js +188 -0
  149. package/build/dist/Server/Utils/AI/Toolbox/Serializer.js.map +1 -0
  150. package/build/dist/Server/Utils/AI/Toolbox/ToolTypes.js +142 -0
  151. package/build/dist/Server/Utils/AI/Toolbox/ToolTypes.js.map +1 -0
  152. package/build/dist/Server/Utils/AI/Toolbox/TraceTools.js +309 -0
  153. package/build/dist/Server/Utils/AI/Toolbox/TraceTools.js.map +1 -0
  154. package/build/dist/Server/Utils/AI/Toolbox/WidgetBuilder.js +120 -0
  155. package/build/dist/Server/Utils/AI/Toolbox/WidgetBuilder.js.map +1 -0
  156. package/build/dist/Server/Utils/LLM/LLMService.js +372 -88
  157. package/build/dist/Server/Utils/LLM/LLMService.js.map +1 -1
  158. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +378 -15
  159. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
  160. package/build/dist/Server/Utils/Workspace/Slack/Slack.js +59 -0
  161. package/build/dist/Server/Utils/Workspace/Slack/Slack.js.map +1 -1
  162. package/build/dist/Server/Utils/Workspace/Slack/app-manifest.json +16 -1
  163. package/build/dist/Types/AI/AIChatMessageRole.js +7 -0
  164. package/build/dist/Types/AI/AIChatMessageRole.js.map +1 -0
  165. package/build/dist/Types/AI/AIChatMessageStatus.js +27 -0
  166. package/build/dist/Types/AI/AIChatMessageStatus.js.map +1 -0
  167. package/build/dist/Types/AI/AIChatPermissionMode.js +53 -0
  168. package/build/dist/Types/AI/AIChatPermissionMode.js.map +1 -0
  169. package/build/dist/Types/AI/AIChatTypes.js +74 -0
  170. package/build/dist/Types/AI/AIChatTypes.js.map +1 -0
  171. package/build/dist/Types/AI/AIRunEventType.js +18 -0
  172. package/build/dist/Types/AI/AIRunEventType.js.map +1 -0
  173. package/build/dist/Types/AI/AIRunStatus.js +26 -0
  174. package/build/dist/Types/AI/AIRunStatus.js.map +1 -0
  175. package/build/dist/Types/AI/AIRunType.js +7 -0
  176. package/build/dist/Types/AI/AIRunType.js.map +1 -0
  177. package/build/dist/Types/LLM/LlmType.js +6 -0
  178. package/build/dist/Types/LLM/LlmType.js.map +1 -1
  179. package/build/dist/UI/Components/Header/HeaderIconDropdownButton.js +10 -5
  180. package/build/dist/UI/Components/Header/HeaderIconDropdownButton.js.map +1 -1
  181. package/build/dist/UI/Components/Markdown.tsx/MarkdownViewer.js +18 -4
  182. package/build/dist/UI/Components/Markdown.tsx/MarkdownViewer.js.map +1 -1
  183. package/build/dist/UI/Components/Page/Page.js +3 -1
  184. package/build/dist/UI/Components/Page/Page.js.map +1 -1
  185. package/build/dist/UI/Utils/LlmTypeDropdownOptions.js +38 -0
  186. package/build/dist/UI/Utils/LlmTypeDropdownOptions.js.map +1 -0
  187. package/build/dist/UI/Utils/TestLLMProvider.js +37 -0
  188. package/build/dist/UI/Utils/TestLLMProvider.js.map +1 -0
  189. package/package.json +1 -1
@@ -0,0 +1,231 @@
1
+ import { JSONObject } from "../JSON";
2
+
3
+ /*
4
+ * Shared types for the AI observability chat feature. These shapes are
5
+ * persisted as JSON columns on AIConversationMessage / AIRun / AIRunEvent and
6
+ * rendered by the dashboard, so they must stay JSON-serializable.
7
+ */
8
+
9
+ /*
10
+ * Where a citation chip should deep-link to in the dashboard. The dashboard
11
+ * maps each target type onto its own RouteMap entry.
12
+ */
13
+ export enum AIChatCitationTargetType {
14
+ Logs = "Logs",
15
+ Traces = "Traces",
16
+ TraceView = "TraceView",
17
+ Metrics = "Metrics",
18
+ Exceptions = "Exceptions",
19
+ Incidents = "Incidents",
20
+ IncidentView = "IncidentView",
21
+ Alerts = "Alerts",
22
+ AlertView = "AlertView",
23
+ Monitors = "Monitors",
24
+ MonitorView = "MonitorView",
25
+ }
26
+
27
+ export interface AIChatCitationTarget {
28
+ type: AIChatCitationTargetType;
29
+ // Route params keyed by param name (e.g. { traceId: "abc" }).
30
+ params?: { [key: string]: string };
31
+ }
32
+
33
+ /*
34
+ * Citations are minted server-side from validated tool arguments — never by
35
+ * the model. rowCount 0 is meaningful: it is proof of absence ("checked,
36
+ * found nothing").
37
+ */
38
+ export interface AIChatCitation {
39
+ id: string; // "C1", "C2" — sequential per message
40
+ toolName: string;
41
+ label: string;
42
+ queryArguments: JSONObject; // validated tool args as executed — replayable
43
+ rowCount: number;
44
+ target?: AIChatCitationTarget | undefined;
45
+ }
46
+
47
+ /*
48
+ * ---------------------------------------------------------------------------
49
+ * Inline widgets
50
+ * ---------------------------------------------------------------------------
51
+ * A tool can attach a widget to its result. The widget is persisted on the
52
+ * assistant message (AIConversationMessage.widgets) and rendered inline in the
53
+ * chat by the dashboard. Widget `data` is type-specific and MUST stay
54
+ * JSON-serializable. Unlike the text sent to the LLM, widget data is built from
55
+ * the RAW rows (not the redacted/truncated LLM payload) because it is rendered
56
+ * back to the same user who already has RBAC access to it.
57
+ */
58
+ export enum AIChatWidgetType {
59
+ // A time-series line chart (e.g. a metric aggregated over time).
60
+ TimeSeriesChart = "TimeSeriesChart",
61
+ // A bar chart, optionally stacked (e.g. log volume by severity over time).
62
+ BarChart = "BarChart",
63
+ // A generic sortable table.
64
+ Table = "Table",
65
+ // A trace waterfall (span tree with durations).
66
+ TraceWaterfall = "TraceWaterfall",
67
+ // A list of incident cards.
68
+ IncidentList = "IncidentList",
69
+ // A list of alert cards.
70
+ AlertList = "AlertList",
71
+ // A list of exception cards.
72
+ ExceptionList = "ExceptionList",
73
+ // KPI stat tiles.
74
+ StatCards = "StatCards",
75
+ // A single created/updated resource (e.g. the incident the agent just made).
76
+ ResourceCard = "ResourceCard",
77
+ }
78
+
79
+ /*
80
+ * One point in a chart series. x is an ISO timestamp (time charts) or a
81
+ * category label (categorical charts); y is the numeric value.
82
+ */
83
+ export interface AIChatWidgetPoint {
84
+ x: string;
85
+ y: number | null;
86
+ }
87
+
88
+ export interface AIChatWidgetSeries {
89
+ name: string;
90
+ points: Array<AIChatWidgetPoint>;
91
+ }
92
+
93
+ export interface AIChatWidgetColumn {
94
+ key: string;
95
+ title: string;
96
+ // Rough hint for alignment/formatting in the renderer.
97
+ type?: "text" | "number" | "date" | undefined;
98
+ }
99
+
100
+ export interface AIChatWidgetStat {
101
+ label: string;
102
+ value: string | number;
103
+ unit?: string | undefined;
104
+ }
105
+
106
+ export interface AIChatWidgetSpan {
107
+ spanId: string;
108
+ parentSpanId?: string | undefined;
109
+ name: string;
110
+ // Offset of the span start from the trace start, in milliseconds.
111
+ startOffsetMs: number;
112
+ durationMs: number;
113
+ isError: boolean;
114
+ serviceName?: string | undefined;
115
+ }
116
+
117
+ /*
118
+ * Type-specific widget payload. Only the fields relevant to `type` are set:
119
+ * - TimeSeriesChart / BarChart: `series` (+ `stacked`, `unit`, `xIsTime`)
120
+ * - Table: `columns` + `rows`
121
+ * - TraceWaterfall: `spans` + `totalDurationMs`
122
+ * - IncidentList / AlertList / ExceptionList: `items`
123
+ * - StatCards: `stats`
124
+ * - ResourceCard: `resourceType`, `heading`, `subheading`, `fields`, `link`
125
+ */
126
+ export interface AIChatWidgetData {
127
+ series?: Array<AIChatWidgetSeries> | undefined;
128
+ stacked?: boolean | undefined;
129
+ xIsTime?: boolean | undefined;
130
+ unit?: string | undefined;
131
+ valueLabel?: string | undefined;
132
+ columns?: Array<AIChatWidgetColumn> | undefined;
133
+ rows?: Array<JSONObject> | undefined;
134
+ spans?: Array<AIChatWidgetSpan> | undefined;
135
+ totalDurationMs?: number | undefined;
136
+ items?: Array<JSONObject> | undefined;
137
+ stats?: Array<AIChatWidgetStat> | undefined;
138
+ resourceType?: string | undefined;
139
+ heading?: string | undefined;
140
+ subheading?: string | undefined;
141
+ fields?: Array<{ label: string; value: string }> | undefined;
142
+ link?: AIChatCitationTarget | undefined;
143
+ }
144
+
145
+ export interface AIChatWidget {
146
+ id: string; // "W1", "W2" — sequential per message
147
+ citationId?: string | undefined; // correlates to the citation of the same tool call
148
+ type: AIChatWidgetType;
149
+ title: string;
150
+ description?: string | undefined;
151
+ data: AIChatWidgetData;
152
+ }
153
+
154
+ /*
155
+ * ---------------------------------------------------------------------------
156
+ * Tool actions (mutations + approvals)
157
+ * ---------------------------------------------------------------------------
158
+ * When the agent wants to run a mutating tool it records an AIChatToolAction on
159
+ * the assistant message. In "Ask for approval" mode the action starts Pending
160
+ * and the run pauses until the user approves/denies it; the resumed run then
161
+ * executes approved actions and marks them Executed/Failed.
162
+ */
163
+ export enum AIChatToolActionStatus {
164
+ Pending = "Pending", // awaiting the user's decision
165
+ Approved = "Approved", // user approved; not yet executed
166
+ Denied = "Denied", // user declined
167
+ Executed = "Executed", // ran successfully
168
+ Failed = "Failed", // ran but failed
169
+ Skipped = "Skipped", // not run (e.g. budget exhausted)
170
+ }
171
+
172
+ export interface AIChatToolAction {
173
+ id: string; // equals the LLM tool call id
174
+ toolName: string;
175
+ title: string; // human label, e.g. "Create incident: Checkout is down"
176
+ description?: string | undefined;
177
+ arguments: JSONObject;
178
+ isMutation: boolean;
179
+ requiresApproval: boolean;
180
+ status: AIChatToolActionStatus;
181
+ resultSummary?: string | undefined;
182
+ }
183
+
184
+ export interface AIRunEventResultSummary {
185
+ rowCount?: number | undefined;
186
+ durationInMs?: number | undefined;
187
+ isTruncated?: boolean | undefined;
188
+ bytesSentToLlm?: number | undefined;
189
+ errorMessage?: string | undefined;
190
+ }
191
+
192
+ // What was sent to which LLM during a run — the per-run egress manifest.
193
+ export interface AIRunEgressManifestToolEntry {
194
+ toolName: string;
195
+ rowCount: number;
196
+ bytesSentToLlm: number;
197
+ redactionCount: number;
198
+ }
199
+
200
+ /*
201
+ * The serialized in-flight state of a turn that paused to wait for tool
202
+ * approval. Persisted on AIRun.pausedState so ChatAgentRunner.resumeTurn can
203
+ * rehydrate the exact conversation and continue the loop. `messages` and
204
+ * `pendingToolCalls` are JSON projections of the runner's internal
205
+ * LLMMessage / LLMToolCall types (kept as JSONObject here so this shared type
206
+ * does not depend on server code).
207
+ */
208
+ export interface AIRunPausedState {
209
+ messages: Array<JSONObject>;
210
+ pendingToolCalls: Array<JSONObject>;
211
+ llmCallCount: number;
212
+ toolCallCount: number;
213
+ totalTokens: number;
214
+ totalCostInUSDCents: number;
215
+ eventSequence: number;
216
+ citations: Array<AIChatCitation>;
217
+ widgets: Array<AIChatWidget>;
218
+ toolActions: Array<AIChatToolAction>;
219
+ egressToolEntries: Array<AIRunEgressManifestToolEntry>;
220
+ startedAtMs: number;
221
+ }
222
+
223
+ export interface AIRunEgressManifest {
224
+ llmProviderName?: string | undefined;
225
+ llmType?: string | undefined;
226
+ modelName?: string | undefined;
227
+ isGlobalProvider?: boolean | undefined;
228
+ llmCallCount: number;
229
+ totalTokens: number;
230
+ toolDataSentToLlm: Array<AIRunEgressManifestToolEntry>;
231
+ }
@@ -0,0 +1,17 @@
1
+ enum AIRunEventType {
2
+ RunStarted = "RunStarted",
3
+ LlmCallStarted = "LlmCallStarted",
4
+ LlmCallCompleted = "LlmCallCompleted",
5
+ ToolCallStarted = "ToolCallStarted",
6
+ ToolCallCompleted = "ToolCallCompleted",
7
+ ToolCallFailed = "ToolCallFailed",
8
+ // The run paused to ask the user to approve one or more mutating actions.
9
+ ApprovalRequested = "ApprovalRequested",
10
+ // A mutating action the user approved was executed (or denied and skipped).
11
+ ActionExecuted = "ActionExecuted",
12
+ ActionDenied = "ActionDenied",
13
+ RunCompleted = "RunCompleted",
14
+ RunFailed = "RunFailed",
15
+ }
16
+
17
+ export default AIRunEventType;
@@ -0,0 +1,28 @@
1
+ enum AIRunStatus {
2
+ Running = "Running",
3
+ /*
4
+ * The run paused mid-turn to wait for the user to approve pending tool
5
+ * actions (see AIChatPermissionMode.AskForApproval). It is not terminal — it
6
+ * resumes when the user responds — but it is also not actively consuming
7
+ * compute, so the stale-run sweeper (which only touches Running runs) leaves
8
+ * it alone.
9
+ */
10
+ WaitingForApproval = "WaitingForApproval",
11
+ Completed = "Completed",
12
+ Error = "Error",
13
+ Cancelled = "Cancelled",
14
+ Stale = "Stale",
15
+ }
16
+
17
+ export default AIRunStatus;
18
+
19
+ export class AIRunStatusHelper {
20
+ public static isTerminalStatus(status: AIRunStatus): boolean {
21
+ return (
22
+ status === AIRunStatus.Completed ||
23
+ status === AIRunStatus.Error ||
24
+ status === AIRunStatus.Cancelled ||
25
+ status === AIRunStatus.Stale
26
+ );
27
+ }
28
+ }
@@ -0,0 +1,6 @@
1
+ enum AIRunType {
2
+ Chat = "Chat",
3
+ Investigation = "Investigation",
4
+ }
5
+
6
+ export default AIRunType;
@@ -5,6 +5,12 @@ enum LlmType {
5
5
  Groq = "Groq",
6
6
  Mistral = "Mistral",
7
7
  Ollama = "Ollama",
8
+ /*
9
+ * Generic OpenAI-compatible servers (vLLM, LocalAI, LM Studio, text-gen-webui,
10
+ * etc.) that speak the OpenAI /chat/completions wire format but are typically
11
+ * self-hosted at a custom base URL and often require no API key.
12
+ */
13
+ OpenAICompatible = "OpenAICompatible",
8
14
  }
9
15
 
10
16
  export default LlmType;
@@ -37,21 +37,28 @@ const HeaderIconDropdownButton: FunctionComponent<ComponentProps> = (
37
37
  setIsComponentVisible(Boolean(props.showDropdown));
38
38
  }, [props.showDropdown]);
39
39
 
40
+ const hasLabel: boolean = Boolean(props.title);
41
+ const hasDropdown: boolean = Boolean(props.children);
42
+
43
+ const sizeClassName: string = hasLabel
44
+ ? "h-9 gap-1.5 pl-2.5 pr-3"
45
+ : "h-9 w-9 justify-center";
46
+
40
47
  return (
41
- <div className="relative ml-1 flex-shrink-0">
48
+ <div className="relative ml-2 flex-shrink-0">
42
49
  <div>
43
50
  <button
44
51
  type="button"
45
- className="flex items-center justify-center h-9 w-9 rounded-lg bg-gray-50 border border-gray-200 hover:bg-gray-100 hover:border-gray-300 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-1 transition-all duration-150"
46
- id="user-menu-button"
47
- aria-expanded="false"
48
- aria-haspopup="true"
52
+ className={`flex items-center rounded-lg border border-gray-200 bg-gray-50 hover:bg-gray-100 hover:border-gray-300 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-1 transition-all duration-150 ${sizeClassName}`}
53
+ id={hasDropdown ? "user-menu-button" : undefined}
54
+ aria-expanded={hasDropdown ? isDropdownVisible : undefined}
55
+ aria-haspopup={hasDropdown ? "true" : undefined}
49
56
  onClick={() => {
50
57
  props.onClick?.();
51
58
  setIsComponentVisible(!isDropdownVisible);
52
59
  }}
53
60
  >
54
- <span className="sr-only">{props.name}</span>
61
+ {!hasLabel && <span className="sr-only">{props.name}</span>}
55
62
  {props.iconImageUrl && (
56
63
  <Image
57
64
  className="h-7 w-7 rounded-md object-cover"
@@ -63,14 +70,17 @@ const HeaderIconDropdownButton: FunctionComponent<ComponentProps> = (
63
70
  />
64
71
  )}
65
72
  {props.icon && (
66
- <Icon className="h-5 w-5 text-gray-500" icon={props.icon} />
73
+ <Icon
74
+ className={`${hasLabel ? "h-4 w-4" : "h-5 w-5"} text-gray-500`}
75
+ icon={props.icon}
76
+ />
77
+ )}
78
+ {hasLabel && (
79
+ <span className="text-sm font-medium text-gray-700 whitespace-nowrap">
80
+ {props.title}
81
+ </span>
67
82
  )}
68
83
  </button>
69
- {props.title && (
70
- <span className="ml-2 text-sm font-medium text-gray-700">
71
- {props.title}
72
- </span>
73
- )}
74
84
  {props.badge && props.badge > 0 && (
75
85
  <span className="absolute -top-1 -right-1 flex h-5 w-5 items-center justify-center rounded-full bg-red-500 text-xs font-semibold text-white ring-2 ring-white">
76
86
  {props.badge}
@@ -144,6 +144,14 @@ const MermaidDiagram: FunctionComponent<{ chart: string }> = ({
144
144
 
145
145
  export interface ComponentProps {
146
146
  text: string;
147
+ /*
148
+ * Hardened rendering for untrusted / LLM-generated content. When true, links
149
+ * render as non-clickable text and images are never fetched — both are
150
+ * neutralized on react-markdown's PARSED tree, so (unlike pre-processing the
151
+ * raw markdown with regexes) no CommonMark syntax trick can smuggle a
152
+ * clickable exfil link or a zero-click tracking pixel through.
153
+ */
154
+ safeMode?: boolean;
147
155
  }
148
156
 
149
157
  // Language display names
@@ -348,6 +356,9 @@ const extractTextFromChildren: (children: unknown) => string = (
348
356
  const MarkdownViewer: FunctionComponent<ComponentProps> = (
349
357
  props: ComponentProps,
350
358
  ): ReactElement => {
359
+ // Captured here because the component overrides below shadow `props`.
360
+ const safeMode: boolean = props.safeMode === true;
361
+
351
362
  return (
352
363
  <div className="max-w-none">
353
364
  <ReactMarkdown
@@ -409,19 +420,40 @@ const MarkdownViewer: FunctionComponent<ComponentProps> = (
409
420
  />
410
421
  );
411
422
  },
412
- a: ({ ...props }: any) => {
423
+ a: ({ children, ...props }: any) => {
424
+ if (safeMode) {
425
+ // Keep the link text, drop the href: no navigation, no exfil.
426
+ return (
427
+ <span className="underline decoration-dotted text-gray-700 font-medium">
428
+ {children}
429
+ </span>
430
+ );
431
+ }
413
432
  return (
414
433
  <a
415
434
  className="underline text-blue-600 hover:text-blue-800 font-medium transition-colors"
416
435
  {...props}
417
- />
436
+ >
437
+ {children}
438
+ </a>
418
439
  );
419
440
  },
420
- img: ({ ...props }: any) => {
441
+ img: ({ alt, ...props }: any) => {
442
+ if (safeMode) {
443
+ /*
444
+ * Never emit an <img> for untrusted content — the browser would
445
+ * fetch its src on render (a zero-click exfil channel). Show the
446
+ * alt text (already plain, React-escaped) instead.
447
+ */
448
+ return alt ? (
449
+ <span className="text-gray-500 italic">{`[image: ${alt}]`}</span>
450
+ ) : null;
451
+ }
421
452
  return (
422
453
  <img
423
454
  className="max-w-full h-auto rounded-md border border-gray-200 my-3"
424
455
  loading="lazy"
456
+ alt={alt}
425
457
  {...props}
426
458
  />
427
459
  );
@@ -10,6 +10,12 @@ import React, { FunctionComponent, ReactElement, useEffect } from "react";
10
10
 
11
11
  export interface ComponentProps {
12
12
  title?: string | undefined;
13
+ /*
14
+ * Optional one-line subtitle rendered under the title. Use it to say what the
15
+ * page is for when the title alone is not self-explanatory. Kept optional so
16
+ * existing pages that carry their description in a content card are unchanged.
17
+ */
18
+ description?: string | undefined;
13
19
  breadcrumbLinks?: Array<Link> | undefined;
14
20
  children: Array<ReactElement> | ReactElement;
15
21
  sideMenu?: undefined | ReactElement;
@@ -25,6 +31,9 @@ const Page: FunctionComponent<ComponentProps> = (
25
31
  ): ReactElement => {
26
32
  const { translateString } = useTranslateValue();
27
33
  const translatedTitle: string | undefined = translateString(props.title);
34
+ const translatedDescription: string | undefined = translateString(
35
+ props.description,
36
+ );
28
37
 
29
38
  useEffect(() => {
30
39
  if (props.breadcrumbLinks && props.breadcrumbLinks.length > 0) {
@@ -92,6 +101,11 @@ const Page: FunctionComponent<ComponentProps> = (
92
101
  <h1 className="text-xl font-semibold leading-7 text-gray-900 sm:text-xl sm:tracking-tight sm:truncate">
93
102
  {translatedTitle}
94
103
  </h1>
104
+ {translatedDescription && (
105
+ <p className="max-w-3xl text-sm leading-6 text-gray-500">
106
+ {translatedDescription}
107
+ </p>
108
+ )}
95
109
  </div>
96
110
  {props.headerRight && (
97
111
  <div className="flex flex-wrap items-center sm:justify-end gap-3">
@@ -0,0 +1,40 @@
1
+ import { DropdownOption } from "../Components/Dropdown/Dropdown";
2
+ import LlmType from "../../Types/LLM/LlmType";
3
+
4
+ /*
5
+ * Human-friendly display labels for each LLM provider. The dropdown `value`
6
+ * remains the raw LlmType enum value (which is persisted in the database), so
7
+ * only the label shown to the user changes.
8
+ */
9
+ const LlmTypeDropdownOptions: Array<DropdownOption> = [
10
+ {
11
+ label: "OpenAI",
12
+ value: LlmType.OpenAI,
13
+ },
14
+ {
15
+ label: "Azure OpenAI",
16
+ value: LlmType.AzureOpenAI,
17
+ },
18
+ {
19
+ label: "Anthropic",
20
+ value: LlmType.Anthropic,
21
+ },
22
+ {
23
+ label: "Groq",
24
+ value: LlmType.Groq,
25
+ },
26
+ {
27
+ label: "Mistral",
28
+ value: LlmType.Mistral,
29
+ },
30
+ {
31
+ label: "Ollama",
32
+ value: LlmType.Ollama,
33
+ },
34
+ {
35
+ label: "OpenAI-Compatible (vLLM, LocalAI, etc.)",
36
+ value: LlmType.OpenAICompatible,
37
+ },
38
+ ];
39
+
40
+ export default LlmTypeDropdownOptions;
@@ -0,0 +1,59 @@
1
+ import URL from "../../Types/API/URL";
2
+ import Dictionary from "../../Types/Dictionary";
3
+ import HTTPErrorResponse from "../../Types/API/HTTPErrorResponse";
4
+ import HTTPResponse from "../../Types/API/HTTPResponse";
5
+ import { JSONObject } from "../../Types/JSON";
6
+ import { APP_API_URL } from "../Config";
7
+ import API from "./API/API";
8
+
9
+ export interface LLMProviderTestResult {
10
+ success: boolean;
11
+ message: string;
12
+ }
13
+
14
+ export default class TestLLMProvider {
15
+ /*
16
+ * Sends a small test prompt to an LLM provider (by id) via the
17
+ * /llm-provider/test endpoint and returns a friendly success/failure result.
18
+ * Callers pass the appropriate common headers (project tenant header for
19
+ * project providers, or none for admin/global providers).
20
+ */
21
+ public static async test(data: {
22
+ llmProviderId: string;
23
+ headers?: Dictionary<string> | undefined;
24
+ }): Promise<LLMProviderTestResult> {
25
+ try {
26
+ const response: HTTPResponse<JSONObject> | HTTPErrorResponse =
27
+ await API.post({
28
+ url: URL.fromString(APP_API_URL.toString()).addRoute(
29
+ "/llm-provider/test",
30
+ ),
31
+ data: {
32
+ llmProviderId: data.llmProviderId,
33
+ },
34
+ ...(data.headers ? { headers: data.headers } : {}),
35
+ });
36
+
37
+ if (response.isFailure()) {
38
+ return {
39
+ success: false,
40
+ message: API.getFriendlyMessage(response),
41
+ };
42
+ }
43
+
44
+ const responseData: JSONObject = response.data as JSONObject;
45
+
46
+ return {
47
+ success: true,
48
+ message:
49
+ (responseData["message"] as string) ||
50
+ "Connection successful. The LLM provider responded to a test prompt.",
51
+ };
52
+ } catch (err) {
53
+ return {
54
+ success: false,
55
+ message: API.getFriendlyMessage(err),
56
+ };
57
+ }
58
+ }
59
+ }