@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,367 @@
1
+ import { spawnSync } from "node:child_process";
2
+ import fs from "node:fs";
3
+ import os from "node:os";
4
+ import path from "node:path";
5
+ const RELEASE_CHECK_DIR = ".cursor-rpc/release-check";
6
+ const PACKAGE_SMOKE_COMMAND = "pnpm pack, pnpm add --offline <tarball>, cursorkit --help";
7
+ export const REQUIRED_PACKAGE_ENTRIES = [
8
+ "package/package.json",
9
+ "package/README.md",
10
+ "package/DISCLAIMER.md",
11
+ "package/dist/src/cli.js",
12
+ "package/dist/src/cli.d.ts",
13
+ "package/dist/src/ck.js",
14
+ "package/dist/src/ck.d.ts",
15
+ "package/proto/agent/v1/agent.proto",
16
+ "package/proto/aiserver/v1/aiserver.proto",
17
+ "package/docs/protocol.md",
18
+ "package/docs/release-gates.md",
19
+ "package/docs/testing-harness.md",
20
+ "package/docs/test-manifest.json",
21
+ "package/docs/route-contract-manifest.json",
22
+ "package/docs/release-summary.json",
23
+ ];
24
+ export const EXCLUDED_PACKAGE_ENTRY_PREFIXES = ["package/examples/"];
25
+ const GATES = [
26
+ {
27
+ id: "baseline-drift",
28
+ description: "Generated route/config/docs inventory drift check.",
29
+ command: "pnpm",
30
+ args: ["baseline:check"],
31
+ deterministic: true,
32
+ categories: ["baseline-drift", "protocol", "final-gate"],
33
+ },
34
+ {
35
+ id: "build",
36
+ description: "TypeScript build and declaration output.",
37
+ command: "pnpm",
38
+ args: ["build"],
39
+ deterministic: true,
40
+ categories: ["static", "final-gate"],
41
+ },
42
+ {
43
+ id: "unit-tests",
44
+ description: "Deterministic unit and integration test suites.",
45
+ command: "pnpm",
46
+ args: ["test"],
47
+ deterministic: true,
48
+ categories: [
49
+ "protocol",
50
+ "mlx",
51
+ "tool",
52
+ "reliability-security",
53
+ "final-gate",
54
+ ],
55
+ },
56
+ {
57
+ id: "format-check",
58
+ description: "Repository formatting and docs formatting check.",
59
+ command: "pnpm",
60
+ args: ["format:check"],
61
+ deterministic: true,
62
+ categories: ["static", "final-gate"],
63
+ },
64
+ {
65
+ id: "examples-typecheck",
66
+ description: "Typecheck packaged extension examples against built exports.",
67
+ command: "pnpm",
68
+ args: ["examples:check"],
69
+ deterministic: true,
70
+ categories: ["packaging", "final-gate"],
71
+ },
72
+ {
73
+ id: "package-smoke",
74
+ description: "Pack tarball, inspect contents, install from tarball, and execute packed binary.",
75
+ deterministic: true,
76
+ categories: ["packaging", "final-gate"],
77
+ run: runPackageSmoke,
78
+ },
79
+ ];
80
+ const OPTIONAL_LIVE_SUITES = [
81
+ {
82
+ id: "mlx-live",
83
+ category: "mlx",
84
+ status: "skipped_with_reason",
85
+ reason: "Optional live MLX backend checks require an operator-provided backend and stay outside deterministic release readiness.",
86
+ command: "pnpm test:harness -- --suite local-backend --base-url <mlx-url> --provider-model <mlx-model>",
87
+ prerequisites: ["Running MLX OpenAI-compatible backend"],
88
+ },
89
+ {
90
+ id: "desktop-live",
91
+ category: "desktop-optional-live",
92
+ status: "skipped_with_reason",
93
+ reason: "Desktop live checks require Cursor desktop, local auth state, and optional MLX backend access.",
94
+ command: "pnpm test:harness -- --suite desktop-ui-experimental --include-experimental --base-url <mlx-url> --model <cursor-model> --provider-model <mlx-model>",
95
+ prerequisites: [
96
+ "Cursor desktop installed",
97
+ "Cursor auth available",
98
+ "Running MLX OpenAI-compatible backend",
99
+ ],
100
+ },
101
+ ];
102
+ export function runReleaseCheck(repoRoot = process.cwd()) {
103
+ const results = [];
104
+ let blockedReason;
105
+ for (const gate of GATES) {
106
+ const started = Date.now();
107
+ const commandLine = gateCommandLine(gate);
108
+ if (blockedReason !== undefined) {
109
+ results.push({
110
+ ...gate,
111
+ commandLine,
112
+ status: "skipped_with_reason",
113
+ exitCode: null,
114
+ signal: null,
115
+ durationMs: 0,
116
+ reason: blockedReason,
117
+ });
118
+ continue;
119
+ }
120
+ console.log(`\n==> ${gate.id}: ${commandLine}`);
121
+ const result = runGate(gate, repoRoot);
122
+ const gateResult = {
123
+ ...gate,
124
+ commandLine,
125
+ status: result.status,
126
+ exitCode: result.exitCode,
127
+ signal: result.signal,
128
+ durationMs: Date.now() - started,
129
+ details: result.details,
130
+ };
131
+ results.push(gateResult);
132
+ if (gateResult.status === "failed") {
133
+ blockedReason = `Skipped because ${gate.id} failed.`;
134
+ }
135
+ }
136
+ const summaryPath = writeReleaseSummary(repoRoot, results, OPTIONAL_LIVE_SUITES);
137
+ console.log(`\nRelease summary: ${summaryPath}`);
138
+ return results.some((result) => result.status === "failed") ? 1 : 0;
139
+ }
140
+ export function buildReleaseCategoryStatuses(results, optionalSuites = OPTIONAL_LIVE_SUITES) {
141
+ const categories = [
142
+ "baseline-drift",
143
+ "static",
144
+ "protocol",
145
+ "mlx",
146
+ "tool",
147
+ "desktop-optional-live",
148
+ "reliability-security",
149
+ "packaging",
150
+ "final-gate",
151
+ ];
152
+ return categories.map((category) => {
153
+ const categoryResults = results.filter((result) => result.categories.includes(category));
154
+ const categoryOptionalSuites = optionalSuites.filter((suite) => suite.category === category);
155
+ const status = categoryResults.some((result) => result.status === "failed")
156
+ ? "failed"
157
+ : categoryResults.length > 0 &&
158
+ categoryResults.every((result) => result.status === "passed")
159
+ ? "passed"
160
+ : "skipped_with_reason";
161
+ return {
162
+ id: category,
163
+ status,
164
+ gateIds: categoryResults.map((result) => result.id),
165
+ optionalSuiteIds: categoryOptionalSuites.map((suite) => suite.id),
166
+ summary: categorySummary(category, status, categoryOptionalSuites),
167
+ };
168
+ });
169
+ }
170
+ function writeReleaseSummary(repoRoot, results, optionalSuites) {
171
+ const outputDir = path.join(repoRoot, RELEASE_CHECK_DIR);
172
+ fs.mkdirSync(outputDir, { recursive: true });
173
+ const summaryPath = path.join(outputDir, "release-summary.json");
174
+ const failed = results.some((result) => result.status === "failed");
175
+ fs.writeFileSync(summaryPath, `${JSON.stringify({
176
+ schemaVersion: 1,
177
+ status: failed ? "failed" : "passed",
178
+ generatedAt: new Date().toISOString(),
179
+ deterministic: true,
180
+ categories: buildReleaseCategoryStatuses(results, optionalSuites),
181
+ gates: results.map((result) => ({
182
+ id: result.id,
183
+ description: result.description,
184
+ command: result.commandLine,
185
+ categories: result.categories,
186
+ deterministic: result.deterministic,
187
+ status: result.status,
188
+ exitCode: result.exitCode,
189
+ signal: result.signal,
190
+ durationMs: result.durationMs,
191
+ reason: result.reason,
192
+ details: result.details,
193
+ })),
194
+ optionalLiveSuites: optionalSuites,
195
+ }, null, 2)}\n`);
196
+ return summaryPath;
197
+ }
198
+ function runGate(gate, repoRoot) {
199
+ if ("run" in gate) {
200
+ return gate.run(repoRoot);
201
+ }
202
+ const result = spawnSync(gate.command, gate.args, {
203
+ cwd: repoRoot,
204
+ env: process.env,
205
+ stdio: "inherit",
206
+ });
207
+ return {
208
+ status: result.status === 0 ? "passed" : "failed",
209
+ exitCode: result.status,
210
+ signal: result.signal,
211
+ details: {},
212
+ };
213
+ }
214
+ function runPackageSmoke(repoRoot) {
215
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "cursor-rpc-release-"));
216
+ const packDir = path.join(tempDir, "pack");
217
+ const extractDir = path.join(tempDir, "extract");
218
+ const installDir = path.join(tempDir, "install");
219
+ fs.mkdirSync(packDir, { recursive: true });
220
+ fs.mkdirSync(extractDir, { recursive: true });
221
+ fs.mkdirSync(installDir, { recursive: true });
222
+ const details = { tempDir };
223
+ const pack = spawnSync("pnpm", ["pack", "--pack-destination", packDir], {
224
+ cwd: repoRoot,
225
+ env: process.env,
226
+ stdio: "inherit",
227
+ });
228
+ if (pack.status !== 0) {
229
+ return failedExecution(pack.status, pack.signal, details);
230
+ }
231
+ const tarball = fs
232
+ .readdirSync(packDir)
233
+ .filter((entry) => entry.endsWith(".tgz"))
234
+ .sort()
235
+ .map((entry) => path.join(packDir, entry))
236
+ .at(-1);
237
+ if (tarball === undefined) {
238
+ details.error = "pnpm pack did not create a .tgz artifact";
239
+ return failedExecution(1, null, details);
240
+ }
241
+ details.tarball = tarball;
242
+ const list = spawnSync("tar", ["-tzf", tarball], {
243
+ cwd: repoRoot,
244
+ encoding: "utf8",
245
+ });
246
+ if (list.status !== 0) {
247
+ details.error = list.stderr;
248
+ return failedExecution(list.status, list.signal, details);
249
+ }
250
+ const entries = list.stdout
251
+ .split(/\r?\n/)
252
+ .filter((entry) => entry.length > 0)
253
+ .sort();
254
+ const missing = REQUIRED_PACKAGE_ENTRIES.filter((required) => !entries.includes(required));
255
+ const unexpectedlyIncluded = entries.filter((entry) => EXCLUDED_PACKAGE_ENTRY_PREFIXES.some((prefix) => entry.startsWith(prefix)));
256
+ details.entryCount = entries.length;
257
+ details.requiredEntries = REQUIRED_PACKAGE_ENTRIES;
258
+ details.excludedEntryPrefixes = EXCLUDED_PACKAGE_ENTRY_PREFIXES;
259
+ if (missing.length > 0) {
260
+ details.missingEntries = missing;
261
+ return failedExecution(1, null, details);
262
+ }
263
+ if (unexpectedlyIncluded.length > 0) {
264
+ details.unexpectedlyIncluded = unexpectedlyIncluded;
265
+ return failedExecution(1, null, details);
266
+ }
267
+ const extract = spawnSync("tar", ["-xzf", tarball, "-C", extractDir], {
268
+ cwd: repoRoot,
269
+ stdio: "inherit",
270
+ });
271
+ if (extract.status !== 0) {
272
+ return failedExecution(extract.status, extract.signal, details);
273
+ }
274
+ const packedPackageJsonPath = path.join(extractDir, "package", "package.json");
275
+ const packedPackageJson = JSON.parse(fs.readFileSync(packedPackageJsonPath, "utf8"));
276
+ const expectedBins = new Map([
277
+ ["cursorkit", "./dist/src/cli.js"],
278
+ ["ck", "./dist/src/ck.js"],
279
+ ]);
280
+ const invalidBins = Array.from(expectedBins).filter(([name, target]) => packedPackageJson.bin?.[name] !== target);
281
+ if (invalidBins.length > 0) {
282
+ details.error =
283
+ "packed package.json bin entries do not match expected bins";
284
+ details.bin = packedPackageJson.bin;
285
+ details.invalidBins = invalidBins.map(([name]) => name);
286
+ return failedExecution(1, null, details);
287
+ }
288
+ details.bin = packedPackageJson.bin;
289
+ fs.writeFileSync(path.join(installDir, "package.json"), `${JSON.stringify({ private: true, type: "module" }, null, 2)}\n`);
290
+ const install = spawnSync("pnpm", ["add", "--offline", "--ignore-scripts", tarball], {
291
+ cwd: installDir,
292
+ env: process.env,
293
+ stdio: "inherit",
294
+ });
295
+ if (install.status !== 0) {
296
+ details.error =
297
+ "offline install-from-tarball smoke failed; run pnpm install first to seed the pnpm store";
298
+ return failedExecution(install.status, install.signal, details);
299
+ }
300
+ const cursorkitBin = path.join(installDir, "node_modules", ".bin", "cursorkit");
301
+ const help = spawnSync(cursorkitBin, ["--help"], {
302
+ cwd: installDir,
303
+ env: process.env,
304
+ encoding: "utf8",
305
+ });
306
+ details.binary = "cursorkit --help";
307
+ if (help.status !== 0 || !help.stdout.includes("cursorkit")) {
308
+ details.error =
309
+ help.stderr || "cursorkit --help did not print expected help";
310
+ details.stdout = help.stdout;
311
+ return failedExecution(help.status ?? 1, help.signal, details);
312
+ }
313
+ return {
314
+ status: "passed",
315
+ exitCode: 0,
316
+ signal: null,
317
+ details,
318
+ };
319
+ }
320
+ function failedExecution(exitCode, signal, details) {
321
+ return {
322
+ status: "failed",
323
+ exitCode,
324
+ signal,
325
+ details,
326
+ };
327
+ }
328
+ function gateCommandLine(gate) {
329
+ if ("command" in gate) {
330
+ return [gate.command, ...gate.args].join(" ");
331
+ }
332
+ return PACKAGE_SMOKE_COMMAND;
333
+ }
334
+ function categorySummary(category, status, optionalSuites) {
335
+ const optionalNote = optionalSuites.length === 0
336
+ ? ""
337
+ : ` Optional live: ${optionalSuites
338
+ .map((suite) => `${suite.id} ${suite.status}`)
339
+ .join(", ")}.`;
340
+ switch (category) {
341
+ case "baseline-drift":
342
+ return `Generated route/config/docs inventory and release summary drift checks ${status}.${optionalNote}`;
343
+ case "static":
344
+ return `Build, formatting, and repository static checks ${status}.${optionalNote}`;
345
+ case "protocol":
346
+ return `Protocol route contracts, fixture/replay tests, and Connect shape coverage ${status}.${optionalNote}`;
347
+ case "mlx":
348
+ return `MLX scripted contract coverage ${status}.${optionalNote}`;
349
+ case "tool":
350
+ return `Agent tool runtime schema/result coverage ${status}.${optionalNote}`;
351
+ case "desktop-optional-live":
352
+ return `Desktop live acceptance remains optional and separately reported ${status}.${optionalNote}`;
353
+ case "reliability-security":
354
+ return `Reliability and security regression coverage ${status}.${optionalNote}`;
355
+ case "packaging":
356
+ return `Examples typecheck and packed artifact smoke ${status}.${optionalNote}`;
357
+ case "final-gate":
358
+ return `Deterministic final release gate ${status}.${optionalNote}`;
359
+ default: {
360
+ const exhaustive = category;
361
+ return exhaustive;
362
+ }
363
+ }
364
+ }
365
+ if (import.meta.url === `file://${process.argv[1]}`) {
366
+ process.exitCode = runReleaseCheck();
367
+ }
@@ -0,0 +1,39 @@
1
+ /**
2
+ * fusion-trace-event.v1 emitter for the Cursorkit bridge.
3
+ *
4
+ * Self-contained (no @warrant dependency) mirror of the shared fusion-trace
5
+ * contract so the Cursor edge can appear on the same observable session as the
6
+ * gateway run. Emission is a no-op unless FUSION_TRACE_URL or FUSION_TRACE_DIR
7
+ * is set, so it never affects normal bridge operation.
8
+ */
9
+ export declare const TRACE_ID_HEADER = "x-fusion-trace-id";
10
+ export declare const TRACE_SPAN_HEADER = "x-fusion-span-id";
11
+ export declare const TRACE_PARENT_SPAN_HEADER = "x-fusion-parent-span-id";
12
+ export declare const TRACE_CANDIDATE_HEADER = "x-fusion-candidate-id";
13
+ declare const FUSION_TRACE_EVENT_SCHEMA = "fusion-trace-event.v1";
14
+ export type CursorTraceEventType = "session.started" | "session.finished" | "model.call.started" | "model.call.finished" | "cursor.route" | "log";
15
+ export interface CursorTraceEvent {
16
+ schema: typeof FUSION_TRACE_EVENT_SCHEMA;
17
+ schema_version: string;
18
+ trace_id: string;
19
+ span_id: string;
20
+ parent_span_id?: string;
21
+ seq: number;
22
+ ts: number;
23
+ component: "cursor-bridge";
24
+ event_type: CursorTraceEventType;
25
+ model_id?: string;
26
+ payload?: Record<string, unknown>;
27
+ }
28
+ export interface EmitInput {
29
+ event_type: CursorTraceEventType;
30
+ traceId?: string;
31
+ spanId?: string;
32
+ parentSpanId?: string;
33
+ modelId?: string;
34
+ payload?: Record<string, unknown>;
35
+ }
36
+ export declare function newTraceId(): string;
37
+ export declare function newSpanId(): string;
38
+ export declare function emitTrace(input: EmitInput): void;
39
+ export {};
@@ -0,0 +1,106 @@
1
+ /**
2
+ * fusion-trace-event.v1 emitter for the Cursorkit bridge.
3
+ *
4
+ * Self-contained (no @warrant dependency) mirror of the shared fusion-trace
5
+ * contract so the Cursor edge can appear on the same observable session as the
6
+ * gateway run. Emission is a no-op unless FUSION_TRACE_URL or FUSION_TRACE_DIR
7
+ * is set, so it never affects normal bridge operation.
8
+ */
9
+ import { appendFileSync, mkdirSync } from "node:fs";
10
+ import { join } from "node:path";
11
+ import { randomUUID } from "node:crypto";
12
+ export const TRACE_ID_HEADER = "x-fusion-trace-id";
13
+ export const TRACE_SPAN_HEADER = "x-fusion-span-id";
14
+ export const TRACE_PARENT_SPAN_HEADER = "x-fusion-parent-span-id";
15
+ export const TRACE_CANDIDATE_HEADER = "x-fusion-candidate-id";
16
+ const FUSION_TRACE_EVENT_SCHEMA = "fusion-trace-event.v1";
17
+ const FUSION_TRACE_EVENT_VERSION = "1.0.0";
18
+ export function newTraceId() {
19
+ return `trace_${randomUUID().replace(/-/g, "")}`;
20
+ }
21
+ export function newSpanId() {
22
+ return `span_${randomUUID().replace(/-/g, "").slice(0, 12)}`;
23
+ }
24
+ function ambientTraceId() {
25
+ const value = process.env.FUSION_TRACE_ID;
26
+ return value && value.length > 0 ? value : undefined;
27
+ }
28
+ class CursorTraceEmitter {
29
+ url;
30
+ dir;
31
+ enabled;
32
+ seq = 0;
33
+ dirReady = false;
34
+ constructor() {
35
+ this.url = process.env.FUSION_TRACE_URL ?? undefined;
36
+ this.dir = process.env.FUSION_TRACE_DIR ?? undefined;
37
+ this.enabled = Boolean(this.url ?? this.dir);
38
+ }
39
+ emit(input) {
40
+ if (!this.enabled) {
41
+ return;
42
+ }
43
+ const traceId = input.traceId ?? ambientTraceId();
44
+ if (traceId === undefined) {
45
+ return;
46
+ }
47
+ const event = {
48
+ schema: FUSION_TRACE_EVENT_SCHEMA,
49
+ schema_version: FUSION_TRACE_EVENT_VERSION,
50
+ trace_id: traceId,
51
+ span_id: input.spanId ?? newSpanId(),
52
+ seq: this.seq++,
53
+ ts: Date.now(),
54
+ component: "cursor-bridge",
55
+ event_type: input.event_type,
56
+ ...(input.parentSpanId !== undefined
57
+ ? { parent_span_id: input.parentSpanId }
58
+ : {}),
59
+ ...(input.modelId !== undefined ? { model_id: input.modelId } : {}),
60
+ ...(input.payload !== undefined ? { payload: input.payload } : {}),
61
+ };
62
+ this.writeJsonl(event);
63
+ void this.post(event);
64
+ }
65
+ writeJsonl(event) {
66
+ if (this.dir === undefined) {
67
+ return;
68
+ }
69
+ try {
70
+ if (!this.dirReady) {
71
+ mkdirSync(this.dir, { recursive: true });
72
+ this.dirReady = true;
73
+ }
74
+ appendFileSync(join(this.dir, `${event.trace_id}.jsonl`), `${JSON.stringify(event)}\n`);
75
+ }
76
+ catch {
77
+ // best-effort durable fallback
78
+ }
79
+ }
80
+ async post(event) {
81
+ if (this.url === undefined) {
82
+ return;
83
+ }
84
+ try {
85
+ const controller = new AbortController();
86
+ const timer = setTimeout(() => controller.abort(), 2_000);
87
+ await fetch(this.url, {
88
+ method: "POST",
89
+ headers: { "content-type": "application/json" },
90
+ body: JSON.stringify({ events: [event] }),
91
+ signal: controller.signal,
92
+ }).catch(() => undefined);
93
+ clearTimeout(timer);
94
+ }
95
+ catch {
96
+ // collector being down must never break the bridge
97
+ }
98
+ }
99
+ }
100
+ let emitter;
101
+ export function emitTrace(input) {
102
+ if (emitter === undefined) {
103
+ emitter = new CursorTraceEmitter();
104
+ }
105
+ emitter.emit(input);
106
+ }
@@ -0,0 +1,6 @@
1
+ import { type StreamUnifiedChatRequestWithTools } from "./gen/aiserver/v1/aiserver_pb.js";
2
+ export interface ChatMessage {
3
+ role: "system" | "user" | "assistant";
4
+ content: string;
5
+ }
6
+ export declare function cursorRequestToOpenAI(value: StreamUnifiedChatRequestWithTools): ChatMessage[];
@@ -0,0 +1,22 @@
1
+ import { ConversationMessage_MessageType, } from "./gen/aiserver/v1/aiserver_pb.js";
2
+ export function cursorRequestToOpenAI(value) {
3
+ const conversation = value.streamUnifiedChatRequest?.conversation ?? [];
4
+ const messages = [];
5
+ for (const item of conversation) {
6
+ const content = item.text;
7
+ if (content.length === 0) {
8
+ continue;
9
+ }
10
+ messages.push({
11
+ role: item.type ===
12
+ ConversationMessage_MessageType.CONVERSATION_MESSAGE_MESSAGE_TYPE_MESSAGE_TYPE_AI
13
+ ? "assistant"
14
+ : "user",
15
+ content,
16
+ });
17
+ }
18
+ if (messages.length === 0) {
19
+ messages.push({ role: "user", content: "Hello" });
20
+ }
21
+ return messages;
22
+ }
@@ -0,0 +1,20 @@
1
+ import { type RequestOptions, type IncomingMessage, type ServerResponse } from "node:http";
2
+ import type { BridgeConfig } from "./config.js";
3
+ import type { Logger } from "./logger.js";
4
+ export declare class RequestBodyTooLargeError extends Error {
5
+ readonly maxBytes: number;
6
+ constructor(maxBytes: number);
7
+ }
8
+ export declare class UpstreamRequestTimeoutError extends Error {
9
+ readonly timeoutMs: number;
10
+ constructor(timeoutMs: number);
11
+ }
12
+ export type UpstreamRequestOptions = RequestOptions & {
13
+ servername?: string;
14
+ };
15
+ export declare function readRequestBody(request: IncomingMessage, maxBytes: number): Promise<Buffer>;
16
+ export declare function proxyRequest(request: IncomingMessage, response: ServerResponse, config: BridgeConfig, logger: Logger): void;
17
+ export declare function proxyBufferedRequest(request: IncomingMessage, response: ServerResponse, body: Buffer, config: BridgeConfig, logger: Logger): Promise<void>;
18
+ export declare function fetchUpstreamBuffer(request: IncomingMessage, body: Buffer, config: BridgeConfig): Promise<Buffer | undefined>;
19
+ export declare function upstreamRequestUrl(request: IncomingMessage, config: BridgeConfig): URL;
20
+ export declare function upstreamRequestOptions(request: IncomingMessage, config: BridgeConfig, upstreamUrl?: URL): UpstreamRequestOptions;