pi-cursor-sdk 0.5.3 → 0.5.4
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/CHANGELOG.md +40 -1
- package/README.md +62 -816
- package/dist/context.js +11 -5
- package/dist/cursor-agents-context.js +6 -6
- package/dist/cursor-api-key.js +1 -1
- package/dist/cursor-bridge-contract.js +1 -13
- package/dist/cursor-cloud-local-state.js +0 -1
- package/dist/cursor-config.js +1 -17
- package/dist/cursor-fallback-models.generated.js +264 -1
- package/dist/cursor-live-run-coordinator.js +1 -0
- package/dist/cursor-mcp-timeout-override.js +6 -17
- package/dist/cursor-native-tool-display-replay.js +5 -5
- package/dist/cursor-native-tools.js +11 -0
- package/dist/cursor-pi-tool-bridge-env.js +25 -2
- package/dist/cursor-pi-tool-bridge-server.js +98 -48
- package/dist/cursor-pi-tool-bridge-snapshot.js +1 -1
- package/dist/cursor-pi-tool-bridge.js +4 -12
- package/dist/cursor-provider-errors.js +0 -6
- package/dist/cursor-provider-live-run-drain.js +8 -3
- package/dist/cursor-provider-overflow.js +7 -21
- package/dist/cursor-provider-turn-prepare.js +8 -1
- package/dist/cursor-provider-turn-runner.js +17 -6
- package/dist/cursor-provider.js +1 -4
- package/dist/cursor-runtime-state.js +3 -3
- package/dist/cursor-sdk-event-debug.js +2 -2
- package/dist/cursor-sdk-output-filter.js +1 -1
- package/dist/cursor-sdk-process-error-guard.js +1 -2
- package/dist/cursor-session-agent.js +6 -8
- package/dist/cursor-session-scope.js +1 -7
- package/dist/cursor-session-store.js +1 -1
- package/dist/cursor-state.js +9 -23
- package/dist/cursor-tool-lifecycle.js +0 -3
- package/dist/cursor-tool-manifest.js +3 -1
- package/dist/cursor-tool-presentation-registry.js +2 -4
- package/dist/cursor-tool-visibility.js +0 -3
- package/dist/cursor-transcript-tool-specs.js +1 -2
- package/dist/index.js +21 -0
- package/dist/model-discovery.js +0 -1
- package/dist/model-list-cache.js +4 -9
- package/docs/cursor-live-smoke-checklist.md +3 -3
- package/docs/cursor-model-ux-spec.md +16 -8
- package/docs/cursor-native-tool-replay.md +2 -2
- package/docs/cursor-native-tool-visual-audit.md +1 -1
- package/docs/cursor-testing-lessons.md +15 -3
- package/docs/cursor-tool-surfaces.md +32 -3
- package/docs/development.md +93 -0
- package/docs/platform-smoke-implementation.md +3 -2
- package/docs/reference.md +806 -0
- package/package.json +6 -4
- package/scripts/platform-smoke/artifact-secrets.mjs +13 -4
- package/scripts/platform-smoke/local-resume-runner.mjs +0 -1
- package/scripts/platform-smoke/target-runtime.mjs +4 -2
- package/scripts/platform-smoke/targets.mjs +0 -1
- package/shared/cursor-sdk-output-filter.d.mts +2 -0
- package/shared/cursor-sdk-output-filter.mjs +92 -28
- package/shared/cursor-sensitive-text.d.mts +1 -0
- package/shared/cursor-sensitive-text.mjs +7 -0
- package/src/context.ts +16 -9
- package/src/cursor-agents-context.ts +6 -6
- package/src/cursor-api-key.ts +1 -1
- package/src/cursor-bridge-contract.ts +1 -15
- package/src/cursor-cloud-local-state.ts +0 -2
- package/src/cursor-config.ts +1 -24
- package/src/cursor-fallback-models.generated.ts +264 -1
- package/src/cursor-live-run-coordinator.ts +5 -0
- package/src/cursor-mcp-timeout-override.ts +6 -30
- package/src/cursor-native-tool-display-replay.ts +4 -6
- package/src/cursor-native-tools.ts +14 -0
- package/src/cursor-pi-tool-bridge-env.ts +31 -2
- package/src/cursor-pi-tool-bridge-server.ts +89 -45
- package/src/cursor-pi-tool-bridge-snapshot.ts +0 -1
- package/src/cursor-pi-tool-bridge.ts +2 -18
- package/src/cursor-provider-errors.ts +0 -8
- package/src/cursor-provider-live-run-drain.ts +8 -2
- package/src/cursor-provider-overflow.ts +6 -24
- package/src/cursor-provider-turn-prepare.ts +10 -1
- package/src/cursor-provider-turn-runner.ts +17 -6
- package/src/cursor-provider.ts +0 -5
- package/src/cursor-runtime-state.ts +3 -3
- package/src/cursor-sdk-event-debug.ts +2 -2
- package/src/cursor-sdk-output-filter.ts +2 -1
- package/src/cursor-sdk-process-error-guard.ts +1 -3
- package/src/cursor-session-agent.ts +8 -8
- package/src/cursor-session-scope.ts +1 -9
- package/src/cursor-session-store.ts +1 -1
- package/src/cursor-state.ts +9 -36
- package/src/cursor-tool-lifecycle.ts +0 -4
- package/src/cursor-tool-manifest.ts +4 -1
- package/src/cursor-tool-presentation-registry.ts +3 -5
- package/src/cursor-tool-visibility.ts +0 -4
- package/src/cursor-transcript-tool-specs.ts +0 -3
- package/src/index.ts +18 -0
- package/src/model-discovery.ts +0 -1
- package/src/model-list-cache.ts +4 -10
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ModelListItem } from "@cursor/sdk";
|
|
2
2
|
|
|
3
|
-
// Generated with @cursor/sdk@1.0.37 from
|
|
3
|
+
// Generated with @cursor/sdk@1.0.37 from 48 Cursor models.
|
|
4
4
|
// Refresh with: npm run refresh:cursor-snapshots -- --write
|
|
5
5
|
// Do not add secrets; this file stores public model metadata only.
|
|
6
6
|
export const FALLBACK_MODEL_ITEMS = [
|
|
@@ -6518,6 +6518,60 @@ export const FALLBACK_MODEL_ITEMS = [
|
|
|
6518
6518
|
}
|
|
6519
6519
|
]
|
|
6520
6520
|
},
|
|
6521
|
+
{
|
|
6522
|
+
id: "gemini-4-argon",
|
|
6523
|
+
displayName: "Gemini 4 Argon",
|
|
6524
|
+
parameters: [
|
|
6525
|
+
{
|
|
6526
|
+
id: "reasoning_effort",
|
|
6527
|
+
displayName: "Effort",
|
|
6528
|
+
values: [
|
|
6529
|
+
{
|
|
6530
|
+
value: "low",
|
|
6531
|
+
displayName: "Low"
|
|
6532
|
+
},
|
|
6533
|
+
{
|
|
6534
|
+
value: "medium",
|
|
6535
|
+
displayName: "Medium"
|
|
6536
|
+
},
|
|
6537
|
+
{
|
|
6538
|
+
value: "high",
|
|
6539
|
+
displayName: "High"
|
|
6540
|
+
}
|
|
6541
|
+
]
|
|
6542
|
+
}
|
|
6543
|
+
],
|
|
6544
|
+
variants: [
|
|
6545
|
+
{
|
|
6546
|
+
params: [
|
|
6547
|
+
{
|
|
6548
|
+
id: "reasoning_effort",
|
|
6549
|
+
value: "low"
|
|
6550
|
+
}
|
|
6551
|
+
],
|
|
6552
|
+
displayName: "Gemini 4 Argon Low"
|
|
6553
|
+
},
|
|
6554
|
+
{
|
|
6555
|
+
params: [
|
|
6556
|
+
{
|
|
6557
|
+
id: "reasoning_effort",
|
|
6558
|
+
value: "medium"
|
|
6559
|
+
}
|
|
6560
|
+
],
|
|
6561
|
+
displayName: "Gemini 4 Argon Medium",
|
|
6562
|
+
isDefault: true
|
|
6563
|
+
},
|
|
6564
|
+
{
|
|
6565
|
+
params: [
|
|
6566
|
+
{
|
|
6567
|
+
id: "reasoning_effort",
|
|
6568
|
+
value: "high"
|
|
6569
|
+
}
|
|
6570
|
+
],
|
|
6571
|
+
displayName: "Gemini 4 Argon High"
|
|
6572
|
+
}
|
|
6573
|
+
]
|
|
6574
|
+
},
|
|
6521
6575
|
{
|
|
6522
6576
|
id: "glm-5.2",
|
|
6523
6577
|
displayName: "GLM 5.2",
|
|
@@ -10157,5 +10211,214 @@ export const FALLBACK_MODEL_ITEMS = [
|
|
|
10157
10211
|
displayName: "Muse Spark 1.3"
|
|
10158
10212
|
}
|
|
10159
10213
|
]
|
|
10214
|
+
},
|
|
10215
|
+
{
|
|
10216
|
+
id: "muse-wonder-1.0",
|
|
10217
|
+
displayName: "Muse Wonder",
|
|
10218
|
+
parameters: [
|
|
10219
|
+
{
|
|
10220
|
+
id: "context",
|
|
10221
|
+
displayName: "Context",
|
|
10222
|
+
values: [
|
|
10223
|
+
{
|
|
10224
|
+
value: "300k",
|
|
10225
|
+
displayName: "300K"
|
|
10226
|
+
},
|
|
10227
|
+
{
|
|
10228
|
+
value: "1m",
|
|
10229
|
+
displayName: "1M"
|
|
10230
|
+
}
|
|
10231
|
+
]
|
|
10232
|
+
},
|
|
10233
|
+
{
|
|
10234
|
+
id: "reasoning_effort",
|
|
10235
|
+
displayName: "Reasoning",
|
|
10236
|
+
values: [
|
|
10237
|
+
{
|
|
10238
|
+
value: "minimal",
|
|
10239
|
+
displayName: "Minimal"
|
|
10240
|
+
},
|
|
10241
|
+
{
|
|
10242
|
+
value: "low",
|
|
10243
|
+
displayName: "Low"
|
|
10244
|
+
},
|
|
10245
|
+
{
|
|
10246
|
+
value: "medium",
|
|
10247
|
+
displayName: "Medium"
|
|
10248
|
+
},
|
|
10249
|
+
{
|
|
10250
|
+
value: "high",
|
|
10251
|
+
displayName: "High"
|
|
10252
|
+
},
|
|
10253
|
+
{
|
|
10254
|
+
value: "xhigh",
|
|
10255
|
+
displayName: "Extra High"
|
|
10256
|
+
},
|
|
10257
|
+
{
|
|
10258
|
+
value: "max",
|
|
10259
|
+
displayName: "Max"
|
|
10260
|
+
}
|
|
10261
|
+
]
|
|
10262
|
+
}
|
|
10263
|
+
],
|
|
10264
|
+
variants: [
|
|
10265
|
+
{
|
|
10266
|
+
params: [
|
|
10267
|
+
{
|
|
10268
|
+
id: "context",
|
|
10269
|
+
value: "300k"
|
|
10270
|
+
},
|
|
10271
|
+
{
|
|
10272
|
+
id: "reasoning_effort",
|
|
10273
|
+
value: "minimal"
|
|
10274
|
+
}
|
|
10275
|
+
],
|
|
10276
|
+
displayName: "Muse Wonder Minimal"
|
|
10277
|
+
},
|
|
10278
|
+
{
|
|
10279
|
+
params: [
|
|
10280
|
+
{
|
|
10281
|
+
id: "context",
|
|
10282
|
+
value: "300k"
|
|
10283
|
+
},
|
|
10284
|
+
{
|
|
10285
|
+
id: "reasoning_effort",
|
|
10286
|
+
value: "low"
|
|
10287
|
+
}
|
|
10288
|
+
],
|
|
10289
|
+
displayName: "Muse Wonder Low"
|
|
10290
|
+
},
|
|
10291
|
+
{
|
|
10292
|
+
params: [
|
|
10293
|
+
{
|
|
10294
|
+
id: "context",
|
|
10295
|
+
value: "300k"
|
|
10296
|
+
},
|
|
10297
|
+
{
|
|
10298
|
+
id: "reasoning_effort",
|
|
10299
|
+
value: "medium"
|
|
10300
|
+
}
|
|
10301
|
+
],
|
|
10302
|
+
displayName: "Muse Wonder Medium"
|
|
10303
|
+
},
|
|
10304
|
+
{
|
|
10305
|
+
params: [
|
|
10306
|
+
{
|
|
10307
|
+
id: "context",
|
|
10308
|
+
value: "300k"
|
|
10309
|
+
},
|
|
10310
|
+
{
|
|
10311
|
+
id: "reasoning_effort",
|
|
10312
|
+
value: "high"
|
|
10313
|
+
}
|
|
10314
|
+
],
|
|
10315
|
+
displayName: "Muse Wonder High"
|
|
10316
|
+
},
|
|
10317
|
+
{
|
|
10318
|
+
params: [
|
|
10319
|
+
{
|
|
10320
|
+
id: "context",
|
|
10321
|
+
value: "300k"
|
|
10322
|
+
},
|
|
10323
|
+
{
|
|
10324
|
+
id: "reasoning_effort",
|
|
10325
|
+
value: "xhigh"
|
|
10326
|
+
}
|
|
10327
|
+
],
|
|
10328
|
+
displayName: "Muse Wonder Extra High"
|
|
10329
|
+
},
|
|
10330
|
+
{
|
|
10331
|
+
params: [
|
|
10332
|
+
{
|
|
10333
|
+
id: "context",
|
|
10334
|
+
value: "300k"
|
|
10335
|
+
},
|
|
10336
|
+
{
|
|
10337
|
+
id: "reasoning_effort",
|
|
10338
|
+
value: "max"
|
|
10339
|
+
}
|
|
10340
|
+
],
|
|
10341
|
+
displayName: "Muse Wonder Max"
|
|
10342
|
+
},
|
|
10343
|
+
{
|
|
10344
|
+
params: [
|
|
10345
|
+
{
|
|
10346
|
+
id: "context",
|
|
10347
|
+
value: "1m"
|
|
10348
|
+
},
|
|
10349
|
+
{
|
|
10350
|
+
id: "reasoning_effort",
|
|
10351
|
+
value: "minimal"
|
|
10352
|
+
}
|
|
10353
|
+
],
|
|
10354
|
+
displayName: "Muse Wonder 1M Minimal"
|
|
10355
|
+
},
|
|
10356
|
+
{
|
|
10357
|
+
params: [
|
|
10358
|
+
{
|
|
10359
|
+
id: "context",
|
|
10360
|
+
value: "1m"
|
|
10361
|
+
},
|
|
10362
|
+
{
|
|
10363
|
+
id: "reasoning_effort",
|
|
10364
|
+
value: "low"
|
|
10365
|
+
}
|
|
10366
|
+
],
|
|
10367
|
+
displayName: "Muse Wonder 1M Low"
|
|
10368
|
+
},
|
|
10369
|
+
{
|
|
10370
|
+
params: [
|
|
10371
|
+
{
|
|
10372
|
+
id: "context",
|
|
10373
|
+
value: "1m"
|
|
10374
|
+
},
|
|
10375
|
+
{
|
|
10376
|
+
id: "reasoning_effort",
|
|
10377
|
+
value: "medium"
|
|
10378
|
+
}
|
|
10379
|
+
],
|
|
10380
|
+
displayName: "Muse Wonder 1M Medium"
|
|
10381
|
+
},
|
|
10382
|
+
{
|
|
10383
|
+
params: [
|
|
10384
|
+
{
|
|
10385
|
+
id: "context",
|
|
10386
|
+
value: "1m"
|
|
10387
|
+
},
|
|
10388
|
+
{
|
|
10389
|
+
id: "reasoning_effort",
|
|
10390
|
+
value: "high"
|
|
10391
|
+
}
|
|
10392
|
+
],
|
|
10393
|
+
displayName: "Muse Wonder 1M High"
|
|
10394
|
+
},
|
|
10395
|
+
{
|
|
10396
|
+
params: [
|
|
10397
|
+
{
|
|
10398
|
+
id: "context",
|
|
10399
|
+
value: "1m"
|
|
10400
|
+
},
|
|
10401
|
+
{
|
|
10402
|
+
id: "reasoning_effort",
|
|
10403
|
+
value: "xhigh"
|
|
10404
|
+
}
|
|
10405
|
+
],
|
|
10406
|
+
displayName: "Muse Wonder 1M Extra High",
|
|
10407
|
+
isDefault: true
|
|
10408
|
+
},
|
|
10409
|
+
{
|
|
10410
|
+
params: [
|
|
10411
|
+
{
|
|
10412
|
+
id: "context",
|
|
10413
|
+
value: "1m"
|
|
10414
|
+
},
|
|
10415
|
+
{
|
|
10416
|
+
id: "reasoning_effort",
|
|
10417
|
+
value: "max"
|
|
10418
|
+
}
|
|
10419
|
+
],
|
|
10420
|
+
displayName: "Muse Wonder 1M Max"
|
|
10421
|
+
}
|
|
10422
|
+
]
|
|
10160
10423
|
}
|
|
10161
10424
|
] satisfies ModelListItem[];
|
|
@@ -39,6 +39,8 @@ export interface CursorLiveSdkRun {
|
|
|
39
39
|
export interface CursorLiveRun {
|
|
40
40
|
id: string;
|
|
41
41
|
agent: SDKAgent;
|
|
42
|
+
/** Effective ordinary main-loop policy captured when this SDK run was created. */
|
|
43
|
+
nativeToolsEnabled?: boolean;
|
|
42
44
|
bridgeRun?: CursorPiToolBridgeRun;
|
|
43
45
|
sessionBridgeRun?: CursorPiToolBridgeRun;
|
|
44
46
|
sessionAgentScopeKey: string;
|
|
@@ -65,6 +67,8 @@ export interface CursorLiveRun {
|
|
|
65
67
|
export interface CursorLiveRunCreateParams {
|
|
66
68
|
id: string;
|
|
67
69
|
agent: SDKAgent;
|
|
70
|
+
/** Effective ordinary main-loop policy captured when this SDK run was created. */
|
|
71
|
+
nativeToolsEnabled?: boolean;
|
|
68
72
|
bridgeRun?: CursorPiToolBridgeRun;
|
|
69
73
|
sessionBridgeRun?: CursorPiToolBridgeRun;
|
|
70
74
|
sessionAgentScopeKey?: string;
|
|
@@ -293,6 +297,7 @@ export function createCursorLiveRunCoordinator(deps: CursorLiveRunCoordinatorDep
|
|
|
293
297
|
const run: CursorLiveRun = {
|
|
294
298
|
id: params.id,
|
|
295
299
|
agent: params.agent,
|
|
300
|
+
nativeToolsEnabled: params.nativeToolsEnabled !== false,
|
|
296
301
|
bridgeRun: params.bridgeRun,
|
|
297
302
|
sessionBridgeRun: params.sessionBridgeRun,
|
|
298
303
|
sessionAgentScopeKey,
|
|
@@ -8,12 +8,6 @@ const CURSOR_MCP_TOOL_TIMEOUT_SECONDS_ENV = "PI_CURSOR_MCP_TOOL_TIMEOUT_SECONDS"
|
|
|
8
8
|
const CURSOR_MCP_CONNECT_TIMEOUT_MS_ENV = "PI_CURSOR_MCP_CONNECT_TIMEOUT_MS";
|
|
9
9
|
const CURSOR_MCP_CONNECT_TIMEOUT_SECONDS_ENV = "PI_CURSOR_MCP_CONNECT_TIMEOUT_SECONDS";
|
|
10
10
|
|
|
11
|
-
interface CursorMcpToolTimeoutOverrideOptions {
|
|
12
|
-
timeoutMs?: number;
|
|
13
|
-
connectTimeoutMs?: number;
|
|
14
|
-
env?: Record<string, string | undefined>;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
11
|
interface CursorMcpToolTimeoutOverrideState {
|
|
18
12
|
installed: boolean;
|
|
19
13
|
timeoutMs: number;
|
|
@@ -61,7 +55,7 @@ function normalizeConnectTimeoutMs(timeoutMs: number): number {
|
|
|
61
55
|
);
|
|
62
56
|
}
|
|
63
57
|
|
|
64
|
-
|
|
58
|
+
function resolveCursorMcpConnectTimeoutMs(
|
|
65
59
|
env: Record<string, string | undefined> = process.env,
|
|
66
60
|
): number {
|
|
67
61
|
const explicitMs = parsePositiveNumber(env[CURSOR_MCP_CONNECT_TIMEOUT_MS_ENV]);
|
|
@@ -81,7 +75,7 @@ function isCursorSdkMcpProtocolTimeoutStack(stack: string | undefined): boolean
|
|
|
81
75
|
);
|
|
82
76
|
}
|
|
83
77
|
|
|
84
|
-
|
|
78
|
+
function isCursorSdkMcpToolTimeoutStack(stack: string | undefined): boolean {
|
|
85
79
|
if (!stack) return false;
|
|
86
80
|
return (
|
|
87
81
|
isCursorSdkMcpProtocolTimeoutStack(stack) &&
|
|
@@ -89,7 +83,7 @@ export function isCursorSdkMcpToolTimeoutStack(stack: string | undefined): boole
|
|
|
89
83
|
);
|
|
90
84
|
}
|
|
91
85
|
|
|
92
|
-
|
|
86
|
+
function isCursorSdkMcpConnectTimeoutStack(stack: string | undefined): boolean {
|
|
93
87
|
if (!stack || !isCursorSdkMcpProtocolTimeoutStack(stack)) return false;
|
|
94
88
|
// The SDK minifies the Client class name; the protocol method and SDK
|
|
95
89
|
// frame, not that constructor's spelling, establish this timeout purpose.
|
|
@@ -121,15 +115,9 @@ function patchedSetTimeout(
|
|
|
121
115
|
return Reflect.apply(delegate, globalThis, [handler, nextDelay, ...args]) as ReturnType<GlobalSetTimeout>;
|
|
122
116
|
}
|
|
123
117
|
|
|
124
|
-
export function installCursorMcpToolTimeoutOverride(
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
installedToolTimeoutMs = normalizeOverrideTimeoutMs(
|
|
128
|
-
options.timeoutMs ?? resolveCursorMcpToolTimeoutMs(options.env),
|
|
129
|
-
);
|
|
130
|
-
installedConnectTimeoutMs = normalizeConnectTimeoutMs(
|
|
131
|
-
options.connectTimeoutMs ?? resolveCursorMcpConnectTimeoutMs(options.env),
|
|
132
|
-
);
|
|
118
|
+
export function installCursorMcpToolTimeoutOverride(): CursorMcpToolTimeoutOverrideState {
|
|
119
|
+
installedToolTimeoutMs = resolveCursorMcpToolTimeoutMs();
|
|
120
|
+
installedConnectTimeoutMs = resolveCursorMcpConnectTimeoutMs();
|
|
133
121
|
|
|
134
122
|
if (!originalSetTimeout) {
|
|
135
123
|
originalSetTimeout = globalThis.setTimeout;
|
|
@@ -152,15 +140,3 @@ export function restoreCursorMcpToolTimeoutOverride(): void {
|
|
|
152
140
|
installedToolTimeoutMs = DEFAULT_CURSOR_MCP_TOOL_TIMEOUT_MS;
|
|
153
141
|
installedConnectTimeoutMs = DEFAULT_CURSOR_MCP_CONNECT_TIMEOUT_MS;
|
|
154
142
|
}
|
|
155
|
-
|
|
156
|
-
export const cursorMcpToolTimeoutOverrideDefaults = {
|
|
157
|
-
cursorSdkDefaultTimeoutMs: CURSOR_SDK_MCP_DEFAULT_TIMEOUT_MS,
|
|
158
|
-
defaultOverrideTimeoutMs: DEFAULT_CURSOR_MCP_TOOL_TIMEOUT_MS,
|
|
159
|
-
defaultConnectTimeoutMs: DEFAULT_CURSOR_MCP_CONNECT_TIMEOUT_MS,
|
|
160
|
-
minConnectTimeoutMs: MIN_CURSOR_MCP_CONNECT_TIMEOUT_MS,
|
|
161
|
-
maxNodeTimerDelayMs: MAX_NODE_TIMER_DELAY_MS,
|
|
162
|
-
timeoutMsEnv: CURSOR_MCP_TOOL_TIMEOUT_MS_ENV,
|
|
163
|
-
timeoutSecondsEnv: CURSOR_MCP_TOOL_TIMEOUT_SECONDS_ENV,
|
|
164
|
-
connectTimeoutMsEnv: CURSOR_MCP_CONNECT_TIMEOUT_MS_ENV,
|
|
165
|
-
connectTimeoutSecondsEnv: CURSOR_MCP_CONNECT_TIMEOUT_SECONDS_ENV,
|
|
166
|
-
} as const;
|
|
@@ -34,14 +34,12 @@ export type {
|
|
|
34
34
|
} from "./cursor-replay-tool-details.js";
|
|
35
35
|
export {
|
|
36
36
|
isCursorReplayNativeEditDetails,
|
|
37
|
-
isCursorReplayGenerateImageDetails,
|
|
38
|
-
isCursorReplayActivityDetails,
|
|
39
37
|
isCursorReplayNativeWriteDetails,
|
|
40
38
|
parseCursorReplayToolDetails,
|
|
41
39
|
} from "./cursor-replay-tool-details.js";
|
|
42
40
|
|
|
43
41
|
export const CURSOR_REPLAY_COLLAPSED_PREVIEW_LINES = 8;
|
|
44
|
-
|
|
42
|
+
const CURSOR_REPLAY_PREVIEW_MAX_CHARS = 4000;
|
|
45
43
|
export const CURSOR_REPLAY_PREVIEW_MAX_LINE_CHARS = 240;
|
|
46
44
|
const CURSOR_REPLAY_HIGHLIGHT_MAX_CHARS = 12000;
|
|
47
45
|
export const cursorReplayToolSchema = Type.Object({}, { additionalProperties: true });
|
|
@@ -75,7 +73,7 @@ function buildImageReplayComponent(text: string, imageData: string, mimeType: st
|
|
|
75
73
|
};
|
|
76
74
|
}
|
|
77
75
|
|
|
78
|
-
|
|
76
|
+
function getCursorReplayPath(
|
|
79
77
|
args: Record<string, unknown> | undefined,
|
|
80
78
|
details: Pick<CursorReplayNativeEditDetails, "path"> | Pick<CursorReplayNativeWriteDetails, "path"> | undefined,
|
|
81
79
|
): string {
|
|
@@ -282,7 +280,7 @@ function formatCursorReplayActivityPreview(
|
|
|
282
280
|
return stripHeader ? formatCursorReplayPreview(text, theme, maxLines, true) : formatMutedBlock(text, theme);
|
|
283
281
|
}
|
|
284
282
|
|
|
285
|
-
|
|
283
|
+
function formatCursorReplayPreview(
|
|
286
284
|
text: string,
|
|
287
285
|
theme: CursorReplayRenderTheme,
|
|
288
286
|
maxLines = CURSOR_REPLAY_COLLAPSED_PREVIEW_LINES,
|
|
@@ -330,7 +328,7 @@ function getCursorReplayCardTitle(toolName: CursorReplayToolName, args: Record<s
|
|
|
330
328
|
return "Cursor activity";
|
|
331
329
|
}
|
|
332
330
|
|
|
333
|
-
|
|
331
|
+
function renderCursorReplayCall(
|
|
334
332
|
toolName: CursorReplayToolName,
|
|
335
333
|
args: Record<string, unknown> | undefined,
|
|
336
334
|
theme: CursorReplayRenderTheme,
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { parseEnvBoolean } from "./cursor-env-boolean.js";
|
|
2
|
+
|
|
3
|
+
export const CURSOR_NATIVE_TOOLS_ENV = "PI_CURSOR_NATIVE_TOOLS";
|
|
4
|
+
|
|
5
|
+
/** Default preserves the SDK toolset; false selects the public MCP-only main loop. */
|
|
6
|
+
export function resolveCursorNativeToolsEnabled(env: Record<string, string | undefined> = process.env): boolean {
|
|
7
|
+
return parseEnvBoolean(env[CURSOR_NATIVE_TOOLS_ENV], true);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function assertCursorNativeToolsRuntime(runtime: "local" | "cloud", nativeToolsEnabled: boolean): void {
|
|
11
|
+
if (runtime === "cloud" && !nativeToolsEnabled) {
|
|
12
|
+
throw new Error("PI_CURSOR_NATIVE_TOOLS=0 requires local runtime; Cursor Cloud does not support the SDK tools restriction. Select local runtime or remove the opt-out.");
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -2,8 +2,8 @@ import { parseEnvBoolean } from "./cursor-env-boolean.js";
|
|
|
2
2
|
import { resolveCursorMcpToolTimeoutMs } from "./cursor-mcp-timeout-override.js";
|
|
3
3
|
|
|
4
4
|
export const CURSOR_PI_TOOL_BRIDGE_ENV = "PI_CURSOR_PI_TOOL_BRIDGE";
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
const CURSOR_PI_TOOL_BRIDGE_BUILTINS_ENV = "PI_CURSOR_EXPOSE_BUILTIN_TOOLS";
|
|
6
|
+
const CURSOR_PI_TOOL_BRIDGE_CALL_TIMEOUT_MS_ENV = "PI_CURSOR_PI_BRIDGE_CALL_TIMEOUT_MS";
|
|
7
7
|
|
|
8
8
|
export function resolveCursorPiToolBridgeEnabled(env: Record<string, string | undefined> = process.env): boolean {
|
|
9
9
|
return parseEnvBoolean(env[CURSOR_PI_TOOL_BRIDGE_ENV], true);
|
|
@@ -19,3 +19,32 @@ export function resolveCursorPiToolBridgeCallTimeoutMs(env: Record<string, strin
|
|
|
19
19
|
if (!Number.isFinite(parsed) || parsed <= 0) return mcpToolTimeoutMs;
|
|
20
20
|
return Math.min(Math.max(Math.trunc(parsed), 1), mcpToolTimeoutMs);
|
|
21
21
|
}
|
|
22
|
+
|
|
23
|
+
const CURSOR_PI_TOOL_BRIDGE_HOST_ENV = "PI_CURSOR_PI_TOOL_BRIDGE_HOST";
|
|
24
|
+
const CURSOR_PI_TOOL_BRIDGE_PORT_ENV = "PI_CURSOR_PI_TOOL_BRIDGE_PORT";
|
|
25
|
+
const DEFAULT_URL_HOST = "127.0.0.1";
|
|
26
|
+
const PORT_PATTERN = /^\d{1,5}$/;
|
|
27
|
+
|
|
28
|
+
export interface CursorPiToolBridgePortRange {
|
|
29
|
+
readonly first: number;
|
|
30
|
+
readonly last: number;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function resolveCursorPiToolBridgeUrlHost(env: Record<string, string | undefined> = process.env): string {
|
|
34
|
+
return env[CURSOR_PI_TOOL_BRIDGE_HOST_ENV]?.trim().toLowerCase() === "localhost" ? "localhost" : DEFAULT_URL_HOST;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function parsePort(text: string): number | undefined {
|
|
38
|
+
if (!PORT_PATTERN.test(text)) return undefined;
|
|
39
|
+
const port = Number(text);
|
|
40
|
+
return port >= 1 && port <= 65535 ? port : undefined;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function resolveCursorPiToolBridgePortRange(env: Record<string, string | undefined> = process.env): CursorPiToolBridgePortRange | undefined {
|
|
44
|
+
const parts = env[CURSOR_PI_TOOL_BRIDGE_PORT_ENV]?.trim().split("-");
|
|
45
|
+
if (!parts || parts.length > 2) return undefined;
|
|
46
|
+
const first = parsePort(parts[0]!);
|
|
47
|
+
const last = parts.length === 2 ? parsePort(parts[1]!) : first;
|
|
48
|
+
if (first === undefined || last === undefined || first > last) return undefined;
|
|
49
|
+
return { first, last };
|
|
50
|
+
}
|
|
@@ -6,6 +6,10 @@ import type {
|
|
|
6
6
|
CursorPiToolBridgeRunOptions,
|
|
7
7
|
CursorPiToolBridgeSnapshotApi,
|
|
8
8
|
} from "./cursor-pi-tool-bridge-types.js";
|
|
9
|
+
import {
|
|
10
|
+
resolveCursorPiToolBridgePortRange,
|
|
11
|
+
resolveCursorPiToolBridgeUrlHost,
|
|
12
|
+
} from "./cursor-pi-tool-bridge-env.js";
|
|
9
13
|
import { asRecord } from "./cursor-record-utils.js";
|
|
10
14
|
import { CursorPiToolBridgeRunImpl } from "./cursor-pi-tool-bridge-run.js";
|
|
11
15
|
import {
|
|
@@ -16,15 +20,54 @@ import {
|
|
|
16
20
|
resolveCursorPiToolBridgeEnabled,
|
|
17
21
|
} from "./cursor-pi-tool-bridge-snapshot.js";
|
|
18
22
|
|
|
19
|
-
|
|
23
|
+
const LOOPBACK_HOST = "127.0.0.1";
|
|
20
24
|
const HTTP_SERVER_CLOSE_GRACE_MS = 250;
|
|
25
|
+
const EPHEMERAL_BIND_ATTEMPTS = 10;
|
|
26
|
+
|
|
27
|
+
function listenOnce(server: HttpServer, port: number, host = LOOPBACK_HOST): Promise<void> {
|
|
28
|
+
return new Promise<void>((resolve, reject) => {
|
|
29
|
+
const onError = (error: Error) => {
|
|
30
|
+
server.off("listening", onListening);
|
|
31
|
+
reject(error);
|
|
32
|
+
};
|
|
33
|
+
const onListening = () => {
|
|
34
|
+
server.off("error", onError);
|
|
35
|
+
resolve();
|
|
36
|
+
};
|
|
37
|
+
server.once("error", onError);
|
|
38
|
+
server.once("listening", onListening);
|
|
39
|
+
server.listen({ port, host, ipv6Only: host === "::1" });
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async function closeServers(servers: readonly HttpServer[]): Promise<void> {
|
|
44
|
+
await Promise.all(servers.map(async (server) => {
|
|
45
|
+
if (!server.listening) return;
|
|
46
|
+
await new Promise<void>((resolve, reject) => {
|
|
47
|
+
let settled = false;
|
|
48
|
+
const settle = (error?: Error): void => {
|
|
49
|
+
if (settled) return;
|
|
50
|
+
settled = true;
|
|
51
|
+
clearTimeout(closeTimer);
|
|
52
|
+
if (error) reject(error);
|
|
53
|
+
else resolve();
|
|
54
|
+
};
|
|
55
|
+
const closeTimer = setTimeout(() => settle(), HTTP_SERVER_CLOSE_GRACE_MS);
|
|
56
|
+
closeTimer.unref?.();
|
|
57
|
+
server.close((error) => settle(error ?? undefined));
|
|
58
|
+
server.closeIdleConnections();
|
|
59
|
+
server.closeAllConnections();
|
|
60
|
+
});
|
|
61
|
+
}));
|
|
62
|
+
}
|
|
21
63
|
|
|
22
64
|
export class CursorPiToolBridgeRegistry implements CursorPiToolBridge {
|
|
23
65
|
private readonly pi: CursorPiToolBridgeSnapshotApi;
|
|
24
66
|
private readonly env: Record<string, string | undefined>;
|
|
25
67
|
private readonly runs = new Set<CursorPiToolBridgeRunImpl>();
|
|
26
68
|
private readonly routes = new Map<string, CursorPiToolBridgeRunImpl>();
|
|
27
|
-
private
|
|
69
|
+
private httpServers: HttpServer[] = [];
|
|
70
|
+
private advertisedHost?: string;
|
|
28
71
|
private listenPromise?: Promise<void>;
|
|
29
72
|
private serverLifecycle = Promise.resolve();
|
|
30
73
|
|
|
@@ -57,7 +100,14 @@ export class CursorPiToolBridgeRegistry implements CursorPiToolBridge {
|
|
|
57
100
|
: createEmptySnapshot();
|
|
58
101
|
const run = new CursorPiToolBridgeRunImpl(this, this.env, snapshot, bridgeEnabled && snapshot.tools.length > 0, options);
|
|
59
102
|
this.runs.add(run);
|
|
60
|
-
|
|
103
|
+
try {
|
|
104
|
+
await run.start();
|
|
105
|
+
} catch (error) {
|
|
106
|
+
try { await run.dispose(); }
|
|
107
|
+
catch { /* Preserve the original startup failure, not a secondary cleanup error. */ }
|
|
108
|
+
this.runs.delete(run);
|
|
109
|
+
throw error;
|
|
110
|
+
}
|
|
61
111
|
run.emitStartDiagnostics(bridgeEnabled);
|
|
62
112
|
return run;
|
|
63
113
|
}
|
|
@@ -74,8 +124,8 @@ export class CursorPiToolBridgeRegistry implements CursorPiToolBridge {
|
|
|
74
124
|
await this.ensureHttpServer();
|
|
75
125
|
this.routes.set(pathname, run);
|
|
76
126
|
const address = this.getHttpServerAddress();
|
|
77
|
-
if (!address) throw new Error("Cursor pi tool bridge HTTP server is not listening");
|
|
78
|
-
return `http://${
|
|
127
|
+
if (!address || !this.advertisedHost) throw new Error("Cursor pi tool bridge HTTP server is not listening");
|
|
128
|
+
return `http://${this.advertisedHost}:${address.port}${pathname}`;
|
|
79
129
|
});
|
|
80
130
|
}
|
|
81
131
|
|
|
@@ -94,7 +144,7 @@ export class CursorPiToolBridgeRegistry implements CursorPiToolBridge {
|
|
|
94
144
|
}
|
|
95
145
|
|
|
96
146
|
getHttpServerAddress(): AddressInfo | undefined {
|
|
97
|
-
const address = asRecord(this.
|
|
147
|
+
const address = asRecord(this.httpServers[0]?.address());
|
|
98
148
|
if (typeof address?.port !== "number") return undefined;
|
|
99
149
|
return {
|
|
100
150
|
address: typeof address.address === "string" ? address.address : LOOPBACK_HOST,
|
|
@@ -123,7 +173,8 @@ export class CursorPiToolBridgeRegistry implements CursorPiToolBridge {
|
|
|
123
173
|
|
|
124
174
|
private async ensureHttpServer(): Promise<void> {
|
|
125
175
|
this.listenPromise ??= this.startHttpServer().catch((error: unknown) => {
|
|
126
|
-
this.
|
|
176
|
+
this.httpServers = [];
|
|
177
|
+
this.advertisedHost = undefined;
|
|
127
178
|
this.listenPromise = undefined;
|
|
128
179
|
throw error;
|
|
129
180
|
});
|
|
@@ -131,6 +182,9 @@ export class CursorPiToolBridgeRegistry implements CursorPiToolBridge {
|
|
|
131
182
|
}
|
|
132
183
|
|
|
133
184
|
private async startHttpServer(): Promise<void> {
|
|
185
|
+
// A hostname is a listener policy, not a mutable per-run URL preference.
|
|
186
|
+
const advertisedHost = resolveCursorPiToolBridgeUrlHost(this.env);
|
|
187
|
+
const range = resolveCursorPiToolBridgePortRange(this.env);
|
|
134
188
|
const [{ getRequestListener }, { createMcpHonoApp }] = await Promise.all([
|
|
135
189
|
import("@hono/node-server"),
|
|
136
190
|
import("@modelcontextprotocol/hono"),
|
|
@@ -142,49 +196,39 @@ export class CursorPiToolBridgeRegistry implements CursorPiToolBridge {
|
|
|
142
196
|
).get("parsedBody");
|
|
143
197
|
return this.handleHttpRequest(context.req.raw, parsedBody);
|
|
144
198
|
});
|
|
145
|
-
const
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
const
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
199
|
+
const listener = getRequestListener(app.fetch, { overrideGlobalObjects: false });
|
|
200
|
+
const attempts = range ? range.last - range.first + 1 : advertisedHost === "localhost" ? EPHEMERAL_BIND_ATTEMPTS : 1;
|
|
201
|
+
for (let attempt = 0; attempt < attempts; attempt += 1) {
|
|
202
|
+
const servers = [createServer(listener)];
|
|
203
|
+
try {
|
|
204
|
+
await listenOnce(servers[0]!, range ? range.first + attempt : 0);
|
|
205
|
+
if (advertisedHost === "localhost") {
|
|
206
|
+
const port = (servers[0]!.address() as AddressInfo).port;
|
|
207
|
+
const ipv6 = createServer(listener);
|
|
208
|
+
servers.push(ipv6);
|
|
209
|
+
// Own both explicit loopbacks before publishing any tokenized URL.
|
|
210
|
+
await listenOnce(ipv6, port, "::1");
|
|
211
|
+
}
|
|
212
|
+
this.httpServers = servers;
|
|
213
|
+
this.advertisedHost = advertisedHost;
|
|
214
|
+
return;
|
|
215
|
+
} catch (error) {
|
|
216
|
+
try { await closeServers(servers); }
|
|
217
|
+
catch { throw error; } // Never retry or replace the bind failure when rollback fails.
|
|
218
|
+
if ((error as NodeJS.ErrnoException).code !== "EADDRINUSE" || (!range && advertisedHost !== "localhost")) throw error;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
if (range) throw new Error(`Cursor pi tool bridge could not listen on any port in ${range.first}-${range.last} (all in use)`);
|
|
222
|
+
throw new Error("Cursor pi tool bridge could not own both localhost loopback listeners on an ephemeral port");
|
|
160
223
|
}
|
|
161
224
|
|
|
162
225
|
private async closeHttpServer(): Promise<void> {
|
|
163
226
|
await this.listenPromise?.catch(() => undefined);
|
|
164
|
-
const
|
|
165
|
-
|
|
166
|
-
this.
|
|
227
|
+
const servers = this.httpServers;
|
|
228
|
+
this.httpServers = [];
|
|
229
|
+
this.advertisedHost = undefined;
|
|
167
230
|
this.listenPromise = undefined;
|
|
168
|
-
await
|
|
169
|
-
let settled = false;
|
|
170
|
-
let closeTimer: ReturnType<typeof setTimeout> | undefined;
|
|
171
|
-
const settle = (error?: Error): void => {
|
|
172
|
-
if (settled) return;
|
|
173
|
-
settled = true;
|
|
174
|
-
if (closeTimer) clearTimeout(closeTimer);
|
|
175
|
-
if (error) reject(error);
|
|
176
|
-
else resolve();
|
|
177
|
-
};
|
|
178
|
-
|
|
179
|
-
closeTimer = setTimeout(() => settle(), HTTP_SERVER_CLOSE_GRACE_MS);
|
|
180
|
-
closeTimer.unref?.();
|
|
181
|
-
|
|
182
|
-
server.close((error) => {
|
|
183
|
-
settle(error ?? undefined);
|
|
184
|
-
});
|
|
185
|
-
server.closeIdleConnections();
|
|
186
|
-
server.closeAllConnections();
|
|
187
|
-
});
|
|
231
|
+
await closeServers(servers);
|
|
188
232
|
}
|
|
189
233
|
|
|
190
234
|
private async handleHttpRequest(request: Request, parsedBody?: unknown): Promise<Response> {
|
|
@@ -6,7 +6,6 @@ import type {
|
|
|
6
6
|
} from "./cursor-pi-tool-bridge-types.js";
|
|
7
7
|
import { createMcpToolName, normalizeMcpInputSchema, stableNameHash } from "./cursor-pi-tool-bridge-mcp.js";
|
|
8
8
|
export {
|
|
9
|
-
CURSOR_PI_TOOL_BRIDGE_BUILTINS_ENV,
|
|
10
9
|
CURSOR_PI_TOOL_BRIDGE_ENV,
|
|
11
10
|
resolveCursorPiToolBridgeBuiltinsEnabled,
|
|
12
11
|
resolveCursorPiToolBridgeEnabled,
|