@velum-labs/cursorkit 0.1.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.
Files changed (142) hide show
  1. package/DISCLAIMER.md +12 -0
  2. package/README.md +157 -0
  3. package/dist/src/agentTools/diff.d.ts +11 -0
  4. package/dist/src/agentTools/diff.js +88 -0
  5. package/dist/src/agentTools/policy.d.ts +3 -0
  6. package/dist/src/agentTools/policy.js +12 -0
  7. package/dist/src/agentTools/registry.d.ts +114 -0
  8. package/dist/src/agentTools/registry.js +663 -0
  9. package/dist/src/agentTools/results.d.ts +14 -0
  10. package/dist/src/agentTools/results.js +117 -0
  11. package/dist/src/agentTools/schemas.d.ts +3 -0
  12. package/dist/src/agentTools/schemas.js +89 -0
  13. package/dist/src/agentTools/surface.d.ts +11 -0
  14. package/dist/src/agentTools/surface.js +251 -0
  15. package/dist/src/certs.d.ts +8 -0
  16. package/dist/src/certs.js +34 -0
  17. package/dist/src/ck.d.ts +2 -0
  18. package/dist/src/ck.js +6 -0
  19. package/dist/src/ckLauncher.d.ts +150 -0
  20. package/dist/src/ckLauncher.js +1496 -0
  21. package/dist/src/cli.d.ts +2 -0
  22. package/dist/src/cli.js +265 -0
  23. package/dist/src/config.d.ts +52 -0
  24. package/dist/src/config.js +210 -0
  25. package/dist/src/connectEnvelope.d.ts +16 -0
  26. package/dist/src/connectEnvelope.js +70 -0
  27. package/dist/src/desktop.d.ts +19 -0
  28. package/dist/src/desktop.js +167 -0
  29. package/dist/src/desktopConnectProxy.d.ts +26 -0
  30. package/dist/src/desktopConnectProxy.js +175 -0
  31. package/dist/src/extensions/index.d.ts +2 -0
  32. package/dist/src/extensions/index.js +1 -0
  33. package/dist/src/extensions/registry.d.ts +8 -0
  34. package/dist/src/extensions/registry.js +52 -0
  35. package/dist/src/extensions/types.d.ts +42 -0
  36. package/dist/src/extensions/types.js +1 -0
  37. package/dist/src/fixtures/modelFusion.d.ts +103 -0
  38. package/dist/src/fixtures/modelFusion.js +404 -0
  39. package/dist/src/fixtures/replay.d.ts +9 -0
  40. package/dist/src/fixtures/replay.js +41 -0
  41. package/dist/src/fixtures/sanitizer.d.ts +9 -0
  42. package/dist/src/fixtures/sanitizer.js +43 -0
  43. package/dist/src/fixtures/schema.d.ts +38 -0
  44. package/dist/src/fixtures/schema.js +33 -0
  45. package/dist/src/gen/agent/v1/agent_pb.d.ts +21577 -0
  46. package/dist/src/gen/agent/v1/agent_pb.js +5325 -0
  47. package/dist/src/gen/aiserver/v1/aiserver_pb.d.ts +135242 -0
  48. package/dist/src/gen/aiserver/v1/aiserver_pb.js +34430 -0
  49. package/dist/src/gen/anyrun/v1/anyrun_pb.d.ts +1163 -0
  50. package/dist/src/gen/anyrun/v1/anyrun_pb.js +374 -0
  51. package/dist/src/gen/google/protobuf/google_pb.d.ts +142 -0
  52. package/dist/src/gen/google/protobuf/google_pb.js +54 -0
  53. package/dist/src/gen/internapi/v1/internapi_pb.d.ts +121 -0
  54. package/dist/src/gen/internapi/v1/internapi_pb.js +79 -0
  55. package/dist/src/logger.d.ts +8 -0
  56. package/dist/src/logger.js +37 -0
  57. package/dist/src/modelFusion/cursorHarness.d.ts +146 -0
  58. package/dist/src/modelFusion/cursorHarness.js +647 -0
  59. package/dist/src/modelFusion/index.d.ts +4 -0
  60. package/dist/src/modelFusion/index.js +2 -0
  61. package/dist/src/models/registry.d.ts +22 -0
  62. package/dist/src/models/registry.js +30 -0
  63. package/dist/src/proto.d.ts +13 -0
  64. package/dist/src/proto.js +61 -0
  65. package/dist/src/providers/openai.d.ts +64 -0
  66. package/dist/src/providers/openai.js +355 -0
  67. package/dist/src/redaction.d.ts +4 -0
  68. package/dist/src/redaction.js +65 -0
  69. package/dist/src/routeInventory.d.ts +16 -0
  70. package/dist/src/routeInventory.js +39 -0
  71. package/dist/src/routes.d.ts +37 -0
  72. package/dist/src/routes.js +227 -0
  73. package/dist/src/server.d.ts +50 -0
  74. package/dist/src/server.js +1353 -0
  75. package/dist/src/services/agent.d.ts +1 -0
  76. package/dist/src/services/agent.js +7 -0
  77. package/dist/src/services/agentRun.d.ts +60 -0
  78. package/dist/src/services/agentRun.js +391 -0
  79. package/dist/src/services/chat.d.ts +11 -0
  80. package/dist/src/services/chat.js +47 -0
  81. package/dist/src/services/models.d.ts +10 -0
  82. package/dist/src/services/models.js +216 -0
  83. package/dist/src/services/serverConfig.d.ts +2 -0
  84. package/dist/src/services/serverConfig.js +19 -0
  85. package/dist/src/testing/artifacts.d.ts +14 -0
  86. package/dist/src/testing/artifacts.js +92 -0
  87. package/dist/src/testing/cli.d.ts +4 -0
  88. package/dist/src/testing/cli.js +192 -0
  89. package/dist/src/testing/localBackend.d.ts +24 -0
  90. package/dist/src/testing/localBackend.js +310 -0
  91. package/dist/src/testing/processRunner.d.ts +7 -0
  92. package/dist/src/testing/processRunner.js +74 -0
  93. package/dist/src/testing/runner.d.ts +9 -0
  94. package/dist/src/testing/runner.js +85 -0
  95. package/dist/src/testing/scenarios.d.ts +3 -0
  96. package/dist/src/testing/scenarios.js +2535 -0
  97. package/dist/src/testing/types.d.ts +66 -0
  98. package/dist/src/testing/types.js +1 -0
  99. package/dist/src/tools/baselineInventory.d.ts +12 -0
  100. package/dist/src/tools/baselineInventory.js +680 -0
  101. package/dist/src/tools/checkModelFusionProtocol.d.ts +1 -0
  102. package/dist/src/tools/checkModelFusionProtocol.js +274 -0
  103. package/dist/src/tools/checkReleasePublishConfig.d.ts +1 -0
  104. package/dist/src/tools/checkReleasePublishConfig.js +99 -0
  105. package/dist/src/tools/generateProtoInventory.d.ts +1 -0
  106. package/dist/src/tools/generateProtoInventory.js +89 -0
  107. package/dist/src/tools/normalizeGeneratedCode.d.ts +1 -0
  108. package/dist/src/tools/normalizeGeneratedCode.js +18 -0
  109. package/dist/src/tools/releaseCheck.d.ts +26 -0
  110. package/dist/src/tools/releaseCheck.js +367 -0
  111. package/dist/src/trace.d.ts +39 -0
  112. package/dist/src/trace.js +106 -0
  113. package/dist/src/translation.d.ts +6 -0
  114. package/dist/src/translation.js +22 -0
  115. package/dist/src/upstream.d.ts +20 -0
  116. package/dist/src/upstream.js +270 -0
  117. package/docs/configuration.md +55 -0
  118. package/docs/cursor-app.md +263 -0
  119. package/docs/implementation-inventory.json +609 -0
  120. package/docs/learnings.md +363 -0
  121. package/docs/model-fusion-protocol-origin.json +126 -0
  122. package/docs/model-fusion-protocol.md +110 -0
  123. package/docs/plugin-authoring.md +24 -0
  124. package/docs/proto-inventory.md +1477 -0
  125. package/docs/protocol-surface-audit.md +92 -0
  126. package/docs/protocol.md +52 -0
  127. package/docs/refreshing-protos.md +78 -0
  128. package/docs/release-gates.md +110 -0
  129. package/docs/release-summary.json +86 -0
  130. package/docs/route-contract-manifest.json +288 -0
  131. package/docs/route-policy.json +133 -0
  132. package/docs/service-manifest.json +9490 -0
  133. package/docs/test-manifest.json +155 -0
  134. package/docs/testing-harness.md +204 -0
  135. package/docs/troubleshooting.md +36 -0
  136. package/docs/type-manifest-summary.json +28927 -0
  137. package/package.json +93 -0
  138. package/proto/agent/v1/agent.proto +5371 -0
  139. package/proto/aiserver/v1/aiserver.proto +32944 -0
  140. package/proto/anyrun/v1/anyrun.proto +294 -0
  141. package/proto/google/protobuf/google.proto +37 -0
  142. package/proto/internapi/v1/internapi.proto +32 -0
