@builder.io/ai-utils 0.82.0 → 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.d.ts +1 -0
- package/src/codegen.js +1 -0
- 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/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 +1 -0
- package/src/index.js +1 -0
- package/src/organization.d.ts +9 -0
- package/src/projects.d.ts +1 -1
package/package.json
CHANGED
package/src/codegen.d.ts
CHANGED
|
@@ -3297,6 +3297,7 @@ export interface GenerateCompletionStepGit {
|
|
|
3297
3297
|
*/
|
|
3298
3298
|
diagnostics?: GitStatusDiagnostics;
|
|
3299
3299
|
}
|
|
3300
|
+
export declare const FUSION_GENERATION_APP_PLACEHOLDER_COMMENT = "{/* TODO: FUSION_GENERATION_APP_PLACEHOLDER replace everything here with the actual app! */}";
|
|
3300
3301
|
/**
|
|
3301
3302
|
* A typed comment added to the session via the generic `AddComment` tool. The
|
|
3302
3303
|
* `commentType` mirrors the tool's `type` field (e.g. `"code-review"`) and
|
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" });
|
|
@@ -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,82 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export declare const BUILDER_EMBEDDING_MODEL = "builder-multimodal-embedding";
|
|
3
|
+
export declare const BUILDER_EMBEDDING_DIMENSIONS = 1024;
|
|
4
|
+
export declare const BUILDER_EMBEDDING_IMAGE_MIME_TYPES: readonly ["image/png", "image/jpeg", "image/webp", "image/gif"];
|
|
5
|
+
export declare const BuilderEmbeddingImageSchema: z.ZodObject<{
|
|
6
|
+
mimeType: z.ZodEnum<{
|
|
7
|
+
"image/gif": "image/gif";
|
|
8
|
+
"image/jpeg": "image/jpeg";
|
|
9
|
+
"image/png": "image/png";
|
|
10
|
+
"image/webp": "image/webp";
|
|
11
|
+
}>;
|
|
12
|
+
data: z.ZodString;
|
|
13
|
+
}, z.core.$strip>;
|
|
14
|
+
export declare const BuilderEmbeddingInputSchema: z.ZodObject<{
|
|
15
|
+
text: z.ZodOptional<z.ZodString>;
|
|
16
|
+
images: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
17
|
+
mimeType: z.ZodEnum<{
|
|
18
|
+
"image/gif": "image/gif";
|
|
19
|
+
"image/jpeg": "image/jpeg";
|
|
20
|
+
"image/png": "image/png";
|
|
21
|
+
"image/webp": "image/webp";
|
|
22
|
+
}>;
|
|
23
|
+
data: z.ZodString;
|
|
24
|
+
}, z.core.$strip>>>;
|
|
25
|
+
}, z.core.$strip>;
|
|
26
|
+
export declare const BuilderEmbeddingsRequestSchema: z.ZodObject<{
|
|
27
|
+
model: z.ZodDefault<z.ZodEnum<{
|
|
28
|
+
auto: "auto";
|
|
29
|
+
"builder-multimodal-embedding": "builder-multimodal-embedding";
|
|
30
|
+
}>>;
|
|
31
|
+
inputType: z.ZodDefault<z.ZodEnum<{
|
|
32
|
+
document: "document";
|
|
33
|
+
query: "query";
|
|
34
|
+
}>>;
|
|
35
|
+
inputs: z.ZodArray<z.ZodObject<{
|
|
36
|
+
text: z.ZodOptional<z.ZodString>;
|
|
37
|
+
images: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
38
|
+
mimeType: z.ZodEnum<{
|
|
39
|
+
"image/gif": "image/gif";
|
|
40
|
+
"image/jpeg": "image/jpeg";
|
|
41
|
+
"image/png": "image/png";
|
|
42
|
+
"image/webp": "image/webp";
|
|
43
|
+
}>;
|
|
44
|
+
data: z.ZodString;
|
|
45
|
+
}, z.core.$strip>>>;
|
|
46
|
+
}, z.core.$strip>>;
|
|
47
|
+
source: z.ZodOptional<z.ZodObject<{
|
|
48
|
+
appId: z.ZodOptional<z.ZodString>;
|
|
49
|
+
feature: z.ZodOptional<z.ZodString>;
|
|
50
|
+
resourceId: z.ZodOptional<z.ZodString>;
|
|
51
|
+
userId: z.ZodOptional<z.ZodString>;
|
|
52
|
+
}, z.core.$strip>>;
|
|
53
|
+
metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
54
|
+
}, z.core.$strip>;
|
|
55
|
+
export type BuilderEmbeddingsRequestInput = z.input<typeof BuilderEmbeddingsRequestSchema>;
|
|
56
|
+
export type BuilderEmbeddingsRequest = z.output<typeof BuilderEmbeddingsRequestSchema>;
|
|
57
|
+
export declare const BuilderEmbeddingsResponseSchema: z.ZodObject<{
|
|
58
|
+
id: z.ZodString;
|
|
59
|
+
object: z.ZodLiteral<"list">;
|
|
60
|
+
model: z.ZodObject<{
|
|
61
|
+
publicId: z.ZodLiteral<"builder-multimodal-embedding">;
|
|
62
|
+
provider: z.ZodLiteral<"voyage">;
|
|
63
|
+
providerModel: z.ZodLiteral<"voyage-multimodal-3.5">;
|
|
64
|
+
version: z.ZodLiteral<"3.5">;
|
|
65
|
+
dimensions: z.ZodLiteral<1024>;
|
|
66
|
+
}, z.core.$strip>;
|
|
67
|
+
inputType: z.ZodEnum<{
|
|
68
|
+
document: "document";
|
|
69
|
+
query: "query";
|
|
70
|
+
}>;
|
|
71
|
+
data: z.ZodArray<z.ZodObject<{
|
|
72
|
+
object: z.ZodLiteral<"embedding">;
|
|
73
|
+
index: z.ZodNumber;
|
|
74
|
+
embedding: z.ZodArray<z.ZodNumber>;
|
|
75
|
+
}, z.core.$strip>>;
|
|
76
|
+
usage: z.ZodObject<{
|
|
77
|
+
textTokens: z.ZodNumber;
|
|
78
|
+
imagePixels: z.ZodNumber;
|
|
79
|
+
totalTokens: z.ZodNumber;
|
|
80
|
+
}, z.core.$strip>;
|
|
81
|
+
}, z.core.$strip>;
|
|
82
|
+
export type BuilderEmbeddingsResponse = z.infer<typeof BuilderEmbeddingsResponseSchema>;
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export const BUILDER_EMBEDDING_MODEL = "builder-multimodal-embedding";
|
|
3
|
+
export const BUILDER_EMBEDDING_DIMENSIONS = 1024;
|
|
4
|
+
export const BUILDER_EMBEDDING_IMAGE_MIME_TYPES = [
|
|
5
|
+
"image/png",
|
|
6
|
+
"image/jpeg",
|
|
7
|
+
"image/webp",
|
|
8
|
+
"image/gif",
|
|
9
|
+
];
|
|
10
|
+
const MAX_IMAGE_BASE64_LENGTH = 14000000;
|
|
11
|
+
const MAX_REQUEST_BASE64_LENGTH = 24000000;
|
|
12
|
+
const MAX_REQUEST_TEXT_LENGTH = 256000;
|
|
13
|
+
export const BuilderEmbeddingImageSchema = z.object({
|
|
14
|
+
mimeType: z.enum(BUILDER_EMBEDDING_IMAGE_MIME_TYPES),
|
|
15
|
+
data: z
|
|
16
|
+
.string()
|
|
17
|
+
.min(1)
|
|
18
|
+
.max(MAX_IMAGE_BASE64_LENGTH)
|
|
19
|
+
.regex(/^[A-Za-z0-9+/]*={0,2}$/, "Image data must be raw base64."),
|
|
20
|
+
});
|
|
21
|
+
export const BuilderEmbeddingInputSchema = z
|
|
22
|
+
.object({
|
|
23
|
+
text: z.string().trim().min(1).max(32000).optional(),
|
|
24
|
+
images: z.array(BuilderEmbeddingImageSchema).max(6).default([]),
|
|
25
|
+
})
|
|
26
|
+
.superRefine((input, ctx) => {
|
|
27
|
+
if (!input.text && input.images.length === 0) {
|
|
28
|
+
ctx.addIssue({
|
|
29
|
+
code: "custom",
|
|
30
|
+
message: "Each input must contain text, an image, or both.",
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
export const BuilderEmbeddingsRequestSchema = z
|
|
35
|
+
.object({
|
|
36
|
+
model: z.enum(["auto", BUILDER_EMBEDDING_MODEL]).default("auto"),
|
|
37
|
+
inputType: z.enum(["query", "document"]).default("document"),
|
|
38
|
+
inputs: z.array(BuilderEmbeddingInputSchema).min(1).max(32),
|
|
39
|
+
source: z
|
|
40
|
+
.object({
|
|
41
|
+
appId: z.string().max(100).optional(),
|
|
42
|
+
feature: z.string().max(100).optional(),
|
|
43
|
+
resourceId: z.string().max(200).optional(),
|
|
44
|
+
userId: z.string().max(200).optional(),
|
|
45
|
+
})
|
|
46
|
+
.optional(),
|
|
47
|
+
metadata: z.record(z.string().max(100), z.string().max(1000)).optional(),
|
|
48
|
+
})
|
|
49
|
+
.superRefine((request, ctx) => {
|
|
50
|
+
var _a;
|
|
51
|
+
var _b;
|
|
52
|
+
let base64Length = 0;
|
|
53
|
+
let textLength = 0;
|
|
54
|
+
for (const input of request.inputs) {
|
|
55
|
+
textLength += (_b = (_a = input.text) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 0;
|
|
56
|
+
for (const image of input.images)
|
|
57
|
+
base64Length += image.data.length;
|
|
58
|
+
}
|
|
59
|
+
if (base64Length > MAX_REQUEST_BASE64_LENGTH) {
|
|
60
|
+
ctx.addIssue({
|
|
61
|
+
code: "too_big",
|
|
62
|
+
maximum: MAX_REQUEST_BASE64_LENGTH,
|
|
63
|
+
origin: "string",
|
|
64
|
+
inclusive: true,
|
|
65
|
+
message: "Combined image data is too large.",
|
|
66
|
+
path: ["inputs"],
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
if (textLength > MAX_REQUEST_TEXT_LENGTH) {
|
|
70
|
+
ctx.addIssue({
|
|
71
|
+
code: "too_big",
|
|
72
|
+
maximum: MAX_REQUEST_TEXT_LENGTH,
|
|
73
|
+
origin: "string",
|
|
74
|
+
inclusive: true,
|
|
75
|
+
message: "Combined text input is too large.",
|
|
76
|
+
path: ["inputs"],
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
})
|
|
80
|
+
.meta({
|
|
81
|
+
description: "Request body for POST /agent-native/embeddings/v1/embeddings.",
|
|
82
|
+
});
|
|
83
|
+
export const BuilderEmbeddingsResponseSchema = z.object({
|
|
84
|
+
id: z.string(),
|
|
85
|
+
object: z.literal("list"),
|
|
86
|
+
model: z.object({
|
|
87
|
+
publicId: z.literal(BUILDER_EMBEDDING_MODEL),
|
|
88
|
+
provider: z.literal("voyage"),
|
|
89
|
+
providerModel: z.literal("voyage-multimodal-3.5"),
|
|
90
|
+
version: z.literal("3.5"),
|
|
91
|
+
dimensions: z.literal(BUILDER_EMBEDDING_DIMENSIONS),
|
|
92
|
+
}),
|
|
93
|
+
inputType: z.enum(["query", "document"]),
|
|
94
|
+
data: z.array(z.object({
|
|
95
|
+
object: z.literal("embedding"),
|
|
96
|
+
index: z.number().int().nonnegative(),
|
|
97
|
+
embedding: z
|
|
98
|
+
.array(z.number().finite())
|
|
99
|
+
.length(BUILDER_EMBEDDING_DIMENSIONS),
|
|
100
|
+
})),
|
|
101
|
+
usage: z.object({
|
|
102
|
+
textTokens: z.number().int().nonnegative(),
|
|
103
|
+
imagePixels: z.number().int().nonnegative(),
|
|
104
|
+
totalTokens: z.number().int().nonnegative(),
|
|
105
|
+
}),
|
|
106
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { BUILDER_EMBEDDING_MODEL, BuilderEmbeddingsRequestSchema, } from "./embeddings";
|
|
3
|
+
describe("BuilderEmbeddingsRequestSchema", () => {
|
|
4
|
+
it("accepts retrieval text and multimodal inputs", () => {
|
|
5
|
+
var _a;
|
|
6
|
+
const parsed = BuilderEmbeddingsRequestSchema.parse({
|
|
7
|
+
model: BUILDER_EMBEDDING_MODEL,
|
|
8
|
+
inputType: "document",
|
|
9
|
+
inputs: [
|
|
10
|
+
{ text: "A product launch slide" },
|
|
11
|
+
{
|
|
12
|
+
text: "A warm editorial campaign",
|
|
13
|
+
images: [{ mimeType: "image/png", data: "aGVsbG8=" }],
|
|
14
|
+
},
|
|
15
|
+
],
|
|
16
|
+
});
|
|
17
|
+
expect(parsed.inputs).toHaveLength(2);
|
|
18
|
+
expect((_a = parsed.inputs[1]) === null || _a === void 0 ? void 0 : _a.images).toHaveLength(1);
|
|
19
|
+
});
|
|
20
|
+
it("rejects empty inputs and data URLs", () => {
|
|
21
|
+
expect(() => BuilderEmbeddingsRequestSchema.parse({ inputs: [{}] })).toThrow(/text, an image, or both/);
|
|
22
|
+
expect(() => BuilderEmbeddingsRequestSchema.parse({
|
|
23
|
+
inputs: [
|
|
24
|
+
{
|
|
25
|
+
images: [
|
|
26
|
+
{
|
|
27
|
+
mimeType: "image/png",
|
|
28
|
+
data: "data:image/png;base64,aGVsbG8=",
|
|
29
|
+
},
|
|
30
|
+
],
|
|
31
|
+
},
|
|
32
|
+
],
|
|
33
|
+
})).toThrow(/raw base64/);
|
|
34
|
+
});
|
|
35
|
+
it("rejects unsupported image formats and oversized batches", () => {
|
|
36
|
+
expect(() => BuilderEmbeddingsRequestSchema.parse({
|
|
37
|
+
inputs: [{ images: [{ mimeType: "image/svg+xml", data: "PHN2Zz4=" }] }],
|
|
38
|
+
})).toThrow();
|
|
39
|
+
expect(() => BuilderEmbeddingsRequestSchema.parse({
|
|
40
|
+
inputs: Array.from({ length: 33 }, () => ({ text: "text" })),
|
|
41
|
+
})).toThrow();
|
|
42
|
+
});
|
|
43
|
+
});
|
package/src/index.d.ts
CHANGED
|
@@ -21,4 +21,5 @@ export * from "./single-tenancy.js";
|
|
|
21
21
|
export * from "./design-systems.js";
|
|
22
22
|
export * from "./editor-ai.js";
|
|
23
23
|
export * from "./realtime.js";
|
|
24
|
+
export * from "./embeddings.js";
|
|
24
25
|
export { connectivityErrorCodeToLikelyCause, mapConnectivityErrorMessage, } from "./connectivity/error-codes.js";
|
package/src/index.js
CHANGED
|
@@ -21,4 +21,5 @@ export * from "./single-tenancy.js";
|
|
|
21
21
|
export * from "./design-systems.js";
|
|
22
22
|
export * from "./editor-ai.js";
|
|
23
23
|
export * from "./realtime.js";
|
|
24
|
+
export * from "./embeddings.js";
|
|
24
25
|
export { connectivityErrorCodeToLikelyCause, mapConnectivityErrorMessage, } from "./connectivity/error-codes.js";
|
package/src/organization.d.ts
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import type { PrivacyMode, ReviewEffort } from "./codegen";
|
|
2
2
|
import type { EnvironmentVariable } from "./common-schemas";
|
|
3
|
+
export interface GitlabEnterpriseSetupValue {
|
|
4
|
+
host: string;
|
|
5
|
+
secondaryHost?: string;
|
|
6
|
+
setupType: "oauth" | "pat";
|
|
7
|
+
clientId?: string;
|
|
8
|
+
}
|
|
3
9
|
export interface GithubEnterpriseSetupValue {
|
|
4
10
|
host: string;
|
|
5
11
|
clientId: string;
|
|
@@ -19,6 +25,8 @@ export interface GitlabEnterprisePATValue {
|
|
|
19
25
|
host: string;
|
|
20
26
|
botUsername: string;
|
|
21
27
|
secondaryHost?: string;
|
|
28
|
+
createdBy?: string;
|
|
29
|
+
createdAt?: number;
|
|
22
30
|
}
|
|
23
31
|
export interface GitlabCloudFallbackToken {
|
|
24
32
|
token: string;
|
|
@@ -79,6 +87,7 @@ interface OrganizationSettings {
|
|
|
79
87
|
isUserPluginIntegrationRequestGranted?: boolean;
|
|
80
88
|
shopify?: boolean;
|
|
81
89
|
githubEnterpriseSetupValue?: GithubEnterpriseSetupValue;
|
|
90
|
+
gitlabEnterpriseSetupValue?: GitlabEnterpriseSetupValue;
|
|
82
91
|
gitlabEnterprisePAT?: GitlabEnterprisePATValue;
|
|
83
92
|
gitlabCloudFallbackToken?: GitlabCloudFallbackToken;
|
|
84
93
|
azureCloudFallbackToken?: AzureCloudFallbackToken;
|
package/src/projects.d.ts
CHANGED
|
@@ -153,7 +153,7 @@ export interface ReadyMessage extends BaseMessage {
|
|
|
153
153
|
}
|
|
154
154
|
export type MachineState = "unknown" | "created" | "starting" | "started" | "stopping" | "stopped" | "suspending" | "suspended" | "replacing" | "destroying" | "destroyed" | "not-found" | "running" | "failed";
|
|
155
155
|
export type FlyVolumeState = "unknown" | "creating" | "created" | "extending" | "restoring" | "enabling_remote_export" | "hydrating" | "recovering" | "scheduling_destroy" | "pending_destroy" | "failed";
|
|
156
|
-
export type GitAuthErrorCode = "git-auth-failed" | "git-auth-failed-root-repo" | "git-auth-failed-folder-added-by" | "git-auth-failed-folder-created-by" | "git-auth-failed-repo-not-found" | "git-auth-failed-repo-renamed" | "git-auth-failed-folder-server-token" | "git-auth-failed-root-repo-server-token" | "git-auth-failed-ghes-unreachable";
|
|
156
|
+
export type GitAuthErrorCode = "git-auth-failed" | "git-auth-failed-root-repo" | "git-auth-failed-folder-added-by" | "git-auth-failed-folder-created-by" | "git-auth-failed-repo-not-found" | "git-auth-failed-repo-renamed" | "git-auth-failed-folder-server-token" | "git-auth-failed-root-repo-server-token" | "git-auth-failed-ghes-unreachable" | "git-auth-reauth-required";
|
|
157
157
|
/**
|
|
158
158
|
* Git provider types for diagnostics.
|
|
159
159
|
*/
|