@oneuptime/common 11.5.7 → 11.5.9
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/AIAgentTaskPullRequest.ts +15 -6
- package/Models/DatabaseModels/AIRun.ts +68 -0
- package/Models/DatabaseModels/AIRunEvent.ts +34 -1
- package/Models/DatabaseModels/NetworkDeviceDiscoveryScan.ts +215 -0
- package/Models/DatabaseModels/Project.ts +25 -0
- package/Models/DatabaseModels/StatusPage.ts +42 -0
- package/Server/API/AIAgentTaskAPI.ts +38 -12
- package/Server/API/AIAgentTaskLogAPI.ts +49 -0
- package/Server/API/AIChatAPI.ts +16 -0
- package/Server/API/AIReadinessAPI.ts +84 -0
- package/Server/API/CodeFixRunAPI.ts +190 -57
- package/Server/API/TelemetryExceptionAPI.ts +6 -1
- package/Server/Infrastructure/Postgres/SchemaMigrations/1784105912819-BackfillCodeFixTaskType.ts +41 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1784124919694-AddAITaskNumberAndRunTranscript.ts +95 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1784135099754-AllowNullAiAgentOnPullRequest.ts +52 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1784137457184-AddEnableMcpServerToStatusPage.ts +30 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1784200000000-AddSnmpV3ColumnsToNetworkDeviceDiscoveryScan.ts +49 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +10 -0
- package/Server/Middleware/MasterAdminAuthorization.ts +1 -1
- package/Server/Middleware/ProjectAuthorization.ts +1 -1
- package/Server/Services/AIRunEventService.ts +15 -1
- package/Server/Services/AIRunService.ts +48 -12
- package/Server/Services/LlmProviderService.ts +2 -0
- package/Server/Services/ProjectService.ts +42 -0
- package/Server/Services/StatusPageService.ts +87 -0
- package/Server/Services/TelemetryExceptionService.ts +19 -90
- package/Server/Types/Markdown.ts +9 -9
- package/Server/Types/MarkdownSlugify.ts +47 -0
- package/Server/Utils/AI/AIRunPrivacyFilter.ts +106 -0
- package/Server/Utils/AI/AIRunTranscript.ts +240 -0
- package/Server/Utils/AI/Chat/ChatAgentRunner.ts +9 -0
- package/Server/Utils/AI/Chat/ObservabilityChatPrompt.ts +119 -5
- package/Server/Utils/AI/CodeFix/CodeFixAgentCompletion.ts +75 -0
- package/Server/Utils/AI/CodeFix/CodeFixReadiness.ts +235 -0
- package/Server/Utils/AI/SRE/Insights/FixRouting.ts +2 -3
- package/Server/Utils/AI/Toolbox/CodeTools.ts +883 -0
- package/Server/Utils/AI/Toolbox/CodeWriteTools.ts +519 -0
- package/Server/Utils/AI/Toolbox/Index.ts +27 -0
- package/Server/Utils/AI/Toolbox/ScheduledMaintenanceTools.ts +279 -0
- package/Server/Utils/AnalyticsDatabase/ClickhouseCapacity.ts +19 -1
- package/Server/Utils/CodeRepository/GitHub/GitHub.ts +600 -0
- package/Server/Utils/CodeRepository/StackTraceRepoResolver.ts +7 -2
- package/Server/Utils/LLM/LLMService.ts +397 -18
- package/Server/Utils/Monitor/NetworkDeviceHydrationUtil.ts +4 -11
- package/Tests/Models/StatusPageEnableMcpServer.test.ts +125 -0
- package/Tests/Server/Services/StatusPageServiceMcp.test.ts +223 -0
- package/Tests/Server/Services/TelemetryExceptionAIFixReadiness.test.ts +26 -3
- package/Tests/Server/Services/TelemetryExceptionCodeFixRun.test.ts +12 -0
- package/Tests/Server/Types/Markdown.test.ts +106 -0
- package/Tests/Server/Utils/AI/AIRunPrivacyFilter.test.ts +295 -0
- package/Tests/Server/Utils/AI/AIRunTranscript.test.ts +182 -0
- package/Tests/Server/Utils/AI/CodeFixReadiness.test.ts +411 -0
- package/Tests/Server/Utils/AI/CodeTools.test.ts +596 -0
- package/Tests/Server/Utils/AI/CodeWriteTools.test.ts +475 -0
- package/Tests/Server/Utils/AI/LLMServiceModelCompatibility.test.ts +548 -0
- package/Tests/Server/Utils/AI/ObservabilityChatPrompt.test.ts +142 -0
- package/Tests/Server/Utils/AI/ScheduledMaintenanceTools.test.ts +176 -0
- package/Tests/Server/Utils/AnalyticsDatabase/ClickhouseCapacity.test.ts +63 -0
- package/Tests/Server/Utils/GitHubGetFileContent.test.ts +197 -0
- package/Tests/Server/Utils/Monitor/NetworkDeviceHydrationUtil.test.ts +502 -0
- package/Tests/Types/AI/AIAgentTaskStatus.test.ts +7 -6
- package/Tests/Types/AI/AIChatPageContext.test.ts +0 -0
- package/Tests/Types/AI/AIRunStatus.test.ts +42 -0
- package/Tests/Types/Log/LogSeverity.test.ts +78 -0
- package/Tests/UI/Components/BasicFormDropdownNormalization.test.tsx +167 -0
- package/Tests/UI/Components/Charts/AxisTickColor.test.tsx +66 -0
- package/Tests/UI/Utils/AIChatExport/ChatWidgetData.test.ts +319 -0
- package/Tests/UI/Utils/AIChatExport/ChatWidgetMarkdown.test.ts +188 -0
- package/Types/AI/AIAgentTaskStatus.ts +13 -0
- package/Types/AI/AIChatPageContext.ts +137 -0
- package/Types/AI/AIChatTypes.ts +44 -0
- package/Types/AI/AIFixReadiness.ts +46 -0
- package/Types/AI/AIRunStatus.ts +11 -0
- package/Types/Email/EmailTemplateType.ts +2 -0
- package/Types/Log/LogSeverity.ts +61 -0
- package/Types/Monitor/SnmpMonitor/SnmpVersion.ts +40 -0
- package/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.tsx +8 -2
- package/UI/Components/Charts/ChartLibrary/BarChart/BarChart.tsx +3 -1
- package/UI/Components/Charts/ChartLibrary/LineChart/LineChart.tsx +8 -2
- package/UI/Components/EditionLabel/EditionLabel.tsx +821 -331
- package/UI/Components/Forms/BasicForm.tsx +26 -8
- package/UI/Styles/Theme.css +1314 -0
- package/UI/Utils/AIChatExport/ChatWidgetData.ts +316 -0
- package/UI/Utils/AIChatExport/ChatWidgetMarkdown.ts +349 -0
- package/UI/Utils/AIChatExport/ConversationMarkdown.ts +177 -0
- package/UI/Utils/AIChatExport/MarkdownBlocks.ts +327 -0
- package/UI/Utils/AIChatExport/MarkdownSafety.ts +215 -0
- package/UI/Utils/DownloadFile.ts +54 -0
- package/build/dist/Models/DatabaseModels/AIAgentTaskPullRequest.js +15 -7
- package/build/dist/Models/DatabaseModels/AIAgentTaskPullRequest.js.map +1 -1
- package/build/dist/Models/DatabaseModels/AIRun.js +69 -0
- package/build/dist/Models/DatabaseModels/AIRun.js.map +1 -1
- package/build/dist/Models/DatabaseModels/AIRunEvent.js +31 -0
- package/build/dist/Models/DatabaseModels/AIRunEvent.js.map +1 -1
- package/build/dist/Models/DatabaseModels/NetworkDeviceDiscoveryScan.js +225 -0
- package/build/dist/Models/DatabaseModels/NetworkDeviceDiscoveryScan.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Project.js +27 -0
- package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPage.js +43 -0
- package/build/dist/Models/DatabaseModels/StatusPage.js.map +1 -1
- package/build/dist/Server/API/AIAgentTaskAPI.js +37 -11
- package/build/dist/Server/API/AIAgentTaskAPI.js.map +1 -1
- package/build/dist/Server/API/AIAgentTaskLogAPI.js +32 -7
- package/build/dist/Server/API/AIAgentTaskLogAPI.js.map +1 -1
- package/build/dist/Server/API/AIChatAPI.js +9 -0
- package/build/dist/Server/API/AIChatAPI.js.map +1 -1
- package/build/dist/Server/API/AIReadinessAPI.js +57 -0
- package/build/dist/Server/API/AIReadinessAPI.js.map +1 -0
- package/build/dist/Server/API/CodeFixRunAPI.js +165 -51
- package/build/dist/Server/API/CodeFixRunAPI.js.map +1 -1
- package/build/dist/Server/API/TelemetryExceptionAPI.js +4 -0
- package/build/dist/Server/API/TelemetryExceptionAPI.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784105912819-BackfillCodeFixTaskType.js +36 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784105912819-BackfillCodeFixTaskType.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784124919694-AddAITaskNumberAndRunTranscript.js +80 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784124919694-AddAITaskNumberAndRunTranscript.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784135099754-AllowNullAiAgentOnPullRequest.js +37 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784135099754-AllowNullAiAgentOnPullRequest.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784137457184-AddEnableMcpServerToStatusPage.js +23 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784137457184-AddEnableMcpServerToStatusPage.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784200000000-AddSnmpV3ColumnsToNetworkDeviceDiscoveryScan.js +22 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784200000000-AddSnmpV3ColumnsToNetworkDeviceDiscoveryScan.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +10 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Middleware/MasterAdminAuthorization.js +1 -1
- package/build/dist/Server/Middleware/ProjectAuthorization.js +1 -1
- package/build/dist/Server/Services/AIRunEventService.js +8 -0
- package/build/dist/Server/Services/AIRunEventService.js.map +1 -1
- package/build/dist/Server/Services/AIRunService.js +39 -3
- package/build/dist/Server/Services/AIRunService.js.map +1 -1
- package/build/dist/Server/Services/LlmProviderService.js +2 -0
- package/build/dist/Server/Services/LlmProviderService.js.map +1 -1
- package/build/dist/Server/Services/ProjectService.js +39 -0
- package/build/dist/Server/Services/ProjectService.js.map +1 -1
- package/build/dist/Server/Services/StatusPageService.js +81 -0
- package/build/dist/Server/Services/StatusPageService.js.map +1 -1
- package/build/dist/Server/Services/TelemetryExceptionService.js +14 -61
- package/build/dist/Server/Services/TelemetryExceptionService.js.map +1 -1
- package/build/dist/Server/Types/Markdown.js +8 -8
- package/build/dist/Server/Types/Markdown.js.map +1 -1
- package/build/dist/Server/Types/MarkdownSlugify.js +47 -0
- package/build/dist/Server/Types/MarkdownSlugify.js.map +1 -0
- package/build/dist/Server/Utils/AI/AIRunPrivacyFilter.js +82 -0
- package/build/dist/Server/Utils/AI/AIRunPrivacyFilter.js.map +1 -0
- package/build/dist/Server/Utils/AI/AIRunTranscript.js +180 -0
- package/build/dist/Server/Utils/AI/AIRunTranscript.js.map +1 -0
- package/build/dist/Server/Utils/AI/Chat/ChatAgentRunner.js +1 -0
- package/build/dist/Server/Utils/AI/Chat/ChatAgentRunner.js.map +1 -1
- package/build/dist/Server/Utils/AI/Chat/ObservabilityChatPrompt.js +103 -5
- package/build/dist/Server/Utils/AI/Chat/ObservabilityChatPrompt.js.map +1 -1
- package/build/dist/Server/Utils/AI/CodeFix/CodeFixAgentCompletion.js +55 -0
- package/build/dist/Server/Utils/AI/CodeFix/CodeFixAgentCompletion.js.map +1 -1
- package/build/dist/Server/Utils/AI/CodeFix/CodeFixReadiness.js +211 -0
- package/build/dist/Server/Utils/AI/CodeFix/CodeFixReadiness.js.map +1 -0
- package/build/dist/Server/Utils/AI/SRE/Insights/FixRouting.js.map +1 -1
- package/build/dist/Server/Utils/AI/Toolbox/CodeTools.js +654 -0
- package/build/dist/Server/Utils/AI/Toolbox/CodeTools.js.map +1 -0
- package/build/dist/Server/Utils/AI/Toolbox/CodeWriteTools.js +389 -0
- package/build/dist/Server/Utils/AI/Toolbox/CodeWriteTools.js.map +1 -0
- package/build/dist/Server/Utils/AI/Toolbox/Index.js +19 -0
- package/build/dist/Server/Utils/AI/Toolbox/Index.js.map +1 -1
- package/build/dist/Server/Utils/AI/Toolbox/ScheduledMaintenanceTools.js +239 -0
- package/build/dist/Server/Utils/AI/Toolbox/ScheduledMaintenanceTools.js.map +1 -0
- package/build/dist/Server/Utils/AnalyticsDatabase/ClickhouseCapacity.js +19 -1
- package/build/dist/Server/Utils/AnalyticsDatabase/ClickhouseCapacity.js.map +1 -1
- package/build/dist/Server/Utils/CodeRepository/GitHub/GitHub.js +439 -0
- package/build/dist/Server/Utils/CodeRepository/GitHub/GitHub.js.map +1 -1
- package/build/dist/Server/Utils/CodeRepository/StackTraceRepoResolver.js +1 -1
- package/build/dist/Server/Utils/CodeRepository/StackTraceRepoResolver.js.map +1 -1
- package/build/dist/Server/Utils/LLM/LLMService.js +275 -19
- package/build/dist/Server/Utils/LLM/LLMService.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/NetworkDeviceHydrationUtil.js +2 -11
- package/build/dist/Server/Utils/Monitor/NetworkDeviceHydrationUtil.js.map +1 -1
- package/build/dist/Types/AI/AIAgentTaskStatus.js +12 -0
- package/build/dist/Types/AI/AIAgentTaskStatus.js.map +1 -1
- package/build/dist/Types/AI/AIChatPageContext.js +101 -0
- package/build/dist/Types/AI/AIChatPageContext.js.map +1 -0
- package/build/dist/Types/AI/AIChatTypes.js +2 -0
- package/build/dist/Types/AI/AIChatTypes.js.map +1 -1
- package/build/dist/Types/AI/AIFixReadiness.js +9 -0
- package/build/dist/Types/AI/AIFixReadiness.js.map +1 -0
- package/build/dist/Types/AI/AIRunStatus.js +11 -0
- package/build/dist/Types/AI/AIRunStatus.js.map +1 -1
- package/build/dist/Types/Email/EmailTemplateType.js +1 -0
- package/build/dist/Types/Email/EmailTemplateType.js.map +1 -1
- package/build/dist/Types/Log/LogSeverity.js +56 -0
- package/build/dist/Types/Log/LogSeverity.js.map +1 -1
- package/build/dist/Types/Monitor/SnmpMonitor/SnmpVersion.js +38 -0
- package/build/dist/Types/Monitor/SnmpMonitor/SnmpVersion.js.map +1 -1
- package/build/dist/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.js +9 -2
- package/build/dist/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/ChartLibrary/BarChart/BarChart.js +3 -1
- package/build/dist/UI/Components/Charts/ChartLibrary/BarChart/BarChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/ChartLibrary/LineChart/LineChart.js +9 -2
- package/build/dist/UI/Components/Charts/ChartLibrary/LineChart/LineChart.js.map +1 -1
- package/build/dist/UI/Components/EditionLabel/EditionLabel.js +424 -139
- package/build/dist/UI/Components/EditionLabel/EditionLabel.js.map +1 -1
- package/build/dist/UI/Components/Forms/BasicForm.js +24 -6
- package/build/dist/UI/Components/Forms/BasicForm.js.map +1 -1
- package/build/dist/UI/Utils/AIChatExport/ChatWidgetData.js +217 -0
- package/build/dist/UI/Utils/AIChatExport/ChatWidgetData.js.map +1 -0
- package/build/dist/UI/Utils/AIChatExport/ChatWidgetMarkdown.js +234 -0
- package/build/dist/UI/Utils/AIChatExport/ChatWidgetMarkdown.js.map +1 -0
- package/build/dist/UI/Utils/AIChatExport/ConversationMarkdown.js +111 -0
- package/build/dist/UI/Utils/AIChatExport/ConversationMarkdown.js.map +1 -0
- package/build/dist/UI/Utils/AIChatExport/MarkdownBlocks.js +233 -0
- package/build/dist/UI/Utils/AIChatExport/MarkdownBlocks.js.map +1 -0
- package/build/dist/UI/Utils/AIChatExport/MarkdownSafety.js +134 -0
- package/build/dist/UI/Utils/AIChatExport/MarkdownSafety.js.map +1 -0
- package/build/dist/UI/Utils/DownloadFile.js +41 -0
- package/build/dist/UI/Utils/DownloadFile.js.map +1 -0
- package/package.json +5 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import HTTPErrorResponse from "../../../Types/API/HTTPErrorResponse";
|
|
2
2
|
import HTTPResponse from "../../../Types/API/HTTPResponse";
|
|
3
|
+
import Headers from "../../../Types/API/Headers";
|
|
3
4
|
import URL from "../../../Types/API/URL";
|
|
4
5
|
import { JSONArray, JSONObject } from "../../../Types/JSON";
|
|
5
6
|
import JSONFunctions from "../../../Types/JSONFunctions";
|
|
@@ -85,6 +86,32 @@ export interface LLMProviderConfig {
|
|
|
85
86
|
modelName?: string;
|
|
86
87
|
}
|
|
87
88
|
|
|
89
|
+
/**
|
|
90
|
+
* Which spelling of the output-token cap an OpenAI-style endpoint accepts.
|
|
91
|
+
*
|
|
92
|
+
* - Default: send the legacy `max_tokens` unless additionalParams opted into
|
|
93
|
+
* `max_completion_tokens`. This is the historical behavior.
|
|
94
|
+
* - MaxCompletionTokens: the model rejects `max_tokens` (reasoning models).
|
|
95
|
+
* - MaxTokens: the model rejects `max_completion_tokens` (older models and
|
|
96
|
+
* Azure API versions before 2024-08-01-preview).
|
|
97
|
+
*/
|
|
98
|
+
enum TokenLimitParam {
|
|
99
|
+
Default = "Default",
|
|
100
|
+
MaxCompletionTokens = "MaxCompletionTokens",
|
|
101
|
+
MaxTokens = "MaxTokens",
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Per-endpoint compatibility adjustments for the OpenAI chat-completions wire
|
|
106
|
+
* format. Model families disagree about which generation parameters they
|
|
107
|
+
* accept, so a request may need reshaping before a given deployment takes it.
|
|
108
|
+
*/
|
|
109
|
+
interface OpenAIRequestAdaptation {
|
|
110
|
+
tokenLimitParam: TokenLimitParam;
|
|
111
|
+
/** Generation params this model rejects outright, so we omit them. */
|
|
112
|
+
unsupportedParams: Set<string>;
|
|
113
|
+
}
|
|
114
|
+
|
|
88
115
|
export default class LLMService {
|
|
89
116
|
/*
|
|
90
117
|
* Provider-level parameters allowed on protected, unattended completions.
|
|
@@ -116,6 +143,145 @@ export default class LLMService {
|
|
|
116
143
|
"verbosity",
|
|
117
144
|
]);
|
|
118
145
|
|
|
146
|
+
/*
|
|
147
|
+
* Generation params that reasoning models (o-series, gpt-5) reject outright.
|
|
148
|
+
* When a provider names one of these in an error, we drop it and retry
|
|
149
|
+
* rather than failing the whole completion. Only params that merely tune
|
|
150
|
+
* sampling are droppable — never anything that changes what the model is
|
|
151
|
+
* asked to do.
|
|
152
|
+
*/
|
|
153
|
+
private static readonly DROPPABLE_UNSUPPORTED_PARAMETERS: Set<string> =
|
|
154
|
+
new Set([
|
|
155
|
+
"frequency_penalty",
|
|
156
|
+
"logit_bias",
|
|
157
|
+
"logprobs",
|
|
158
|
+
"presence_penalty",
|
|
159
|
+
"temperature",
|
|
160
|
+
"top_logprobs",
|
|
161
|
+
"top_p",
|
|
162
|
+
]);
|
|
163
|
+
|
|
164
|
+
/*
|
|
165
|
+
* Reasoning models require `max_completion_tokens` and reject the legacy
|
|
166
|
+
* `max_tokens`. Detecting them by name lets the first request succeed
|
|
167
|
+
* instead of burning a round trip on a rejected one.
|
|
168
|
+
*
|
|
169
|
+
* On Azure this is only a hint: the `model` field there is the *deployment*
|
|
170
|
+
* name, which the operator chooses freely ("dumbo_o3", "prod-fast"), so it
|
|
171
|
+
* may not resemble the underlying model at all. A wrong guess in either
|
|
172
|
+
* direction is corrected by the error-driven retry in
|
|
173
|
+
* postOpenAIChatCompletion, which is what actually makes this correct.
|
|
174
|
+
*/
|
|
175
|
+
private static readonly REASONING_MODEL_NAME_REGEX: RegExp =
|
|
176
|
+
/^(?:o\d+(?:[-_.]|$)|gpt-?5|codex-mini)/i;
|
|
177
|
+
|
|
178
|
+
/*
|
|
179
|
+
* Backstop only. A provider names one offending parameter per response, so
|
|
180
|
+
* the worst legitimate chain is both token spellings plus one drop for each
|
|
181
|
+
* droppable param. The loop already terminates on its own — every rule
|
|
182
|
+
* refuses to re-suggest a fix it has applied — so this just stops a
|
|
183
|
+
* misbehaving endpoint from looping forever. Sizing it below the reachable
|
|
184
|
+
* chain would fail requests the next attempt would have fixed.
|
|
185
|
+
*/
|
|
186
|
+
private static readonly MAX_REQUEST_ADAPTATION_ATTEMPTS: number =
|
|
187
|
+
2 + LLMService.DROPPABLE_UNSUPPORTED_PARAMETERS.size;
|
|
188
|
+
|
|
189
|
+
/*
|
|
190
|
+
* Remembers what a given endpoint actually accepted, so only the first
|
|
191
|
+
* completion per provider pays for the discovery. API.post retries 4xx, so
|
|
192
|
+
* a rejected request costs several full prompt uploads plus backoff —
|
|
193
|
+
* re-learning that on every call would be expensive on hot paths like the
|
|
194
|
+
* AI agent loop.
|
|
195
|
+
*
|
|
196
|
+
* Keyed by provider + base URL + model, so editing any of them re-probes.
|
|
197
|
+
* Entries also expire: what a deployment accepts is not fixed forever — the
|
|
198
|
+
* operator can repoint a deployment at a different model, upgrade the Azure
|
|
199
|
+
* api-version, or change reasoning_effort (gpt-5.1 accepts temperature when
|
|
200
|
+
* it is "none"). Without expiry we would keep dropping a parameter the model
|
|
201
|
+
* has since started accepting.
|
|
202
|
+
*/
|
|
203
|
+
private static readonly requestAdaptationCache: Map<
|
|
204
|
+
string,
|
|
205
|
+
{ adaptation: OpenAIRequestAdaptation; learnedAt: number }
|
|
206
|
+
> = new Map();
|
|
207
|
+
|
|
208
|
+
private static readonly MAX_ADAPTATION_CACHE_ENTRIES: number = 500;
|
|
209
|
+
|
|
210
|
+
private static readonly ADAPTATION_CACHE_TTL_IN_MS: number = 60 * 60 * 1000;
|
|
211
|
+
|
|
212
|
+
private static getAdaptationCacheKey(
|
|
213
|
+
config: LLMProviderConfig,
|
|
214
|
+
modelName: string,
|
|
215
|
+
): string {
|
|
216
|
+
return `${config.llmType}|${config.baseUrl || ""}|${modelName}`;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
private static getInitialRequestAdaptation(
|
|
220
|
+
config: LLMProviderConfig,
|
|
221
|
+
modelName: string,
|
|
222
|
+
): OpenAIRequestAdaptation {
|
|
223
|
+
const key: string = this.getAdaptationCacheKey(config, modelName);
|
|
224
|
+
const cached:
|
|
225
|
+
| { adaptation: OpenAIRequestAdaptation; learnedAt: number }
|
|
226
|
+
| undefined = this.requestAdaptationCache.get(key);
|
|
227
|
+
|
|
228
|
+
if (cached) {
|
|
229
|
+
if (Date.now() - cached.learnedAt < this.ADAPTATION_CACHE_TTL_IN_MS) {
|
|
230
|
+
return {
|
|
231
|
+
tokenLimitParam: cached.adaptation.tokenLimitParam,
|
|
232
|
+
unsupportedParams: new Set(cached.adaptation.unsupportedParams),
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
this.requestAdaptationCache.delete(key);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
return {
|
|
240
|
+
tokenLimitParam: this.REASONING_MODEL_NAME_REGEX.test(modelName)
|
|
241
|
+
? TokenLimitParam.MaxCompletionTokens
|
|
242
|
+
: TokenLimitParam.Default,
|
|
243
|
+
unsupportedParams: new Set(),
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
private static cacheRequestAdaptation(
|
|
248
|
+
config: LLMProviderConfig,
|
|
249
|
+
modelName: string,
|
|
250
|
+
adaptation: OpenAIRequestAdaptation,
|
|
251
|
+
): void {
|
|
252
|
+
const key: string = this.getAdaptationCacheKey(config, modelName);
|
|
253
|
+
|
|
254
|
+
// Bound the cache; Map iterates in insertion order, so this drops the oldest.
|
|
255
|
+
if (
|
|
256
|
+
!this.requestAdaptationCache.has(key) &&
|
|
257
|
+
this.requestAdaptationCache.size >= this.MAX_ADAPTATION_CACHE_ENTRIES
|
|
258
|
+
) {
|
|
259
|
+
const oldestKey: string | undefined = this.requestAdaptationCache
|
|
260
|
+
.keys()
|
|
261
|
+
.next().value;
|
|
262
|
+
|
|
263
|
+
if (oldestKey !== undefined) {
|
|
264
|
+
this.requestAdaptationCache.delete(oldestKey);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
this.requestAdaptationCache.set(key, {
|
|
269
|
+
adaptation: {
|
|
270
|
+
tokenLimitParam: adaptation.tokenLimitParam,
|
|
271
|
+
unsupportedParams: new Set(adaptation.unsupportedParams),
|
|
272
|
+
},
|
|
273
|
+
learnedAt: Date.now(),
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Test seam: provider compatibility is remembered process-wide, so tests
|
|
279
|
+
* must be able to start from a clean slate.
|
|
280
|
+
*/
|
|
281
|
+
public static clearRequestAdaptationCache(): void {
|
|
282
|
+
this.requestAdaptationCache.clear();
|
|
283
|
+
}
|
|
284
|
+
|
|
119
285
|
@CaptureSpan()
|
|
120
286
|
public static async getCompletion(
|
|
121
287
|
request: LLMCompletionRequest,
|
|
@@ -268,6 +434,7 @@ export default class LLMService {
|
|
|
268
434
|
private static buildOpenAIRequestBody(
|
|
269
435
|
modelName: string,
|
|
270
436
|
request: LLMCompletionRequest,
|
|
437
|
+
adaptation: OpenAIRequestAdaptation,
|
|
271
438
|
): JSONObject {
|
|
272
439
|
const data: JSONObject = {
|
|
273
440
|
model: modelName,
|
|
@@ -346,9 +513,229 @@ export default class LLMService {
|
|
|
346
513
|
delete data["max_tokens"];
|
|
347
514
|
}
|
|
348
515
|
|
|
516
|
+
/*
|
|
517
|
+
* Applied last so it overrides every other source: this is what the
|
|
518
|
+
* endpoint has actually told us (or the model name strongly implies) it
|
|
519
|
+
* will accept, and a request it rejects is worth nothing.
|
|
520
|
+
*/
|
|
521
|
+
this.applyRequestAdaptation(data, adaptation);
|
|
522
|
+
|
|
349
523
|
return data;
|
|
350
524
|
}
|
|
351
525
|
|
|
526
|
+
private static applyRequestAdaptation(
|
|
527
|
+
data: JSONObject,
|
|
528
|
+
adaptation: OpenAIRequestAdaptation,
|
|
529
|
+
): void {
|
|
530
|
+
if (adaptation.tokenLimitParam === TokenLimitParam.MaxCompletionTokens) {
|
|
531
|
+
const tokenLimit: unknown =
|
|
532
|
+
data["max_tokens"] ?? data["max_completion_tokens"];
|
|
533
|
+
|
|
534
|
+
delete data["max_tokens"];
|
|
535
|
+
|
|
536
|
+
if (tokenLimit !== undefined) {
|
|
537
|
+
data["max_completion_tokens"] = tokenLimit as number;
|
|
538
|
+
}
|
|
539
|
+
} else if (adaptation.tokenLimitParam === TokenLimitParam.MaxTokens) {
|
|
540
|
+
const tokenLimit: unknown =
|
|
541
|
+
data["max_completion_tokens"] ?? data["max_tokens"];
|
|
542
|
+
|
|
543
|
+
delete data["max_completion_tokens"];
|
|
544
|
+
|
|
545
|
+
if (tokenLimit !== undefined) {
|
|
546
|
+
data["max_tokens"] = tokenLimit as number;
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
for (const unsupportedParam of adaptation.unsupportedParams) {
|
|
551
|
+
delete data[unsupportedParam];
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
/**
|
|
556
|
+
* Work out how to reshape a request that the provider rejected on
|
|
557
|
+
* parameter-compatibility grounds. Returns undefined when the error is
|
|
558
|
+
* something else (bad key, unknown model, rate limit) or when we have
|
|
559
|
+
* already applied the only remedy we have — the caller then surfaces the
|
|
560
|
+
* provider's error as-is.
|
|
561
|
+
*/
|
|
562
|
+
private static getRequestAdaptationForError(
|
|
563
|
+
response: HTTPErrorResponse,
|
|
564
|
+
adaptation: OpenAIRequestAdaptation,
|
|
565
|
+
attemptedTokenLimitParams: Set<TokenLimitParam>,
|
|
566
|
+
): OpenAIRequestAdaptation | undefined {
|
|
567
|
+
const error: JSONObject | undefined = (response.data as JSONObject)?.[
|
|
568
|
+
"error"
|
|
569
|
+
] as JSONObject | undefined;
|
|
570
|
+
|
|
571
|
+
if (!error) {
|
|
572
|
+
return undefined;
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
const param: string = (error["param"] as string) || "";
|
|
576
|
+
const code: string = (error["code"] as string) || "";
|
|
577
|
+
const message: string = (error["message"] as string) || "";
|
|
578
|
+
|
|
579
|
+
/*
|
|
580
|
+
* "Unsupported parameter: 'max_tokens' is not supported with this model.
|
|
581
|
+
* Use 'max_completion_tokens' instead." — every o-series and gpt-5 model.
|
|
582
|
+
*
|
|
583
|
+
* The code/message check matters: a too-large or wrong-type value is also
|
|
584
|
+
* reported with param "max_tokens", and renaming the parameter would not
|
|
585
|
+
* fix it. It would only replace the provider's real complaint with a
|
|
586
|
+
* confusing one about a parameter the caller never set.
|
|
587
|
+
*/
|
|
588
|
+
if (
|
|
589
|
+
param === "max_tokens" &&
|
|
590
|
+
(code === "unsupported_parameter" ||
|
|
591
|
+
message.includes("max_completion_tokens")) &&
|
|
592
|
+
!attemptedTokenLimitParams.has(TokenLimitParam.MaxCompletionTokens)
|
|
593
|
+
) {
|
|
594
|
+
return {
|
|
595
|
+
...adaptation,
|
|
596
|
+
tokenLimitParam: TokenLimitParam.MaxCompletionTokens,
|
|
597
|
+
};
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
/*
|
|
601
|
+
* The reverse: older models and Azure API versions before
|
|
602
|
+
* 2024-08-01-preview reject max_completion_tokens. This one reports param
|
|
603
|
+
* and code as null, so the message is the only signal. Matching
|
|
604
|
+
* "argument" rather than "argument supplied" also catches the plural the
|
|
605
|
+
* endpoint uses when more than one parameter is unrecognized.
|
|
606
|
+
*
|
|
607
|
+
* Both spellings are tried at most once each. A deployment that rejects
|
|
608
|
+
* both (a reasoning model behind an API version too old to know the new
|
|
609
|
+
* parameter) has no working request, so we stop and let the provider's
|
|
610
|
+
* own error explain it rather than flip-flopping until the attempt cap.
|
|
611
|
+
*/
|
|
612
|
+
if (
|
|
613
|
+
(message.includes("Unrecognized request argument") ||
|
|
614
|
+
(param === "max_completion_tokens" &&
|
|
615
|
+
code === "unsupported_parameter")) &&
|
|
616
|
+
message.includes("max_completion_tokens") &&
|
|
617
|
+
!attemptedTokenLimitParams.has(TokenLimitParam.MaxTokens)
|
|
618
|
+
) {
|
|
619
|
+
return {
|
|
620
|
+
...adaptation,
|
|
621
|
+
tokenLimitParam: TokenLimitParam.MaxTokens,
|
|
622
|
+
};
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
/*
|
|
626
|
+
* Reasoning models reject temperature, top_p and the penalty/logprob
|
|
627
|
+
* params. Both codes appear in the wild for these:
|
|
628
|
+
* unsupported_parameter ("is not supported with this model") and
|
|
629
|
+
* unsupported_value ("does not support 0 with this model").
|
|
630
|
+
*/
|
|
631
|
+
if (
|
|
632
|
+
(code === "unsupported_parameter" || code === "unsupported_value") &&
|
|
633
|
+
this.DROPPABLE_UNSUPPORTED_PARAMETERS.has(param) &&
|
|
634
|
+
!adaptation.unsupportedParams.has(param)
|
|
635
|
+
) {
|
|
636
|
+
return {
|
|
637
|
+
...adaptation,
|
|
638
|
+
unsupportedParams: new Set(adaptation.unsupportedParams).add(param),
|
|
639
|
+
};
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
return undefined;
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
/**
|
|
646
|
+
* POST an OpenAI-style chat completion, reshaping and retrying when the
|
|
647
|
+
* endpoint rejects a generation parameter it does not support.
|
|
648
|
+
*
|
|
649
|
+
* This is error-driven rather than model-driven on purpose. Azure sends the
|
|
650
|
+
* deployment name in `model`, and operators name deployments whatever they
|
|
651
|
+
* like, so there is no reliable way to know up front whether a deployment is
|
|
652
|
+
* a reasoning model. The provider itself is the only authority, and it names
|
|
653
|
+
* the offending parameter in the 400 — one parameter per response, hence the
|
|
654
|
+
* loop.
|
|
655
|
+
*/
|
|
656
|
+
private static async postOpenAIChatCompletion(data: {
|
|
657
|
+
requestUrl: string;
|
|
658
|
+
headers: Headers;
|
|
659
|
+
config: LLMProviderConfig;
|
|
660
|
+
modelName: string;
|
|
661
|
+
request: LLMCompletionRequest;
|
|
662
|
+
}): Promise<HTTPResponse<JSONObject> | HTTPErrorResponse> {
|
|
663
|
+
let adaptation: OpenAIRequestAdaptation = this.getInitialRequestAdaptation(
|
|
664
|
+
data.config,
|
|
665
|
+
data.modelName,
|
|
666
|
+
);
|
|
667
|
+
|
|
668
|
+
/*
|
|
669
|
+
* Which spellings have actually gone out on the wire. Read off the built
|
|
670
|
+
* body rather than the adaptation, because TokenLimitParam.Default is a
|
|
671
|
+
* sentinel meaning "whatever the body already carried" — recording it
|
|
672
|
+
* would mark neither spelling as tried and let the loop resend a
|
|
673
|
+
* byte-identical request.
|
|
674
|
+
*/
|
|
675
|
+
const attemptedTokenLimitParams: Set<TokenLimitParam> = new Set();
|
|
676
|
+
|
|
677
|
+
const post: () => Promise<
|
|
678
|
+
HTTPResponse<JSONObject> | HTTPErrorResponse
|
|
679
|
+
> = (): Promise<HTTPResponse<JSONObject> | HTTPErrorResponse> => {
|
|
680
|
+
const body: JSONObject = this.buildOpenAIRequestBody(
|
|
681
|
+
data.modelName,
|
|
682
|
+
data.request,
|
|
683
|
+
adaptation,
|
|
684
|
+
);
|
|
685
|
+
|
|
686
|
+
if (body["max_completion_tokens"] !== undefined) {
|
|
687
|
+
attemptedTokenLimitParams.add(TokenLimitParam.MaxCompletionTokens);
|
|
688
|
+
} else if (body["max_tokens"] !== undefined) {
|
|
689
|
+
attemptedTokenLimitParams.add(TokenLimitParam.MaxTokens);
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
return API.post<JSONObject>({
|
|
693
|
+
url: URL.fromString(data.requestUrl),
|
|
694
|
+
data: body,
|
|
695
|
+
headers: data.headers,
|
|
696
|
+
options: {
|
|
697
|
+
retries: data.request.requestRetries ?? 2,
|
|
698
|
+
exponentialBackoff: true,
|
|
699
|
+
timeout: data.request.requestTimeoutInMs ?? 120000,
|
|
700
|
+
},
|
|
701
|
+
});
|
|
702
|
+
};
|
|
703
|
+
|
|
704
|
+
let response: HTTPResponse<JSONObject> | HTTPErrorResponse = await post();
|
|
705
|
+
|
|
706
|
+
for (
|
|
707
|
+
let attempt: number = 0;
|
|
708
|
+
attempt < this.MAX_REQUEST_ADAPTATION_ATTEMPTS &&
|
|
709
|
+
response instanceof HTTPErrorResponse;
|
|
710
|
+
attempt++
|
|
711
|
+
) {
|
|
712
|
+
const nextAdaptation: OpenAIRequestAdaptation | undefined =
|
|
713
|
+
this.getRequestAdaptationForError(
|
|
714
|
+
response,
|
|
715
|
+
adaptation,
|
|
716
|
+
attemptedTokenLimitParams,
|
|
717
|
+
);
|
|
718
|
+
|
|
719
|
+
if (!nextAdaptation) {
|
|
720
|
+
return response;
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
adaptation = nextAdaptation;
|
|
724
|
+
|
|
725
|
+
logger.debug(
|
|
726
|
+
`${data.config.llmType} rejected a generation parameter for model ${data.modelName}. Retrying with a request adapted to what the model accepts.`,
|
|
727
|
+
);
|
|
728
|
+
|
|
729
|
+
response = await post();
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
if (!(response instanceof HTTPErrorResponse)) {
|
|
733
|
+
this.cacheRequestAdaptation(data.config, data.modelName, adaptation);
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
return response;
|
|
737
|
+
}
|
|
738
|
+
|
|
352
739
|
private static parseOpenAIResponse(
|
|
353
740
|
jsonData: JSONObject,
|
|
354
741
|
providerName: string,
|
|
@@ -545,11 +932,8 @@ export default class LLMService {
|
|
|
545
932
|
const modelName: string =
|
|
546
933
|
config.modelName || defaultModels[config.llmType] || "gpt-4o";
|
|
547
934
|
const response: HTTPErrorResponse | HTTPResponse<JSONObject> =
|
|
548
|
-
await
|
|
549
|
-
|
|
550
|
-
this.buildOpenAICompatibleChatCompletionsUrl(baseUrl),
|
|
551
|
-
),
|
|
552
|
-
data: this.buildOpenAIRequestBody(modelName, request),
|
|
935
|
+
await this.postOpenAIChatCompletion({
|
|
936
|
+
requestUrl: this.buildOpenAICompatibleChatCompletionsUrl(baseUrl),
|
|
553
937
|
headers: {
|
|
554
938
|
"Content-Type": "application/json",
|
|
555
939
|
/*
|
|
@@ -561,11 +945,9 @@ export default class LLMService {
|
|
|
561
945
|
? { Authorization: `Bearer ${config.apiKey}` }
|
|
562
946
|
: {}),
|
|
563
947
|
},
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
timeout: request.requestTimeoutInMs ?? 120000,
|
|
568
|
-
},
|
|
948
|
+
config: config,
|
|
949
|
+
modelName: modelName,
|
|
950
|
+
request: request,
|
|
569
951
|
});
|
|
570
952
|
|
|
571
953
|
const logAttributes: LogAttributes = {
|
|
@@ -632,18 +1014,15 @@ export default class LLMService {
|
|
|
632
1014
|
);
|
|
633
1015
|
|
|
634
1016
|
const response: HTTPErrorResponse | HTTPResponse<JSONObject> =
|
|
635
|
-
await
|
|
636
|
-
|
|
637
|
-
data: this.buildOpenAIRequestBody(modelName, request),
|
|
1017
|
+
await this.postOpenAIChatCompletion({
|
|
1018
|
+
requestUrl: requestUrl,
|
|
638
1019
|
headers: {
|
|
639
1020
|
"api-key": config.apiKey,
|
|
640
1021
|
"Content-Type": "application/json",
|
|
641
1022
|
},
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
timeout: request.requestTimeoutInMs ?? 120000,
|
|
646
|
-
},
|
|
1023
|
+
config: config,
|
|
1024
|
+
modelName: modelName,
|
|
1025
|
+
request: request,
|
|
647
1026
|
});
|
|
648
1027
|
|
|
649
1028
|
const logAttributes: LogAttributes = {
|
|
@@ -4,7 +4,9 @@ import MonitorSteps from "../../../Types/Monitor/MonitorSteps";
|
|
|
4
4
|
import QueryHelper from "../../Types/Database/QueryHelper";
|
|
5
5
|
import LIMIT_MAX from "../../../Types/Database/LimitMax";
|
|
6
6
|
import MonitorType from "../../../Types/Monitor/MonitorType";
|
|
7
|
-
import SnmpVersion
|
|
7
|
+
import SnmpVersion, {
|
|
8
|
+
SnmpVersionUtil,
|
|
9
|
+
} from "../../../Types/Monitor/SnmpMonitor/SnmpVersion";
|
|
8
10
|
import SnmpV3Auth from "../../../Types/Monitor/SnmpMonitor/SnmpV3Auth";
|
|
9
11
|
import SnmpSecurityLevel from "../../../Types/Monitor/SnmpMonitor/SnmpSecurityLevel";
|
|
10
12
|
import SnmpAuthProtocol from "../../../Types/Monitor/SnmpMonitor/SnmpAuthProtocol";
|
|
@@ -162,16 +164,7 @@ export default class NetworkDeviceHydrationUtil {
|
|
|
162
164
|
|
|
163
165
|
// Tolerates both enum values ("2c") and enum keys ("V2c") in stored config.
|
|
164
166
|
private static parseSnmpVersion(value: string | undefined): SnmpVersion {
|
|
165
|
-
|
|
166
|
-
case "1":
|
|
167
|
-
case "v1":
|
|
168
|
-
return SnmpVersion.V1;
|
|
169
|
-
case "3":
|
|
170
|
-
case "v3":
|
|
171
|
-
return SnmpVersion.V3;
|
|
172
|
-
default:
|
|
173
|
-
return SnmpVersion.V2c;
|
|
174
|
-
}
|
|
167
|
+
return SnmpVersionUtil.parse(value);
|
|
175
168
|
}
|
|
176
169
|
|
|
177
170
|
/*
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* StatusPage.enableMcpServer column contract.
|
|
3
|
+
*
|
|
4
|
+
* The feature's headline promise is "MCP stays on unless the owner turns it
|
|
5
|
+
* off". Three separate mechanisms carry that promise, and they are easy to
|
|
6
|
+
* confuse:
|
|
7
|
+
* - NEW rows get true from the Postgres column default, declared by
|
|
8
|
+
* @Column({ default: true }) and stored in TypeORM's metadata
|
|
9
|
+
* - EXISTING rows got true from the migration's NOT NULL DEFAULT true
|
|
10
|
+
* - @TableColumn({ defaultValue: true }) is documentation only — it feeds the
|
|
11
|
+
* generated API schema and form metadata, and defaults nothing at runtime
|
|
12
|
+
*
|
|
13
|
+
* All three are pinned below, because a one-word edit to any of them silently
|
|
14
|
+
* flips the default for everyone. Asserting only the @TableColumn metadata
|
|
15
|
+
* would be a guard that catches nothing: flipping @Column to default: false
|
|
16
|
+
* leaves that assertion green while every new status page ships with MCP off.
|
|
17
|
+
*
|
|
18
|
+
* isDefaultValueColumn is pinned too: it keeps the column optional on create,
|
|
19
|
+
* where required: true would make DatabaseService reject creates that omit it.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import StatusPage from "../../Models/DatabaseModels/StatusPage";
|
|
23
|
+
import { TableColumnMetadata } from "../../Types/Database/TableColumn";
|
|
24
|
+
import TableColumnType from "../../Types/Database/TableColumnType";
|
|
25
|
+
import Permission from "../../Types/Permission";
|
|
26
|
+
import { describe, expect, test } from "@jest/globals";
|
|
27
|
+
import { getMetadataArgsStorage } from "typeorm";
|
|
28
|
+
import { ColumnMetadataArgs } from "typeorm/metadata-args/ColumnMetadataArgs";
|
|
29
|
+
import fs from "fs";
|
|
30
|
+
import path from "path";
|
|
31
|
+
|
|
32
|
+
const COLUMN: string = "enableMcpServer";
|
|
33
|
+
|
|
34
|
+
const MIGRATION_PATH: string = path.join(
|
|
35
|
+
__dirname,
|
|
36
|
+
"..",
|
|
37
|
+
"..",
|
|
38
|
+
"Server",
|
|
39
|
+
"Infrastructure",
|
|
40
|
+
"Postgres",
|
|
41
|
+
"SchemaMigrations",
|
|
42
|
+
"1784137457184-AddEnableMcpServerToStatusPage.ts",
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
function metadata(): TableColumnMetadata {
|
|
46
|
+
return new StatusPage().getTableColumnMetadata(COLUMN);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function typeOrmColumn(): ColumnMetadataArgs | undefined {
|
|
50
|
+
return getMetadataArgsStorage().columns.find((column: ColumnMetadataArgs) => {
|
|
51
|
+
return column.target === StatusPage && column.propertyName === COLUMN;
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
describe("StatusPage.enableMcpServer", () => {
|
|
56
|
+
test("exists as a boolean column", () => {
|
|
57
|
+
expect(metadata()).toBeDefined();
|
|
58
|
+
expect(metadata().type).toBe(TableColumnType.Boolean);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test("a newly created status page has MCP enabled", () => {
|
|
62
|
+
/*
|
|
63
|
+
* This is the assertion that actually protects new status pages: the
|
|
64
|
+
* Postgres column default. Nothing else populates the column when a create
|
|
65
|
+
* omits it.
|
|
66
|
+
*/
|
|
67
|
+
expect(typeOrmColumn()).toBeDefined();
|
|
68
|
+
expect(typeOrmColumn()?.options.default).toBe(true);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test("every pre-existing status page was backfilled as enabled", () => {
|
|
72
|
+
/*
|
|
73
|
+
* The migration is the only thing that decided the value for status pages
|
|
74
|
+
* that existed before this feature. Because isMcpServerEnabled queries
|
|
75
|
+
* `enableMcpServer: true`, losing this default would silently gate every
|
|
76
|
+
* status page created before the migration ran.
|
|
77
|
+
*/
|
|
78
|
+
const migration: string = fs.readFileSync(MIGRATION_PATH, "utf8");
|
|
79
|
+
|
|
80
|
+
expect(migration).toContain('ALTER TABLE "StatusPage"');
|
|
81
|
+
expect(migration).toContain(
|
|
82
|
+
`ADD "${COLUMN}" boolean NOT NULL DEFAULT true`,
|
|
83
|
+
);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test("is documented as defaulting to enabled", () => {
|
|
87
|
+
/*
|
|
88
|
+
* Feeds the generated API schema and form metadata. Documentation only —
|
|
89
|
+
* see the tests above for the defaults that actually apply.
|
|
90
|
+
*/
|
|
91
|
+
expect(metadata().defaultValue).toBe(true);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test("is a default-value column, so create may omit it", () => {
|
|
95
|
+
expect(new StatusPage().isDefaultValueColumn(COLUMN)).toBe(true);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
test("is not required, so existing create calls keep working", () => {
|
|
99
|
+
expect(metadata().required).toBeFalsy();
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test("is readable by status page viewers", () => {
|
|
103
|
+
const accessControl: Array<Permission> | undefined =
|
|
104
|
+
new StatusPage().getColumnAccessControlFor(COLUMN)?.read;
|
|
105
|
+
|
|
106
|
+
expect(accessControl).toContain(Permission.ProjectMember);
|
|
107
|
+
expect(accessControl).toContain(Permission.StatusPageAdmin);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
test("is editable by status page editors", () => {
|
|
111
|
+
const accessControl: Array<Permission> | undefined =
|
|
112
|
+
new StatusPage().getColumnAccessControlFor(COLUMN)?.update;
|
|
113
|
+
|
|
114
|
+
expect(accessControl).toContain(Permission.EditProjectStatusPage);
|
|
115
|
+
expect(accessControl).toContain(Permission.StatusPageAdmin);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
test("is not editable by a read-only viewer", () => {
|
|
119
|
+
const accessControl: Array<Permission> | undefined =
|
|
120
|
+
new StatusPage().getColumnAccessControlFor(COLUMN)?.update;
|
|
121
|
+
|
|
122
|
+
expect(accessControl).not.toContain(Permission.Viewer);
|
|
123
|
+
expect(accessControl).not.toContain(Permission.StatusPageViewer);
|
|
124
|
+
});
|
|
125
|
+
});
|