@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
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import { create, fromBinary, toBinary } from "@bufbuild/protobuf";
|
|
2
|
+
import { encodeEnvelope, encodeEndStream } from "../connectEnvelope.js";
|
|
3
|
+
import { AgentV1_ApiKeyCredentialsSchema, AgentV1_GetDefaultModelForCliResponseSchema, AgentV1_GetUsableModelsResponseSchema, AgentV1_ModelDetailsSchema, AgentV1_RequestedModel_ModelParameterValueSchema, AvailableModelsResponse_DegradationStatus, AvailableModelsResponse_AvailableModelSchema, AvailableModelsResponse_ModelVariantConfigSchema, AvailableModelsResponse_TooltipDataSchema, AvailableModelsResponse_ModelVendorId, AvailableModelsResponse_ModelVendorSchema, AvailableModelsResponseSchema, GetDefaultModelResponseSchema, ModelParameterDefinition_EnumParameterDefinition_EnumParameterValueSchema, ModelParameterDefinition_EnumParameterDefinitionSchema, ModelParameterDefinition_ModelParameterTypeSchema, ModelParameterDefinitionSchema, } from "../gen/aiserver/v1/aiserver_pb.js";
|
|
4
|
+
export function mergeAvailableModels(upstreamPayload, models) {
|
|
5
|
+
const upstream = upstreamPayload === undefined
|
|
6
|
+
? create(AvailableModelsResponseSchema)
|
|
7
|
+
: fromBinary(AvailableModelsResponseSchema, upstreamPayload);
|
|
8
|
+
const modelNames = new Set(upstream.modelNames);
|
|
9
|
+
const upstreamModels = [...upstream.models];
|
|
10
|
+
for (const model of models.list()) {
|
|
11
|
+
if (modelNames.has(model.id)) {
|
|
12
|
+
continue;
|
|
13
|
+
}
|
|
14
|
+
modelNames.add(model.id);
|
|
15
|
+
upstreamModels.push(create(AvailableModelsResponse_AvailableModelSchema, {
|
|
16
|
+
name: model.id,
|
|
17
|
+
defaultOn: false,
|
|
18
|
+
isChatOnly: false,
|
|
19
|
+
supportsAgent: true,
|
|
20
|
+
degradationStatus: AvailableModelsResponse_DegradationStatus.AVAILABLE_MODELS_RESPONSE_DEGRADATION_STATUS_DEGRADATION_STATUS_UNSPECIFIED,
|
|
21
|
+
supportsThinking: false,
|
|
22
|
+
supportsImages: false,
|
|
23
|
+
supportsAutoContext: false,
|
|
24
|
+
supportsMaxMode: true,
|
|
25
|
+
supportsNonMaxMode: true,
|
|
26
|
+
supportsPlanMode: true,
|
|
27
|
+
supportsSandboxing: false,
|
|
28
|
+
supportsCmdK: false,
|
|
29
|
+
cloudAgentEffortModes: [],
|
|
30
|
+
clientDisplayName: model.displayName,
|
|
31
|
+
serverModelName: model.id,
|
|
32
|
+
inputboxShortModelName: model.displayName,
|
|
33
|
+
contextTokenLimit: model.contextTokenLimit,
|
|
34
|
+
isUserAdded: true,
|
|
35
|
+
parameterDefinitions: localParameterDefinitions(),
|
|
36
|
+
legacySlugs: [model.id],
|
|
37
|
+
idAliases: uniqueStrings([model.id, model.displayName]),
|
|
38
|
+
namedModelSectionIndex: 1,
|
|
39
|
+
visibleInRoutedModelView: true,
|
|
40
|
+
vendorName: "local",
|
|
41
|
+
vendor: create(AvailableModelsResponse_ModelVendorSchema, {
|
|
42
|
+
id: AvailableModelsResponse_ModelVendorId.AVAILABLE_MODELS_RESPONSE_MODEL_VENDOR_ID_MODEL_VENDOR_ID_UNSPECIFIED,
|
|
43
|
+
displayName: "Local",
|
|
44
|
+
}),
|
|
45
|
+
variants: [
|
|
46
|
+
localVariantConfig(model, false),
|
|
47
|
+
localVariantConfig(model, true),
|
|
48
|
+
],
|
|
49
|
+
}));
|
|
50
|
+
}
|
|
51
|
+
return Buffer.from(toBinary(AvailableModelsResponseSchema, {
|
|
52
|
+
...upstream,
|
|
53
|
+
modelNames: Array.from(modelNames),
|
|
54
|
+
models: upstreamModels,
|
|
55
|
+
}));
|
|
56
|
+
}
|
|
57
|
+
export function mergeUsableModels(upstreamPayload, models) {
|
|
58
|
+
const upstream = upstreamPayload === undefined
|
|
59
|
+
? create(AgentV1_GetUsableModelsResponseSchema)
|
|
60
|
+
: fromBinary(AgentV1_GetUsableModelsResponseSchema, upstreamPayload);
|
|
61
|
+
const modelIds = new Set(upstream.models.map((model) => model.modelId));
|
|
62
|
+
const mergedModels = [...upstream.models];
|
|
63
|
+
for (const model of models.list()) {
|
|
64
|
+
if (modelIds.has(model.id)) {
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
modelIds.add(model.id);
|
|
68
|
+
mergedModels.push(localCliModelDetails(model));
|
|
69
|
+
}
|
|
70
|
+
return Buffer.from(toBinary(AgentV1_GetUsableModelsResponseSchema, {
|
|
71
|
+
...upstream,
|
|
72
|
+
models: mergedModels,
|
|
73
|
+
}));
|
|
74
|
+
}
|
|
75
|
+
export function mergeDefaultModelForCli(upstreamPayload, models) {
|
|
76
|
+
if (upstreamPayload !== undefined) {
|
|
77
|
+
const upstream = fromBinary(AgentV1_GetDefaultModelForCliResponseSchema, upstreamPayload);
|
|
78
|
+
if (upstream.model !== undefined) {
|
|
79
|
+
return Buffer.from(toBinary(AgentV1_GetDefaultModelForCliResponseSchema, upstream));
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
const firstLocalModel = models.list()[0];
|
|
83
|
+
const model = firstLocalModel === undefined
|
|
84
|
+
? undefined
|
|
85
|
+
: localCliModelDetails(firstLocalModel);
|
|
86
|
+
return Buffer.from(toBinary(AgentV1_GetDefaultModelForCliResponseSchema, create(AgentV1_GetDefaultModelForCliResponseSchema, {
|
|
87
|
+
model,
|
|
88
|
+
})));
|
|
89
|
+
}
|
|
90
|
+
export function mergeDefaultModel(upstreamPayload, models) {
|
|
91
|
+
const upstream = upstreamPayload === undefined
|
|
92
|
+
? create(GetDefaultModelResponseSchema)
|
|
93
|
+
: fromBinary(GetDefaultModelResponseSchema, upstreamPayload);
|
|
94
|
+
const firstLocalModel = models.list()[0];
|
|
95
|
+
if (firstLocalModel === undefined) {
|
|
96
|
+
return Buffer.from(toBinary(GetDefaultModelResponseSchema, upstream));
|
|
97
|
+
}
|
|
98
|
+
return Buffer.from(toBinary(GetDefaultModelResponseSchema, create(GetDefaultModelResponseSchema, {
|
|
99
|
+
...upstream,
|
|
100
|
+
model: firstLocalModel.id,
|
|
101
|
+
thinkingModel: firstLocalModel.id,
|
|
102
|
+
maxMode: true,
|
|
103
|
+
})));
|
|
104
|
+
}
|
|
105
|
+
export function writeAvailableModelsResponse(response, payload, logger, format) {
|
|
106
|
+
writeModelResponse(response, payload, logger, "served available models", format);
|
|
107
|
+
}
|
|
108
|
+
export function writeModelResponse(response, payload, logger, message, format) {
|
|
109
|
+
response.statusCode = 200;
|
|
110
|
+
if (format === "connect") {
|
|
111
|
+
response.setHeader("content-type", "application/connect+proto");
|
|
112
|
+
response.write(encodeEnvelope(payload));
|
|
113
|
+
response.end(encodeEndStream());
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
response.setHeader("content-type", "application/proto");
|
|
117
|
+
response.end(payload);
|
|
118
|
+
}
|
|
119
|
+
logger.info(message, { bytes: payload.length });
|
|
120
|
+
}
|
|
121
|
+
function localCliModelDetails(model) {
|
|
122
|
+
return create(AgentV1_ModelDetailsSchema, {
|
|
123
|
+
modelId: model.id,
|
|
124
|
+
displayModelId: model.id,
|
|
125
|
+
displayName: model.displayName,
|
|
126
|
+
displayNameShort: model.displayName,
|
|
127
|
+
aliases: uniqueStrings([model.id, model.displayName]),
|
|
128
|
+
maxMode: false,
|
|
129
|
+
apiKeyCredentials: create(AgentV1_ApiKeyCredentialsSchema, {
|
|
130
|
+
apiKey: model.apiKey,
|
|
131
|
+
baseUrl: model.baseUrl,
|
|
132
|
+
}),
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
function localVariantConfig(model, isMaxMode) {
|
|
136
|
+
return create(AvailableModelsResponse_ModelVariantConfigSchema, {
|
|
137
|
+
displayName: model.displayName,
|
|
138
|
+
displayNameOutsidePicker: model.displayName,
|
|
139
|
+
isMaxMode,
|
|
140
|
+
isDefaultMaxConfig: isMaxMode,
|
|
141
|
+
isDefaultNonMaxConfig: !isMaxMode,
|
|
142
|
+
parameterValues: localParameterValues(isMaxMode),
|
|
143
|
+
tooltipData: create(AvailableModelsResponse_TooltipDataSchema, {
|
|
144
|
+
markdownContent: `**${model.displayName}**<br />Local OpenAI-compatible model.`,
|
|
145
|
+
}),
|
|
146
|
+
variantStringRepresentation: localVariantString(model.id, isMaxMode),
|
|
147
|
+
legacySlug: model.id,
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
function localVariantString(modelId, isMaxMode) {
|
|
151
|
+
const context = isMaxMode ? "1m" : "272k";
|
|
152
|
+
return `${modelId}[context=${context},reasoning=medium,fast=false]`;
|
|
153
|
+
}
|
|
154
|
+
function localParameterDefinitions() {
|
|
155
|
+
return [
|
|
156
|
+
create(ModelParameterDefinitionSchema, {
|
|
157
|
+
id: "context",
|
|
158
|
+
name: "Context",
|
|
159
|
+
markdownTooltip: "Context size the model has available.",
|
|
160
|
+
parameterType: create(ModelParameterDefinition_ModelParameterTypeSchema, {
|
|
161
|
+
enumParameter: create(ModelParameterDefinition_EnumParameterDefinitionSchema, {
|
|
162
|
+
values: [
|
|
163
|
+
create(ModelParameterDefinition_EnumParameterDefinition_EnumParameterValueSchema, { value: "272k", displayName: "272K" }),
|
|
164
|
+
create(ModelParameterDefinition_EnumParameterDefinition_EnumParameterValueSchema, { value: "1m", displayName: "1M" }),
|
|
165
|
+
],
|
|
166
|
+
}),
|
|
167
|
+
}),
|
|
168
|
+
}),
|
|
169
|
+
create(ModelParameterDefinitionSchema, {
|
|
170
|
+
id: "reasoning",
|
|
171
|
+
name: "Reasoning",
|
|
172
|
+
markdownTooltip: "Reasoning effort the model uses to generate its response.",
|
|
173
|
+
parameterType: create(ModelParameterDefinition_ModelParameterTypeSchema, {
|
|
174
|
+
enumParameter: create(ModelParameterDefinition_EnumParameterDefinitionSchema, {
|
|
175
|
+
values: [
|
|
176
|
+
create(ModelParameterDefinition_EnumParameterDefinition_EnumParameterValueSchema, { value: "none", displayName: "None" }),
|
|
177
|
+
create(ModelParameterDefinition_EnumParameterDefinition_EnumParameterValueSchema, { value: "low", displayName: "Low" }),
|
|
178
|
+
create(ModelParameterDefinition_EnumParameterDefinition_EnumParameterValueSchema, { value: "medium", displayName: "Medium" }),
|
|
179
|
+
create(ModelParameterDefinition_EnumParameterDefinition_EnumParameterValueSchema, { value: "high", displayName: "High" }),
|
|
180
|
+
create(ModelParameterDefinition_EnumParameterDefinition_EnumParameterValueSchema, { value: "extra-high", displayName: "Extra High" }),
|
|
181
|
+
],
|
|
182
|
+
}),
|
|
183
|
+
}),
|
|
184
|
+
isCycleableByHotkey: true,
|
|
185
|
+
}),
|
|
186
|
+
create(ModelParameterDefinitionSchema, {
|
|
187
|
+
id: "fast",
|
|
188
|
+
name: "Fast",
|
|
189
|
+
markdownTooltip: "Use the provider's fast lane when supported.",
|
|
190
|
+
parameterType: create(ModelParameterDefinition_ModelParameterTypeSchema, {
|
|
191
|
+
booleanParameter: {
|
|
192
|
+
values: [{ value: "false" }, { value: "true", displayName: "Fast" }],
|
|
193
|
+
},
|
|
194
|
+
}),
|
|
195
|
+
}),
|
|
196
|
+
];
|
|
197
|
+
}
|
|
198
|
+
function localParameterValues(isMaxMode) {
|
|
199
|
+
return [
|
|
200
|
+
create(AgentV1_RequestedModel_ModelParameterValueSchema, {
|
|
201
|
+
id: "context",
|
|
202
|
+
value: isMaxMode ? "1m" : "272k",
|
|
203
|
+
}),
|
|
204
|
+
create(AgentV1_RequestedModel_ModelParameterValueSchema, {
|
|
205
|
+
id: "reasoning",
|
|
206
|
+
value: "medium",
|
|
207
|
+
}),
|
|
208
|
+
create(AgentV1_RequestedModel_ModelParameterValueSchema, {
|
|
209
|
+
id: "fast",
|
|
210
|
+
value: "false",
|
|
211
|
+
}),
|
|
212
|
+
];
|
|
213
|
+
}
|
|
214
|
+
function uniqueStrings(values) {
|
|
215
|
+
return Array.from(new Set(values.filter((value) => value.length > 0)));
|
|
216
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { create, fromBinary, toBinary } from "@bufbuild/protobuf";
|
|
2
|
+
import { AgentUrlConfigSchema, GetServerConfigResponseSchema, Http2Config, } from "../gen/aiserver/v1/aiserver_pb.js";
|
|
3
|
+
export function rewriteServerConfigAgentUrls(upstreamPayload, bridgeOrigin) {
|
|
4
|
+
const upstream = fromBinary(GetServerConfigResponseSchema, upstreamPayload);
|
|
5
|
+
return encodeServerConfig(upstream, bridgeOrigin);
|
|
6
|
+
}
|
|
7
|
+
export function buildLocalServerConfig(bridgeOrigin) {
|
|
8
|
+
return encodeServerConfig(create(GetServerConfigResponseSchema), bridgeOrigin);
|
|
9
|
+
}
|
|
10
|
+
function encodeServerConfig(upstream, bridgeOrigin) {
|
|
11
|
+
return Buffer.from(toBinary(GetServerConfigResponseSchema, {
|
|
12
|
+
...upstream,
|
|
13
|
+
http2Config: Http2Config.HTTP2_CONFIG_FORCE_ALL_DISABLED,
|
|
14
|
+
agentUrlConfig: create(AgentUrlConfigSchema, {
|
|
15
|
+
agentUrl: bridgeOrigin,
|
|
16
|
+
agentnUrl: bridgeOrigin,
|
|
17
|
+
}),
|
|
18
|
+
}));
|
|
19
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { ArtifactWriter, ScenarioResult } from "./types.js";
|
|
2
|
+
export declare class FileArtifactStore implements ArtifactWriter {
|
|
3
|
+
readonly rootDir: string;
|
|
4
|
+
constructor(rootDir: string);
|
|
5
|
+
pathFor(name: string): string;
|
|
6
|
+
writeText(name: string, contents: string): string;
|
|
7
|
+
writeJson(name: string, contents: unknown): string;
|
|
8
|
+
}
|
|
9
|
+
export declare function createRunDirectory(options: {
|
|
10
|
+
cwd: string;
|
|
11
|
+
artifactsDir?: string;
|
|
12
|
+
now?: Date;
|
|
13
|
+
}): string;
|
|
14
|
+
export declare function writeRunSummary(artifacts: ArtifactWriter, results: ScenarioResult[]): Record<string, string>;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
export class FileArtifactStore {
|
|
4
|
+
rootDir;
|
|
5
|
+
constructor(rootDir) {
|
|
6
|
+
this.rootDir = rootDir;
|
|
7
|
+
fs.mkdirSync(rootDir, { recursive: true });
|
|
8
|
+
}
|
|
9
|
+
pathFor(name) {
|
|
10
|
+
return path.join(this.rootDir, sanitizeArtifactName(name));
|
|
11
|
+
}
|
|
12
|
+
writeText(name, contents) {
|
|
13
|
+
const artifactPath = this.pathFor(name);
|
|
14
|
+
fs.mkdirSync(path.dirname(artifactPath), { recursive: true });
|
|
15
|
+
fs.writeFileSync(artifactPath, contents);
|
|
16
|
+
return artifactPath;
|
|
17
|
+
}
|
|
18
|
+
writeJson(name, contents) {
|
|
19
|
+
return this.writeText(name, `${JSON.stringify(contents, null, 2)}\n`);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
export function createRunDirectory(options) {
|
|
23
|
+
if (options.artifactsDir !== undefined) {
|
|
24
|
+
return path.resolve(options.cwd, options.artifactsDir);
|
|
25
|
+
}
|
|
26
|
+
const timestamp = (options.now ?? new Date())
|
|
27
|
+
.toISOString()
|
|
28
|
+
.replace(/[:.]/g, "-");
|
|
29
|
+
return path.join(options.cwd, ".cursor-rpc", "test-runs", timestamp);
|
|
30
|
+
}
|
|
31
|
+
export function writeRunSummary(artifacts, results) {
|
|
32
|
+
return {
|
|
33
|
+
json: artifacts.writeJson("summary.json", {
|
|
34
|
+
status: results.some((result) => result.status === "failed")
|
|
35
|
+
? "failed"
|
|
36
|
+
: "passed",
|
|
37
|
+
results,
|
|
38
|
+
}),
|
|
39
|
+
markdown: artifacts.writeText("summary.md", renderMarkdownSummary(results)),
|
|
40
|
+
junit: artifacts.writeText("junit.xml", renderJunit(results)),
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
function renderMarkdownSummary(results) {
|
|
44
|
+
const lines = ["# Harness Summary", ""];
|
|
45
|
+
for (const result of results) {
|
|
46
|
+
lines.push(`- ${result.status.toUpperCase()} ${result.id}: ${result.message}`);
|
|
47
|
+
}
|
|
48
|
+
lines.push("");
|
|
49
|
+
return lines.join("\n");
|
|
50
|
+
}
|
|
51
|
+
function renderJunit(results) {
|
|
52
|
+
const failures = results.filter((result) => result.status === "failed");
|
|
53
|
+
const skipped = results.filter((result) => result.status === "skipped");
|
|
54
|
+
const testcases = results
|
|
55
|
+
.map((result) => {
|
|
56
|
+
const seconds = (result.durationMs / 1000).toFixed(3);
|
|
57
|
+
if (result.status === "failed") {
|
|
58
|
+
return ` <testcase classname="cursor-rpc.harness" name="${escapeXml(result.id)}" time="${seconds}"><failure message="${escapeXml(result.message)}">${escapeXml(result.failureCode ?? "command_failed")}</failure></testcase>`;
|
|
59
|
+
}
|
|
60
|
+
if (result.status === "skipped") {
|
|
61
|
+
return ` <testcase classname="cursor-rpc.harness" name="${escapeXml(result.id)}" time="${seconds}"><skipped message="${escapeXml(result.message)}" /></testcase>`;
|
|
62
|
+
}
|
|
63
|
+
return ` <testcase classname="cursor-rpc.harness" name="${escapeXml(result.id)}" time="${seconds}" />`;
|
|
64
|
+
})
|
|
65
|
+
.join("\n");
|
|
66
|
+
return [
|
|
67
|
+
`<?xml version="1.0" encoding="UTF-8"?>`,
|
|
68
|
+
`<testsuite name="cursor-rpc-harness" tests="${results.length}" failures="${failures.length}" skipped="${skipped.length}">`,
|
|
69
|
+
testcases,
|
|
70
|
+
`</testsuite>`,
|
|
71
|
+
"",
|
|
72
|
+
].join("\n");
|
|
73
|
+
}
|
|
74
|
+
function sanitizeArtifactName(name) {
|
|
75
|
+
return name
|
|
76
|
+
.split("/")
|
|
77
|
+
.map((part) => {
|
|
78
|
+
if (part === "." || part === ".." || part.length === 0) {
|
|
79
|
+
return "_";
|
|
80
|
+
}
|
|
81
|
+
return part.replace(/[^A-Za-z0-9._-]/g, "_");
|
|
82
|
+
})
|
|
83
|
+
.join("/");
|
|
84
|
+
}
|
|
85
|
+
function escapeXml(value) {
|
|
86
|
+
return value
|
|
87
|
+
.replace(/&/g, "&")
|
|
88
|
+
.replace(/</g, "<")
|
|
89
|
+
.replace(/>/g, ">")
|
|
90
|
+
.replace(/"/g, """)
|
|
91
|
+
.replace(/'/g, "'");
|
|
92
|
+
}
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { FileArtifactStore, createRunDirectory, writeRunSummary, } from "./artifacts.js";
|
|
4
|
+
import { ChildProcessRunner } from "./processRunner.js";
|
|
5
|
+
import { ScenarioRunner } from "./runner.js";
|
|
6
|
+
import { createScenarios } from "./scenarios.js";
|
|
7
|
+
const HELP = `cursorkit test harness
|
|
8
|
+
|
|
9
|
+
Usage:
|
|
10
|
+
pnpm test:harness -- --suite static
|
|
11
|
+
pnpm test:harness -- --suite traffic
|
|
12
|
+
pnpm test:harness -- --suite acp
|
|
13
|
+
pnpm test:harness -- --suite local-backend --base-url http://127.0.0.1:8080/v1 --model mlx-community/Qwen3.5-4B-8bit
|
|
14
|
+
pnpm test:harness -- --suite desktop --use-default-profile --timeout-ms 30000
|
|
15
|
+
|
|
16
|
+
Options:
|
|
17
|
+
--suite <name> Suite to run; repeat or comma-separate. Values: all, static, bridge-protocol, local-backend, cursor-agent, cursor-agent-traffic, traffic, acp, cli, desktop, desktop-route
|
|
18
|
+
--base-url <url> OpenAI-compatible base URL
|
|
19
|
+
--model <id> Model ID for local backend/desktop scenarios
|
|
20
|
+
--provider-model <id> OpenAI-compatible backend model ID when different from --model
|
|
21
|
+
--display-name <name> Cursor-facing display name
|
|
22
|
+
--api-key <key> API key for local backend requests
|
|
23
|
+
--timeout-ms <ms> Per-scenario timeout
|
|
24
|
+
--use-default-profile Desktop: reuse signed-in Cursor profile
|
|
25
|
+
--include-experimental Include ACP/UI experimental suites with all
|
|
26
|
+
--artifacts-dir <path> Artifact output directory
|
|
27
|
+
`;
|
|
28
|
+
export function parseHarnessArgs(argv, env = process.env, cwd = process.cwd()) {
|
|
29
|
+
const suites = [];
|
|
30
|
+
let baseUrl = env.MODEL_BASE_URL ?? "http://127.0.0.1:8080/v1";
|
|
31
|
+
let model = env.MODEL_NAME ?? "mlx-community/Qwen3.5-4B-8bit";
|
|
32
|
+
let providerModel = env.MODEL_PROVIDER_MODEL ?? model;
|
|
33
|
+
let providerModelExplicit = env.MODEL_PROVIDER_MODEL !== undefined;
|
|
34
|
+
let displayName = env.MODEL_DISPLAY_NAME ?? "local-qwen";
|
|
35
|
+
let apiKey = env.MODEL_API_KEY ?? "local";
|
|
36
|
+
let timeoutMs = 30_000;
|
|
37
|
+
let useDefaultProfile = false;
|
|
38
|
+
let includeExperimental = false;
|
|
39
|
+
let artifactsDir;
|
|
40
|
+
const args = argv.slice(2);
|
|
41
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
42
|
+
const arg = args[index];
|
|
43
|
+
switch (arg) {
|
|
44
|
+
case "--":
|
|
45
|
+
break;
|
|
46
|
+
case "--suite": {
|
|
47
|
+
const value = requiredValue(args, index, arg);
|
|
48
|
+
suites.push(...parseSuites(value));
|
|
49
|
+
index += 1;
|
|
50
|
+
break;
|
|
51
|
+
}
|
|
52
|
+
case "--base-url":
|
|
53
|
+
baseUrl = requiredValue(args, index, arg);
|
|
54
|
+
index += 1;
|
|
55
|
+
break;
|
|
56
|
+
case "--model":
|
|
57
|
+
model = requiredValue(args, index, arg);
|
|
58
|
+
if (!providerModelExplicit) {
|
|
59
|
+
providerModel = model;
|
|
60
|
+
}
|
|
61
|
+
index += 1;
|
|
62
|
+
break;
|
|
63
|
+
case "--provider-model":
|
|
64
|
+
providerModel = requiredValue(args, index, arg);
|
|
65
|
+
providerModelExplicit = true;
|
|
66
|
+
index += 1;
|
|
67
|
+
break;
|
|
68
|
+
case "--display-name":
|
|
69
|
+
displayName = requiredValue(args, index, arg);
|
|
70
|
+
index += 1;
|
|
71
|
+
break;
|
|
72
|
+
case "--api-key":
|
|
73
|
+
apiKey = requiredValue(args, index, arg);
|
|
74
|
+
index += 1;
|
|
75
|
+
break;
|
|
76
|
+
case "--timeout-ms":
|
|
77
|
+
timeoutMs = parsePositiveInteger(requiredValue(args, index, arg), arg);
|
|
78
|
+
index += 1;
|
|
79
|
+
break;
|
|
80
|
+
case "--use-default-profile":
|
|
81
|
+
useDefaultProfile = true;
|
|
82
|
+
break;
|
|
83
|
+
case "--include-experimental":
|
|
84
|
+
includeExperimental = true;
|
|
85
|
+
break;
|
|
86
|
+
case "--artifacts-dir":
|
|
87
|
+
artifactsDir = requiredValue(args, index, arg);
|
|
88
|
+
index += 1;
|
|
89
|
+
break;
|
|
90
|
+
case "--help":
|
|
91
|
+
case "-h":
|
|
92
|
+
throw new HelpRequested();
|
|
93
|
+
default:
|
|
94
|
+
throw new Error(`Unknown harness argument: ${arg}\n\n${HELP}`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return {
|
|
98
|
+
suites: suites.length > 0 ? suites : ["all"],
|
|
99
|
+
cwd,
|
|
100
|
+
artifactsDir,
|
|
101
|
+
baseUrl,
|
|
102
|
+
model,
|
|
103
|
+
providerModel,
|
|
104
|
+
displayName,
|
|
105
|
+
apiKey,
|
|
106
|
+
timeoutMs,
|
|
107
|
+
useDefaultProfile,
|
|
108
|
+
includeExperimental,
|
|
109
|
+
env,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
export async function runHarnessCli(argv = process.argv) {
|
|
113
|
+
let options;
|
|
114
|
+
try {
|
|
115
|
+
options = parseHarnessArgs(argv);
|
|
116
|
+
}
|
|
117
|
+
catch (error) {
|
|
118
|
+
if (error instanceof HelpRequested) {
|
|
119
|
+
console.log(HELP);
|
|
120
|
+
return 0;
|
|
121
|
+
}
|
|
122
|
+
throw error;
|
|
123
|
+
}
|
|
124
|
+
const runDir = createRunDirectory({
|
|
125
|
+
cwd: options.cwd,
|
|
126
|
+
artifactsDir: options.artifactsDir,
|
|
127
|
+
});
|
|
128
|
+
const artifacts = new FileArtifactStore(runDir);
|
|
129
|
+
const processRunner = new ChildProcessRunner(options.cwd, artifacts);
|
|
130
|
+
const runner = new ScenarioRunner(createScenarios(), artifacts, processRunner);
|
|
131
|
+
const results = await runner.run(options);
|
|
132
|
+
const summaryPaths = writeRunSummary(artifacts, results);
|
|
133
|
+
printSummary(results, summaryPaths.json);
|
|
134
|
+
return results.some((result) => result.status === "failed") ? 1 : 0;
|
|
135
|
+
}
|
|
136
|
+
class HelpRequested extends Error {
|
|
137
|
+
}
|
|
138
|
+
function parseSuites(value) {
|
|
139
|
+
return value
|
|
140
|
+
.split(",")
|
|
141
|
+
.map((suite) => suite.trim())
|
|
142
|
+
.filter((suite) => suite.length > 0)
|
|
143
|
+
.map((suite) => {
|
|
144
|
+
if (isHarnessSuiteInput(suite)) {
|
|
145
|
+
return suite;
|
|
146
|
+
}
|
|
147
|
+
throw new Error(`Unknown suite: ${suite}`);
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
function isHarnessSuiteInput(value) {
|
|
151
|
+
return [
|
|
152
|
+
"all",
|
|
153
|
+
"acp",
|
|
154
|
+
"cli",
|
|
155
|
+
"desktop",
|
|
156
|
+
"static",
|
|
157
|
+
"bridge-protocol",
|
|
158
|
+
"local-backend",
|
|
159
|
+
"cursor-agent",
|
|
160
|
+
"cursor-agent-traffic",
|
|
161
|
+
"cursor-agent-acp-experimental",
|
|
162
|
+
"desktop-route",
|
|
163
|
+
"desktop-ui-experimental",
|
|
164
|
+
"traffic",
|
|
165
|
+
].includes(value);
|
|
166
|
+
}
|
|
167
|
+
function requiredValue(args, index, name) {
|
|
168
|
+
const value = args[index + 1];
|
|
169
|
+
if (value === undefined || value.startsWith("--")) {
|
|
170
|
+
throw new Error(`${name} requires a value`);
|
|
171
|
+
}
|
|
172
|
+
return value;
|
|
173
|
+
}
|
|
174
|
+
function parsePositiveInteger(value, name) {
|
|
175
|
+
const parsed = Number(value);
|
|
176
|
+
if (!Number.isInteger(parsed) || parsed <= 0) {
|
|
177
|
+
throw new Error(`${name} must be a positive integer`);
|
|
178
|
+
}
|
|
179
|
+
return parsed;
|
|
180
|
+
}
|
|
181
|
+
function printSummary(results, summaryPath) {
|
|
182
|
+
console.log("Harness Summary");
|
|
183
|
+
for (const result of results) {
|
|
184
|
+
console.log(`${result.status.toUpperCase()} ${result.id}: ${result.message}`);
|
|
185
|
+
}
|
|
186
|
+
console.log(`Artifacts: ${path.dirname(summaryPath)}`);
|
|
187
|
+
}
|
|
188
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
189
|
+
runHarnessCli().then((exitCode) => {
|
|
190
|
+
process.exitCode = exitCode;
|
|
191
|
+
});
|
|
192
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { FailureCode } from "./types.js";
|
|
2
|
+
export interface LocalBackendProbeOptions {
|
|
3
|
+
baseUrl: string;
|
|
4
|
+
model: string;
|
|
5
|
+
apiKey: string;
|
|
6
|
+
timeoutMs: number;
|
|
7
|
+
checkToolCalls?: boolean;
|
|
8
|
+
}
|
|
9
|
+
export interface LocalBackendProbeReport {
|
|
10
|
+
ok: boolean;
|
|
11
|
+
failureCode?: FailureCode;
|
|
12
|
+
message: string;
|
|
13
|
+
modelsStatus?: number;
|
|
14
|
+
chatStatus?: number;
|
|
15
|
+
streamingChatStatus?: number;
|
|
16
|
+
toolChatStatus?: number;
|
|
17
|
+
models: string[];
|
|
18
|
+
selectedModelFound?: boolean;
|
|
19
|
+
nonStreamingSupported?: boolean;
|
|
20
|
+
streamingSupported?: boolean;
|
|
21
|
+
toolCallsSupported?: boolean;
|
|
22
|
+
completionPreview?: string;
|
|
23
|
+
}
|
|
24
|
+
export declare function probeLocalBackend(options: LocalBackendProbeOptions): Promise<LocalBackendProbeReport>;
|