@builder.io/ai-utils 0.81.3 → 0.83.0
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/package.json +1 -1
- package/src/codegen/investigation-context.d.ts +1 -1
- package/src/codegen.d.ts +10 -0
- package/src/codegen.js +1 -0
- package/src/completion.d.ts +2 -1
- package/src/connectivity/checks/http-check.d.ts +7 -0
- package/src/connectivity/checks/http-check.js +13 -5
- package/src/connectivity/checks/http-check.spec.js +58 -0
- package/src/connectivity/environment.js +6 -0
- package/src/connectivity/node.d.ts +1 -1
- package/src/connectivity/node.js +1 -1
- package/src/connectivity/run-checks.js +13 -6
- package/src/connectivity/targets.d.ts +11 -1
- package/src/connectivity/targets.js +34 -1
- package/src/connectivity/targets.spec.js +51 -1
- package/src/connectivity/types.d.ts +17 -1
- package/src/editor-ai.d.ts +53 -0
- package/src/editor-ai.js +69 -0
- package/src/editor-ai.test.d.ts +1 -0
- package/src/editor-ai.test.js +37 -0
- package/src/embeddings.d.ts +82 -0
- package/src/embeddings.js +106 -0
- package/src/embeddings.spec.d.ts +1 -0
- package/src/embeddings.spec.js +43 -0
- package/src/index.d.ts +3 -0
- package/src/index.js +3 -0
- package/src/messages.d.ts +15 -0
- package/src/organization.d.ts +9 -0
- package/src/projects.d.ts +1 -10
- package/src/projects.js +0 -7
- package/src/proxy.d.ts +1 -1
- package/src/realtime.d.ts +232 -0
- package/src/realtime.js +158 -0
- package/src/realtime.spec.d.ts +1 -0
- package/src/realtime.spec.js +122 -0
package/package.json
CHANGED
|
@@ -18,5 +18,5 @@ export interface BuildInvestigationSystemPromptInput {
|
|
|
18
18
|
event: InvestigationEvent;
|
|
19
19
|
completionJson: InvestigationCompletionJSON | undefined;
|
|
20
20
|
}
|
|
21
|
-
export declare function buildInvestigationSystemPrompt({ event, completionJson }: BuildInvestigationSystemPromptInput): string;
|
|
21
|
+
export declare function buildInvestigationSystemPrompt({ event, completionJson, }: BuildInvestigationSystemPromptInput): string;
|
|
22
22
|
export declare const SUGGESTED_QUESTIONS: readonly string[];
|
package/src/codegen.d.ts
CHANGED
|
@@ -2890,6 +2890,8 @@ export declare const CodeGenInputOptionsSchema: z.ZodObject<{
|
|
|
2890
2890
|
systemReminderPrompt: z.ZodOptional<z.ZodString>;
|
|
2891
2891
|
}, z.core.$loose>;
|
|
2892
2892
|
export type CodeGenInputOptions = z.infer<typeof CodeGenInputOptionsSchema> & {
|
|
2893
|
+
/** @internal Derived from BuilderEditorState.components for editor AI. */
|
|
2894
|
+
_allowedComponentNames?: string[];
|
|
2893
2895
|
/**
|
|
2894
2896
|
* Optional factory function to wrap the MCP client with custom behavior
|
|
2895
2897
|
* Used by Editor AI to add permission checks for model operations
|
|
@@ -3295,6 +3297,7 @@ export interface GenerateCompletionStepGit {
|
|
|
3295
3297
|
*/
|
|
3296
3298
|
diagnostics?: GitStatusDiagnostics;
|
|
3297
3299
|
}
|
|
3300
|
+
export declare const FUSION_GENERATION_APP_PLACEHOLDER_COMMENT = "{/* TODO: FUSION_GENERATION_APP_PLACEHOLDER replace everything here with the actual app! */}";
|
|
3298
3301
|
/**
|
|
3299
3302
|
* A typed comment added to the session via the generic `AddComment` tool. The
|
|
3300
3303
|
* `commentType` mirrors the tool's `type` field (e.g. `"code-review"`) and
|
|
@@ -3950,6 +3953,13 @@ export interface MCPAuthRequiredServer {
|
|
|
3950
3953
|
authorizationUrl: string;
|
|
3951
3954
|
scopes: string[];
|
|
3952
3955
|
isReauth: boolean;
|
|
3956
|
+
/**
|
|
3957
|
+
* Server opted in to the LLM-driven `__authenticate` flow via its Firestore
|
|
3958
|
+
* `useAuthenticateTool` field. Servers opted in this way (or, org-wide, via
|
|
3959
|
+
* the `codegen-tool-mcp-authenticate` LD flag) get a synthetic auth tool
|
|
3960
|
+
* instead of a blocking `mcp-auth-required` event.
|
|
3961
|
+
*/
|
|
3962
|
+
useAuthenticateTool?: boolean;
|
|
3953
3963
|
}
|
|
3954
3964
|
export interface GenerateCodeEventMCPAuthRequired {
|
|
3955
3965
|
type: "mcp-auth-required";
|
package/src/codegen.js
CHANGED
|
@@ -1918,6 +1918,7 @@ export const CodeGenInputOptionsSchema = z
|
|
|
1918
1918
|
systemReminderPrompt: z.string().optional(),
|
|
1919
1919
|
})
|
|
1920
1920
|
.meta({ title: "CodeGenInputOptions" });
|
|
1921
|
+
export const FUSION_GENERATION_APP_PLACEHOLDER_COMMENT = "{/* TODO: FUSION_GENERATION_APP_PLACEHOLDER replace everything here with the actual app! */}";
|
|
1921
1922
|
export const AutoPushModeSchema = z
|
|
1922
1923
|
.enum(["force-push", "merge-push", "ff-push", "safe-push", "none"])
|
|
1923
1924
|
.meta({ title: "AutoPushMode" });
|
package/src/completion.d.ts
CHANGED
|
@@ -99,7 +99,8 @@ export interface BuilderEditorState {
|
|
|
99
99
|
*/
|
|
100
100
|
content?: BuilderContent;
|
|
101
101
|
/**
|
|
102
|
-
* Builder
|
|
102
|
+
* Components available in the Builder editor, including built-ins.
|
|
103
|
+
* When present, editor AI restricts generated content to this list.
|
|
103
104
|
*/
|
|
104
105
|
components?: Component[];
|
|
105
106
|
/**
|
|
@@ -7,5 +7,12 @@ export interface HttpCheckOptions {
|
|
|
7
7
|
fetchFn?: ConnectivityFetchFn;
|
|
8
8
|
/** Fetch dispatcher for proxy routing (e.g. undici ProxyAgent). */
|
|
9
9
|
dispatcher?: object;
|
|
10
|
+
/**
|
|
11
|
+
* Treat any 4xx as a failure (not just 5xx). Used by `doctor --browser`: a
|
|
12
|
+
* TLS-inspecting proxy (Zscaler) that blocks browser-shaped traffic typically
|
|
13
|
+
* answers with a 403/407/451 block page, which a plain reachability check
|
|
14
|
+
* would otherwise count as a pass.
|
|
15
|
+
*/
|
|
16
|
+
strictHttpStatus?: boolean;
|
|
10
17
|
}
|
|
11
18
|
export declare function httpCheck(options: HttpCheckOptions): Promise<CheckResult>;
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { mapFetchErrorToConnectivityCode } from "../error-codes.js";
|
|
1
|
+
import { mapFetchErrorToConnectivityCode, mapHttpStatusToErrorCode, } from "../error-codes.js";
|
|
2
2
|
import { isBrowser } from "../environment.js";
|
|
3
3
|
const DEFAULT_TIMEOUT_MS = 30000;
|
|
4
4
|
const LATENCY_THRESHOLD_MS = 5000;
|
|
5
5
|
export async function httpCheck(options) {
|
|
6
|
-
|
|
6
|
+
var _a;
|
|
7
|
+
const { target, source, testId, timeout = DEFAULT_TIMEOUT_MS, fetchFn = fetch, dispatcher, strictHttpStatus = false, } = options;
|
|
7
8
|
const startTime = Date.now();
|
|
8
9
|
const controller = new AbortController();
|
|
9
10
|
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
@@ -46,14 +47,21 @@ export async function httpCheck(options) {
|
|
|
46
47
|
clearTimeout(timeoutId);
|
|
47
48
|
const durationMs = Date.now() - startTime;
|
|
48
49
|
const hasHighLatency = durationMs > LATENCY_THRESHOLD_MS;
|
|
49
|
-
|
|
50
|
-
|
|
50
|
+
// Standard mode only fails on 5xx (server reachable is what matters).
|
|
51
|
+
// Browser (strict) mode also fails on 4xx to surface proxy block pages.
|
|
52
|
+
const isFailingStatus = strictHttpStatus
|
|
53
|
+
? response.status >= 400
|
|
54
|
+
: response.status >= 500;
|
|
55
|
+
if (isFailingStatus) {
|
|
51
56
|
return {
|
|
52
57
|
source,
|
|
53
58
|
testId,
|
|
54
59
|
target,
|
|
55
60
|
passed: false,
|
|
56
|
-
|
|
61
|
+
// Canonical status->code mapping: 401/403/404/407/503/5xx get specific
|
|
62
|
+
// codes; other 4xx (400/405/429/...) map to "unknown_error" instead of
|
|
63
|
+
// being mislabeled as a specific auth failure.
|
|
64
|
+
errorCode: (_a = mapHttpStatusToErrorCode(response.status)) !== null && _a !== void 0 ? _a : "unknown_error",
|
|
57
65
|
durationMs,
|
|
58
66
|
metadata: {
|
|
59
67
|
statusCode: response.status,
|
|
@@ -24,3 +24,61 @@ describe("httpCheck", () => {
|
|
|
24
24
|
expect(result.metadata).toMatchObject({ reachabilityOnly: true });
|
|
25
25
|
});
|
|
26
26
|
});
|
|
27
|
+
describe("httpCheck strictHttpStatus", () => {
|
|
28
|
+
const target = "https://api.builder.io/codegen/health";
|
|
29
|
+
it("treats 403 as a pass by default (server reachable)", async () => {
|
|
30
|
+
const fetchFn = vi
|
|
31
|
+
.fn()
|
|
32
|
+
.mockResolvedValue({ ok: false, status: 403, statusText: "Forbidden" });
|
|
33
|
+
const result = await httpCheck({
|
|
34
|
+
target,
|
|
35
|
+
source: "local",
|
|
36
|
+
testId: "api.builder.io",
|
|
37
|
+
fetchFn,
|
|
38
|
+
});
|
|
39
|
+
expect(result.passed).toBe(true);
|
|
40
|
+
expect(result.metadata).toMatchObject({ statusCode: 403 });
|
|
41
|
+
});
|
|
42
|
+
it("treats 403 as a failure in strict mode (proxy block page)", async () => {
|
|
43
|
+
const fetchFn = vi
|
|
44
|
+
.fn()
|
|
45
|
+
.mockResolvedValue({ ok: false, status: 403, statusText: "Forbidden" });
|
|
46
|
+
const result = await httpCheck({
|
|
47
|
+
target,
|
|
48
|
+
source: "local",
|
|
49
|
+
testId: "api.builder.io",
|
|
50
|
+
fetchFn,
|
|
51
|
+
strictHttpStatus: true,
|
|
52
|
+
});
|
|
53
|
+
expect(result.passed).toBe(false);
|
|
54
|
+
expect(result.errorCode).toBe("http_forbidden");
|
|
55
|
+
expect(result.metadata).toMatchObject({ statusCode: 403 });
|
|
56
|
+
});
|
|
57
|
+
it("maps an unhandled 4xx (429) to unknown_error, not http_forbidden", async () => {
|
|
58
|
+
const fetchFn = vi
|
|
59
|
+
.fn()
|
|
60
|
+
.mockResolvedValue({ ok: false, status: 429, statusText: "Too Many" });
|
|
61
|
+
const result = await httpCheck({
|
|
62
|
+
target,
|
|
63
|
+
source: "local",
|
|
64
|
+
testId: "api.builder.io",
|
|
65
|
+
fetchFn,
|
|
66
|
+
strictHttpStatus: true,
|
|
67
|
+
});
|
|
68
|
+
expect(result.passed).toBe(false);
|
|
69
|
+
expect(result.errorCode).toBe("unknown_error");
|
|
70
|
+
});
|
|
71
|
+
it("still passes on 200 in strict mode", async () => {
|
|
72
|
+
const fetchFn = vi
|
|
73
|
+
.fn()
|
|
74
|
+
.mockResolvedValue({ ok: true, status: 200, statusText: "OK" });
|
|
75
|
+
const result = await httpCheck({
|
|
76
|
+
target,
|
|
77
|
+
source: "local",
|
|
78
|
+
testId: "api.builder.io",
|
|
79
|
+
fetchFn,
|
|
80
|
+
strictHttpStatus: true,
|
|
81
|
+
});
|
|
82
|
+
expect(result.passed).toBe(true);
|
|
83
|
+
});
|
|
84
|
+
});
|
|
@@ -13,6 +13,12 @@ export function getCheckTypeForTestId(testId) {
|
|
|
13
13
|
if (testId.startsWith("git-host:")) {
|
|
14
14
|
return testId.replace("git-host:", "");
|
|
15
15
|
}
|
|
16
|
+
if (testId.startsWith("project-health:")) {
|
|
17
|
+
return testId.replace("project-health:", "");
|
|
18
|
+
}
|
|
19
|
+
if (testId.startsWith("project:")) {
|
|
20
|
+
return testId.replace("project:", "");
|
|
21
|
+
}
|
|
16
22
|
return "http";
|
|
17
23
|
}
|
|
18
24
|
export function isCheckAvailable(checkType) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export type { Source, TestId, Test, RunChecksInput, ProgressEvent, CheckResult, CheckReport, ConnectivityErrorCode, CheckType, Recommendation, LikelyCause, ConnectivityStatus, AnalysisResult, AnalyzeConnectivityInput, ConnectivityFetchFn, } from "./types.js";
|
|
2
2
|
export { runChecks } from "./run-checks.js";
|
|
3
3
|
export { mapNodeErrorToConnectivityCode, mapHttpStatusToErrorCode, mapFetchErrorToConnectivityCode, connectivityErrorCodeToLikelyCause, mapConnectivityErrorMessage, SELF_SIGNED_CERT_ERRORS, CERT_EXPIRED_ERRORS, CERT_NOT_YET_VALID_ERRORS, CERT_INVALID_ERRORS, CERT_HOSTNAME_MISMATCH_ERRORS, SSL_PROTOCOL_ERRORS, SSL_HANDSHAKE_ERRORS, NETWORK_UNREACHABLE_ERRORS, TIMEOUT_ERRORS, PROXY_ERRORS, DNS_ERRORS, } from "./error-codes.js";
|
|
4
|
-
export { BUILDER_TARGETS, DEFAULT_PORTS, DEFAULT_LOCAL_BUILDER_TESTS, BUILDER_TEST_DISPLAY_NAMES, resolveTarget, extractHostname, extractPort, } from "./targets.js";
|
|
4
|
+
export { BUILDER_TARGETS, DEFAULT_PORTS, DEFAULT_LOCAL_BUILDER_TESTS, BUILDER_TEST_DISPLAY_NAMES, resolveTarget, getProjectHealthUrl, extractHostname, extractPort, } from "./targets.js";
|
|
5
5
|
export { isBrowser, isNode, getCheckTypeForTestId, isCheckAvailable, getUnavailabilityReason, } from "./environment.js";
|
|
6
6
|
export { httpCheck } from "./checks/http-check.js";
|
|
7
7
|
export type { HttpCheckOptions } from "./checks/http-check.js";
|
package/src/connectivity/node.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { runChecks } from "./run-checks.js";
|
|
2
2
|
export { mapNodeErrorToConnectivityCode, mapHttpStatusToErrorCode, mapFetchErrorToConnectivityCode, connectivityErrorCodeToLikelyCause, mapConnectivityErrorMessage, SELF_SIGNED_CERT_ERRORS, CERT_EXPIRED_ERRORS, CERT_NOT_YET_VALID_ERRORS, CERT_INVALID_ERRORS, CERT_HOSTNAME_MISMATCH_ERRORS, SSL_PROTOCOL_ERRORS, SSL_HANDSHAKE_ERRORS, NETWORK_UNREACHABLE_ERRORS, TIMEOUT_ERRORS, PROXY_ERRORS, DNS_ERRORS, } from "./error-codes.js";
|
|
3
|
-
export { BUILDER_TARGETS, DEFAULT_PORTS, DEFAULT_LOCAL_BUILDER_TESTS, BUILDER_TEST_DISPLAY_NAMES, resolveTarget, extractHostname, extractPort, } from "./targets.js";
|
|
3
|
+
export { BUILDER_TARGETS, DEFAULT_PORTS, DEFAULT_LOCAL_BUILDER_TESTS, BUILDER_TEST_DISPLAY_NAMES, resolveTarget, getProjectHealthUrl, extractHostname, extractPort, } from "./targets.js";
|
|
4
4
|
export { isBrowser, isNode, getCheckTypeForTestId, isCheckAvailable, getUnavailabilityReason, } from "./environment.js";
|
|
5
5
|
export { httpCheck } from "./checks/http-check.js";
|
|
6
6
|
export { dnsCheck } from "./checks/dns-check.js";
|
|
@@ -7,7 +7,7 @@ import { tcpCheck } from "./checks/tcp-check.js";
|
|
|
7
7
|
import { tlsCheck } from "./checks/tls-check.js";
|
|
8
8
|
import { sshCheck } from "./checks/ssh-check.js";
|
|
9
9
|
export async function runChecks(input) {
|
|
10
|
-
const { tests, gitHost, onProgress, fetchFn, dispatcher, connectFn, sshConnectFn, dnsResolver, } = input;
|
|
10
|
+
const { tests, gitHost, onProgress, fetchFn, dispatcher, connectFn, sshConnectFn, dnsResolver, projectUrl, projectHealthUrl, strictHttpStatus, } = input;
|
|
11
11
|
const results = [];
|
|
12
12
|
const total = tests.length;
|
|
13
13
|
for (let index = 0; index < tests.length; index++) {
|
|
@@ -18,7 +18,7 @@ export async function runChecks(input) {
|
|
|
18
18
|
index,
|
|
19
19
|
total,
|
|
20
20
|
});
|
|
21
|
-
const result = await runSingleCheck(test, gitHost, fetchFn, dispatcher, connectFn, sshConnectFn, dnsResolver);
|
|
21
|
+
const result = await runSingleCheck(test, gitHost, fetchFn, dispatcher, connectFn, sshConnectFn, dnsResolver, { projectUrl, projectHealthUrl, strictHttpStatus });
|
|
22
22
|
results.push(result);
|
|
23
23
|
emitProgress(onProgress, {
|
|
24
24
|
type: "test:complete",
|
|
@@ -37,14 +37,14 @@ export async function runChecks(input) {
|
|
|
37
37
|
results,
|
|
38
38
|
};
|
|
39
39
|
}
|
|
40
|
-
async function runSingleCheck(test, gitHost, fetchFn, dispatcher, connectFn, sshConnectFn, dnsResolver) {
|
|
40
|
+
async function runSingleCheck(test, gitHost, fetchFn, dispatcher, connectFn, sshConnectFn, dnsResolver, extras) {
|
|
41
41
|
const { source, testId } = test;
|
|
42
42
|
const checkType = getCheckTypeForTestId(testId);
|
|
43
43
|
if (!isCheckAvailable(checkType)) {
|
|
44
44
|
const startTime = Date.now();
|
|
45
45
|
let target;
|
|
46
46
|
try {
|
|
47
|
-
target = resolveTarget(testId, gitHost);
|
|
47
|
+
target = resolveTarget(testId, gitHost, extras);
|
|
48
48
|
}
|
|
49
49
|
catch (_a) {
|
|
50
50
|
target = gitHost || testId;
|
|
@@ -64,7 +64,7 @@ async function runSingleCheck(test, gitHost, fetchFn, dispatcher, connectFn, ssh
|
|
|
64
64
|
}
|
|
65
65
|
let target;
|
|
66
66
|
try {
|
|
67
|
-
target = resolveTarget(testId, gitHost);
|
|
67
|
+
target = resolveTarget(testId, gitHost, extras);
|
|
68
68
|
}
|
|
69
69
|
catch (error) {
|
|
70
70
|
return {
|
|
@@ -81,7 +81,14 @@ async function runSingleCheck(test, gitHost, fetchFn, dispatcher, connectFn, ssh
|
|
|
81
81
|
}
|
|
82
82
|
switch (checkType) {
|
|
83
83
|
case "http":
|
|
84
|
-
return httpCheck({
|
|
84
|
+
return httpCheck({
|
|
85
|
+
target,
|
|
86
|
+
source,
|
|
87
|
+
testId,
|
|
88
|
+
fetchFn,
|
|
89
|
+
dispatcher,
|
|
90
|
+
strictHttpStatus: extras === null || extras === void 0 ? void 0 : extras.strictHttpStatus,
|
|
91
|
+
});
|
|
85
92
|
case "websocket":
|
|
86
93
|
return websocketCheck({ target, source, testId });
|
|
87
94
|
case "dns":
|
|
@@ -12,8 +12,18 @@ export declare const DEFAULT_LOCAL_BUILDER_TESTS: Test[];
|
|
|
12
12
|
* UI surfaces to show friendly labels instead of raw testId strings.
|
|
13
13
|
*/
|
|
14
14
|
export declare const BUILDER_TEST_DISPLAY_NAMES: Partial<Record<TestId, string>>;
|
|
15
|
+
/**
|
|
16
|
+
* Given a full project URL (e.g. https://<id>-<branch>.builderio.dev/... ), return
|
|
17
|
+
* the matching health.builderio.* URL the project is routed through, or null when
|
|
18
|
+
* the host is not a known Builder kube domain.
|
|
19
|
+
*/
|
|
20
|
+
export declare function getProjectHealthUrl(projectUrl: string): string | null;
|
|
15
21
|
export declare const DEFAULT_PORTS: Record<string, number>;
|
|
16
|
-
export
|
|
22
|
+
export interface ResolveTargetExtras {
|
|
23
|
+
projectUrl?: string;
|
|
24
|
+
projectHealthUrl?: string;
|
|
25
|
+
}
|
|
26
|
+
export declare function resolveTarget(testId: TestId, gitHost?: string, extras?: ResolveTargetExtras): string;
|
|
17
27
|
export declare function extractHostname(target: string): string;
|
|
18
28
|
/**
|
|
19
29
|
* Extract only an explicitly specified port from a URL, ignoring protocol-specific defaults.
|
|
@@ -37,7 +37,28 @@ export const BUILDER_TEST_DISPLAY_NAMES = {
|
|
|
37
37
|
"builderio.xyz:ws": "*.builderio.xyz (WebSocket)",
|
|
38
38
|
"builderio.dev": "*.builderio.dev",
|
|
39
39
|
"builderio.dev:ws": "*.builderio.dev (WebSocket)",
|
|
40
|
+
"project:dns": "Project URL (DNS)",
|
|
41
|
+
"project:tcp": "Project URL (TCP)",
|
|
42
|
+
"project:tls": "Project URL (TLS)",
|
|
43
|
+
"project:http": "Project URL (HTTP)",
|
|
44
|
+
"project-health:dns": "Health domain (DNS)",
|
|
45
|
+
"project-health:tcp": "Health domain (TCP)",
|
|
46
|
+
"project-health:tls": "Health domain (TLS)",
|
|
47
|
+
"project-health:http": "Health domain (HTTP)",
|
|
40
48
|
};
|
|
49
|
+
/**
|
|
50
|
+
* Given a full project URL (e.g. https://<id>-<branch>.builderio.dev/... ), return
|
|
51
|
+
* the matching health.builderio.* URL the project is routed through, or null when
|
|
52
|
+
* the host is not a known Builder kube domain.
|
|
53
|
+
*/
|
|
54
|
+
export function getProjectHealthUrl(projectUrl) {
|
|
55
|
+
const host = extractHostname(projectUrl);
|
|
56
|
+
if (host.endsWith("builderio.xyz"))
|
|
57
|
+
return BUILDER_TARGETS["builderio.xyz"];
|
|
58
|
+
if (host.endsWith("builderio.dev"))
|
|
59
|
+
return BUILDER_TARGETS["builderio.dev"];
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
41
62
|
export const DEFAULT_PORTS = {
|
|
42
63
|
http: 443,
|
|
43
64
|
https: 443,
|
|
@@ -45,13 +66,25 @@ export const DEFAULT_PORTS = {
|
|
|
45
66
|
tcp: 443,
|
|
46
67
|
tls: 443,
|
|
47
68
|
};
|
|
48
|
-
export function resolveTarget(testId, gitHost) {
|
|
69
|
+
export function resolveTarget(testId, gitHost, extras) {
|
|
49
70
|
if (testId.startsWith("git-host:")) {
|
|
50
71
|
if (!gitHost) {
|
|
51
72
|
throw new Error(`gitHost parameter is required for test "${testId}"`);
|
|
52
73
|
}
|
|
53
74
|
return gitHost;
|
|
54
75
|
}
|
|
76
|
+
if (testId.startsWith("project-health:")) {
|
|
77
|
+
if (!(extras === null || extras === void 0 ? void 0 : extras.projectHealthUrl)) {
|
|
78
|
+
throw new Error(`projectHealthUrl is required for test "${testId}"`);
|
|
79
|
+
}
|
|
80
|
+
return extras.projectHealthUrl;
|
|
81
|
+
}
|
|
82
|
+
if (testId.startsWith("project:")) {
|
|
83
|
+
if (!(extras === null || extras === void 0 ? void 0 : extras.projectUrl)) {
|
|
84
|
+
throw new Error(`projectUrl is required for test "${testId}"`);
|
|
85
|
+
}
|
|
86
|
+
return extras.projectUrl;
|
|
87
|
+
}
|
|
55
88
|
const target = BUILDER_TARGETS[testId];
|
|
56
89
|
if (!target) {
|
|
57
90
|
throw new Error(`Unknown testId: ${testId}`);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { describe, it, expect } from "vitest";
|
|
2
|
-
import { extractPort, extractExplicitPort, BUILDER_TARGETS, DEFAULT_LOCAL_BUILDER_TESTS, BUILDER_TEST_DISPLAY_NAMES, } from "./targets";
|
|
2
|
+
import { extractPort, extractExplicitPort, BUILDER_TARGETS, DEFAULT_LOCAL_BUILDER_TESTS, BUILDER_TEST_DISPLAY_NAMES, resolveTarget, getProjectHealthUrl, } from "./targets";
|
|
3
|
+
import { getCheckTypeForTestId } from "./environment";
|
|
3
4
|
describe("extractPort", () => {
|
|
4
5
|
it("returns explicit port from URL", () => {
|
|
5
6
|
expect(extractPort("https://git.amazon.com:2222", 443)).toBe(2222);
|
|
@@ -76,3 +77,52 @@ describe("BUILDER_TEST_DISPLAY_NAMES", () => {
|
|
|
76
77
|
expect(BUILDER_TEST_DISPLAY_NAMES["builderio.dev:ws"]).toBe("*.builderio.dev (WebSocket)");
|
|
77
78
|
});
|
|
78
79
|
});
|
|
80
|
+
describe("resolveTarget — per-project tests", () => {
|
|
81
|
+
const PROJECT_URL = "https://9739e085f64844e09c9918ce06fa57f5-main.builderio.dev/_builder.io/api/status-v2";
|
|
82
|
+
const HEALTH_URL = "https://health.builderio.dev/health";
|
|
83
|
+
it("resolves project:* testIds to the provided projectUrl", () => {
|
|
84
|
+
for (const id of [
|
|
85
|
+
"project:dns",
|
|
86
|
+
"project:tcp",
|
|
87
|
+
"project:tls",
|
|
88
|
+
"project:http",
|
|
89
|
+
]) {
|
|
90
|
+
expect(resolveTarget(id, undefined, { projectUrl: PROJECT_URL })).toBe(PROJECT_URL);
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
it("resolves project-health:* testIds to the provided projectHealthUrl", () => {
|
|
94
|
+
for (const id of [
|
|
95
|
+
"project-health:dns",
|
|
96
|
+
"project-health:tcp",
|
|
97
|
+
"project-health:tls",
|
|
98
|
+
"project-health:http",
|
|
99
|
+
]) {
|
|
100
|
+
expect(resolveTarget(id, undefined, { projectHealthUrl: HEALTH_URL })).toBe(HEALTH_URL);
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
it("throws when the required project URL is missing", () => {
|
|
104
|
+
expect(() => resolveTarget("project:dns")).toThrow();
|
|
105
|
+
expect(() => resolveTarget("project-health:dns")).toThrow();
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
describe("getProjectHealthUrl", () => {
|
|
109
|
+
it("maps a .dev project URL to the .dev health endpoint", () => {
|
|
110
|
+
expect(getProjectHealthUrl("https://abc-main.builderio.dev/_builder.io/api/status-v2")).toBe("https://health.builderio.dev/health");
|
|
111
|
+
});
|
|
112
|
+
it("maps a .xyz project URL to the .xyz health endpoint", () => {
|
|
113
|
+
expect(getProjectHealthUrl("https://abc-main.builderio.xyz")).toBe("https://health.builderio.xyz/health");
|
|
114
|
+
});
|
|
115
|
+
it("returns null for a non-kube domain", () => {
|
|
116
|
+
expect(getProjectHealthUrl("https://example.com/foo")).toBeNull();
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
describe("getCheckTypeForTestId — per-project tests", () => {
|
|
120
|
+
it("derives the check type from the project testId suffix", () => {
|
|
121
|
+
expect(getCheckTypeForTestId("project:dns")).toBe("dns");
|
|
122
|
+
expect(getCheckTypeForTestId("project:tcp")).toBe("tcp");
|
|
123
|
+
expect(getCheckTypeForTestId("project:tls")).toBe("tls");
|
|
124
|
+
expect(getCheckTypeForTestId("project:http")).toBe("http");
|
|
125
|
+
expect(getCheckTypeForTestId("project-health:dns")).toBe("dns");
|
|
126
|
+
expect(getCheckTypeForTestId("project-health:tls")).toBe("tls");
|
|
127
|
+
});
|
|
128
|
+
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export type Source = "local" | "cloud" | "static-ip" | "vpc";
|
|
2
|
-
export type TestId = "builder.io" | "builder.codes" | "api.builder.io" | "cdn.builder.io" | "builderio.xyz" | "builderio.xyz:ws" | "builderio.dev" | "builderio.dev:ws" | "fly.dev" | "git-host:http" | "git-host:dns" | "git-host:tcp" | "git-host:tls" | "git-host:ssh";
|
|
2
|
+
export type TestId = "builder.io" | "builder.codes" | "api.builder.io" | "cdn.builder.io" | "builderio.xyz" | "builderio.xyz:ws" | "builderio.dev" | "builderio.dev:ws" | "fly.dev" | "git-host:http" | "git-host:dns" | "git-host:tcp" | "git-host:tls" | "git-host:ssh" | "project:dns" | "project:tcp" | "project:tls" | "project:http" | "project-health:dns" | "project-health:tcp" | "project-health:tls" | "project-health:http";
|
|
3
3
|
export interface Test {
|
|
4
4
|
source: Source;
|
|
5
5
|
testId: TestId;
|
|
@@ -25,6 +25,12 @@ export interface RunChecksInput {
|
|
|
25
25
|
* Typically only needed server-side for static IP routing.
|
|
26
26
|
*/
|
|
27
27
|
dispatcher?: object;
|
|
28
|
+
/**
|
|
29
|
+
* Treat 4xx HTTP responses as failures (not just 5xx). Used by
|
|
30
|
+
* `doctor --browser` so a proxy block page (e.g. a 403 from Zscaler) counts
|
|
31
|
+
* as a failure instead of a reachable-server pass.
|
|
32
|
+
*/
|
|
33
|
+
strictHttpStatus?: boolean;
|
|
28
34
|
/**
|
|
29
35
|
* Returns a connected socket tunneled through a proxy (via HTTP CONNECT
|
|
30
36
|
* or SOCKS5). Used by TCP and TLS checks for static IP / VPC routing. The
|
|
@@ -45,6 +51,16 @@ export interface RunChecksInput {
|
|
|
45
51
|
dnsResolver?: {
|
|
46
52
|
servers: string[];
|
|
47
53
|
};
|
|
54
|
+
/**
|
|
55
|
+
* Full URL of a specific project (e.g. a project's status-v2 endpoint) that the
|
|
56
|
+
* `project:*` tests probe. Resolved by resolveTarget for those testIds.
|
|
57
|
+
*/
|
|
58
|
+
projectUrl?: string;
|
|
59
|
+
/**
|
|
60
|
+
* URL of the health.builderio.* domain the project is routed through, probed by
|
|
61
|
+
* the `project-health:*` tests. Derived from projectUrl's kube domain.
|
|
62
|
+
*/
|
|
63
|
+
projectHealthUrl?: string;
|
|
48
64
|
}
|
|
49
65
|
export type ProgressEvent = {
|
|
50
66
|
type: "test:start";
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export declare const EDITOR_AI_READ_DEFAULT_LIMIT = 250;
|
|
3
|
+
export declare const EDITOR_AI_READ_MAX_LIMIT = 1500;
|
|
4
|
+
export declare const EditorAiReadRequestSchema: z.ZodObject<{
|
|
5
|
+
contentId: z.ZodString;
|
|
6
|
+
offset: z.ZodOptional<z.ZodNumber>;
|
|
7
|
+
limit: z.ZodOptional<z.ZodNumber>;
|
|
8
|
+
activeLocale: z.ZodOptional<z.ZodString>;
|
|
9
|
+
}, z.core.$strip>;
|
|
10
|
+
export type EditorAiReadRequest = z.infer<typeof EditorAiReadRequestSchema>;
|
|
11
|
+
export declare const EditorAiReadResponseSchema: z.ZodObject<{
|
|
12
|
+
contentId: z.ZodString;
|
|
13
|
+
totalLines: z.ZodNumber;
|
|
14
|
+
offset: z.ZodNumber;
|
|
15
|
+
limit: z.ZodNumber;
|
|
16
|
+
activeLocale: z.ZodString;
|
|
17
|
+
content: z.ZodString;
|
|
18
|
+
}, z.core.$strip>;
|
|
19
|
+
export type EditorAiReadResponse = z.infer<typeof EditorAiReadResponseSchema>;
|
|
20
|
+
export declare const EditorAiEditRequestSchema: z.ZodObject<{
|
|
21
|
+
contentId: z.ZodString;
|
|
22
|
+
old_str: z.ZodString;
|
|
23
|
+
new_str: z.ZodString;
|
|
24
|
+
activeLocale: z.ZodOptional<z.ZodString>;
|
|
25
|
+
}, z.core.$strip>;
|
|
26
|
+
export type EditorAiEditRequest = z.infer<typeof EditorAiEditRequestSchema>;
|
|
27
|
+
export declare const EditorAiEditResponseSchema: z.ZodObject<{
|
|
28
|
+
contentId: z.ZodString;
|
|
29
|
+
patch: z.ZodString;
|
|
30
|
+
modifiedBuilderContent: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
31
|
+
}, z.core.$strip>;
|
|
32
|
+
export type EditorAiEditResponse = z.infer<typeof EditorAiEditResponseSchema>;
|
|
33
|
+
export declare const EditorAiWriteRequestSchema: z.ZodObject<{
|
|
34
|
+
contentId: z.ZodString;
|
|
35
|
+
content: z.ZodString;
|
|
36
|
+
activeLocale: z.ZodOptional<z.ZodString>;
|
|
37
|
+
}, z.core.$strip>;
|
|
38
|
+
export type EditorAiWriteRequest = z.infer<typeof EditorAiWriteRequestSchema>;
|
|
39
|
+
export declare const EditorAiWriteResponseSchema: z.ZodObject<{
|
|
40
|
+
contentId: z.ZodString;
|
|
41
|
+
modifiedBuilderContent: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
42
|
+
}, z.core.$strip>;
|
|
43
|
+
export type EditorAiWriteResponse = z.infer<typeof EditorAiWriteResponseSchema>;
|
|
44
|
+
export type EditorAiErrorCode = "BAD_REQUEST" | "UNAUTHORIZED" | "FORBIDDEN" | "NOT_FOUND" | "NO_MATCH" | "VALIDATION_FAILED" | "INTERNAL";
|
|
45
|
+
export declare const EditorAiErrorBodySchema: z.ZodObject<{
|
|
46
|
+
error: z.ZodObject<{
|
|
47
|
+
code: z.ZodString;
|
|
48
|
+
message: z.ZodString;
|
|
49
|
+
hint: z.ZodOptional<z.ZodString>;
|
|
50
|
+
details: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
51
|
+
}, z.core.$strip>;
|
|
52
|
+
}, z.core.$strip>;
|
|
53
|
+
export type EditorAiErrorBody = z.infer<typeof EditorAiErrorBodySchema>;
|
package/src/editor-ai.js
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
// Request/response contracts for the stateless /editor/read|edit|write
|
|
3
|
+
// endpoints. Shared so the Builder CMS MCP server reuses the same shapes.
|
|
4
|
+
export const EDITOR_AI_READ_DEFAULT_LIMIT = 250;
|
|
5
|
+
export const EDITOR_AI_READ_MAX_LIMIT = 1500;
|
|
6
|
+
export const EditorAiReadRequestSchema = z
|
|
7
|
+
.object({
|
|
8
|
+
contentId: z.string().min(1),
|
|
9
|
+
offset: z.number().int().min(0).optional(),
|
|
10
|
+
limit: z.number().int().min(1).max(EDITOR_AI_READ_MAX_LIMIT).optional(),
|
|
11
|
+
// Locale to operate on; defaults to "Default" when omitted.
|
|
12
|
+
activeLocale: z.string().min(1).optional(),
|
|
13
|
+
})
|
|
14
|
+
.meta({ title: "EditorAiReadRequest" });
|
|
15
|
+
export const EditorAiReadResponseSchema = z
|
|
16
|
+
.object({
|
|
17
|
+
contentId: z.string(),
|
|
18
|
+
totalLines: z.number().int(),
|
|
19
|
+
offset: z.number().int(),
|
|
20
|
+
limit: z.number().int(),
|
|
21
|
+
activeLocale: z.string(),
|
|
22
|
+
content: z.string(),
|
|
23
|
+
})
|
|
24
|
+
.meta({ title: "EditorAiReadResponse" });
|
|
25
|
+
export const EditorAiEditRequestSchema = z
|
|
26
|
+
.object({
|
|
27
|
+
contentId: z.string().min(1),
|
|
28
|
+
old_str: z
|
|
29
|
+
.string()
|
|
30
|
+
.min(1)
|
|
31
|
+
.refine((s) => s.trim().length > 0, {
|
|
32
|
+
message: "old_str must not be only whitespace",
|
|
33
|
+
}),
|
|
34
|
+
new_str: z.string(),
|
|
35
|
+
// Locale to operate on; defaults to "Default" when omitted.
|
|
36
|
+
activeLocale: z.string().min(1).optional(),
|
|
37
|
+
})
|
|
38
|
+
.meta({ title: "EditorAiEditRequest" });
|
|
39
|
+
export const EditorAiEditResponseSchema = z
|
|
40
|
+
.object({
|
|
41
|
+
contentId: z.string(),
|
|
42
|
+
patch: z.string(),
|
|
43
|
+
modifiedBuilderContent: z.record(z.string(), z.unknown()),
|
|
44
|
+
})
|
|
45
|
+
.meta({ title: "EditorAiEditResponse" });
|
|
46
|
+
export const EditorAiWriteRequestSchema = z
|
|
47
|
+
.object({
|
|
48
|
+
contentId: z.string().min(1),
|
|
49
|
+
content: z.string().min(1),
|
|
50
|
+
// Locale to operate on; defaults to "Default" when omitted.
|
|
51
|
+
activeLocale: z.string().min(1).optional(),
|
|
52
|
+
})
|
|
53
|
+
.meta({ title: "EditorAiWriteRequest" });
|
|
54
|
+
export const EditorAiWriteResponseSchema = z
|
|
55
|
+
.object({
|
|
56
|
+
contentId: z.string(),
|
|
57
|
+
modifiedBuilderContent: z.record(z.string(), z.unknown()),
|
|
58
|
+
})
|
|
59
|
+
.meta({ title: "EditorAiWriteResponse" });
|
|
60
|
+
export const EditorAiErrorBodySchema = z
|
|
61
|
+
.object({
|
|
62
|
+
error: z.object({
|
|
63
|
+
code: z.string(),
|
|
64
|
+
message: z.string(),
|
|
65
|
+
hint: z.string().optional(),
|
|
66
|
+
details: z.record(z.string(), z.unknown()).optional(),
|
|
67
|
+
}),
|
|
68
|
+
})
|
|
69
|
+
.meta({ title: "EditorAiErrorBody" });
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { EditorAiEditRequestSchema } from "./editor-ai.js";
|
|
3
|
+
describe("EditorAiEditRequestSchema", () => {
|
|
4
|
+
it("accepts an old_str with real content (whitespace preserved)", () => {
|
|
5
|
+
const parsed = EditorAiEditRequestSchema.parse({
|
|
6
|
+
contentId: "c1",
|
|
7
|
+
old_str: ' <Text text="hi" />',
|
|
8
|
+
new_str: ' <Text text="bye" />',
|
|
9
|
+
});
|
|
10
|
+
// Value is not trimmed — exact indentation is preserved for matching.
|
|
11
|
+
expect(parsed.old_str).toBe(' <Text text="hi" />');
|
|
12
|
+
});
|
|
13
|
+
it("rejects an empty old_str", () => {
|
|
14
|
+
const result = EditorAiEditRequestSchema.safeParse({
|
|
15
|
+
contentId: "c1",
|
|
16
|
+
old_str: "",
|
|
17
|
+
new_str: "x",
|
|
18
|
+
});
|
|
19
|
+
expect(result.success).toBe(false);
|
|
20
|
+
});
|
|
21
|
+
it("rejects a whitespace-only old_str", () => {
|
|
22
|
+
const result = EditorAiEditRequestSchema.safeParse({
|
|
23
|
+
contentId: "c1",
|
|
24
|
+
old_str: " \n\t",
|
|
25
|
+
new_str: "x",
|
|
26
|
+
});
|
|
27
|
+
expect(result.success).toBe(false);
|
|
28
|
+
});
|
|
29
|
+
it("allows an empty new_str (delete)", () => {
|
|
30
|
+
const result = EditorAiEditRequestSchema.safeParse({
|
|
31
|
+
contentId: "c1",
|
|
32
|
+
old_str: '<Text text="hi" />',
|
|
33
|
+
new_str: "",
|
|
34
|
+
});
|
|
35
|
+
expect(result.success).toBe(true);
|
|
36
|
+
});
|
|
37
|
+
});
|