@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
|
@@ -0,0 +1,411 @@
|
|
|
1
|
+
import CodeFixReadiness from "../../../../Server/Utils/AI/CodeFix/CodeFixReadiness";
|
|
2
|
+
import SubjectCodeFixRun from "../../../../Server/Utils/AI/SRE/SubjectCodeFixRun";
|
|
3
|
+
import AIAgentService from "../../../../Server/Services/AIAgentService";
|
|
4
|
+
import AIService from "../../../../Server/Services/AIService";
|
|
5
|
+
import LlmProviderService from "../../../../Server/Services/LlmProviderService";
|
|
6
|
+
import ProjectService from "../../../../Server/Services/ProjectService";
|
|
7
|
+
import AIAgent from "../../../../Models/DatabaseModels/AIAgent";
|
|
8
|
+
import LlmProvider from "../../../../Models/DatabaseModels/LlmProvider";
|
|
9
|
+
import Project from "../../../../Models/DatabaseModels/Project";
|
|
10
|
+
import ObjectID from "../../../../Types/ObjectID";
|
|
11
|
+
import {
|
|
12
|
+
AIFixReadiness,
|
|
13
|
+
AIFixReadinessCheck,
|
|
14
|
+
} from "../../../../Types/AI/AIFixReadiness";
|
|
15
|
+
import { describe, expect, test, afterEach, beforeEach } from "@jest/globals";
|
|
16
|
+
|
|
17
|
+
/*
|
|
18
|
+
* The gates the AI Tasks page renders. The invariant worth protecting: a
|
|
19
|
+
* project LLM provider is NOT a prerequisite — the shared global provider is
|
|
20
|
+
* a legitimate resolution for the agent path, so a project running on it must
|
|
21
|
+
* read as ready. The banner these checks replaced claimed otherwise.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
const projectId: ObjectID = ObjectID.generate();
|
|
25
|
+
|
|
26
|
+
function fakeProvider(params: {
|
|
27
|
+
name: string;
|
|
28
|
+
isGlobalLlm?: boolean;
|
|
29
|
+
costPerMillionTokensInUSDCents?: number;
|
|
30
|
+
}): LlmProvider {
|
|
31
|
+
return {
|
|
32
|
+
id: ObjectID.generate(),
|
|
33
|
+
name: params.name,
|
|
34
|
+
isGlobalLlm: params.isGlobalLlm ?? false,
|
|
35
|
+
costPerMillionTokensInUSDCents: params.costPerMillionTokensInUSDCents ?? 0,
|
|
36
|
+
} as unknown as LlmProvider;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// No daily-budget limit configured: the common case, and never a blocker.
|
|
40
|
+
function mockBudgetNotExhausted(): void {
|
|
41
|
+
jest.spyOn(AIService, "getAutonomousDailyBudgetStatus").mockResolvedValue({
|
|
42
|
+
exhausted: false,
|
|
43
|
+
limitInTokens: null,
|
|
44
|
+
usedTokensToday: 0,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
describe("CodeFixReadiness.getLlmProviderCheck", () => {
|
|
49
|
+
beforeEach(() => {
|
|
50
|
+
mockBudgetNotExhausted();
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
afterEach(() => {
|
|
54
|
+
jest.restoreAllMocks();
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test("no provider resolves at all: not ready, and says where to add one", async () => {
|
|
58
|
+
jest
|
|
59
|
+
.spyOn(LlmProviderService, "getLlmProviderForMeteredAgentPath")
|
|
60
|
+
.mockResolvedValue(null);
|
|
61
|
+
|
|
62
|
+
const check: AIFixReadinessCheck =
|
|
63
|
+
await CodeFixReadiness.getLlmProviderCheck({ projectId });
|
|
64
|
+
|
|
65
|
+
expect(check.id).toBe("llmProvider");
|
|
66
|
+
expect(check.ok).toBe(false);
|
|
67
|
+
expect(check.detail).toContain("LLM Providers");
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test("a project-owned provider is ready and is named as the project's own", async () => {
|
|
71
|
+
jest
|
|
72
|
+
.spyOn(LlmProviderService, "getLlmProviderForMeteredAgentPath")
|
|
73
|
+
.mockResolvedValue(fakeProvider({ name: "My OpenAI" }));
|
|
74
|
+
|
|
75
|
+
const check: AIFixReadinessCheck =
|
|
76
|
+
await CodeFixReadiness.getLlmProviderCheck({
|
|
77
|
+
projectId,
|
|
78
|
+
billingEnabled: true,
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
expect(check.ok).toBe(true);
|
|
82
|
+
expect(check.title).toContain("My OpenAI");
|
|
83
|
+
expect(check.detail).toContain("this project's own provider");
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
/*
|
|
87
|
+
* The headline case the old banner got wrong: no project provider, a free
|
|
88
|
+
* global one, and the project is nonetheless ready.
|
|
89
|
+
*/
|
|
90
|
+
test("a FREE global provider is ready with no project provider and no balance", async () => {
|
|
91
|
+
jest
|
|
92
|
+
.spyOn(LlmProviderService, "getLlmProviderForMeteredAgentPath")
|
|
93
|
+
.mockResolvedValue(
|
|
94
|
+
fakeProvider({
|
|
95
|
+
name: "Global LLM Provider",
|
|
96
|
+
isGlobalLlm: true,
|
|
97
|
+
costPerMillionTokensInUSDCents: 0,
|
|
98
|
+
}),
|
|
99
|
+
);
|
|
100
|
+
const projectSpy: jest.SpiedFunction<typeof ProjectService.findOneById> =
|
|
101
|
+
jest.spyOn(ProjectService, "findOneById");
|
|
102
|
+
|
|
103
|
+
const check: AIFixReadinessCheck =
|
|
104
|
+
await CodeFixReadiness.getLlmProviderCheck({
|
|
105
|
+
projectId,
|
|
106
|
+
billingEnabled: true,
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
expect(check.ok).toBe(true);
|
|
110
|
+
expect(check.detail).toContain("shared provider");
|
|
111
|
+
// A free provider bills nothing, so the balance must never be consulted.
|
|
112
|
+
expect(projectSpy).not.toHaveBeenCalled();
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
test("a costed global provider with billing OFF is ready and skips the balance check", async () => {
|
|
116
|
+
jest
|
|
117
|
+
.spyOn(LlmProviderService, "getLlmProviderForMeteredAgentPath")
|
|
118
|
+
.mockResolvedValue(
|
|
119
|
+
fakeProvider({
|
|
120
|
+
name: "OneUptime AI",
|
|
121
|
+
isGlobalLlm: true,
|
|
122
|
+
costPerMillionTokensInUSDCents: 500,
|
|
123
|
+
}),
|
|
124
|
+
);
|
|
125
|
+
const projectSpy: jest.SpiedFunction<typeof ProjectService.findOneById> =
|
|
126
|
+
jest.spyOn(ProjectService, "findOneById");
|
|
127
|
+
|
|
128
|
+
const check: AIFixReadinessCheck =
|
|
129
|
+
await CodeFixReadiness.getLlmProviderCheck({
|
|
130
|
+
projectId,
|
|
131
|
+
billingEnabled: false,
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
expect(check.ok).toBe(true);
|
|
135
|
+
expect(projectSpy).not.toHaveBeenCalled();
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
test("a costed global provider with billing ON and a funded balance is ready and says it is metered", async () => {
|
|
139
|
+
jest
|
|
140
|
+
.spyOn(LlmProviderService, "getLlmProviderForMeteredAgentPath")
|
|
141
|
+
.mockResolvedValue(
|
|
142
|
+
fakeProvider({
|
|
143
|
+
name: "OneUptime AI",
|
|
144
|
+
isGlobalLlm: true,
|
|
145
|
+
costPerMillionTokensInUSDCents: 500,
|
|
146
|
+
}),
|
|
147
|
+
);
|
|
148
|
+
jest.spyOn(ProjectService, "findOneById").mockResolvedValue({
|
|
149
|
+
aiCurrentBalanceInUSDCents: 2000,
|
|
150
|
+
} as unknown as Project);
|
|
151
|
+
|
|
152
|
+
const check: AIFixReadinessCheck =
|
|
153
|
+
await CodeFixReadiness.getLlmProviderCheck({
|
|
154
|
+
projectId,
|
|
155
|
+
billingEnabled: true,
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
expect(check.ok).toBe(true);
|
|
159
|
+
expect(check.detail).toContain("AI balance");
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
test("a costed global provider with billing ON and an empty balance is NOT ready", async () => {
|
|
163
|
+
jest
|
|
164
|
+
.spyOn(LlmProviderService, "getLlmProviderForMeteredAgentPath")
|
|
165
|
+
.mockResolvedValue(
|
|
166
|
+
fakeProvider({
|
|
167
|
+
name: "OneUptime AI",
|
|
168
|
+
isGlobalLlm: true,
|
|
169
|
+
costPerMillionTokensInUSDCents: 500,
|
|
170
|
+
}),
|
|
171
|
+
);
|
|
172
|
+
jest.spyOn(ProjectService, "findOneById").mockResolvedValue({
|
|
173
|
+
aiCurrentBalanceInUSDCents: 0,
|
|
174
|
+
} as unknown as Project);
|
|
175
|
+
|
|
176
|
+
const check: AIFixReadinessCheck =
|
|
177
|
+
await CodeFixReadiness.getLlmProviderCheck({
|
|
178
|
+
projectId,
|
|
179
|
+
billingEnabled: true,
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
expect(check.ok).toBe(false);
|
|
183
|
+
expect(check.detail).toContain("AI Credits");
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
/*
|
|
188
|
+
* AI_CODE_FIX_FEATURE is one of AUTONOMOUS_AI_FEATURES, so executeWithLogging
|
|
189
|
+
* runs every fix completion through the daily token budget. Readiness that
|
|
190
|
+
* ignored it would report ready and then die at the run's first completion
|
|
191
|
+
* call — the exact fail-late hole the balance gate exists to close.
|
|
192
|
+
*/
|
|
193
|
+
describe("CodeFixReadiness.getLlmProviderCheck — daily autonomous token budget", () => {
|
|
194
|
+
afterEach(() => {
|
|
195
|
+
jest.restoreAllMocks();
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
test("a zeroed daily limit (the kill switch) is NOT ready, even with a healthy project-owned provider", async () => {
|
|
199
|
+
jest
|
|
200
|
+
.spyOn(LlmProviderService, "getLlmProviderForMeteredAgentPath")
|
|
201
|
+
.mockResolvedValue(fakeProvider({ name: "My OpenAI" }));
|
|
202
|
+
jest.spyOn(AIService, "getAutonomousDailyBudgetStatus").mockResolvedValue({
|
|
203
|
+
exhausted: true,
|
|
204
|
+
limitInTokens: 0,
|
|
205
|
+
usedTokensToday: 0,
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
const check: AIFixReadinessCheck =
|
|
209
|
+
await CodeFixReadiness.getLlmProviderCheck({
|
|
210
|
+
projectId,
|
|
211
|
+
billingEnabled: false,
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
expect(check.ok).toBe(false);
|
|
215
|
+
expect(check.detail).toContain("paused");
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
test("an exhausted daily budget is NOT ready and reports the usage", async () => {
|
|
219
|
+
jest
|
|
220
|
+
.spyOn(LlmProviderService, "getLlmProviderForMeteredAgentPath")
|
|
221
|
+
.mockResolvedValue(fakeProvider({ name: "My OpenAI" }));
|
|
222
|
+
jest.spyOn(AIService, "getAutonomousDailyBudgetStatus").mockResolvedValue({
|
|
223
|
+
exhausted: true,
|
|
224
|
+
limitInTokens: 1000,
|
|
225
|
+
usedTokensToday: 1000,
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
const check: AIFixReadinessCheck =
|
|
229
|
+
await CodeFixReadiness.getLlmProviderCheck({
|
|
230
|
+
projectId,
|
|
231
|
+
billingEnabled: false,
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
expect(check.ok).toBe(false);
|
|
235
|
+
expect(check.detail).toContain("exhausted");
|
|
236
|
+
expect(check.detail).toContain("1,000");
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
test("an unset daily limit never blocks", async () => {
|
|
240
|
+
jest
|
|
241
|
+
.spyOn(LlmProviderService, "getLlmProviderForMeteredAgentPath")
|
|
242
|
+
.mockResolvedValue(fakeProvider({ name: "My OpenAI" }));
|
|
243
|
+
mockBudgetNotExhausted();
|
|
244
|
+
|
|
245
|
+
const check: AIFixReadinessCheck =
|
|
246
|
+
await CodeFixReadiness.getLlmProviderCheck({
|
|
247
|
+
projectId,
|
|
248
|
+
billingEnabled: false,
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
expect(check.ok).toBe(true);
|
|
252
|
+
});
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
describe("CodeFixReadiness.getAgentCheck", () => {
|
|
256
|
+
afterEach(() => {
|
|
257
|
+
jest.restoreAllMocks();
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
test("no agent for the project: not ready", async () => {
|
|
261
|
+
jest.spyOn(AIAgentService, "getAIAgentForProject").mockResolvedValue(null);
|
|
262
|
+
|
|
263
|
+
const check: AIFixReadinessCheck = await CodeFixReadiness.getAgentCheck({
|
|
264
|
+
projectId,
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
expect(check.id).toBe("agentAvailable");
|
|
268
|
+
expect(check.ok).toBe(false);
|
|
269
|
+
expect(check.detail).toContain("No AI agent is available");
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
test("an agent that exists but is not alive: not ready, and names it", async () => {
|
|
273
|
+
jest
|
|
274
|
+
.spyOn(AIAgentService, "getAIAgentForProject")
|
|
275
|
+
.mockResolvedValue({ name: "fix-worker" } as unknown as AIAgent);
|
|
276
|
+
jest.spyOn(AIAgentService, "isAgentAlive").mockReturnValue(false);
|
|
277
|
+
|
|
278
|
+
const check: AIFixReadinessCheck = await CodeFixReadiness.getAgentCheck({
|
|
279
|
+
projectId,
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
expect(check.ok).toBe(false);
|
|
283
|
+
expect(check.detail).toContain("fix-worker");
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
test("a live agent is ready and is named", async () => {
|
|
287
|
+
jest
|
|
288
|
+
.spyOn(AIAgentService, "getAIAgentForProject")
|
|
289
|
+
.mockResolvedValue({ name: "fix-worker" } as unknown as AIAgent);
|
|
290
|
+
jest.spyOn(AIAgentService, "isAgentAlive").mockReturnValue(true);
|
|
291
|
+
|
|
292
|
+
const check: AIFixReadinessCheck = await CodeFixReadiness.getAgentCheck({
|
|
293
|
+
projectId,
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
expect(check.ok).toBe(true);
|
|
297
|
+
expect(check.title).toContain("fix-worker");
|
|
298
|
+
});
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
describe("CodeFixReadiness.getRepositoryConnectedCheck", () => {
|
|
302
|
+
afterEach(() => {
|
|
303
|
+
jest.restoreAllMocks();
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
/*
|
|
307
|
+
* The page-level gate asserts the WEAKER any-repo claim, so its id must
|
|
308
|
+
* stay distinct from the per-exception repositoryResolved — a UI that saw
|
|
309
|
+
* the same id would imply a specific exception will resolve.
|
|
310
|
+
*/
|
|
311
|
+
test("a connected repository is ready under the repositoryConnected id", async () => {
|
|
312
|
+
jest
|
|
313
|
+
.spyOn(SubjectCodeFixRun, "hasGitHubAppConnectedRepository")
|
|
314
|
+
.mockResolvedValue(true);
|
|
315
|
+
|
|
316
|
+
const check: AIFixReadinessCheck =
|
|
317
|
+
await CodeFixReadiness.getRepositoryConnectedCheck({ projectId });
|
|
318
|
+
|
|
319
|
+
expect(check.id).toBe("repositoryConnected");
|
|
320
|
+
expect(check.ok).toBe(true);
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
test("no connected repository: not ready, and points at the GitHub App", async () => {
|
|
324
|
+
jest
|
|
325
|
+
.spyOn(SubjectCodeFixRun, "hasGitHubAppConnectedRepository")
|
|
326
|
+
.mockResolvedValue(false);
|
|
327
|
+
|
|
328
|
+
const check: AIFixReadinessCheck =
|
|
329
|
+
await CodeFixReadiness.getRepositoryConnectedCheck({ projectId });
|
|
330
|
+
|
|
331
|
+
expect(check.ok).toBe(false);
|
|
332
|
+
expect(check.detail).toContain("GitHub App");
|
|
333
|
+
});
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
describe("CodeFixReadiness.getProjectReadiness", () => {
|
|
337
|
+
afterEach(() => {
|
|
338
|
+
jest.restoreAllMocks();
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
type MockAllParams = {
|
|
342
|
+
hasRepo: boolean;
|
|
343
|
+
hasProvider: boolean;
|
|
344
|
+
agentAlive: boolean;
|
|
345
|
+
};
|
|
346
|
+
|
|
347
|
+
function mockAll(params: MockAllParams): void {
|
|
348
|
+
mockBudgetNotExhausted();
|
|
349
|
+
jest
|
|
350
|
+
.spyOn(SubjectCodeFixRun, "hasGitHubAppConnectedRepository")
|
|
351
|
+
.mockResolvedValue(params.hasRepo);
|
|
352
|
+
jest
|
|
353
|
+
.spyOn(LlmProviderService, "getLlmProviderForMeteredAgentPath")
|
|
354
|
+
.mockResolvedValue(
|
|
355
|
+
params.hasProvider ? fakeProvider({ name: "provider" }) : null,
|
|
356
|
+
);
|
|
357
|
+
jest
|
|
358
|
+
.spyOn(AIAgentService, "getAIAgentForProject")
|
|
359
|
+
.mockResolvedValue({ name: "agent" } as unknown as AIAgent);
|
|
360
|
+
jest
|
|
361
|
+
.spyOn(AIAgentService, "isAgentAlive")
|
|
362
|
+
.mockReturnValue(params.agentAlive);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
test("all three gates satisfied: ready", async () => {
|
|
366
|
+
mockAll({ hasRepo: true, hasProvider: true, agentAlive: true });
|
|
367
|
+
|
|
368
|
+
const readiness: AIFixReadiness =
|
|
369
|
+
await CodeFixReadiness.getProjectReadiness({ projectId });
|
|
370
|
+
|
|
371
|
+
expect(readiness.ready).toBe(true);
|
|
372
|
+
expect(readiness.checks).toHaveLength(3);
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
test("the checks are ordered the way the user sets them up", async () => {
|
|
376
|
+
mockAll({ hasRepo: true, hasProvider: true, agentAlive: true });
|
|
377
|
+
|
|
378
|
+
const readiness: AIFixReadiness =
|
|
379
|
+
await CodeFixReadiness.getProjectReadiness({ projectId });
|
|
380
|
+
|
|
381
|
+
expect(
|
|
382
|
+
readiness.checks.map((check: AIFixReadinessCheck) => {
|
|
383
|
+
return check.id;
|
|
384
|
+
}),
|
|
385
|
+
).toEqual(["repositoryConnected", "llmProvider", "agentAvailable"]);
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
test("any single failing gate makes the whole project not ready", async () => {
|
|
389
|
+
const cases: Array<MockAllParams> = [
|
|
390
|
+
{ hasRepo: false, hasProvider: true, agentAlive: true },
|
|
391
|
+
{ hasRepo: true, hasProvider: false, agentAlive: true },
|
|
392
|
+
{ hasRepo: true, hasProvider: true, agentAlive: false },
|
|
393
|
+
];
|
|
394
|
+
|
|
395
|
+
for (const params of cases) {
|
|
396
|
+
jest.restoreAllMocks();
|
|
397
|
+
mockAll(params);
|
|
398
|
+
|
|
399
|
+
const readiness: AIFixReadiness =
|
|
400
|
+
await CodeFixReadiness.getProjectReadiness({ projectId });
|
|
401
|
+
|
|
402
|
+
expect(readiness.ready).toBe(false);
|
|
403
|
+
// The failing gate must still be reported, not swallowed.
|
|
404
|
+
expect(
|
|
405
|
+
readiness.checks.some((check: AIFixReadinessCheck) => {
|
|
406
|
+
return !check.ok;
|
|
407
|
+
}),
|
|
408
|
+
).toBe(true);
|
|
409
|
+
}
|
|
410
|
+
});
|
|
411
|
+
});
|