@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,404 @@
1
+ import { createHash } from "node:crypto";
2
+ export const MODEL_FUSION_SCHEMA_BUNDLE_HASH = "sha256:955da2d6891c88d4c40746a8206439e2dae2efc1e7ffefca015e84d4ce265671";
3
+ const STATUSES = [
4
+ "pending",
5
+ "running",
6
+ "succeeded",
7
+ "failed",
8
+ "canceled",
9
+ "requires_action",
10
+ "skipped",
11
+ "unsupported",
12
+ ];
13
+ const SIDE_EFFECTS = [
14
+ "none",
15
+ "read_only",
16
+ "writes_workspace",
17
+ "network",
18
+ "tool_execution",
19
+ "unknown",
20
+ ];
21
+ const CAPABILITIES = [
22
+ "supported",
23
+ "unsupported",
24
+ "degraded",
25
+ "unknown",
26
+ ];
27
+ const HARNESS_KINDS = [
28
+ "generic",
29
+ "cursor",
30
+ "claude_code",
31
+ "codex",
32
+ "openai_responses",
33
+ ];
34
+ const ARTIFACT_KINDS = [
35
+ "transcript",
36
+ "patch",
37
+ "screenshot",
38
+ "log",
39
+ "metrics",
40
+ "worktree",
41
+ "other",
42
+ ];
43
+ const REDACTION_STATUSES = ["synthetic", "redacted", "raw"];
44
+ const HASH_PATTERN = /^sha256:[0-9a-f]{64}$/;
45
+ const GIT_SHA_PATTERN = /^[0-9a-f]{40}$/;
46
+ export function sha256Prefixed(value) {
47
+ return `sha256:${createHash("sha256").update(value).digest("hex")}`;
48
+ }
49
+ export function cursorRunResultToHarnessRunResult(cursor) {
50
+ const artifacts = [
51
+ ...(cursor.transcript_artifact ? [cursor.transcript_artifact] : []),
52
+ ...(cursor.artifacts ?? []),
53
+ ];
54
+ return {
55
+ schema: "harness-run-result.v1",
56
+ schema_version: cursor.schema_version,
57
+ schema_bundle_hash: cursor.schema_bundle_hash,
58
+ producer: cursor.producer,
59
+ producer_version: cursor.producer_version,
60
+ producer_git_sha: cursor.producer_git_sha,
61
+ created_at: cursor.created_at,
62
+ result_id: cursor.mapped_harness_result_id ??
63
+ `harness_result_${cursor.cursor_run_id.replace(/[^A-Za-z0-9_.:-]/g, "_")}`,
64
+ request_id: cursor.harness_request_id,
65
+ harness_kind: "cursor",
66
+ status: cursor.status,
67
+ candidate_ids: [],
68
+ output_summary: cursor.output_summary,
69
+ ...(artifacts.length > 0 ? { artifacts } : {}),
70
+ capabilities: cursor.capabilities,
71
+ ...(cursor.requested_model !== undefined
72
+ ? { requested_model: cursor.requested_model }
73
+ : {}),
74
+ ...(cursor.observed_model !== undefined
75
+ ? { observed_model: cursor.observed_model }
76
+ : {}),
77
+ ...(cursor.model_id !== undefined ? { model_id: cursor.model_id } : {}),
78
+ ...(cursor.endpoint_id !== undefined
79
+ ? { endpoint_id: cursor.endpoint_id }
80
+ : {}),
81
+ ...(cursor.diagnostics !== undefined
82
+ ? { diagnostics: cursor.diagnostics }
83
+ : {}),
84
+ started_at: cursor.created_at,
85
+ metadata: {
86
+ mapped_from_cursor_result_id: cursor.cursor_run_id,
87
+ },
88
+ };
89
+ }
90
+ export function assertHarnessRunRequestV1(value) {
91
+ const record = assertObject(value, "harness-run-request.v1");
92
+ assertAllowedKeys(record, [
93
+ ...metadataKeys(),
94
+ "request_id",
95
+ "harness_kind",
96
+ "source_repo",
97
+ "base_git_sha",
98
+ "prompt",
99
+ "prompt_hash",
100
+ "allowed_tools",
101
+ "side_effects",
102
+ "requested_capabilities",
103
+ "metadata",
104
+ ], "harness-run-request.v1");
105
+ assertMetadata(record, "harness-run-request.v1");
106
+ assertString(record.request_id, "request_id");
107
+ assertEnum(record.harness_kind, HARNESS_KINDS, "harness_kind");
108
+ assertString(record.source_repo, "source_repo");
109
+ assertGitSha(record.base_git_sha, "base_git_sha");
110
+ assertString(record.prompt, "prompt");
111
+ assertHash(record.prompt_hash, "prompt_hash");
112
+ if (record.allowed_tools !== undefined)
113
+ assertStringArray(record.allowed_tools, "allowed_tools");
114
+ assertEnum(record.side_effects, SIDE_EFFECTS, "side_effects");
115
+ assertCapabilityMap(record.requested_capabilities, "requested_capabilities");
116
+ if (record.metadata !== undefined)
117
+ assertJsonRecord(record.metadata, "metadata");
118
+ }
119
+ export function assertHarnessRunResultV1(value) {
120
+ const record = assertObject(value, "harness-run-result.v1");
121
+ assertAllowedKeys(record, [
122
+ ...metadataKeys(),
123
+ "result_id",
124
+ "request_id",
125
+ "harness_kind",
126
+ "status",
127
+ "candidate_ids",
128
+ "output_summary",
129
+ "artifacts",
130
+ "capabilities",
131
+ "requested_model",
132
+ "observed_model",
133
+ "model_id",
134
+ "endpoint_id",
135
+ "diagnostics",
136
+ "started_at",
137
+ "finished_at",
138
+ "errors",
139
+ "metadata",
140
+ ], "harness-run-result.v1");
141
+ assertMetadata(record, "harness-run-result.v1");
142
+ assertString(record.result_id, "result_id");
143
+ assertString(record.request_id, "request_id");
144
+ assertEnum(record.harness_kind, HARNESS_KINDS, "harness_kind");
145
+ assertEnum(record.status, STATUSES, "status");
146
+ assertStringArray(record.candidate_ids, "candidate_ids");
147
+ if (record.output_summary !== undefined)
148
+ assertString(record.output_summary, "output_summary");
149
+ if (record.artifacts !== undefined)
150
+ assertArtifacts(record.artifacts, "artifacts");
151
+ assertCapabilityMap(record.capabilities, "capabilities");
152
+ if (record.requested_model !== undefined)
153
+ assertString(record.requested_model, "requested_model");
154
+ if (record.observed_model !== undefined)
155
+ assertString(record.observed_model, "observed_model");
156
+ if (record.model_id !== undefined)
157
+ assertString(record.model_id, "model_id");
158
+ if (record.endpoint_id !== undefined)
159
+ assertString(record.endpoint_id, "endpoint_id");
160
+ if (record.diagnostics !== undefined)
161
+ assertDiagnostics(record.diagnostics, "diagnostics");
162
+ assertDateTime(record.started_at, "started_at");
163
+ if (record.finished_at !== undefined)
164
+ assertDateTime(record.finished_at, "finished_at");
165
+ if (record.errors !== undefined)
166
+ assertErrors(record.errors, "errors");
167
+ if (record.metadata !== undefined)
168
+ assertJsonRecord(record.metadata, "metadata");
169
+ }
170
+ export function assertCursorRunRequestV1(value) {
171
+ const record = assertObject(value, "cursor-run-request.v1");
172
+ assertAllowedKeys(record, [
173
+ ...metadataKeys(),
174
+ "cursor_run_id",
175
+ "harness_request_id",
176
+ "workspace_path",
177
+ "prompt",
178
+ "prompt_hash",
179
+ "requested_model",
180
+ "allowed_tools",
181
+ "side_effects",
182
+ "requested_capabilities",
183
+ ], "cursor-run-request.v1");
184
+ assertMetadata(record, "cursor-run-request.v1");
185
+ assertString(record.cursor_run_id, "cursor_run_id");
186
+ assertString(record.harness_request_id, "harness_request_id");
187
+ assertString(record.workspace_path, "workspace_path");
188
+ assertString(record.prompt, "prompt");
189
+ assertHash(record.prompt_hash, "prompt_hash");
190
+ if (record.requested_model !== undefined)
191
+ assertString(record.requested_model, "requested_model");
192
+ if (record.allowed_tools !== undefined)
193
+ assertStringArray(record.allowed_tools, "allowed_tools");
194
+ assertEnum(record.side_effects, SIDE_EFFECTS, "side_effects");
195
+ assertCapabilityMap(record.requested_capabilities, "requested_capabilities");
196
+ }
197
+ export function assertCursorRunResultV1(value) {
198
+ const record = assertObject(value, "cursor-run-result.v1");
199
+ assertAllowedKeys(record, [
200
+ ...metadataKeys(),
201
+ "cursor_run_id",
202
+ "harness_request_id",
203
+ "mapped_harness_result_id",
204
+ "status",
205
+ "output_summary",
206
+ "transcript_artifact",
207
+ "artifacts",
208
+ "capabilities",
209
+ "requested_model",
210
+ "observed_model",
211
+ "model_id",
212
+ "endpoint_id",
213
+ "diagnostics",
214
+ "raw_hash",
215
+ "redacted_hash",
216
+ ], "cursor-run-result.v1");
217
+ assertMetadata(record, "cursor-run-result.v1");
218
+ assertString(record.cursor_run_id, "cursor_run_id");
219
+ assertString(record.harness_request_id, "harness_request_id");
220
+ if (record.mapped_harness_result_id !== undefined) {
221
+ assertString(record.mapped_harness_result_id, "mapped_harness_result_id");
222
+ }
223
+ assertEnum(record.status, STATUSES, "status");
224
+ assertString(record.output_summary, "output_summary");
225
+ if (record.transcript_artifact !== undefined) {
226
+ assertArtifact(record.transcript_artifact, "transcript_artifact");
227
+ }
228
+ if (record.artifacts !== undefined)
229
+ assertArtifacts(record.artifacts, "artifacts");
230
+ assertCapabilityMap(record.capabilities, "capabilities");
231
+ if (record.requested_model !== undefined)
232
+ assertString(record.requested_model, "requested_model");
233
+ if (record.observed_model !== undefined)
234
+ assertString(record.observed_model, "observed_model");
235
+ if (record.model_id !== undefined)
236
+ assertString(record.model_id, "model_id");
237
+ if (record.endpoint_id !== undefined)
238
+ assertString(record.endpoint_id, "endpoint_id");
239
+ if (record.diagnostics !== undefined)
240
+ assertDiagnostics(record.diagnostics, "diagnostics");
241
+ assertHash(record.raw_hash, "raw_hash");
242
+ assertHash(record.redacted_hash, "redacted_hash");
243
+ }
244
+ function metadataKeys() {
245
+ return [
246
+ "schema",
247
+ "schema_version",
248
+ "schema_bundle_hash",
249
+ "producer",
250
+ "producer_version",
251
+ "producer_git_sha",
252
+ "created_at",
253
+ ];
254
+ }
255
+ function assertMetadata(record, schema) {
256
+ if (record.schema !== schema)
257
+ throw new Error(`schema must be ${schema}`);
258
+ if (record.schema_version !== "v1")
259
+ throw new Error("schema_version must be v1");
260
+ assertHash(record.schema_bundle_hash, "schema_bundle_hash");
261
+ assertString(record.producer, "producer");
262
+ assertString(record.producer_version, "producer_version");
263
+ assertGitSha(record.producer_git_sha, "producer_git_sha");
264
+ assertDateTime(record.created_at, "created_at");
265
+ }
266
+ function assertArtifact(value, context) {
267
+ const artifact = assertObject(value, context);
268
+ assertAllowedKeys(artifact, ["artifact_id", "kind", "uri", "hash", "redaction_status"], context);
269
+ assertString(artifact.artifact_id, `${context}.artifact_id`);
270
+ assertEnum(artifact.kind, ARTIFACT_KINDS, `${context}.kind`);
271
+ if (artifact.uri !== undefined)
272
+ assertString(artifact.uri, `${context}.uri`);
273
+ assertHash(artifact.hash, `${context}.hash`);
274
+ if (artifact.redaction_status !== undefined) {
275
+ assertEnum(artifact.redaction_status, REDACTION_STATUSES, `${context}.redaction_status`);
276
+ }
277
+ }
278
+ function assertArtifacts(value, context) {
279
+ if (!Array.isArray(value))
280
+ throw new Error(`${context} must be an array`);
281
+ value.forEach((artifact, index) => assertArtifact(artifact, `${context}[${index}]`));
282
+ }
283
+ function assertCapabilityMap(value, context) {
284
+ const record = assertObject(value, context);
285
+ for (const [key, item] of Object.entries(record)) {
286
+ if (typeof key !== "string" || key.length === 0) {
287
+ throw new Error(`${context} capability keys must be non-empty strings`);
288
+ }
289
+ assertEnum(item, CAPABILITIES, `${context}.${key}`);
290
+ }
291
+ }
292
+ function assertErrors(value, context) {
293
+ if (!Array.isArray(value))
294
+ throw new Error(`${context} must be an array`);
295
+ for (const [index, error] of value.entries()) {
296
+ const record = assertObject(error, `${context}[${index}]`);
297
+ assertAllowedKeys(record, ["kind", "message", "retryable"], `${context}[${index}]`);
298
+ assertString(record.kind, `${context}[${index}].kind`);
299
+ assertString(record.message, `${context}[${index}].message`);
300
+ if (typeof record.retryable !== "boolean") {
301
+ throw new Error(`${context}[${index}].retryable must be a boolean`);
302
+ }
303
+ }
304
+ }
305
+ function assertDiagnostics(value, context) {
306
+ if (!Array.isArray(value))
307
+ throw new Error(`${context} must be an array`);
308
+ for (const [index, diagnostic] of value.entries()) {
309
+ const record = assertObject(diagnostic, `${context}[${index}]`);
310
+ assertAllowedKeys(record, [
311
+ "kind",
312
+ "message",
313
+ "retryable",
314
+ "capability",
315
+ "status",
316
+ "requested_status",
317
+ ], `${context}[${index}]`);
318
+ if (record.kind !== "capability_missing") {
319
+ throw new Error(`${context}[${index}].kind must be capability_missing`);
320
+ }
321
+ assertString(record.message, `${context}[${index}].message`);
322
+ if (typeof record.retryable !== "boolean") {
323
+ throw new Error(`${context}[${index}].retryable must be a boolean`);
324
+ }
325
+ if (record.capability !== undefined) {
326
+ assertString(record.capability, `${context}[${index}].capability`);
327
+ }
328
+ if (record.status !== undefined) {
329
+ assertEnum(record.status, CAPABILITIES, `${context}[${index}].status`);
330
+ }
331
+ if (record.requested_status !== undefined) {
332
+ assertEnum(record.requested_status, CAPABILITIES, `${context}[${index}].requested_status`);
333
+ }
334
+ }
335
+ }
336
+ function assertObject(value, context) {
337
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
338
+ throw new Error(`${context} must be an object`);
339
+ }
340
+ return value;
341
+ }
342
+ function assertAllowedKeys(value, allowed, context) {
343
+ const set = new Set(allowed);
344
+ for (const key of Object.keys(value)) {
345
+ if (!set.has(key))
346
+ throw new Error(`${context}.${key} is an unsupported field`);
347
+ }
348
+ }
349
+ function assertString(value, context) {
350
+ if (typeof value !== "string" || value.length === 0) {
351
+ throw new Error(`${context} must be a non-empty string`);
352
+ }
353
+ }
354
+ function assertStringArray(value, context) {
355
+ if (!Array.isArray(value))
356
+ throw new Error(`${context} must be an array`);
357
+ value.forEach((item, index) => assertString(item, `${context}[${index}]`));
358
+ }
359
+ function assertHash(value, context) {
360
+ if (typeof value !== "string" || !HASH_PATTERN.test(value)) {
361
+ throw new Error(`${context} must be a sha256-prefixed hash`);
362
+ }
363
+ }
364
+ function assertGitSha(value, context) {
365
+ if (typeof value !== "string" || !GIT_SHA_PATTERN.test(value)) {
366
+ throw new Error(`${context} must be a git SHA`);
367
+ }
368
+ }
369
+ function assertDateTime(value, context) {
370
+ if (typeof value !== "string" || Number.isNaN(Date.parse(value))) {
371
+ throw new Error(`${context} must be an ISO datetime string`);
372
+ }
373
+ }
374
+ function assertEnum(value, allowed, context) {
375
+ if (typeof value !== "string" || !allowed.includes(value)) {
376
+ throw new Error(`${context} must be one of ${allowed.join(", ")}`);
377
+ }
378
+ }
379
+ function assertJsonRecord(value, context) {
380
+ const record = assertObject(value, context);
381
+ for (const [key, item] of Object.entries(record)) {
382
+ assertJsonValue(item, `${context}.${key}`);
383
+ }
384
+ }
385
+ function assertJsonValue(value, context) {
386
+ if (value === null || typeof value === "string" || typeof value === "boolean")
387
+ return;
388
+ if (typeof value === "number") {
389
+ if (!Number.isFinite(value))
390
+ throw new Error(`${context} must be JSON-safe`);
391
+ return;
392
+ }
393
+ if (Array.isArray(value)) {
394
+ value.forEach((item, index) => assertJsonValue(item, `${context}[${index}]`));
395
+ return;
396
+ }
397
+ if (typeof value === "object") {
398
+ for (const [key, item] of Object.entries(value)) {
399
+ assertJsonValue(item, `${context}.${key}`);
400
+ }
401
+ return;
402
+ }
403
+ throw new Error(`${context} must be JSON-safe`);
404
+ }
@@ -0,0 +1,9 @@
1
+ import type { CursorProto } from "../proto.js";
2
+ import { type ProtocolFixture } from "./schema.js";
3
+ export interface ReplayResult {
4
+ fixturePath: string;
5
+ requestFrames: number;
6
+ responseFrames: number;
7
+ }
8
+ export declare function loadFixture(filePath: string): ProtocolFixture;
9
+ export declare function replayFixture(proto: CursorProto, fixturePath: string): ReplayResult;
@@ -0,0 +1,41 @@
1
+ import fs from "node:fs";
2
+ import { decodeEnvelopes, isCompressedEnvelope } from "../connectEnvelope.js";
3
+ import { assertFixture } from "./schema.js";
4
+ import { bodySha256 } from "./sanitizer.js";
5
+ export function loadFixture(filePath) {
6
+ const parsed = JSON.parse(fs.readFileSync(filePath, "utf8"));
7
+ assertFixture(parsed);
8
+ return parsed;
9
+ }
10
+ export function replayFixture(proto, fixturePath) {
11
+ const fixture = loadFixture(fixturePath);
12
+ const requestBody = Buffer.from(fixture.request.bodyBase64, "base64");
13
+ const responseBody = Buffer.from(fixture.response.bodyBase64, "base64");
14
+ if (bodySha256(requestBody) !== fixture.request.bodySha256) {
15
+ throw new Error(`${fixturePath} request body hash mismatch`);
16
+ }
17
+ if (bodySha256(responseBody) !== fixture.response.bodySha256) {
18
+ throw new Error(`${fixturePath} response body hash mismatch`);
19
+ }
20
+ const requestFrames = decodeEnvelopes(requestBody);
21
+ const responseFrames = decodeEnvelopes(responseBody);
22
+ if (requestFrames.some(isCompressedEnvelope) ||
23
+ responseFrames.some(isCompressedEnvelope)) {
24
+ throw new Error(`${fixturePath} uses compressed frames; decompression is not fixture-backed yet`);
25
+ }
26
+ if (fixture.decoded?.requestType !== undefined) {
27
+ proto.root
28
+ .lookupType(fixture.decoded.requestType)
29
+ .decode(requestFrames[0]?.payload ?? Buffer.alloc(0));
30
+ }
31
+ if (fixture.decoded?.responseType !== undefined) {
32
+ proto.root
33
+ .lookupType(fixture.decoded.responseType)
34
+ .decode(responseFrames[0]?.payload ?? Buffer.alloc(0));
35
+ }
36
+ return {
37
+ fixturePath,
38
+ requestFrames: requestFrames.length,
39
+ responseFrames: responseFrames.length,
40
+ };
41
+ }
@@ -0,0 +1,9 @@
1
+ import type { ModelFusionPayloadSanitization, ProtocolFixture } from "./schema.js";
2
+ export declare const SANITIZER_VERSION = "1";
3
+ export declare function sanitizeFixture(fixture: ProtocolFixture): ProtocolFixture;
4
+ export declare function bodySha256(body: Uint8Array): string;
5
+ export declare function sha256Prefixed(body: string | Uint8Array): string;
6
+ export declare function sanitizeModelFusionPayload(input: {
7
+ rawPayload: string;
8
+ synthetic?: boolean;
9
+ }): ModelFusionPayloadSanitization;
@@ -0,0 +1,43 @@
1
+ import { createHash } from "node:crypto";
2
+ import { redactHeaders, redactValue } from "../redaction.js";
3
+ export const SANITIZER_VERSION = "1";
4
+ export function sanitizeFixture(fixture) {
5
+ return {
6
+ ...fixture,
7
+ sanitizerVersion: SANITIZER_VERSION,
8
+ redaction: {
9
+ status: "sanitized",
10
+ notes: "headers and URLs redacted by cursor-rpc sanitizer",
11
+ },
12
+ request: sanitizeMessage(fixture.request),
13
+ response: sanitizeMessage(fixture.response),
14
+ };
15
+ }
16
+ export function bodySha256(body) {
17
+ return createHash("sha256").update(body).digest("hex");
18
+ }
19
+ export function sha256Prefixed(body) {
20
+ return `sha256:${bodySha256(typeof body === "string" ? Buffer.from(body) : body)}`;
21
+ }
22
+ export function sanitizeModelFusionPayload(input) {
23
+ const redacted = input.synthetic
24
+ ? input.rawPayload
25
+ : redactValue(input.rawPayload);
26
+ return {
27
+ redactionStatus: input.synthetic ? "synthetic" : "redacted",
28
+ raw_hash: sha256Prefixed(input.rawPayload),
29
+ redacted_hash: sha256Prefixed(redacted),
30
+ persistedPayload: redacted,
31
+ };
32
+ }
33
+ function sanitizeMessage(message) {
34
+ const body = Buffer.from(message.bodyBase64, "base64");
35
+ return {
36
+ ...message,
37
+ path: redactValue(message.path),
38
+ query: message.query === undefined ? undefined : redactValue(message.query),
39
+ headers: redactHeaders(message.headers),
40
+ trailers: redactHeaders(message.trailers),
41
+ bodySha256: bodySha256(body),
42
+ };
43
+ }
@@ -0,0 +1,38 @@
1
+ export interface ProtocolFixture {
2
+ schemaVersion: 1;
3
+ cursorVersion: string;
4
+ protoVersion: string;
5
+ capturedAt: string;
6
+ sanitizerVersion: string;
7
+ redaction: {
8
+ status: "sanitized";
9
+ notes?: string;
10
+ };
11
+ request: FixtureHttpMessage;
12
+ response: FixtureHttpMessage;
13
+ decoded?: {
14
+ requestType?: string;
15
+ responseType?: string;
16
+ };
17
+ }
18
+ export interface FixtureHttpMessage {
19
+ httpVersion: string;
20
+ alpn?: string;
21
+ method?: string;
22
+ path: string;
23
+ query?: string;
24
+ status?: number;
25
+ headers: Record<string, string>;
26
+ trailers: Record<string, string>;
27
+ contentType?: string;
28
+ bodySha256: string;
29
+ bodyBase64: string;
30
+ }
31
+ export type FixturePayloadMarker = "raw" | "redacted" | "synthetic";
32
+ export interface ModelFusionPayloadSanitization {
33
+ redactionStatus: FixturePayloadMarker;
34
+ raw_hash: string;
35
+ redacted_hash: string;
36
+ persistedPayload: string;
37
+ }
38
+ export declare function assertFixture(value: unknown): asserts value is ProtocolFixture;
@@ -0,0 +1,33 @@
1
+ export function assertFixture(value) {
2
+ if (!isRecord(value)) {
3
+ throw new Error("Fixture must be an object");
4
+ }
5
+ if (value.schemaVersion !== 1) {
6
+ throw new Error("Unsupported fixture schemaVersion");
7
+ }
8
+ if (!isRecord(value.redaction) || value.redaction.status !== "sanitized") {
9
+ throw new Error("Fixture must be sanitized before use");
10
+ }
11
+ assertMessage(value.request, "request");
12
+ assertMessage(value.response, "response");
13
+ }
14
+ function assertMessage(value, name) {
15
+ if (!isRecord(value)) {
16
+ throw new Error(`${name} must be an object`);
17
+ }
18
+ for (const field of [
19
+ "httpVersion",
20
+ "path",
21
+ "headers",
22
+ "trailers",
23
+ "bodySha256",
24
+ "bodyBase64",
25
+ ]) {
26
+ if (!(field in value)) {
27
+ throw new Error(`${name}.${field} is required`);
28
+ }
29
+ }
30
+ }
31
+ function isRecord(value) {
32
+ return typeof value === "object" && value !== null;
33
+ }