@@ -0,0 +1,167 @@
1
+ import { X509Certificate } from "node:crypto";
2
+ import { lookup } from "node:dns/promises";
3
+ import fs from "node:fs";
4
+ import http from "node:http";
5
+ import https from "node:https";
6
+ import path from "node:path";
7
+ import { generateTlsMaterial } from "./certs.js";
8
+ export const DESKTOP_HOSTNAME = "api2.cursor.sh";
9
+ export const DESKTOP_HOSTNAMES = [
10
+ DESKTOP_HOSTNAME,
11
+ "api3.cursor.sh",
12
+ "agent.api5.cursor.sh",
13
+ "agentn.api5.cursor.sh",
14
+ "agentn.global.api5.cursor.sh",
15
+ ];
16
+ export const DESKTOP_CERT_DIR = path.join(".cursor-rpc", "certs");
17
+ export const DESKTOP_CERT_PATH = path.join(DESKTOP_CERT_DIR, `${DESKTOP_HOSTNAME}.crt`);
18
+ export const DESKTOP_KEY_PATH = path.join(DESKTOP_CERT_DIR, `${DESKTOP_HOSTNAME}.key`);
19
+ export function desktopEnv(env) {
20
+ const next = { ...env };
21
+ next.BRIDGE_DESKTOP_MODE ??= "true";
22
+ next.BRIDGE_USE_TLS ??= "true";
23
+ next.BRIDGE_TLS_HOSTNAMES ??= `${DESKTOP_HOSTNAMES.join(",")},localhost,127.0.0.1,::1`;
24
+ next.BRIDGE_PUBLIC_ORIGIN ??= `https://${DESKTOP_HOSTNAME}`;
25
+ next.CURSOR_UPSTREAM_BASE_URL ??= `https://${DESKTOP_HOSTNAME}`;
26
+ if (next.BRIDGE_CERT_PATH === undefined &&
27
+ next.BRIDGE_KEY_PATH === undefined &&
28
+ fs.existsSync(DESKTOP_CERT_PATH) &&
29
+ fs.existsSync(DESKTOP_KEY_PATH)) {
30
+ next.BRIDGE_CERT_PATH = DESKTOP_CERT_PATH;
31
+ next.BRIDGE_KEY_PATH = DESKTOP_KEY_PATH;
32
+ }
33
+ return next;
34
+ }
35
+ export function desktopTrustCommand(certPath = DESKTOP_CERT_PATH) {
36
+ return [
37
+ "sudo",
38
+ "security",
39
+ "add-trusted-cert",
40
+ "-d",
41
+ "-r",
42
+ "trustRoot",
43
+ "-k",
44
+ "/Library/Keychains/System.keychain",
45
+ certPath,
46
+ ];
47
+ }
48
+ export async function writeDesktopCertificate() {
49
+ if (fs.existsSync(DESKTOP_CERT_PATH) &&
50
+ fs.existsSync(DESKTOP_KEY_PATH) &&
51
+ desktopCertificateCoversHostnames(DESKTOP_CERT_PATH, [...DESKTOP_HOSTNAMES])) {
52
+ return {
53
+ certPath: DESKTOP_CERT_PATH,
54
+ keyPath: DESKTOP_KEY_PATH,
55
+ created: false,
56
+ };
57
+ }
58
+ fs.mkdirSync(DESKTOP_CERT_DIR, { recursive: true });
59
+ const tls = await generateTlsMaterial([
60
+ ...DESKTOP_HOSTNAMES,
61
+ "localhost",
62
+ "127.0.0.1",
63
+ "::1",
64
+ ]);
65
+ fs.writeFileSync(DESKTOP_CERT_PATH, tls.cert, { mode: 0o644 });
66
+ fs.writeFileSync(DESKTOP_KEY_PATH, tls.key, { mode: 0o600 });
67
+ return {
68
+ certPath: DESKTOP_CERT_PATH,
69
+ keyPath: DESKTOP_KEY_PATH,
70
+ created: true,
71
+ };
72
+ }
73
+ export function desktopCertificateStatus(config) {
74
+ if (config.certPath === undefined || config.keyPath === undefined) {
75
+ return "not configured; run cursorkit desktop-cert and set BRIDGE_CERT_PATH/BRIDGE_KEY_PATH";
76
+ }
77
+ if (!fs.existsSync(config.certPath) || !fs.existsSync(config.keyPath)) {
78
+ return "missing cert or key path";
79
+ }
80
+ try {
81
+ const cert = new X509Certificate(fs.readFileSync(config.certPath));
82
+ const altNames = cert.subjectAltName ?? "";
83
+ const missing = DESKTOP_HOSTNAMES.filter((hostname) => !altNames.includes(`DNS:${hostname}`));
84
+ return missing.length === 0
85
+ ? `covers ${DESKTOP_HOSTNAMES.join(", ")}`
86
+ : `missing ${missing.join(", ")}`;
87
+ }
88
+ catch (error) {
89
+ return `invalid certificate: ${error instanceof Error ? error.message : String(error)}`;
90
+ }
91
+ }
92
+ function desktopCertificateCoversHostnames(certPath, hostnames) {
93
+ try {
94
+ const cert = new X509Certificate(fs.readFileSync(certPath));
95
+ const altNames = cert.subjectAltName ?? "";
96
+ return hostnames.every((hostname) => altNames.includes(`DNS:${hostname}`));
97
+ }
98
+ catch {
99
+ return false;
100
+ }
101
+ }
102
+ export async function desktopDnsStatus(config) {
103
+ try {
104
+ const result = await lookup(DESKTOP_HOSTNAME);
105
+ const localRedirect = result.address === "127.0.0.1" || result.address === "::1";
106
+ if (localRedirect && config.upstreamConnectHost === undefined) {
107
+ return `${DESKTOP_HOSTNAME} -> ${result.address}; set CURSOR_UPSTREAM_CONNECT_HOST to avoid proxy loops`;
108
+ }
109
+ return `${DESKTOP_HOSTNAME} -> ${result.address}`;
110
+ }
111
+ catch (error) {
112
+ return `lookup failed: ${error instanceof Error ? error.message : String(error)}`;
113
+ }
114
+ }
115
+ export async function upstreamReachabilityStatus(config) {
116
+ if (config.upstreamBaseUrl === undefined) {
117
+ return "not configured";
118
+ }
119
+ const upstreamUrl = new URL(config.upstreamBaseUrl);
120
+ const client = upstreamUrl.protocol === "https:" ? https : http;
121
+ const host = config.upstreamConnectHost ?? upstreamUrl.hostname;
122
+ const port = config.upstreamConnectPort ??
123
+ (upstreamUrl.port.length > 0
124
+ ? Number(upstreamUrl.port)
125
+ : upstreamUrl.protocol === "https:"
126
+ ? 443
127
+ : 80);
128
+ return new Promise((resolve) => {
129
+ const request = client.request({
130
+ method: "HEAD",
131
+ hostname: host,
132
+ port,
133
+ path: "/",
134
+ headers: { host: upstreamUrl.host },
135
+ servername: upstreamUrl.protocol === "https:" ? upstreamUrl.hostname : undefined,
136
+ timeout: 5_000,
137
+ }, (response) => {
138
+ response.resume();
139
+ resolve(`HTTP ${response.statusCode ?? 0}`);
140
+ });
141
+ request.on("timeout", () => {
142
+ request.destroy(new Error("timeout"));
143
+ });
144
+ request.on("error", (error) => {
145
+ resolve(error.message);
146
+ });
147
+ request.end();
148
+ });
149
+ }
150
+ export async function localModelBackendStatus(config) {
151
+ const firstModel = config.models[0];
152
+ if (firstModel === undefined) {
153
+ return "no local models configured";
154
+ }
155
+ const modelsUrl = new URL(`${firstModel.baseUrl.replace(/\/$/, "")}/models`);
156
+ try {
157
+ const response = await fetch(modelsUrl, {
158
+ headers: firstModel.apiKey
159
+ ? { authorization: `Bearer ${firstModel.apiKey}` }
160
+ : {},
161
+ });
162
+ return `${modelsUrl.toString()} -> HTTP ${response.status}`;
163
+ }
164
+ catch (error) {
165
+ return `${modelsUrl.toString()} -> ${error instanceof Error ? error.message : String(error)}`;
166
+ }
167
+ }
@@ -0,0 +1,26 @@
1
+ import net from "node:net";
2
+ export interface DesktopConnectProxyOptions {
3
+ host: string;
4
+ port: number;
5
+ bridgeHost: string;
6
+ bridgePort: number;
7
+ logPath?: string;
8
+ passthrough?: boolean;
9
+ cursorHostnames?: readonly string[];
10
+ headerTimeoutMs?: number;
11
+ }
12
+ export interface DesktopConnectProxy {
13
+ server: net.Server;
14
+ close(): Promise<void>;
15
+ }
16
+ export interface DesktopConnectProxyEvent {
17
+ message: "desktop connect proxy";
18
+ event: "listening" | "connect" | "http-rejected" | "passthrough-blocked" | "header-timeout" | "upstream-error" | "client-closed";
19
+ host?: string;
20
+ port?: number;
21
+ targetHost?: string;
22
+ targetPort?: number;
23
+ cursorBackend?: boolean;
24
+ error?: string;
25
+ }
26
+ export declare function startDesktopConnectProxy(options: DesktopConnectProxyOptions): Promise<DesktopConnectProxy>;
@@ -0,0 +1,175 @@
1
+ import fs from "node:fs";
2
+ import net from "node:net";
3
+ import { DESKTOP_HOSTNAMES } from "./desktop.js";
4
+ const CONNECT_LINE_PATTERN = /^CONNECT\s+([^\s]+)\s+HTTP\/\d(?:\.\d)?$/i;
5
+ const DEFAULT_HEADER_TIMEOUT_MS = 5_000;
6
+ const MAX_CONNECT_HEADER_BYTES = 32 * 1024;
7
+ export async function startDesktopConnectProxy(options) {
8
+ const cursorHostnames = new Set(options.cursorHostnames ?? DESKTOP_HOSTNAMES);
9
+ const passthrough = options.passthrough ?? true;
10
+ const activeSockets = new Set();
11
+ const server = net.createServer((client) => {
12
+ activeSockets.add(client);
13
+ client.once("close", () => activeSockets.delete(client));
14
+ handleClient(client, options, cursorHostnames, passthrough, activeSockets);
15
+ });
16
+ await new Promise((resolve, reject) => {
17
+ server.once("error", reject);
18
+ server.listen(options.port, options.host, () => {
19
+ server.off("error", reject);
20
+ logProxyEvent(options.logPath, {
21
+ message: "desktop connect proxy",
22
+ event: "listening",
23
+ host: options.host,
24
+ port: options.port,
25
+ targetHost: options.bridgeHost,
26
+ targetPort: options.bridgePort,
27
+ });
28
+ resolve();
29
+ });
30
+ });
31
+ return {
32
+ server,
33
+ close: () => new Promise((resolve, reject) => {
34
+ for (const socket of activeSockets) {
35
+ socket.destroy();
36
+ }
37
+ server.close((error) => {
38
+ if (error !== undefined) {
39
+ reject(error);
40
+ return;
41
+ }
42
+ resolve();
43
+ });
44
+ }),
45
+ };
46
+ }
47
+ function handleClient(client, options, cursorHostnames, passthrough, activeSockets) {
48
+ let upstream;
49
+ let buffered = Buffer.alloc(0);
50
+ let tunnelEstablished = false;
51
+ const headerTimer = setTimeout(() => {
52
+ logProxyEvent(options.logPath, {
53
+ message: "desktop connect proxy",
54
+ event: "header-timeout",
55
+ });
56
+ client.end("HTTP/1.1 408 Request Timeout\r\nConnection: close\r\n\r\n");
57
+ }, options.headerTimeoutMs ?? DEFAULT_HEADER_TIMEOUT_MS);
58
+ const cleanup = () => {
59
+ clearTimeout(headerTimer);
60
+ if (upstream !== undefined) {
61
+ upstream.destroy();
62
+ }
63
+ if (tunnelEstablished) {
64
+ logProxyEvent(options.logPath, {
65
+ message: "desktop connect proxy",
66
+ event: "client-closed",
67
+ });
68
+ }
69
+ };
70
+ client.once("close", cleanup);
71
+ client.on("error", () => {
72
+ upstream?.destroy();
73
+ });
74
+ client.on("data", function onData(chunk) {
75
+ buffered = Buffer.concat([buffered, chunk]);
76
+ if (buffered.length > MAX_CONNECT_HEADER_BYTES) {
77
+ clearTimeout(headerTimer);
78
+ client.off("data", onData);
79
+ logProxyEvent(options.logPath, {
80
+ message: "desktop connect proxy",
81
+ event: "http-rejected",
82
+ error: "connect header exceeded maximum size",
83
+ });
84
+ client.end("HTTP/1.1 431 Request Header Fields Too Large\r\nConnection: close\r\n\r\n");
85
+ return;
86
+ }
87
+ const endOfHeaders = buffered.indexOf("\r\n\r\n");
88
+ if (endOfHeaders === -1) {
89
+ return;
90
+ }
91
+ clearTimeout(headerTimer);
92
+ client.off("data", onData);
93
+ const headerBytes = buffered.subarray(0, endOfHeaders + 4);
94
+ const firstLine = headerBytes.toString("utf8").split("\r\n")[0] ?? "";
95
+ const match = CONNECT_LINE_PATTERN.exec(firstLine);
96
+ if (match === null) {
97
+ logProxyEvent(options.logPath, {
98
+ message: "desktop connect proxy",
99
+ event: "http-rejected",
100
+ });
101
+ client.end("HTTP/1.1 405 Method Not Allowed\r\nConnection: close\r\n\r\n");
102
+ return;
103
+ }
104
+ const destination = parseConnectDestination(match[1]);
105
+ const cursorBackend = cursorHostnames.has(destination.host);
106
+ if (!cursorBackend && !passthrough) {
107
+ logProxyEvent(options.logPath, {
108
+ message: "desktop connect proxy",
109
+ event: "passthrough-blocked",
110
+ host: destination.host,
111
+ port: destination.port,
112
+ });
113
+ client.end("HTTP/1.1 502 Bad Gateway\r\nConnection: close\r\n\r\n");
114
+ return;
115
+ }
116
+ const targetHost = cursorBackend ? options.bridgeHost : destination.host;
117
+ const targetPort = cursorBackend ? options.bridgePort : destination.port;
118
+ logProxyEvent(options.logPath, {
119
+ message: "desktop connect proxy",
120
+ event: "connect",
121
+ host: destination.host,
122
+ port: destination.port,
123
+ targetHost,
124
+ targetPort,
125
+ cursorBackend,
126
+ });
127
+ upstream = net.connect(targetPort, targetHost, () => {
128
+ if (upstream !== undefined) {
129
+ activeSockets.add(upstream);
130
+ upstream.once("close", () => activeSockets.delete(upstream));
131
+ }
132
+ tunnelEstablished = true;
133
+ client.write("HTTP/1.1 200 Connection Established\r\n\r\n");
134
+ const remaining = buffered.subarray(endOfHeaders + 4);
135
+ if (remaining.length > 0) {
136
+ upstream?.write(remaining);
137
+ }
138
+ if (upstream !== undefined) {
139
+ client.pipe(upstream);
140
+ upstream.pipe(client);
141
+ }
142
+ });
143
+ upstream.on("error", (error) => {
144
+ logProxyEvent(options.logPath, {
145
+ message: "desktop connect proxy",
146
+ event: "upstream-error",
147
+ host: destination.host,
148
+ port: destination.port,
149
+ targetHost,
150
+ targetPort,
151
+ cursorBackend,
152
+ error: error.message,
153
+ });
154
+ client.destroy(error);
155
+ });
156
+ });
157
+ }
158
+ function parseConnectDestination(value) {
159
+ if (value.startsWith("[")) {
160
+ const closeBracket = value.indexOf("]");
161
+ if (closeBracket !== -1) {
162
+ const host = value.slice(1, closeBracket);
163
+ const port = Number(value.slice(closeBracket + 2)) || 443;
164
+ return { host, port };
165
+ }
166
+ }
167
+ const [host, portText] = value.split(":");
168
+ return { host: host ?? value, port: Number(portText ?? "443") || 443 };
169
+ }
170
+ function logProxyEvent(logPath, event) {
171
+ if (logPath === undefined) {
172
+ return;
173
+ }
174
+ fs.appendFileSync(logPath, `${JSON.stringify({ ts: new Date().toISOString(), ...event })}\n`);
175
+ }
@@ -0,0 +1,2 @@
1
+ export type { CursorExtension, ExtensionContext, MiddlewareRegistry, RequestMiddleware, ResponseMiddleware, RouteInterceptor, RouteRegistry, } from "./types.js";
2
+ export { createExtensionManager } from "./registry.js";
@@ -0,0 +1 @@
1
+ export { createExtensionManager } from "./registry.js";
@@ -0,0 +1,8 @@
1
+ import type { Logger } from "../logger.js";
2
+ import type { ModelRegistry } from "../models/registry.js";
3
+ import type { CursorExtension, ExtensionContext } from "./types.js";
4
+ export interface ExtensionManager {
5
+ context: ExtensionContext;
6
+ load(extension: CursorExtension): Promise<void>;
7
+ }
8
+ export declare function createExtensionManager(models: ModelRegistry, logger: Logger): ExtensionManager;
@@ -0,0 +1,52 @@
1
+ class DefaultRouteRegistry {
2
+ routes = new Map();
3
+ register(route) {
4
+ if (this.routes.has(route.path)) {
5
+ throw new Error(`Route already registered: ${route.path}`);
6
+ }
7
+ this.routes.set(route.path, route);
8
+ }
9
+ list() {
10
+ return Array.from(this.routes.values());
11
+ }
12
+ }
13
+ class DefaultMiddlewareRegistry {
14
+ middleware = [];
15
+ register(middleware) {
16
+ this.middleware.push(middleware);
17
+ }
18
+ list() {
19
+ return [...this.middleware];
20
+ }
21
+ }
22
+ export function createExtensionManager(models, logger) {
23
+ const context = {
24
+ models,
25
+ routes: new DefaultRouteRegistry(),
26
+ middleware: new DefaultMiddlewareRegistry(),
27
+ logger,
28
+ };
29
+ return {
30
+ context,
31
+ async load(extension) {
32
+ if (extension.manifest !== undefined) {
33
+ validateManifest(extension.name, extension.manifest);
34
+ }
35
+ logger.info("loading trusted local extension", {
36
+ extension: extension.name,
37
+ apiVersion: extension.manifest?.apiVersion ?? "implicit-local",
38
+ trusted: extension.manifest?.trusted ?? "local",
39
+ permissions: extension.manifest?.permissions ?? ["legacy-full-local"],
40
+ });
41
+ await extension.setup(context);
42
+ },
43
+ };
44
+ }
45
+ function validateManifest(name, manifest) {
46
+ if (manifest.apiVersion !== "cursor-rpc/v1") {
47
+ throw new Error(`Extension ${name} declares unsupported apiVersion`);
48
+ }
49
+ if (manifest.trusted !== "local") {
50
+ throw new Error(`Extension ${name} must declare trusted=local`);
51
+ }
52
+ }
@@ -0,0 +1,42 @@
1
+ import type { IncomingMessage, ServerResponse } from "node:http";
2
+ import type { Logger } from "../logger.js";
3
+ import type { ModelRegistry } from "../models/registry.js";
4
+ export interface CursorExtension {
5
+ name: string;
6
+ manifest?: CursorExtensionManifest;
7
+ setup(context: ExtensionContext): void | Promise<void>;
8
+ }
9
+ export interface CursorExtensionManifest {
10
+ apiVersion: "cursor-rpc/v1";
11
+ trusted: "local";
12
+ permissions: CursorExtensionPermission[];
13
+ }
14
+ export type CursorExtensionPermission = "models:register" | "routes:register" | "middleware:register";
15
+ export interface ExtensionContext {
16
+ models: ModelRegistry;
17
+ routes: RouteRegistry;
18
+ middleware: MiddlewareRegistry;
19
+ logger: Logger;
20
+ }
21
+ export interface RouteRegistry {
22
+ register(route: RouteInterceptor): void;
23
+ list(): RouteInterceptor[];
24
+ }
25
+ export interface RouteInterceptor {
26
+ path: string;
27
+ bodyAccess: "metadata-only" | "consume";
28
+ handle(request: IncomingMessage, response: ServerResponse): Promise<boolean>;
29
+ }
30
+ export interface MiddlewareRegistry {
31
+ register(middleware: RequestMiddleware | ResponseMiddleware): void;
32
+ list(): Array<RequestMiddleware | ResponseMiddleware>;
33
+ }
34
+ export interface RequestMiddleware {
35
+ kind: "request";
36
+ bodyAccess: "metadata-only" | "consume";
37
+ run(request: IncomingMessage): Promise<void> | void;
38
+ }
39
+ export interface ResponseMiddleware {
40
+ kind: "response";
41
+ run(response: ServerResponse): Promise<void> | void;
42
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,103 @@
1
+ export declare const MODEL_FUSION_SCHEMA_BUNDLE_HASH = "sha256:955da2d6891c88d4c40746a8206439e2dae2efc1e7ffefca015e84d4ce265671";
2
+ export type JsonValue = null | boolean | number | string | JsonValue[] | {
3
+ [key: string]: JsonValue;
4
+ };
5
+ export type ModelFusionStatus = "pending" | "running" | "succeeded" | "failed" | "canceled" | "requires_action" | "skipped" | "unsupported";
6
+ export type ModelFusionSideEffects = "none" | "read_only" | "writes_workspace" | "network" | "tool_execution" | "unknown";
7
+ export type ModelFusionCapabilityStatus = "supported" | "unsupported" | "degraded" | "unknown";
8
+ export type ModelFusionHarnessKind = "generic" | "cursor" | "claude_code" | "codex" | "openai_responses";
9
+ export type ModelFusionArtifactKind = "transcript" | "patch" | "screenshot" | "log" | "metrics" | "worktree" | "other";
10
+ export type ModelFusionRedactionStatus = "synthetic" | "redacted" | "raw";
11
+ export type ContractMetadata<S extends string> = {
12
+ schema: S;
13
+ schema_version: "v1";
14
+ schema_bundle_hash: string;
15
+ producer: string;
16
+ producer_version: string;
17
+ producer_git_sha: string;
18
+ created_at: string;
19
+ };
20
+ export type ArtifactRef = {
21
+ artifact_id: string;
22
+ kind: ModelFusionArtifactKind;
23
+ uri?: string;
24
+ hash: string;
25
+ redaction_status?: ModelFusionRedactionStatus;
26
+ };
27
+ export type ModelFusionDiagnostic = {
28
+ kind: "capability_missing";
29
+ message: string;
30
+ retryable: boolean;
31
+ capability?: string;
32
+ status?: ModelFusionCapabilityStatus;
33
+ requested_status?: ModelFusionCapabilityStatus;
34
+ };
35
+ export type HarnessRunRequestV1 = ContractMetadata<"harness-run-request.v1"> & {
36
+ request_id: string;
37
+ harness_kind: ModelFusionHarnessKind;
38
+ source_repo: string;
39
+ base_git_sha: string;
40
+ prompt: string;
41
+ prompt_hash: string;
42
+ allowed_tools?: string[];
43
+ side_effects: ModelFusionSideEffects;
44
+ requested_capabilities: Record<string, ModelFusionCapabilityStatus>;
45
+ metadata?: Record<string, JsonValue>;
46
+ };
47
+ export type HarnessRunResultV1 = ContractMetadata<"harness-run-result.v1"> & {
48
+ result_id: string;
49
+ request_id: string;
50
+ harness_kind: ModelFusionHarnessKind;
51
+ status: ModelFusionStatus;
52
+ candidate_ids: string[];
53
+ output_summary?: string;
54
+ artifacts?: ArtifactRef[];
55
+ capabilities: Record<string, ModelFusionCapabilityStatus>;
56
+ requested_model?: string;
57
+ observed_model?: string;
58
+ model_id?: string;
59
+ endpoint_id?: string;
60
+ diagnostics?: ModelFusionDiagnostic[];
61
+ started_at: string;
62
+ finished_at?: string;
63
+ errors?: Array<{
64
+ kind: string;
65
+ message: string;
66
+ retryable: boolean;
67
+ }>;
68
+ metadata?: Record<string, JsonValue>;
69
+ };
70
+ export type CursorRunRequestV1 = ContractMetadata<"cursor-run-request.v1"> & {
71
+ cursor_run_id: string;
72
+ harness_request_id: string;
73
+ workspace_path: string;
74
+ prompt: string;
75
+ prompt_hash: string;
76
+ requested_model?: string;
77
+ allowed_tools?: string[];
78
+ side_effects: ModelFusionSideEffects;
79
+ requested_capabilities: Record<string, ModelFusionCapabilityStatus>;
80
+ };
81
+ export type CursorRunResultV1 = ContractMetadata<"cursor-run-result.v1"> & {
82
+ cursor_run_id: string;
83
+ harness_request_id: string;
84
+ mapped_harness_result_id?: string;
85
+ status: ModelFusionStatus;
86
+ output_summary: string;
87
+ transcript_artifact?: ArtifactRef;
88
+ artifacts?: ArtifactRef[];
89
+ capabilities: Record<string, ModelFusionCapabilityStatus>;
90
+ requested_model?: string;
91
+ observed_model?: string;
92
+ model_id?: string;
93
+ endpoint_id?: string;
94
+ diagnostics?: ModelFusionDiagnostic[];
95
+ raw_hash: string;
96
+ redacted_hash: string;
97
+ };
98
+ export declare function sha256Prefixed(value: string | Uint8Array): string;
99
+ export declare function cursorRunResultToHarnessRunResult(cursor: CursorRunResultV1): HarnessRunResultV1;
100
+ export declare function assertHarnessRunRequestV1(value: unknown): asserts value is HarnessRunRequestV1;
101
+ export declare function assertHarnessRunResultV1(value: unknown): asserts value is HarnessRunResultV1;
102
+ export declare function assertCursorRunRequestV1(value: unknown): asserts value is CursorRunRequestV1;
103
+ export declare function assertCursorRunResultV1(value: unknown): asserts value is CursorRunResultV1;