@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.
- package/DISCLAIMER.md +12 -0
- package/README.md +157 -0
- package/dist/src/agentTools/diff.d.ts +11 -0
- package/dist/src/agentTools/diff.js +88 -0
- package/dist/src/agentTools/policy.d.ts +3 -0
- package/dist/src/agentTools/policy.js +12 -0
- package/dist/src/agentTools/registry.d.ts +114 -0
- package/dist/src/agentTools/registry.js +663 -0
- package/dist/src/agentTools/results.d.ts +14 -0
- package/dist/src/agentTools/results.js +117 -0
- package/dist/src/agentTools/schemas.d.ts +3 -0
- package/dist/src/agentTools/schemas.js +89 -0
- package/dist/src/agentTools/surface.d.ts +11 -0
- package/dist/src/agentTools/surface.js +251 -0
- package/dist/src/certs.d.ts +8 -0
- package/dist/src/certs.js +34 -0
- package/dist/src/ck.d.ts +2 -0
- package/dist/src/ck.js +6 -0
- package/dist/src/ckLauncher.d.ts +150 -0
- package/dist/src/ckLauncher.js +1496 -0
- package/dist/src/cli.d.ts +2 -0
- package/dist/src/cli.js +265 -0
- package/dist/src/config.d.ts +52 -0
- package/dist/src/config.js +210 -0
- package/dist/src/connectEnvelope.d.ts +16 -0
- package/dist/src/connectEnvelope.js +70 -0
- package/dist/src/desktop.d.ts +19 -0
- package/dist/src/desktop.js +167 -0
- package/dist/src/desktopConnectProxy.d.ts +26 -0
- package/dist/src/desktopConnectProxy.js +175 -0
- package/dist/src/extensions/index.d.ts +2 -0
- package/dist/src/extensions/index.js +1 -0
- package/dist/src/extensions/registry.d.ts +8 -0
- package/dist/src/extensions/registry.js +52 -0
- package/dist/src/extensions/types.d.ts +42 -0
- package/dist/src/extensions/types.js +1 -0
- package/dist/src/fixtures/modelFusion.d.ts +103 -0
- package/dist/src/fixtures/modelFusion.js +404 -0
- package/dist/src/fixtures/replay.d.ts +9 -0
- package/dist/src/fixtures/replay.js +41 -0
- package/dist/src/fixtures/sanitizer.d.ts +9 -0
- package/dist/src/fixtures/sanitizer.js +43 -0
- package/dist/src/fixtures/schema.d.ts +38 -0
- package/dist/src/fixtures/schema.js +33 -0
- package/dist/src/gen/agent/v1/agent_pb.d.ts +21577 -0
- package/dist/src/gen/agent/v1/agent_pb.js +5325 -0
- package/dist/src/gen/aiserver/v1/aiserver_pb.d.ts +135242 -0
- package/dist/src/gen/aiserver/v1/aiserver_pb.js +34430 -0
- package/dist/src/gen/anyrun/v1/anyrun_pb.d.ts +1163 -0
- package/dist/src/gen/anyrun/v1/anyrun_pb.js +374 -0
- package/dist/src/gen/google/protobuf/google_pb.d.ts +142 -0
- package/dist/src/gen/google/protobuf/google_pb.js +54 -0
- package/dist/src/gen/internapi/v1/internapi_pb.d.ts +121 -0
- package/dist/src/gen/internapi/v1/internapi_pb.js +79 -0
- package/dist/src/logger.d.ts +8 -0
- package/dist/src/logger.js +37 -0
- package/dist/src/modelFusion/cursorHarness.d.ts +146 -0
- package/dist/src/modelFusion/cursorHarness.js +647 -0
- package/dist/src/modelFusion/index.d.ts +4 -0
- package/dist/src/modelFusion/index.js +2 -0
- package/dist/src/models/registry.d.ts +22 -0
- package/dist/src/models/registry.js +30 -0
- package/dist/src/proto.d.ts +13 -0
- package/dist/src/proto.js +61 -0
- package/dist/src/providers/openai.d.ts +64 -0
- package/dist/src/providers/openai.js +355 -0
- package/dist/src/redaction.d.ts +4 -0
- package/dist/src/redaction.js +65 -0
- package/dist/src/routeInventory.d.ts +16 -0
- package/dist/src/routeInventory.js +39 -0
- package/dist/src/routes.d.ts +37 -0
- package/dist/src/routes.js +227 -0
- package/dist/src/server.d.ts +50 -0
- package/dist/src/server.js +1353 -0
- package/dist/src/services/agent.d.ts +1 -0
- package/dist/src/services/agent.js +7 -0
- package/dist/src/services/agentRun.d.ts +60 -0
- package/dist/src/services/agentRun.js +391 -0
- package/dist/src/services/chat.d.ts +11 -0
- package/dist/src/services/chat.js +47 -0
- package/dist/src/services/models.d.ts +10 -0
- package/dist/src/services/models.js +216 -0
- package/dist/src/services/serverConfig.d.ts +2 -0
- package/dist/src/services/serverConfig.js +19 -0
- package/dist/src/testing/artifacts.d.ts +14 -0
- package/dist/src/testing/artifacts.js +92 -0
- package/dist/src/testing/cli.d.ts +4 -0
- package/dist/src/testing/cli.js +192 -0
- package/dist/src/testing/localBackend.d.ts +24 -0
- package/dist/src/testing/localBackend.js +310 -0
- package/dist/src/testing/processRunner.d.ts +7 -0
- package/dist/src/testing/processRunner.js +74 -0
- package/dist/src/testing/runner.d.ts +9 -0
- package/dist/src/testing/runner.js +85 -0
- package/dist/src/testing/scenarios.d.ts +3 -0
- package/dist/src/testing/scenarios.js +2535 -0
- package/dist/src/testing/types.d.ts +66 -0
- package/dist/src/testing/types.js +1 -0
- package/dist/src/tools/baselineInventory.d.ts +12 -0
- package/dist/src/tools/baselineInventory.js +680 -0
- package/dist/src/tools/checkModelFusionProtocol.d.ts +1 -0
- package/dist/src/tools/checkModelFusionProtocol.js +274 -0
- package/dist/src/tools/checkReleasePublishConfig.d.ts +1 -0
- package/dist/src/tools/checkReleasePublishConfig.js +99 -0
- package/dist/src/tools/generateProtoInventory.d.ts +1 -0
- package/dist/src/tools/generateProtoInventory.js +89 -0
- package/dist/src/tools/normalizeGeneratedCode.d.ts +1 -0
- package/dist/src/tools/normalizeGeneratedCode.js +18 -0
- package/dist/src/tools/releaseCheck.d.ts +26 -0
- package/dist/src/tools/releaseCheck.js +367 -0
- package/dist/src/trace.d.ts +39 -0
- package/dist/src/trace.js +106 -0
- package/dist/src/translation.d.ts +6 -0
- package/dist/src/translation.js +22 -0
- package/dist/src/upstream.d.ts +20 -0
- package/dist/src/upstream.js +270 -0
- package/docs/configuration.md +55 -0
- package/docs/cursor-app.md +263 -0
- package/docs/implementation-inventory.json +609 -0
- package/docs/learnings.md +363 -0
- package/docs/model-fusion-protocol-origin.json +126 -0
- package/docs/model-fusion-protocol.md +110 -0
- package/docs/plugin-authoring.md +24 -0
- package/docs/proto-inventory.md +1477 -0
- package/docs/protocol-surface-audit.md +92 -0
- package/docs/protocol.md +52 -0
- package/docs/refreshing-protos.md +78 -0
- package/docs/release-gates.md +110 -0
- package/docs/release-summary.json +86 -0
- package/docs/route-contract-manifest.json +288 -0
- package/docs/route-policy.json +133 -0
- package/docs/service-manifest.json +9490 -0
- package/docs/test-manifest.json +155 -0
- package/docs/testing-harness.md +204 -0
- package/docs/troubleshooting.md +36 -0
- package/docs/type-manifest-summary.json +28927 -0
- package/package.json +93 -0
- package/proto/agent/v1/agent.proto +5371 -0
- package/proto/aiserver/v1/aiserver.proto +32944 -0
- package/proto/anyrun/v1/anyrun.proto +294 -0
- package/proto/google/protobuf/google.proto +37 -0
- package/proto/internapi/v1/internapi.proto +32 -0
package/dist/src/cli.js
ADDED
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { loadConfig } from "./config.js";
|
|
5
|
+
import { loadTlsMaterial } from "./certs.js";
|
|
6
|
+
import { desktopCertificateStatus, desktopDnsStatus, desktopEnv, desktopTrustCommand, localModelBackendStatus, upstreamReachabilityStatus, writeDesktopCertificate, } from "./desktop.js";
|
|
7
|
+
import { createLogger } from "./logger.js";
|
|
8
|
+
import { assertCursorRunRequestV1, assertCursorRunResultV1, assertHarnessRunRequestV1, assertHarnessRunResultV1, } from "./fixtures/modelFusion.js";
|
|
9
|
+
import { listProtoFiles, loadCursorProto, resolveProtoDirectory, } from "./proto.js";
|
|
10
|
+
import { createBridgeRuntime, startServer } from "./server.js";
|
|
11
|
+
const HELP = `cursorkit
|
|
12
|
+
|
|
13
|
+
Usage:
|
|
14
|
+
cursorkit serve Start the local bridge
|
|
15
|
+
cursorkit doctor Check local configuration and proto availability
|
|
16
|
+
cursorkit desktop-cert Generate local TLS material for Cursor desktop proxying
|
|
17
|
+
cursorkit desktop-proxy Start the bridge with Cursor desktop proxy defaults
|
|
18
|
+
cursorkit desktop-doctor Check Cursor desktop proxy prerequisites
|
|
19
|
+
cursorkit capture Print capture-mode guidance
|
|
20
|
+
cursorkit fixtures Validate committed fixture metadata
|
|
21
|
+
cursorkit --help Show this help
|
|
22
|
+
|
|
23
|
+
Environment:
|
|
24
|
+
BRIDGE_HOST=127.0.0.1
|
|
25
|
+
BRIDGE_PORT=9443
|
|
26
|
+
CURSOR_UPSTREAM_BASE_URL=https://example.cursor-backend.local
|
|
27
|
+
MODEL_BASE_URL=http://localhost:8080/v1
|
|
28
|
+
MODEL_NAME=local-model
|
|
29
|
+
`;
|
|
30
|
+
async function main(argv) {
|
|
31
|
+
const command = parseCommand(argv);
|
|
32
|
+
if (command === "help") {
|
|
33
|
+
console.log(HELP);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
const config = loadConfig(command === "desktop-proxy" || command === "desktop-doctor"
|
|
37
|
+
? desktopEnv(process.env)
|
|
38
|
+
: process.env);
|
|
39
|
+
const logger = createLogger(config.logLevel);
|
|
40
|
+
switch (command) {
|
|
41
|
+
case "serve": {
|
|
42
|
+
const runtime = await createBridgeRuntime(config, logger);
|
|
43
|
+
installGracefulShutdown(await startServer(runtime), logger);
|
|
44
|
+
break;
|
|
45
|
+
}
|
|
46
|
+
case "doctor": {
|
|
47
|
+
await doctor(config);
|
|
48
|
+
break;
|
|
49
|
+
}
|
|
50
|
+
case "capture": {
|
|
51
|
+
capture(config);
|
|
52
|
+
break;
|
|
53
|
+
}
|
|
54
|
+
case "fixtures": {
|
|
55
|
+
validateFixtures(config.captureDir);
|
|
56
|
+
break;
|
|
57
|
+
}
|
|
58
|
+
case "desktop-cert": {
|
|
59
|
+
await desktopCert();
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
62
|
+
case "desktop-proxy": {
|
|
63
|
+
const runtime = await createBridgeRuntime(config, logger);
|
|
64
|
+
installGracefulShutdown(await startServer(runtime), logger);
|
|
65
|
+
break;
|
|
66
|
+
}
|
|
67
|
+
case "desktop-doctor": {
|
|
68
|
+
await doctor(config);
|
|
69
|
+
await desktopDoctor(config);
|
|
70
|
+
break;
|
|
71
|
+
}
|
|
72
|
+
default: {
|
|
73
|
+
const exhaustive = command;
|
|
74
|
+
throw new Error(`Unhandled command: ${exhaustive}`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
function parseCommand(argv) {
|
|
79
|
+
const first = argv[2];
|
|
80
|
+
if (first === undefined ||
|
|
81
|
+
first === "--help" ||
|
|
82
|
+
first === "-h" ||
|
|
83
|
+
first === "help") {
|
|
84
|
+
return "help";
|
|
85
|
+
}
|
|
86
|
+
if (first === "serve" ||
|
|
87
|
+
first === "doctor" ||
|
|
88
|
+
first === "capture" ||
|
|
89
|
+
first === "fixtures" ||
|
|
90
|
+
first === "desktop-cert" ||
|
|
91
|
+
first === "desktop-proxy" ||
|
|
92
|
+
first === "desktop-doctor") {
|
|
93
|
+
return first;
|
|
94
|
+
}
|
|
95
|
+
throw new Error(`Unknown command: ${first}\n\n${HELP}`);
|
|
96
|
+
}
|
|
97
|
+
async function doctor(config) {
|
|
98
|
+
const checks = [];
|
|
99
|
+
const protoDir = resolveProtoDirectory();
|
|
100
|
+
checks.push(["proto directory", protoDir]);
|
|
101
|
+
checks.push(["proto files", String(listProtoFiles(protoDir).length)]);
|
|
102
|
+
const proto = await loadCursorProto();
|
|
103
|
+
checks.push([
|
|
104
|
+
"available models type",
|
|
105
|
+
proto.AvailableModelsResponse.fullName,
|
|
106
|
+
]);
|
|
107
|
+
checks.push(["chat type", proto.StreamUnifiedChatRequestWithTools.fullName]);
|
|
108
|
+
checks.push(["bind", `${config.host}:${config.port}`]);
|
|
109
|
+
checks.push(["tls", config.useTls ? "enabled" : "disabled"]);
|
|
110
|
+
checks.push(["upstream", config.upstreamBaseUrl ?? "not configured"]);
|
|
111
|
+
checks.push([
|
|
112
|
+
"local models",
|
|
113
|
+
config.models.map((model) => model.id).join(", "),
|
|
114
|
+
]);
|
|
115
|
+
checks.push([
|
|
116
|
+
"capture",
|
|
117
|
+
config.captureEnabled ? `enabled -> ${config.captureDir}` : "disabled",
|
|
118
|
+
]);
|
|
119
|
+
checks.push(["fail-open", String(config.failOpen)]);
|
|
120
|
+
checks.push([
|
|
121
|
+
"auth",
|
|
122
|
+
config.authToken === undefined ? "disabled" : "enabled",
|
|
123
|
+
]);
|
|
124
|
+
checks.push([
|
|
125
|
+
"non-localhost unsafe mode",
|
|
126
|
+
String(config.unsafeAllowNonLocalhost),
|
|
127
|
+
]);
|
|
128
|
+
if (config.useTls) {
|
|
129
|
+
const tls = await loadTlsMaterial(config);
|
|
130
|
+
checks.push([
|
|
131
|
+
"tls material",
|
|
132
|
+
tls.generated
|
|
133
|
+
? "generated self-signed dev certificate"
|
|
134
|
+
: "custom certificate",
|
|
135
|
+
]);
|
|
136
|
+
}
|
|
137
|
+
for (const [name, value] of checks) {
|
|
138
|
+
console.log(`${name}: ${value}`);
|
|
139
|
+
}
|
|
140
|
+
if (config.upstreamBaseUrl === undefined) {
|
|
141
|
+
console.warn("warning: pass-through traffic needs CURSOR_UPSTREAM_BASE_URL");
|
|
142
|
+
}
|
|
143
|
+
if (config.captureEnabled) {
|
|
144
|
+
console.warn("warning: capture mode treats traffic as sensitive; sanitize before committing fixtures");
|
|
145
|
+
}
|
|
146
|
+
if (config.modelPayloadLogging === "full") {
|
|
147
|
+
console.warn("warning: BRIDGE_LOG_MODEL_PAYLOADS=full may log prompt and tool payloads; use only for local debugging");
|
|
148
|
+
}
|
|
149
|
+
if (config.unsafeAllowNonLocalhost) {
|
|
150
|
+
console.warn("warning: non-localhost unsafe mode exposes the bridge without built-in auth");
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
function installGracefulShutdown(server, logger) {
|
|
154
|
+
let shuttingDown = false;
|
|
155
|
+
const shutdown = (signal) => {
|
|
156
|
+
if (shuttingDown) {
|
|
157
|
+
logger.warn("forcing bridge shutdown", { signal });
|
|
158
|
+
process.exit(1);
|
|
159
|
+
}
|
|
160
|
+
shuttingDown = true;
|
|
161
|
+
logger.info("shutting down bridge", { signal });
|
|
162
|
+
const forceTimer = setTimeout(() => {
|
|
163
|
+
logger.error("bridge shutdown timed out", { signal });
|
|
164
|
+
process.exit(1);
|
|
165
|
+
}, 5_000);
|
|
166
|
+
server.close((error) => {
|
|
167
|
+
clearTimeout(forceTimer);
|
|
168
|
+
if (error !== undefined) {
|
|
169
|
+
logger.error("bridge shutdown failed", { error: error.message });
|
|
170
|
+
process.exitCode = 1;
|
|
171
|
+
}
|
|
172
|
+
process.exit();
|
|
173
|
+
});
|
|
174
|
+
};
|
|
175
|
+
process.once("SIGINT", shutdown);
|
|
176
|
+
process.once("SIGTERM", shutdown);
|
|
177
|
+
}
|
|
178
|
+
async function desktopCert() {
|
|
179
|
+
const cert = await writeDesktopCertificate();
|
|
180
|
+
console.log(`cert: ${cert.certPath}`);
|
|
181
|
+
console.log(`key: ${cert.keyPath}`);
|
|
182
|
+
console.log("");
|
|
183
|
+
console.log("Manual macOS trust command:");
|
|
184
|
+
console.log(desktopTrustCommand(cert.certPath).join(" "));
|
|
185
|
+
console.log("");
|
|
186
|
+
console.log("Then start with:");
|
|
187
|
+
console.log(`BRIDGE_CERT_PATH=${cert.certPath} BRIDGE_KEY_PATH=${cert.keyPath} cursorkit desktop-proxy`);
|
|
188
|
+
}
|
|
189
|
+
async function desktopDoctor(config) {
|
|
190
|
+
const checks = [];
|
|
191
|
+
checks.push(["desktop mode", String(config.desktopMode)]);
|
|
192
|
+
checks.push(["public origin", config.publicOrigin ?? "not configured"]);
|
|
193
|
+
checks.push(["tls hostnames", config.tlsHostnames.join(", ")]);
|
|
194
|
+
checks.push(["route inventory", String(config.routeInventoryEnabled)]);
|
|
195
|
+
checks.push(["desktop upstream", config.upstreamBaseUrl ?? "not configured"]);
|
|
196
|
+
checks.push([
|
|
197
|
+
"desktop upstream connect",
|
|
198
|
+
config.upstreamConnectHost === undefined
|
|
199
|
+
? "system DNS"
|
|
200
|
+
: `${config.upstreamConnectHost}${config.upstreamConnectPort === undefined ? "" : `:${config.upstreamConnectPort}`}`,
|
|
201
|
+
]);
|
|
202
|
+
checks.push(["desktop cert", desktopCertificateStatus(config)]);
|
|
203
|
+
checks.push(["desktop dns", await desktopDnsStatus(config)]);
|
|
204
|
+
checks.push([
|
|
205
|
+
"upstream reachability",
|
|
206
|
+
await upstreamReachabilityStatus(config),
|
|
207
|
+
]);
|
|
208
|
+
checks.push(["local model backend", await localModelBackendStatus(config)]);
|
|
209
|
+
for (const [name, value] of checks) {
|
|
210
|
+
console.log(`${name}: ${value}`);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
function capture(config) {
|
|
214
|
+
console.log(`captureEnabled: ${config.captureEnabled}`);
|
|
215
|
+
console.log(`captureDir: ${config.captureDir}`);
|
|
216
|
+
console.log("Capture mode is explicit and sanitized fixture writing is required before committing data.");
|
|
217
|
+
}
|
|
218
|
+
function validateFixtures(captureDir) {
|
|
219
|
+
const modelFusionCount = validateModelFusionFixtures();
|
|
220
|
+
if (!fs.existsSync(captureDir)) {
|
|
221
|
+
console.log(`No fixture capture directory found at ${captureDir}`);
|
|
222
|
+
console.log(`Validated ${modelFusionCount} model-fusion fixture file(s)`);
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
const files = fs
|
|
226
|
+
.readdirSync(captureDir)
|
|
227
|
+
.filter((file) => file.endsWith(".json"));
|
|
228
|
+
for (const file of files) {
|
|
229
|
+
const fullPath = path.join(captureDir, file);
|
|
230
|
+
const parsed = JSON.parse(fs.readFileSync(fullPath, "utf8"));
|
|
231
|
+
if (parsed.redaction?.status !== "sanitized") {
|
|
232
|
+
throw new Error(`${fullPath} is missing redaction.status=sanitized`);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
console.log(`Validated ${files.length} capture fixture file(s) and ${modelFusionCount} model-fusion fixture file(s)`);
|
|
236
|
+
}
|
|
237
|
+
function validateModelFusionFixtures() {
|
|
238
|
+
const root = path.resolve("fixtures", "model-fusion-contract");
|
|
239
|
+
if (!fs.existsSync(root))
|
|
240
|
+
return 0;
|
|
241
|
+
let count = 0;
|
|
242
|
+
const validators = {
|
|
243
|
+
"harness-run-request.v1": assertHarnessRunRequestV1,
|
|
244
|
+
"harness-run-result.v1": assertHarnessRunResultV1,
|
|
245
|
+
"cursor-run-request.v1": assertCursorRunRequestV1,
|
|
246
|
+
"cursor-run-result.v1": assertCursorRunResultV1,
|
|
247
|
+
};
|
|
248
|
+
for (const [schema, validate] of Object.entries(validators)) {
|
|
249
|
+
const schemaDir = path.join(root, schema);
|
|
250
|
+
if (!fs.existsSync(schemaDir))
|
|
251
|
+
continue;
|
|
252
|
+
for (const file of fs
|
|
253
|
+
.readdirSync(schemaDir)
|
|
254
|
+
.filter((item) => item.endsWith(".json"))) {
|
|
255
|
+
const fixturePath = path.join(schemaDir, file);
|
|
256
|
+
validate(JSON.parse(fs.readFileSync(fixturePath, "utf8")));
|
|
257
|
+
count++;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
return count;
|
|
261
|
+
}
|
|
262
|
+
main(process.argv).catch((error) => {
|
|
263
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
264
|
+
process.exitCode = 1;
|
|
265
|
+
});
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
export interface BridgeConfig {
|
|
2
|
+
host: string;
|
|
3
|
+
port: number;
|
|
4
|
+
upstreamBaseUrl?: string;
|
|
5
|
+
upstreamConnectHost?: string;
|
|
6
|
+
upstreamConnectPort?: number;
|
|
7
|
+
desktopMode: boolean;
|
|
8
|
+
modelBaseUrl: string;
|
|
9
|
+
modelApiKey: string;
|
|
10
|
+
modelName: string;
|
|
11
|
+
models: LocalModelConfig[];
|
|
12
|
+
hardcodedResponse?: string;
|
|
13
|
+
certPath?: string;
|
|
14
|
+
keyPath?: string;
|
|
15
|
+
tlsHostnames: string[];
|
|
16
|
+
useTls: boolean;
|
|
17
|
+
publicOrigin?: string;
|
|
18
|
+
agentPublicOrigin?: string;
|
|
19
|
+
desktopAgentHttpPort?: number;
|
|
20
|
+
captureDir: string;
|
|
21
|
+
captureEnabled: boolean;
|
|
22
|
+
failOpen: boolean;
|
|
23
|
+
logLevel: LogLevel;
|
|
24
|
+
pluginPath?: string;
|
|
25
|
+
authToken?: string;
|
|
26
|
+
unsafeAllowNonLocalhost: boolean;
|
|
27
|
+
maxInterceptBodyBytes: number;
|
|
28
|
+
upstreamRequestTimeoutMs?: number;
|
|
29
|
+
agentRunSseWaitTimeoutMs?: number;
|
|
30
|
+
agentContextTimeoutMs?: number;
|
|
31
|
+
agentNativeContextEnabled: boolean;
|
|
32
|
+
toolResultTimeoutMs?: number;
|
|
33
|
+
extensionSetupTimeoutMs?: number;
|
|
34
|
+
routeInventoryEnabled: boolean;
|
|
35
|
+
modelPayloadLogging: ModelPayloadLogging;
|
|
36
|
+
agentToolPolicy: AgentToolPolicy;
|
|
37
|
+
agentToolMaxIterations: number;
|
|
38
|
+
}
|
|
39
|
+
export type LogLevel = "debug" | "info" | "warn" | "error";
|
|
40
|
+
export type ModelPayloadLogging = "summary" | "full";
|
|
41
|
+
export type AgentToolPolicy = "safe" | "all";
|
|
42
|
+
export interface LocalModelConfig {
|
|
43
|
+
id: string;
|
|
44
|
+
displayName: string;
|
|
45
|
+
providerModel: string;
|
|
46
|
+
baseUrl: string;
|
|
47
|
+
apiKey: string;
|
|
48
|
+
contextTokenLimit: number;
|
|
49
|
+
requestTimeoutMs?: number;
|
|
50
|
+
hardcodedResponse?: string;
|
|
51
|
+
}
|
|
52
|
+
export declare function loadConfig(env?: NodeJS.ProcessEnv): BridgeConfig;
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
export function loadConfig(env = process.env) {
|
|
2
|
+
const port = Number.parseInt(env.BRIDGE_PORT ?? "9443", 10);
|
|
3
|
+
if (!Number.isFinite(port) || port <= 0) {
|
|
4
|
+
throw new Error(`Invalid BRIDGE_PORT: ${env.BRIDGE_PORT}`);
|
|
5
|
+
}
|
|
6
|
+
const host = env.BRIDGE_HOST ?? "127.0.0.1";
|
|
7
|
+
const desktopMode = parseBoolean(env.BRIDGE_DESKTOP_MODE, false);
|
|
8
|
+
const unsafeAllowNonLocalhost = parseBoolean(env.BRIDGE_UNSAFE_ALLOW_NON_LOCALHOST, false);
|
|
9
|
+
const authToken = emptyToUndefined(env.BRIDGE_AUTH_TOKEN);
|
|
10
|
+
if (!unsafeAllowNonLocalhost &&
|
|
11
|
+
authToken === undefined &&
|
|
12
|
+
!isLocalhost(host)) {
|
|
13
|
+
throw new Error(`Refusing to bind bridge to non-localhost host ${host}. Set BRIDGE_AUTH_TOKEN or BRIDGE_UNSAFE_ALLOW_NON_LOCALHOST=true to override.`);
|
|
14
|
+
}
|
|
15
|
+
const modelBaseUrl = env.MODEL_BASE_URL ?? "http://localhost:8080/v1";
|
|
16
|
+
const modelApiKey = env.MODEL_API_KEY ?? "";
|
|
17
|
+
const modelName = env.MODEL_NAME ?? "local-model";
|
|
18
|
+
const providerModel = env.MODEL_PROVIDER_MODEL ?? modelName;
|
|
19
|
+
const hardcodedResponse = env.BRIDGE_HARDCODED_RESPONSE;
|
|
20
|
+
const models = parseModels(env, {
|
|
21
|
+
id: modelName,
|
|
22
|
+
displayName: modelName,
|
|
23
|
+
providerModel,
|
|
24
|
+
baseUrl: modelBaseUrl,
|
|
25
|
+
apiKey: modelApiKey,
|
|
26
|
+
contextTokenLimit: parseInteger(env.MODEL_CONTEXT_TOKEN_LIMIT, 128000),
|
|
27
|
+
requestTimeoutMs: parseOptionalPositiveInteger(env.MODEL_REQUEST_TIMEOUT_MS),
|
|
28
|
+
hardcodedResponse,
|
|
29
|
+
});
|
|
30
|
+
return {
|
|
31
|
+
host,
|
|
32
|
+
port,
|
|
33
|
+
upstreamBaseUrl: emptyToUndefined(env.CURSOR_UPSTREAM_BASE_URL) ??
|
|
34
|
+
(desktopMode ? "https://api2.cursor.sh" : undefined),
|
|
35
|
+
upstreamConnectHost: emptyToUndefined(env.CURSOR_UPSTREAM_CONNECT_HOST),
|
|
36
|
+
upstreamConnectPort: emptyToUndefined(env.CURSOR_UPSTREAM_CONNECT_PORT) === undefined
|
|
37
|
+
? undefined
|
|
38
|
+
: parseInteger(env.CURSOR_UPSTREAM_CONNECT_PORT, 443),
|
|
39
|
+
desktopMode,
|
|
40
|
+
modelBaseUrl,
|
|
41
|
+
modelApiKey,
|
|
42
|
+
modelName,
|
|
43
|
+
models,
|
|
44
|
+
hardcodedResponse,
|
|
45
|
+
certPath: emptyToUndefined(env.BRIDGE_CERT_PATH),
|
|
46
|
+
keyPath: emptyToUndefined(env.BRIDGE_KEY_PATH),
|
|
47
|
+
tlsHostnames: parseCsv(env.BRIDGE_TLS_HOSTNAMES, desktopMode
|
|
48
|
+
? [
|
|
49
|
+
"api2.cursor.sh",
|
|
50
|
+
"api3.cursor.sh",
|
|
51
|
+
"agent.api5.cursor.sh",
|
|
52
|
+
"agentn.api5.cursor.sh",
|
|
53
|
+
"agentn.global.api5.cursor.sh",
|
|
54
|
+
"localhost",
|
|
55
|
+
"127.0.0.1",
|
|
56
|
+
"::1",
|
|
57
|
+
]
|
|
58
|
+
: ["localhost", "127.0.0.1", "::1"]),
|
|
59
|
+
useTls: parseBoolean(env.BRIDGE_USE_TLS, false),
|
|
60
|
+
publicOrigin: emptyToUndefined(env.BRIDGE_PUBLIC_ORIGIN) ??
|
|
61
|
+
(desktopMode ? "https://api2.cursor.sh" : undefined),
|
|
62
|
+
agentPublicOrigin: emptyToUndefined(env.BRIDGE_AGENT_PUBLIC_ORIGIN),
|
|
63
|
+
desktopAgentHttpPort: emptyToUndefined(env.BRIDGE_DESKTOP_AGENT_HTTP_PORT) === undefined
|
|
64
|
+
? undefined
|
|
65
|
+
: parseInteger(env.BRIDGE_DESKTOP_AGENT_HTTP_PORT, 0),
|
|
66
|
+
captureDir: env.BRIDGE_CAPTURE_DIR ?? "fixtures/captures",
|
|
67
|
+
captureEnabled: parseBoolean(env.BRIDGE_CAPTURE_ENABLED, false),
|
|
68
|
+
failOpen: parseBoolean(env.BRIDGE_FAIL_OPEN, true),
|
|
69
|
+
logLevel: parseLogLevel(env.BRIDGE_LOG_LEVEL),
|
|
70
|
+
pluginPath: emptyToUndefined(env.BRIDGE_PLUGIN_PATH),
|
|
71
|
+
authToken,
|
|
72
|
+
unsafeAllowNonLocalhost,
|
|
73
|
+
maxInterceptBodyBytes: parseInteger(env.BRIDGE_MAX_INTERCEPT_BODY_BYTES, 50 * 1024 * 1024),
|
|
74
|
+
upstreamRequestTimeoutMs: parseOptionalPositiveInteger(env.BRIDGE_UPSTREAM_REQUEST_TIMEOUT_MS),
|
|
75
|
+
agentRunSseWaitTimeoutMs: parseOptionalPositiveInteger(env.BRIDGE_AGENT_RUN_SSE_WAIT_TIMEOUT_MS),
|
|
76
|
+
agentContextTimeoutMs: parseOptionalPositiveInteger(env.BRIDGE_AGENT_CONTEXT_TIMEOUT_MS),
|
|
77
|
+
agentNativeContextEnabled: parseBoolean(env.BRIDGE_AGENT_NATIVE_CONTEXT, true),
|
|
78
|
+
toolResultTimeoutMs: parseOptionalPositiveInteger(env.BRIDGE_AGENT_TOOL_RESULT_TIMEOUT_MS),
|
|
79
|
+
extensionSetupTimeoutMs: parseOptionalPositiveInteger(env.BRIDGE_EXTENSION_SETUP_TIMEOUT_MS),
|
|
80
|
+
routeInventoryEnabled: desktopMode || parseBoolean(env.BRIDGE_ROUTE_INVENTORY, false),
|
|
81
|
+
modelPayloadLogging: parseModelPayloadLogging(env.BRIDGE_LOG_MODEL_PAYLOADS),
|
|
82
|
+
agentToolPolicy: parseAgentToolPolicy(env.BRIDGE_AGENT_TOOL_POLICY),
|
|
83
|
+
agentToolMaxIterations: parseInteger(env.BRIDGE_AGENT_TOOL_MAX_ITERATIONS, 8),
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
function parseModelPayloadLogging(value) {
|
|
87
|
+
if (value === "full") {
|
|
88
|
+
return "full";
|
|
89
|
+
}
|
|
90
|
+
return "summary";
|
|
91
|
+
}
|
|
92
|
+
function parseAgentToolPolicy(value) {
|
|
93
|
+
if (value === undefined || value === "") {
|
|
94
|
+
return "safe";
|
|
95
|
+
}
|
|
96
|
+
if (value === "safe" || value === "all") {
|
|
97
|
+
return value;
|
|
98
|
+
}
|
|
99
|
+
throw new Error("BRIDGE_AGENT_TOOL_POLICY must be safe or all");
|
|
100
|
+
}
|
|
101
|
+
function parseModels(env, fallback) {
|
|
102
|
+
const raw = emptyToUndefined(env.BRIDGE_MODELS_JSON);
|
|
103
|
+
if (raw === undefined) {
|
|
104
|
+
return [fallback];
|
|
105
|
+
}
|
|
106
|
+
const parsed = JSON.parse(raw);
|
|
107
|
+
if (!Array.isArray(parsed)) {
|
|
108
|
+
throw new Error("BRIDGE_MODELS_JSON must be a JSON array");
|
|
109
|
+
}
|
|
110
|
+
return parsed.map((item, index) => {
|
|
111
|
+
if (!isRecord(item)) {
|
|
112
|
+
throw new Error(`BRIDGE_MODELS_JSON[${index}] must be an object`);
|
|
113
|
+
}
|
|
114
|
+
const id = requiredString(item.id, `BRIDGE_MODELS_JSON[${index}].id`);
|
|
115
|
+
const baseUrl = requiredString(item.baseUrl, `BRIDGE_MODELS_JSON[${index}].baseUrl`);
|
|
116
|
+
return {
|
|
117
|
+
id,
|
|
118
|
+
displayName: typeof item.displayName === "string" ? item.displayName : id,
|
|
119
|
+
providerModel: typeof item.providerModel === "string" ? item.providerModel : id,
|
|
120
|
+
baseUrl,
|
|
121
|
+
apiKey: typeof item.apiKey === "string" ? item.apiKey : "",
|
|
122
|
+
contextTokenLimit: typeof item.contextTokenLimit === "number" &&
|
|
123
|
+
Number.isFinite(item.contextTokenLimit)
|
|
124
|
+
? item.contextTokenLimit
|
|
125
|
+
: fallback.contextTokenLimit,
|
|
126
|
+
requestTimeoutMs: typeof item.requestTimeoutMs === "number" &&
|
|
127
|
+
Number.isFinite(item.requestTimeoutMs) &&
|
|
128
|
+
item.requestTimeoutMs > 0
|
|
129
|
+
? item.requestTimeoutMs
|
|
130
|
+
: fallback.requestTimeoutMs,
|
|
131
|
+
hardcodedResponse: typeof item.hardcodedResponse === "string"
|
|
132
|
+
? item.hardcodedResponse
|
|
133
|
+
: undefined,
|
|
134
|
+
};
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
function parseLogLevel(value) {
|
|
138
|
+
switch (value) {
|
|
139
|
+
case undefined:
|
|
140
|
+
case "":
|
|
141
|
+
return "info";
|
|
142
|
+
case "debug":
|
|
143
|
+
case "info":
|
|
144
|
+
case "warn":
|
|
145
|
+
case "error":
|
|
146
|
+
return value;
|
|
147
|
+
default:
|
|
148
|
+
throw new Error(`Invalid BRIDGE_LOG_LEVEL: ${value}`);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
function parseBoolean(value, fallback) {
|
|
152
|
+
if (value === undefined || value === "") {
|
|
153
|
+
return fallback;
|
|
154
|
+
}
|
|
155
|
+
if (value === "true" || value === "1") {
|
|
156
|
+
return true;
|
|
157
|
+
}
|
|
158
|
+
if (value === "false" || value === "0") {
|
|
159
|
+
return false;
|
|
160
|
+
}
|
|
161
|
+
throw new Error(`Invalid boolean value: ${value}`);
|
|
162
|
+
}
|
|
163
|
+
function parseInteger(value, fallback) {
|
|
164
|
+
if (value === undefined || value === "") {
|
|
165
|
+
return fallback;
|
|
166
|
+
}
|
|
167
|
+
const parsed = Number.parseInt(value, 10);
|
|
168
|
+
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
169
|
+
throw new Error(`Invalid integer value: ${value}`);
|
|
170
|
+
}
|
|
171
|
+
return parsed;
|
|
172
|
+
}
|
|
173
|
+
function parseOptionalPositiveInteger(value) {
|
|
174
|
+
if (value === undefined || value === "") {
|
|
175
|
+
return undefined;
|
|
176
|
+
}
|
|
177
|
+
const parsed = Number.parseInt(value, 10);
|
|
178
|
+
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
179
|
+
throw new Error(`Invalid integer value: ${value}`);
|
|
180
|
+
}
|
|
181
|
+
return parsed;
|
|
182
|
+
}
|
|
183
|
+
function parseCsv(value, fallback) {
|
|
184
|
+
if (value === undefined || value === "") {
|
|
185
|
+
return fallback;
|
|
186
|
+
}
|
|
187
|
+
const parsed = value
|
|
188
|
+
.split(",")
|
|
189
|
+
.map((item) => item.trim())
|
|
190
|
+
.filter((item) => item.length > 0);
|
|
191
|
+
if (parsed.length === 0) {
|
|
192
|
+
throw new Error("CSV value must contain at least one non-empty item");
|
|
193
|
+
}
|
|
194
|
+
return parsed;
|
|
195
|
+
}
|
|
196
|
+
function isLocalhost(host) {
|
|
197
|
+
return host === "127.0.0.1" || host === "::1" || host === "localhost";
|
|
198
|
+
}
|
|
199
|
+
function emptyToUndefined(value) {
|
|
200
|
+
return value === undefined || value.length === 0 ? undefined : value;
|
|
201
|
+
}
|
|
202
|
+
function requiredString(value, name) {
|
|
203
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
204
|
+
throw new Error(`${name} must be a non-empty string`);
|
|
205
|
+
}
|
|
206
|
+
return value;
|
|
207
|
+
}
|
|
208
|
+
function isRecord(value) {
|
|
209
|
+
return typeof value === "object" && value !== null;
|
|
210
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export interface ConnectEnvelope {
|
|
2
|
+
flags: number;
|
|
3
|
+
payload: Buffer;
|
|
4
|
+
}
|
|
5
|
+
export interface EnvelopeParseResult {
|
|
6
|
+
envelopes: ConnectEnvelope[];
|
|
7
|
+
remainder: Buffer;
|
|
8
|
+
}
|
|
9
|
+
export declare function encodeEnvelope(payload: Uint8Array, flags?: number): Buffer;
|
|
10
|
+
export declare function encodeEndStream(metadata?: Record<string, string>): Buffer;
|
|
11
|
+
export declare function isCompressedEnvelope(envelope: ConnectEnvelope): boolean;
|
|
12
|
+
export declare function isEndStreamEnvelope(envelope: ConnectEnvelope): boolean;
|
|
13
|
+
export declare function decodeEnvelopes(input: Buffer): ConnectEnvelope[];
|
|
14
|
+
export declare function parseEnvelopes(input: Buffer): EnvelopeParseResult;
|
|
15
|
+
export declare function firstMessagePayload(input: Buffer): Buffer;
|
|
16
|
+
export declare function firstMessageEnvelope(input: Buffer): ConnectEnvelope | undefined;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
const HEADER_LENGTH = 5;
|
|
2
|
+
const COMPRESSED_FLAG = 0x01;
|
|
3
|
+
const END_STREAM_FLAG = 0x02;
|
|
4
|
+
export function encodeEnvelope(payload, flags = 0) {
|
|
5
|
+
const body = Buffer.from(payload);
|
|
6
|
+
const frame = Buffer.allocUnsafe(HEADER_LENGTH + body.length);
|
|
7
|
+
frame.writeUInt8(flags, 0);
|
|
8
|
+
frame.writeUInt32BE(body.length, 1);
|
|
9
|
+
body.copy(frame, HEADER_LENGTH);
|
|
10
|
+
return frame;
|
|
11
|
+
}
|
|
12
|
+
export function encodeEndStream(metadata = {}) {
|
|
13
|
+
return encodeEnvelope(Buffer.from(JSON.stringify({ metadata }), "utf8"), END_STREAM_FLAG);
|
|
14
|
+
}
|
|
15
|
+
export function isCompressedEnvelope(envelope) {
|
|
16
|
+
return (envelope.flags & COMPRESSED_FLAG) === COMPRESSED_FLAG;
|
|
17
|
+
}
|
|
18
|
+
export function isEndStreamEnvelope(envelope) {
|
|
19
|
+
return (envelope.flags & END_STREAM_FLAG) === END_STREAM_FLAG;
|
|
20
|
+
}
|
|
21
|
+
export function decodeEnvelopes(input) {
|
|
22
|
+
const result = parseEnvelopes(input);
|
|
23
|
+
if (result.remainder.length > 0) {
|
|
24
|
+
if (result.remainder.length < HEADER_LENGTH) {
|
|
25
|
+
throw new Error("Truncated Connect envelope header");
|
|
26
|
+
}
|
|
27
|
+
throw new Error("Truncated Connect envelope payload");
|
|
28
|
+
}
|
|
29
|
+
return result.envelopes;
|
|
30
|
+
}
|
|
31
|
+
export function parseEnvelopes(input) {
|
|
32
|
+
const envelopes = [];
|
|
33
|
+
let offset = 0;
|
|
34
|
+
while (offset < input.length) {
|
|
35
|
+
if (input.length - offset < HEADER_LENGTH) {
|
|
36
|
+
break;
|
|
37
|
+
}
|
|
38
|
+
const flags = input.readUInt8(offset);
|
|
39
|
+
const length = input.readUInt32BE(offset + 1);
|
|
40
|
+
const payloadStart = offset + HEADER_LENGTH;
|
|
41
|
+
const payloadEnd = payloadStart + length;
|
|
42
|
+
if (payloadEnd > input.length) {
|
|
43
|
+
break;
|
|
44
|
+
}
|
|
45
|
+
envelopes.push({
|
|
46
|
+
flags,
|
|
47
|
+
payload: input.subarray(payloadStart, payloadEnd),
|
|
48
|
+
});
|
|
49
|
+
offset = payloadEnd;
|
|
50
|
+
}
|
|
51
|
+
return { envelopes, remainder: input.subarray(offset) };
|
|
52
|
+
}
|
|
53
|
+
export function firstMessagePayload(input) {
|
|
54
|
+
if (input.length < HEADER_LENGTH) {
|
|
55
|
+
return input;
|
|
56
|
+
}
|
|
57
|
+
const declaredLength = input.readUInt32BE(1);
|
|
58
|
+
if (declaredLength <= input.length - HEADER_LENGTH) {
|
|
59
|
+
try {
|
|
60
|
+
return decodeEnvelopes(input)[0]?.payload ?? Buffer.alloc(0);
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
return input;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return input;
|
|
67
|
+
}
|
|
68
|
+
export function firstMessageEnvelope(input) {
|
|
69
|
+
return decodeEnvelopes(input).find((envelope) => !isEndStreamEnvelope(envelope));
|
|
70
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { loadConfig } from "./config.js";
|
|
2
|
+
export declare const DESKTOP_HOSTNAME = "api2.cursor.sh";
|
|
3
|
+
export declare const DESKTOP_HOSTNAMES: readonly ["api2.cursor.sh", "api3.cursor.sh", "agent.api5.cursor.sh", "agentn.api5.cursor.sh", "agentn.global.api5.cursor.sh"];
|
|
4
|
+
export declare const DESKTOP_CERT_DIR: string;
|
|
5
|
+
export declare const DESKTOP_CERT_PATH: string;
|
|
6
|
+
export declare const DESKTOP_KEY_PATH: string;
|
|
7
|
+
type Config = ReturnType<typeof loadConfig>;
|
|
8
|
+
export declare function desktopEnv(env: NodeJS.ProcessEnv): NodeJS.ProcessEnv;
|
|
9
|
+
export declare function desktopTrustCommand(certPath?: string): string[];
|
|
10
|
+
export declare function writeDesktopCertificate(): Promise<{
|
|
11
|
+
certPath: string;
|
|
12
|
+
keyPath: string;
|
|
13
|
+
created: boolean;
|
|
14
|
+
}>;
|
|
15
|
+
export declare function desktopCertificateStatus(config: Config): string;
|
|
16
|
+
export declare function desktopDnsStatus(config: Config): Promise<string>;
|
|
17
|
+
export declare function upstreamReachabilityStatus(config: Config): Promise<string>;
|
|
18
|
+
export declare function localModelBackendStatus(config: Config): Promise<string>;
|
|
19
|
+
export {};
|