pi-cursor-sdk 0.5.2 → 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 +66 -1
- package/README.md +62 -795
- package/dist/context.js +13 -7
- 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-custom-subagent-definitions.js +1 -1
- package/dist/cursor-fallback-models.generated.js +665 -1
- package/dist/cursor-live-run-coordinator.js +1 -0
- package/dist/cursor-local-agent-ownership.js +17 -0
- package/dist/cursor-mcp-timeout-override.js +7 -18
- package/dist/cursor-native-tool-display-replay.js +5 -5
- package/dist/cursor-native-tools.js +11 -0
- package/dist/cursor-pi-tool-bridge-diagnostics.js +4 -0
- package/dist/cursor-pi-tool-bridge-env.js +25 -2
- package/dist/cursor-pi-tool-bridge-run.js +10 -3
- 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 +2 -8
- 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-turn-summary.js +1 -1
- 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-cleanup.js +10 -1
- package/dist/cursor-session-agent-lifecycle.js +2 -2
- package/dist/cursor-session-agent-lineage.js +11 -7
- package/dist/cursor-session-agent-resume.js +15 -9
- package/dist/cursor-session-agent.js +28 -15
- package/dist/cursor-session-scope.js +28 -21
- package/dist/cursor-session-settings.js +3 -3
- package/dist/cursor-session-store.js +2 -2
- package/dist/cursor-state.js +26 -29
- 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 +8 -10
- package/dist/model-list-cache.js +4 -9
- package/docs/cursor-live-smoke-checklist.md +4 -4
- package/docs/cursor-model-ux-spec.md +36 -19
- package/docs/cursor-native-tool-replay.md +8 -5
- package/docs/cursor-native-tool-visual-audit.md +1 -1
- package/docs/cursor-testing-lessons.md +39 -9
- package/docs/cursor-tool-surfaces.md +48 -4
- package/docs/development.md +93 -0
- package/docs/platform-smoke-implementation.md +3 -2
- package/docs/reference.md +806 -0
- package/package.json +13 -11
- 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 +18 -11
- 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-custom-subagent-definitions.ts +1 -1
- package/src/cursor-fallback-models.generated.ts +665 -1
- package/src/cursor-live-run-coordinator.ts +5 -0
- package/src/cursor-local-agent-ownership.ts +18 -0
- package/src/cursor-mcp-timeout-override.ts +7 -31
- package/src/cursor-native-tool-display-replay.ts +4 -6
- package/src/cursor-native-tools.ts +14 -0
- package/src/cursor-pi-tool-bridge-diagnostics.ts +17 -0
- package/src/cursor-pi-tool-bridge-env.ts +31 -2
- package/src/cursor-pi-tool-bridge-run.ts +10 -3
- 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 +2 -10
- 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-turn-summary.ts +1 -1
- 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-cleanup.ts +6 -1
- package/src/cursor-session-agent-lifecycle.ts +2 -2
- package/src/cursor-session-agent-lineage.ts +10 -5
- package/src/cursor-session-agent-resume.ts +14 -7
- package/src/cursor-session-agent.ts +31 -15
- package/src/cursor-session-scope.ts +34 -20
- package/src/cursor-session-settings.ts +3 -3
- package/src/cursor-session-store.ts +2 -2
- package/src/cursor-state.ts +34 -41
- 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 +7 -9
- package/src/model-list-cache.ts +4 -10
|
@@ -36,7 +36,7 @@ function normalizeConnectTimeoutMs(timeoutMs) {
|
|
|
36
36
|
return DEFAULT_CURSOR_MCP_CONNECT_TIMEOUT_MS;
|
|
37
37
|
return Math.min(Math.max(Math.trunc(timeoutMs), MIN_CURSOR_MCP_CONNECT_TIMEOUT_MS), CURSOR_SDK_MCP_DEFAULT_TIMEOUT_MS);
|
|
38
38
|
}
|
|
39
|
-
|
|
39
|
+
function resolveCursorMcpConnectTimeoutMs(env = process.env) {
|
|
40
40
|
const explicitMs = parsePositiveNumber(env[CURSOR_MCP_CONNECT_TIMEOUT_MS_ENV]);
|
|
41
41
|
if (explicitMs !== undefined)
|
|
42
42
|
return normalizeConnectTimeoutMs(explicitMs);
|
|
@@ -51,16 +51,16 @@ function isCursorSdkMcpProtocolTimeoutStack(stack) {
|
|
|
51
51
|
return (/(?:node_modules[/\\]@cursor[/\\]sdk|node_modules\/\@cursor\/sdk|@cursor\/sdk\/dist)/.test(stack) &&
|
|
52
52
|
/\b_setupTimeout\b|\bProtocol\._setupTimeout\b/.test(stack));
|
|
53
53
|
}
|
|
54
|
-
|
|
54
|
+
function isCursorSdkMcpToolTimeoutStack(stack) {
|
|
55
55
|
if (!stack)
|
|
56
56
|
return false;
|
|
57
57
|
return (isCursorSdkMcpProtocolTimeoutStack(stack) &&
|
|
58
58
|
/\bcallTool\b|\bClient\.callTool\b|\bMcpSdkClient\.callTool\b/.test(stack));
|
|
59
59
|
}
|
|
60
|
-
|
|
60
|
+
function isCursorSdkMcpConnectTimeoutStack(stack) {
|
|
61
61
|
if (!stack || !isCursorSdkMcpProtocolTimeoutStack(stack))
|
|
62
62
|
return false;
|
|
63
|
-
// SDK
|
|
63
|
+
// The SDK minifies the Client class name; the protocol method and SDK
|
|
64
64
|
// frame, not that constructor's spelling, establish this timeout purpose.
|
|
65
65
|
return /^\s*at (?:[\w$]+\.)?(?:connect|listTools|getTools)\s*\(.*(?:node_modules[/\\]@cursor[/\\]sdk|@cursor\/sdk\/dist)/m.test(stack);
|
|
66
66
|
}
|
|
@@ -83,9 +83,9 @@ function patchedSetTimeout(handler, delay, ...args) {
|
|
|
83
83
|
}
|
|
84
84
|
return Reflect.apply(delegate, globalThis, [handler, nextDelay, ...args]);
|
|
85
85
|
}
|
|
86
|
-
export function installCursorMcpToolTimeoutOverride(
|
|
87
|
-
installedToolTimeoutMs =
|
|
88
|
-
installedConnectTimeoutMs =
|
|
86
|
+
export function installCursorMcpToolTimeoutOverride() {
|
|
87
|
+
installedToolTimeoutMs = resolveCursorMcpToolTimeoutMs();
|
|
88
|
+
installedConnectTimeoutMs = resolveCursorMcpConnectTimeoutMs();
|
|
89
89
|
if (!originalSetTimeout) {
|
|
90
90
|
originalSetTimeout = globalThis.setTimeout;
|
|
91
91
|
globalThis.setTimeout = patchedSetTimeout;
|
|
@@ -105,14 +105,3 @@ export function restoreCursorMcpToolTimeoutOverride() {
|
|
|
105
105
|
installedToolTimeoutMs = DEFAULT_CURSOR_MCP_TOOL_TIMEOUT_MS;
|
|
106
106
|
installedConnectTimeoutMs = DEFAULT_CURSOR_MCP_CONNECT_TIMEOUT_MS;
|
|
107
107
|
}
|
|
108
|
-
export const cursorMcpToolTimeoutOverrideDefaults = {
|
|
109
|
-
cursorSdkDefaultTimeoutMs: CURSOR_SDK_MCP_DEFAULT_TIMEOUT_MS,
|
|
110
|
-
defaultOverrideTimeoutMs: DEFAULT_CURSOR_MCP_TOOL_TIMEOUT_MS,
|
|
111
|
-
defaultConnectTimeoutMs: DEFAULT_CURSOR_MCP_CONNECT_TIMEOUT_MS,
|
|
112
|
-
minConnectTimeoutMs: MIN_CURSOR_MCP_CONNECT_TIMEOUT_MS,
|
|
113
|
-
maxNodeTimerDelayMs: MAX_NODE_TIMER_DELAY_MS,
|
|
114
|
-
timeoutMsEnv: CURSOR_MCP_TOOL_TIMEOUT_MS_ENV,
|
|
115
|
-
timeoutSecondsEnv: CURSOR_MCP_TOOL_TIMEOUT_SECONDS_ENV,
|
|
116
|
-
connectTimeoutMsEnv: CURSOR_MCP_CONNECT_TIMEOUT_MS_ENV,
|
|
117
|
-
connectTimeoutSecondsEnv: CURSOR_MCP_CONNECT_TIMEOUT_SECONDS_ENV,
|
|
118
|
-
};
|
|
@@ -8,9 +8,9 @@ import { inferImageMimeType } from "./cursor-tool-result-display-readers.js";
|
|
|
8
8
|
import { LOCAL_READ_PREVIEW_NOTICE, isLocalReadPreviewContent } from "./cursor-transcript-utils.js";
|
|
9
9
|
import { CURSOR_REPLAY_ACTIVITY_TOOL_NAME, getCursorReplayCallSummary, shouldShowCursorReplayCollapsedExpandHint, } from "./cursor-tool-presentation-registry.js";
|
|
10
10
|
import { CURSOR_REPLAY_GENERATE_IMAGE_RESULT_TITLE, isCursorReplayGenerateImageDetails, isCursorReplayActivityDetails, parseCursorReplayToolDetails, } from "./cursor-replay-tool-details.js";
|
|
11
|
-
export { isCursorReplayNativeEditDetails,
|
|
11
|
+
export { isCursorReplayNativeEditDetails, isCursorReplayNativeWriteDetails, parseCursorReplayToolDetails, } from "./cursor-replay-tool-details.js";
|
|
12
12
|
export const CURSOR_REPLAY_COLLAPSED_PREVIEW_LINES = 8;
|
|
13
|
-
|
|
13
|
+
const CURSOR_REPLAY_PREVIEW_MAX_CHARS = 4000;
|
|
14
14
|
export const CURSOR_REPLAY_PREVIEW_MAX_LINE_CHARS = 240;
|
|
15
15
|
const CURSOR_REPLAY_HIGHLIGHT_MAX_CHARS = 12000;
|
|
16
16
|
export const cursorReplayToolSchema = Type.Object({}, { additionalProperties: true });
|
|
@@ -40,7 +40,7 @@ function buildImageReplayComponent(text, imageData, mimeType, filename, theme) {
|
|
|
40
40
|
},
|
|
41
41
|
};
|
|
42
42
|
}
|
|
43
|
-
|
|
43
|
+
function getCursorReplayPath(args, details) {
|
|
44
44
|
const argPath = args?.path;
|
|
45
45
|
return details?.path ?? (typeof argPath === "string" && argPath.trim() ? argPath : "unknown");
|
|
46
46
|
}
|
|
@@ -202,7 +202,7 @@ function formatCursorReplayActivityPreview(details, text, theme, maxLines, strip
|
|
|
202
202
|
}
|
|
203
203
|
return stripHeader ? formatCursorReplayPreview(text, theme, maxLines, true) : formatMutedBlock(text, theme);
|
|
204
204
|
}
|
|
205
|
-
|
|
205
|
+
function formatCursorReplayPreview(text, theme, maxLines = CURSOR_REPLAY_COLLAPSED_PREVIEW_LINES, stripHeader = true) {
|
|
206
206
|
const body = (stripHeader ? stripCursorReplayHeader(text) : text).trimEnd();
|
|
207
207
|
if (!body)
|
|
208
208
|
return undefined;
|
|
@@ -240,7 +240,7 @@ function getCursorReplayCardTitle(toolName, args) {
|
|
|
240
240
|
}
|
|
241
241
|
return "Cursor activity";
|
|
242
242
|
}
|
|
243
|
-
|
|
243
|
+
function renderCursorReplayCall(toolName, args, theme, isPartial) {
|
|
244
244
|
if (!isPartial)
|
|
245
245
|
return new Text("", 0, 0);
|
|
246
246
|
let text = theme.fg("toolTitle", theme.bold(`${getCursorReplayCardTitle(toolName, args)} `));
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { parseEnvBoolean } from "./cursor-env-boolean.js";
|
|
2
|
+
export const CURSOR_NATIVE_TOOLS_ENV = "PI_CURSOR_NATIVE_TOOLS";
|
|
3
|
+
/** Default preserves the SDK toolset; false selects the public MCP-only main loop. */
|
|
4
|
+
export function resolveCursorNativeToolsEnabled(env = process.env) {
|
|
5
|
+
return parseEnvBoolean(env[CURSOR_NATIVE_TOOLS_ENV], true);
|
|
6
|
+
}
|
|
7
|
+
export function assertCursorNativeToolsRuntime(runtime, nativeToolsEnabled) {
|
|
8
|
+
if (runtime === "cloud" && !nativeToolsEnabled) {
|
|
9
|
+
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.");
|
|
10
|
+
}
|
|
11
|
+
}
|
|
@@ -15,6 +15,10 @@ function assertNeverDiagnosticEvent(_event) {
|
|
|
15
15
|
}
|
|
16
16
|
export function serializeCursorPiToolBridgeDiagnostic(event) {
|
|
17
17
|
switch (event.event) {
|
|
18
|
+
case "mcp_initialized":
|
|
19
|
+
return { event: event.event, runId: event.runId };
|
|
20
|
+
case "mcp_tools_list":
|
|
21
|
+
return { event: event.event, runId: event.runId, toolCount: event.toolCount };
|
|
18
22
|
case "run_created":
|
|
19
23
|
return {
|
|
20
24
|
event: event.event,
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { parseEnvBoolean } from "./cursor-env-boolean.js";
|
|
2
2
|
import { resolveCursorMcpToolTimeoutMs } from "./cursor-mcp-timeout-override.js";
|
|
3
3
|
export const CURSOR_PI_TOOL_BRIDGE_ENV = "PI_CURSOR_PI_TOOL_BRIDGE";
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
const CURSOR_PI_TOOL_BRIDGE_BUILTINS_ENV = "PI_CURSOR_EXPOSE_BUILTIN_TOOLS";
|
|
5
|
+
const CURSOR_PI_TOOL_BRIDGE_CALL_TIMEOUT_MS_ENV = "PI_CURSOR_PI_BRIDGE_CALL_TIMEOUT_MS";
|
|
6
6
|
export function resolveCursorPiToolBridgeEnabled(env = process.env) {
|
|
7
7
|
return parseEnvBoolean(env[CURSOR_PI_TOOL_BRIDGE_ENV], true);
|
|
8
8
|
}
|
|
@@ -16,3 +16,26 @@ export function resolveCursorPiToolBridgeCallTimeoutMs(env = process.env) {
|
|
|
16
16
|
return mcpToolTimeoutMs;
|
|
17
17
|
return Math.min(Math.max(Math.trunc(parsed), 1), mcpToolTimeoutMs);
|
|
18
18
|
}
|
|
19
|
+
const CURSOR_PI_TOOL_BRIDGE_HOST_ENV = "PI_CURSOR_PI_TOOL_BRIDGE_HOST";
|
|
20
|
+
const CURSOR_PI_TOOL_BRIDGE_PORT_ENV = "PI_CURSOR_PI_TOOL_BRIDGE_PORT";
|
|
21
|
+
const DEFAULT_URL_HOST = "127.0.0.1";
|
|
22
|
+
const PORT_PATTERN = /^\d{1,5}$/;
|
|
23
|
+
export function resolveCursorPiToolBridgeUrlHost(env = process.env) {
|
|
24
|
+
return env[CURSOR_PI_TOOL_BRIDGE_HOST_ENV]?.trim().toLowerCase() === "localhost" ? "localhost" : DEFAULT_URL_HOST;
|
|
25
|
+
}
|
|
26
|
+
function parsePort(text) {
|
|
27
|
+
if (!PORT_PATTERN.test(text))
|
|
28
|
+
return undefined;
|
|
29
|
+
const port = Number(text);
|
|
30
|
+
return port >= 1 && port <= 65535 ? port : undefined;
|
|
31
|
+
}
|
|
32
|
+
export function resolveCursorPiToolBridgePortRange(env = process.env) {
|
|
33
|
+
const parts = env[CURSOR_PI_TOOL_BRIDGE_PORT_ENV]?.trim().split("-");
|
|
34
|
+
if (!parts || parts.length > 2)
|
|
35
|
+
return undefined;
|
|
36
|
+
const first = parsePort(parts[0]);
|
|
37
|
+
const last = parts.length === 2 ? parsePort(parts[1]) : first;
|
|
38
|
+
if (first === undefined || last === undefined || first > last)
|
|
39
|
+
return undefined;
|
|
40
|
+
return { first, last };
|
|
41
|
+
}
|
|
@@ -208,9 +208,16 @@ export class CursorPiToolBridgeRunImpl {
|
|
|
208
208
|
const transport = new WebStandardStreamableHTTPServerTransport({
|
|
209
209
|
sessionIdGenerator: randomUUID,
|
|
210
210
|
});
|
|
211
|
-
server
|
|
212
|
-
|
|
213
|
-
|
|
211
|
+
// ponytail: server-side receipts only, not SDK/model consumption or ongoing
|
|
212
|
+
// connection health; upgrade when Cursor exposes public MCP lifecycle callbacks.
|
|
213
|
+
server.oninitialized = () => {
|
|
214
|
+
this.emitDiagnostic({ event: "mcp_initialized", runId: this.id });
|
|
215
|
+
};
|
|
216
|
+
server.setRequestHandler("tools/list", async () => {
|
|
217
|
+
const tools = this.snapshot.tools.map(snapshotToolToMcpTool);
|
|
218
|
+
this.emitDiagnostic({ event: "mcp_tools_list", runId: this.id, toolCount: tools.length });
|
|
219
|
+
return { tools };
|
|
220
|
+
});
|
|
214
221
|
server.setRequestHandler("tools/call", async (request, context) => {
|
|
215
222
|
return this.enqueueToolRequest(request.params.name, request.params.arguments, String(context.mcpReq.id), context.mcpReq.signal);
|
|
216
223
|
});
|
|
@@ -1,15 +1,57 @@
|
|
|
1
1
|
import { createServer } from "node:http";
|
|
2
|
+
import { resolveCursorPiToolBridgePortRange, resolveCursorPiToolBridgeUrlHost, } from "./cursor-pi-tool-bridge-env.js";
|
|
2
3
|
import { asRecord } from "./cursor-record-utils.js";
|
|
3
4
|
import { CursorPiToolBridgeRunImpl } from "./cursor-pi-tool-bridge-run.js";
|
|
4
5
|
import { buildCursorPiToolBridgeSnapshot, buildCursorPiToolBridgeSurfaceSignature, createEmptySnapshot, resolveCursorPiToolBridgeBuiltinsEnabled, resolveCursorPiToolBridgeEnabled, } from "./cursor-pi-tool-bridge-snapshot.js";
|
|
5
|
-
|
|
6
|
+
const LOOPBACK_HOST = "127.0.0.1";
|
|
6
7
|
const HTTP_SERVER_CLOSE_GRACE_MS = 250;
|
|
8
|
+
const EPHEMERAL_BIND_ATTEMPTS = 10;
|
|
9
|
+
function listenOnce(server, port, host = LOOPBACK_HOST) {
|
|
10
|
+
return new Promise((resolve, reject) => {
|
|
11
|
+
const onError = (error) => {
|
|
12
|
+
server.off("listening", onListening);
|
|
13
|
+
reject(error);
|
|
14
|
+
};
|
|
15
|
+
const onListening = () => {
|
|
16
|
+
server.off("error", onError);
|
|
17
|
+
resolve();
|
|
18
|
+
};
|
|
19
|
+
server.once("error", onError);
|
|
20
|
+
server.once("listening", onListening);
|
|
21
|
+
server.listen({ port, host, ipv6Only: host === "::1" });
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
async function closeServers(servers) {
|
|
25
|
+
await Promise.all(servers.map(async (server) => {
|
|
26
|
+
if (!server.listening)
|
|
27
|
+
return;
|
|
28
|
+
await new Promise((resolve, reject) => {
|
|
29
|
+
let settled = false;
|
|
30
|
+
const settle = (error) => {
|
|
31
|
+
if (settled)
|
|
32
|
+
return;
|
|
33
|
+
settled = true;
|
|
34
|
+
clearTimeout(closeTimer);
|
|
35
|
+
if (error)
|
|
36
|
+
reject(error);
|
|
37
|
+
else
|
|
38
|
+
resolve();
|
|
39
|
+
};
|
|
40
|
+
const closeTimer = setTimeout(() => settle(), HTTP_SERVER_CLOSE_GRACE_MS);
|
|
41
|
+
closeTimer.unref?.();
|
|
42
|
+
server.close((error) => settle(error ?? undefined));
|
|
43
|
+
server.closeIdleConnections();
|
|
44
|
+
server.closeAllConnections();
|
|
45
|
+
});
|
|
46
|
+
}));
|
|
47
|
+
}
|
|
7
48
|
export class CursorPiToolBridgeRegistry {
|
|
8
49
|
pi;
|
|
9
50
|
env;
|
|
10
51
|
runs = new Set();
|
|
11
52
|
routes = new Map();
|
|
12
|
-
|
|
53
|
+
httpServers = [];
|
|
54
|
+
advertisedHost;
|
|
13
55
|
listenPromise;
|
|
14
56
|
serverLifecycle = Promise.resolve();
|
|
15
57
|
constructor(pi, env = process.env) {
|
|
@@ -36,7 +78,17 @@ export class CursorPiToolBridgeRegistry {
|
|
|
36
78
|
: createEmptySnapshot();
|
|
37
79
|
const run = new CursorPiToolBridgeRunImpl(this, this.env, snapshot, bridgeEnabled && snapshot.tools.length > 0, options);
|
|
38
80
|
this.runs.add(run);
|
|
39
|
-
|
|
81
|
+
try {
|
|
82
|
+
await run.start();
|
|
83
|
+
}
|
|
84
|
+
catch (error) {
|
|
85
|
+
try {
|
|
86
|
+
await run.dispose();
|
|
87
|
+
}
|
|
88
|
+
catch { /* Preserve the original startup failure, not a secondary cleanup error. */ }
|
|
89
|
+
this.runs.delete(run);
|
|
90
|
+
throw error;
|
|
91
|
+
}
|
|
40
92
|
run.emitStartDiagnostics(bridgeEnabled);
|
|
41
93
|
return run;
|
|
42
94
|
}
|
|
@@ -51,9 +103,9 @@ export class CursorPiToolBridgeRegistry {
|
|
|
51
103
|
await this.ensureHttpServer();
|
|
52
104
|
this.routes.set(pathname, run);
|
|
53
105
|
const address = this.getHttpServerAddress();
|
|
54
|
-
if (!address)
|
|
106
|
+
if (!address || !this.advertisedHost)
|
|
55
107
|
throw new Error("Cursor pi tool bridge HTTP server is not listening");
|
|
56
|
-
return `http://${
|
|
108
|
+
return `http://${this.advertisedHost}:${address.port}${pathname}`;
|
|
57
109
|
});
|
|
58
110
|
}
|
|
59
111
|
async unregisterRun(pathname, run) {
|
|
@@ -71,7 +123,7 @@ export class CursorPiToolBridgeRegistry {
|
|
|
71
123
|
return result;
|
|
72
124
|
}
|
|
73
125
|
getHttpServerAddress() {
|
|
74
|
-
const address = asRecord(this.
|
|
126
|
+
const address = asRecord(this.httpServers[0]?.address());
|
|
75
127
|
if (typeof address?.port !== "number")
|
|
76
128
|
return undefined;
|
|
77
129
|
return {
|
|
@@ -99,13 +151,17 @@ export class CursorPiToolBridgeRegistry {
|
|
|
99
151
|
}
|
|
100
152
|
async ensureHttpServer() {
|
|
101
153
|
this.listenPromise ??= this.startHttpServer().catch((error) => {
|
|
102
|
-
this.
|
|
154
|
+
this.httpServers = [];
|
|
155
|
+
this.advertisedHost = undefined;
|
|
103
156
|
this.listenPromise = undefined;
|
|
104
157
|
throw error;
|
|
105
158
|
});
|
|
106
159
|
await this.listenPromise;
|
|
107
160
|
}
|
|
108
161
|
async startHttpServer() {
|
|
162
|
+
// A hostname is a listener policy, not a mutable per-run URL preference.
|
|
163
|
+
const advertisedHost = resolveCursorPiToolBridgeUrlHost(this.env);
|
|
164
|
+
const range = resolveCursorPiToolBridgePortRange(this.env);
|
|
109
165
|
const [{ getRequestListener }, { createMcpHonoApp }] = await Promise.all([
|
|
110
166
|
import("@hono/node-server"),
|
|
111
167
|
import("@modelcontextprotocol/hono"),
|
|
@@ -115,51 +171,45 @@ export class CursorPiToolBridgeRegistry {
|
|
|
115
171
|
const parsedBody = context.get("parsedBody");
|
|
116
172
|
return this.handleHttpRequest(context.req.raw, parsedBody);
|
|
117
173
|
});
|
|
118
|
-
const
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
const
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
174
|
+
const listener = getRequestListener(app.fetch, { overrideGlobalObjects: false });
|
|
175
|
+
const attempts = range ? range.last - range.first + 1 : advertisedHost === "localhost" ? EPHEMERAL_BIND_ATTEMPTS : 1;
|
|
176
|
+
for (let attempt = 0; attempt < attempts; attempt += 1) {
|
|
177
|
+
const servers = [createServer(listener)];
|
|
178
|
+
try {
|
|
179
|
+
await listenOnce(servers[0], range ? range.first + attempt : 0);
|
|
180
|
+
if (advertisedHost === "localhost") {
|
|
181
|
+
const port = servers[0].address().port;
|
|
182
|
+
const ipv6 = createServer(listener);
|
|
183
|
+
servers.push(ipv6);
|
|
184
|
+
// Own both explicit loopbacks before publishing any tokenized URL.
|
|
185
|
+
await listenOnce(ipv6, port, "::1");
|
|
186
|
+
}
|
|
187
|
+
this.httpServers = servers;
|
|
188
|
+
this.advertisedHost = advertisedHost;
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
catch (error) {
|
|
192
|
+
try {
|
|
193
|
+
await closeServers(servers);
|
|
194
|
+
}
|
|
195
|
+
catch {
|
|
196
|
+
throw error;
|
|
197
|
+
} // Never retry or replace the bind failure when rollback fails.
|
|
198
|
+
if (error.code !== "EADDRINUSE" || (!range && advertisedHost !== "localhost"))
|
|
199
|
+
throw error;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
if (range)
|
|
203
|
+
throw new Error(`Cursor pi tool bridge could not listen on any port in ${range.first}-${range.last} (all in use)`);
|
|
204
|
+
throw new Error("Cursor pi tool bridge could not own both localhost loopback listeners on an ephemeral port");
|
|
133
205
|
}
|
|
134
206
|
async closeHttpServer() {
|
|
135
207
|
await this.listenPromise?.catch(() => undefined);
|
|
136
|
-
const
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
this.httpServer = undefined;
|
|
208
|
+
const servers = this.httpServers;
|
|
209
|
+
this.httpServers = [];
|
|
210
|
+
this.advertisedHost = undefined;
|
|
140
211
|
this.listenPromise = undefined;
|
|
141
|
-
await
|
|
142
|
-
let settled = false;
|
|
143
|
-
let closeTimer;
|
|
144
|
-
const settle = (error) => {
|
|
145
|
-
if (settled)
|
|
146
|
-
return;
|
|
147
|
-
settled = true;
|
|
148
|
-
if (closeTimer)
|
|
149
|
-
clearTimeout(closeTimer);
|
|
150
|
-
if (error)
|
|
151
|
-
reject(error);
|
|
152
|
-
else
|
|
153
|
-
resolve();
|
|
154
|
-
};
|
|
155
|
-
closeTimer = setTimeout(() => settle(), HTTP_SERVER_CLOSE_GRACE_MS);
|
|
156
|
-
closeTimer.unref?.();
|
|
157
|
-
server.close((error) => {
|
|
158
|
-
settle(error ?? undefined);
|
|
159
|
-
});
|
|
160
|
-
server.closeIdleConnections();
|
|
161
|
-
server.closeAllConnections();
|
|
162
|
-
});
|
|
212
|
+
await closeServers(servers);
|
|
163
213
|
}
|
|
164
214
|
async handleHttpRequest(request, parsedBody) {
|
|
165
215
|
const url = new URL(request.url);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createMcpToolName, normalizeMcpInputSchema, stableNameHash } from "./cursor-pi-tool-bridge-mcp.js";
|
|
2
|
-
export {
|
|
2
|
+
export { CURSOR_PI_TOOL_BRIDGE_ENV, resolveCursorPiToolBridgeBuiltinsEnabled, resolveCursorPiToolBridgeEnabled, } from "./cursor-pi-tool-bridge-env.js";
|
|
3
3
|
import { isRegisteredCursorNativeToolName } from "./cursor-native-tool-display-state.js";
|
|
4
4
|
import { isExcludedFromCursorBridgeExposure } from "./cursor-tool-presentation-registry.js";
|
|
5
5
|
const OVERLAPPING_CURSOR_NATIVE_PI_BUILTIN_TOOL_NAMES = new Set(["read", "bash", "write", "edit", "grep", "find", "ls"]);
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import { spawnSync } from "node:child_process";
|
|
2
|
-
import {
|
|
3
|
-
import { CURSOR_PI_TOOL_BRIDGE_BUILTINS_ENV, CURSOR_PI_TOOL_BRIDGE_CALL_TIMEOUT_MS_ENV, CURSOR_PI_TOOL_BRIDGE_ENV, } from "./cursor-pi-tool-bridge-env.js";
|
|
2
|
+
import { CURSOR_PI_TOOL_BRIDGE_DIAGNOSTIC_PREFIX, serializeCursorPiToolBridgeDiagnostic, } from "./cursor-pi-tool-bridge-diagnostics.js";
|
|
4
3
|
import { bridgeToolExecutionAbortTracker } from "./cursor-pi-tool-bridge-abort.js";
|
|
5
|
-
import { isCursorPiBridgeToolCallId
|
|
6
|
-
import {
|
|
4
|
+
import { isCursorPiBridgeToolCallId } from "./cursor-pi-tool-bridge-constants.js";
|
|
5
|
+
import { CursorPiToolBridgeRegistry } from "./cursor-pi-tool-bridge-server.js";
|
|
7
6
|
export { resolveCursorPiToolBridgeDebugEnabled } from "./cursor-pi-tool-bridge-diagnostics.js";
|
|
8
|
-
export {
|
|
7
|
+
export { resolveCursorPiToolBridgeBuiltinsEnabled, resolveCursorPiToolBridgeCallTimeoutMs, resolveCursorPiToolBridgeEnabled, } from "./cursor-pi-tool-bridge-env.js";
|
|
9
8
|
export { buildCursorPiToolBridgeSnapshot, buildCursorPiToolBridgeSurfaceSignature, } from "./cursor-pi-tool-bridge-snapshot.js";
|
|
10
9
|
const bridges = new Map();
|
|
11
10
|
const activeCallsByPi = new Map();
|
|
@@ -110,13 +109,7 @@ export function getRegisteredCursorPiToolBridge(pi) {
|
|
|
110
109
|
return pi ? bridges.get(pi) : registeredCursorPiToolBridge;
|
|
111
110
|
}
|
|
112
111
|
export const __testUtils = {
|
|
113
|
-
CURSOR_PI_TOOL_BRIDGE_ENV,
|
|
114
|
-
CURSOR_PI_TOOL_BRIDGE_BUILTINS_ENV,
|
|
115
|
-
CURSOR_PI_TOOL_BRIDGE_CALL_TIMEOUT_MS_ENV,
|
|
116
|
-
CURSOR_PI_TOOL_BRIDGE_DEBUG_ENV,
|
|
117
112
|
CURSOR_PI_TOOL_BRIDGE_DIAGNOSTIC_PREFIX,
|
|
118
|
-
LOOPBACK_HOST,
|
|
119
|
-
MCP_SERVER_NAME,
|
|
120
113
|
createRegistry(pi, env = process.env) {
|
|
121
114
|
return new CursorPiToolBridgeRegistry(pi, env);
|
|
122
115
|
},
|
|
@@ -130,7 +123,6 @@ export const __testUtils = {
|
|
|
130
123
|
return bridgeToolExecutionAbortTracker.getActiveCount();
|
|
131
124
|
},
|
|
132
125
|
buildWindowsBridgeBashAbortCommandForTests: buildWindowsBridgeBashAbortCommand,
|
|
133
|
-
installWindowsBridgeBashAbortMarkerForTests: installWindowsBridgeBashAbortMarker,
|
|
134
126
|
emitBridgeToolExecutionProcessAbortSignalForTests(signal) {
|
|
135
127
|
bridgeToolExecutionAbortTracker.emitProcessAbortSignalForTests(signal);
|
|
136
128
|
},
|
|
@@ -140,7 +140,7 @@ function isCursorSdkStallAbortNetworkError(code, evidence, stackEvidence) {
|
|
|
140
140
|
/\b(?:onStall|reportStall|StallDetected)\b/i.test(stackEvidence));
|
|
141
141
|
}
|
|
142
142
|
/**
|
|
143
|
-
* @cursor/sdk@1.0.
|
|
143
|
+
* @cursor/sdk@1.0.37 throws internal RetriableError (name/kind "RetriableError") with message
|
|
144
144
|
* "Connection stalled" or "Connection stalled repeatedly" after fetchWithRetry exhausts stalls.
|
|
145
145
|
* The ConnectError is only the cause; the top-level error is not a ConnectError.
|
|
146
146
|
*/
|
|
@@ -192,7 +192,7 @@ export function classifyCursorConnectError(error) {
|
|
|
192
192
|
}
|
|
193
193
|
const directAbort = (code === 1 || code === "canceled") &&
|
|
194
194
|
(causeName === "AbortError" || /AbortError/.test(stack));
|
|
195
|
-
// The vendored
|
|
195
|
+
// The vendored SDK transport can wrap a canceled ConnectError as Unknown.
|
|
196
196
|
// Require the observed canceled -> AbortError cause chain, not generic text.
|
|
197
197
|
const wrappedAbort = (code === 2 || code === "unknown") &&
|
|
198
198
|
causeName === "ConnectError" &&
|
|
@@ -214,12 +214,6 @@ export function classifyCursorConnectError(error) {
|
|
|
214
214
|
}
|
|
215
215
|
return undefined;
|
|
216
216
|
}
|
|
217
|
-
export function isCursorSdkAbortConnectError(error) {
|
|
218
|
-
return classifyCursorConnectError(error)?.kind === "abort";
|
|
219
|
-
}
|
|
220
|
-
export function isUnauthenticatedConnectError(error) {
|
|
221
|
-
return classifyCursorConnectError(error)?.kind === "unauthenticated";
|
|
222
|
-
}
|
|
223
217
|
function isLikelyNetworkTimeout(message) {
|
|
224
218
|
return (/\b(ETIMEDOUT|ECONNRESET|ECONNREFUSED|ENETUNREACH|EAI_AGAIN|NGHTTP2_ENHANCE_YOUR_CALM|ERR_HTTP2_STREAM_ERROR|ERR_HTTP2_SESSION_ERROR)\b/i.test(message) ||
|
|
225
219
|
/\bConnectError\b.*\b(unavailable|deadline|timeout|timed out)\b/i.test(message) ||
|
|
@@ -9,7 +9,7 @@ import { trimCurrentTurnAlreadyEmittedCursorText } from "./cursor-run-final-text
|
|
|
9
9
|
import { formatCursorSdkAbortMessage, resolveCursorSdkAbortCause } from "./cursor-provider-errors.js";
|
|
10
10
|
import { formatInactiveCursorReplayTrace } from "./cursor-native-replay-trace.js";
|
|
11
11
|
import { partitionNativeToolsByActiveContext } from "./cursor-native-replay-routing.js";
|
|
12
|
-
|
|
12
|
+
const DEFAULT_CURSOR_NATIVE_REPLAY_IDLE_DISPOSE_MS = 5 * 60 * 1000;
|
|
13
13
|
function parseCursorNativeReplayIdleDisposeMs(raw) {
|
|
14
14
|
if (!raw || /[^0-9]/.test(raw))
|
|
15
15
|
return DEFAULT_CURSOR_NATIVE_REPLAY_IDLE_DISPOSE_MS;
|
|
@@ -36,7 +36,7 @@ export function createCursorNativeReplayId() {
|
|
|
36
36
|
function getCursorNativeReplayIdFromToolCallId(toolCallId) {
|
|
37
37
|
return CURSOR_NATIVE_REPLAY_TOOL_ID_PATTERN.exec(toolCallId)?.[1];
|
|
38
38
|
}
|
|
39
|
-
|
|
39
|
+
function getPendingCursorLiveRun(context, scopeKey) {
|
|
40
40
|
return cursorLiveRuns.getPendingFromContext(context, getCursorNativeReplayIdFromToolCallId, scopeKey);
|
|
41
41
|
}
|
|
42
42
|
export function getActiveCursorLiveRunForCurrentScope(scopeKey) {
|
|
@@ -357,7 +357,7 @@ export async function drainCursorLiveRunTurn(stream, partial, model, context, ru
|
|
|
357
357
|
});
|
|
358
358
|
}
|
|
359
359
|
}
|
|
360
|
-
export async function drainExistingCursorLiveRunBeforeSend(stream, partial, model, context, signal, turnDebugRecorder, scopeKey, occupancyFloor) {
|
|
360
|
+
export async function drainExistingCursorLiveRunBeforeSend(stream, partial, model, context, signal, turnDebugRecorder, scopeKey, occupancyFloor, nativeToolsEnabled = true) {
|
|
361
361
|
turnDebugRecorder?.recordDrainEvent("pre_send_start", {});
|
|
362
362
|
while (true) {
|
|
363
363
|
const run = getPendingCursorLiveRun(context, scopeKey) ?? getActiveCursorLiveRunForCurrentScope(scopeKey);
|
|
@@ -369,6 +369,11 @@ export async function drainExistingCursorLiveRunBeforeSend(stream, partial, mode
|
|
|
369
369
|
const outcome = await cursorLiveRuns.withRunLease(run, signal, async () => {
|
|
370
370
|
if (run.disposed)
|
|
371
371
|
return "continue_send";
|
|
372
|
+
// Check under the drain lease, before consuming/delivering Pi results:
|
|
373
|
+
// resolving a bridge waiter can resume the SDK's old tool surface.
|
|
374
|
+
if ((run.nativeToolsEnabled !== false) !== nativeToolsEnabled) {
|
|
375
|
+
throw new Error("Cursor native tool policy changed while a local run is pending; restore the prior policy to finish it or run /cursor-abort before retrying. Already-executed work is not undone.");
|
|
376
|
+
}
|
|
372
377
|
const consumed = cursorLiveRuns.consumeToolResults(run, context, getCursorNativeReplayIdFromToolCallId);
|
|
373
378
|
await run.bridgeRun?.resolveToolResults(consumed.toolResults);
|
|
374
379
|
const shouldChainUserInput = run.chainUserInputAfterCompletion || hasTrailingUserMessagesAfterToolResults(context);
|
|
@@ -12,7 +12,7 @@ import { CURSOR_PROVIDER } from "./cursor-model.js";
|
|
|
12
12
|
*
|
|
13
13
|
* See pi `docs/custom-provider.md` -> "Context Overflow Errors".
|
|
14
14
|
*/
|
|
15
|
-
|
|
15
|
+
const CURSOR_OVERFLOW_MARKER = "context_length_exceeded";
|
|
16
16
|
/**
|
|
17
17
|
* Context-overflow phrases. These overlap pi's own `OVERFLOW_PATTERNS` so the
|
|
18
18
|
* false-positive surface matches pi's built-in detection for other providers.
|
|
@@ -37,9 +37,9 @@ const CURSOR_THROTTLE_PATTERN = /too many requests|rate.?limit|throttl|retry.?af
|
|
|
37
37
|
* Map a finalized Cursor assistant `errorMessage` to pi's overflow form.
|
|
38
38
|
* Returns `undefined` when the message is not a Cursor context-overflow failure.
|
|
39
39
|
*
|
|
40
|
-
*
|
|
40
|
+
* Idempotent so repeated message delivery does not add another prefix.
|
|
41
41
|
*/
|
|
42
|
-
|
|
42
|
+
function normalizeCursorOverflowErrorMessage(errorMessage) {
|
|
43
43
|
const message = errorMessage?.trim();
|
|
44
44
|
if (!message)
|
|
45
45
|
return undefined;
|
|
@@ -53,22 +53,6 @@ export function normalizeCursorOverflowErrorMessage(errorMessage) {
|
|
|
53
53
|
}
|
|
54
54
|
return undefined;
|
|
55
55
|
}
|
|
56
|
-
/**
|
|
57
|
-
* Rewrite a finalized assistant message into pi's overflow form when it is a
|
|
58
|
-
* Cursor context-overflow failure. Returns the replacement message (for the
|
|
59
|
-
* `message_end` result) or `undefined` to leave it unchanged.
|
|
60
|
-
*
|
|
61
|
-
* `isCursorProvider` carries the provider scoping decision out of the handler so
|
|
62
|
-
* the full logic is unit-testable without a pi event harness.
|
|
63
|
-
*/
|
|
64
|
-
export function rewriteCursorOverflowAssistantMessage(message, isCursorProvider) {
|
|
65
|
-
if (!isCursorProvider || message.stopReason !== "error")
|
|
66
|
-
return undefined;
|
|
67
|
-
const rewritten = normalizeCursorOverflowErrorMessage(message.errorMessage);
|
|
68
|
-
if (!rewritten)
|
|
69
|
-
return undefined;
|
|
70
|
-
return { ...message, errorMessage: rewritten };
|
|
71
|
-
}
|
|
72
56
|
/**
|
|
73
57
|
* Register a `message_end` handler that rewrites Cursor context-overflow
|
|
74
58
|
* failures into the `context_length_exceeded` form pi auto-compacts on.
|
|
@@ -82,9 +66,11 @@ export function registerCursorOverflowNormalization(pi) {
|
|
|
82
66
|
if (message.role !== "assistant")
|
|
83
67
|
return undefined;
|
|
84
68
|
const isCursorProvider = message.provider === CURSOR_PROVIDER || ctx.model?.provider === CURSOR_PROVIDER;
|
|
85
|
-
|
|
69
|
+
if (!isCursorProvider || message.stopReason !== "error")
|
|
70
|
+
return undefined;
|
|
71
|
+
const rewritten = normalizeCursorOverflowErrorMessage(message.errorMessage);
|
|
86
72
|
if (!rewritten)
|
|
87
73
|
return undefined;
|
|
88
|
-
return { message: rewritten };
|
|
74
|
+
return { message: { ...message, errorMessage: rewritten } };
|
|
89
75
|
});
|
|
90
76
|
}
|
|
@@ -24,6 +24,7 @@ import { MISSING_CURSOR_API_KEY_MESSAGE } from "./cursor-provider-errors.js";
|
|
|
24
24
|
import { CursorSdkTurnCoordinator } from "./cursor-provider-turn-coordinator.js";
|
|
25
25
|
import { resolveCursorApiKey } from "./cursor-api-key.js";
|
|
26
26
|
import { loadCursorSdk } from "./cursor-sdk-runtime.js";
|
|
27
|
+
import { assertCursorNativeToolsRuntime } from "./cursor-native-tools.js";
|
|
27
28
|
import { prepareCursorSummaryProviderTurn } from "./cursor-provider-turn-summary.js";
|
|
28
29
|
function buildCursorCloudPromptContext(context, handoff, nativeSourceRoles) {
|
|
29
30
|
if (handoff === "bootstrap")
|
|
@@ -182,7 +183,7 @@ async function prepareCursorCloudProviderTurn(prepareParams) {
|
|
|
182
183
|
}
|
|
183
184
|
}
|
|
184
185
|
async function prepareCursorLocalProviderTurn(prepareParams) {
|
|
185
|
-
const { params, cwd, resolvedApiKey, sdkEventDebug, throwIfAborted, resolvedConfig, agentMode, selection, fastEnabled, agents, customSubagentNames } = prepareParams;
|
|
186
|
+
const { params, cwd, resolvedApiKey, sdkEventDebug, throwIfAborted, resolvedConfig, agentMode, selection, fastEnabled, agents, customSubagentNames, nativeToolsEnabled } = prepareParams;
|
|
186
187
|
const { model, context, options } = params;
|
|
187
188
|
let restoreCursorSdkOutputFilter;
|
|
188
189
|
let sessionAgentScopeKey;
|
|
@@ -212,6 +213,7 @@ async function prepareCursorLocalProviderTurn(prepareParams) {
|
|
|
212
213
|
settingSources,
|
|
213
214
|
localSafety,
|
|
214
215
|
agents,
|
|
216
|
+
nativeToolsEnabled,
|
|
215
217
|
localResume: resolvedConfig.local.resume.value,
|
|
216
218
|
storeRoot: resolvedConfig.local.storeRoot.value,
|
|
217
219
|
useHttp1ForAgent,
|
|
@@ -236,6 +238,7 @@ async function prepareCursorLocalProviderTurn(prepareParams) {
|
|
|
236
238
|
...getCursorPromptOptions(model),
|
|
237
239
|
nativeSourceRoles: params.request.nativeSourceRoles,
|
|
238
240
|
agentMode,
|
|
241
|
+
nativeToolsEnabled,
|
|
239
242
|
includePiBridgeGuidance,
|
|
240
243
|
includePiAskQuestionGuidance: false,
|
|
241
244
|
};
|
|
@@ -245,6 +248,7 @@ async function prepareCursorLocalProviderTurn(prepareParams) {
|
|
|
245
248
|
return {
|
|
246
249
|
...promptOptions,
|
|
247
250
|
toolManifest: buildCursorToolManifestText({
|
|
251
|
+
nativeToolsEnabled,
|
|
248
252
|
bridgeSnapshot: sessionAgentLease.bridgeRun?.snapshot,
|
|
249
253
|
piBridgeEnabled: resolveCursorPiToolBridgeEnabled(),
|
|
250
254
|
includePiBridgeGuidance,
|
|
@@ -295,6 +299,7 @@ async function prepareCursorLocalProviderTurn(prepareParams) {
|
|
|
295
299
|
agentMode,
|
|
296
300
|
localForce: resolvedConfig.local.force.value,
|
|
297
301
|
localResume: resolvedConfig.local.resume.value,
|
|
302
|
+
...(nativeToolsEnabled ? {} : { nativeToolsEnabled: false, tools: ["mcp"] }),
|
|
298
303
|
resumedAgent: sessionAgentLease.resumed,
|
|
299
304
|
activeToolNames: activeToolNames ? [...activeToolNames] : [],
|
|
300
305
|
sessionAgentScopeKey,
|
|
@@ -308,6 +313,7 @@ async function prepareCursorLocalProviderTurn(prepareParams) {
|
|
|
308
313
|
? cursorLiveRuns.start({
|
|
309
314
|
id: useNativeToolReplay ? nativeReplayId : bridgeRun?.id ?? nativeReplayId,
|
|
310
315
|
agent,
|
|
316
|
+
nativeToolsEnabled,
|
|
311
317
|
bridgeRun,
|
|
312
318
|
sessionBridgeRun,
|
|
313
319
|
sessionAgentScopeKey,
|
|
@@ -387,6 +393,7 @@ async function prepareCursorLocalProviderTurn(prepareParams) {
|
|
|
387
393
|
export async function prepareCursorProviderTurn(prepareParams) {
|
|
388
394
|
const { params, resolvedConfig } = prepareParams;
|
|
389
395
|
const { model, options } = params;
|
|
396
|
+
assertCursorNativeToolsRuntime(resolvedConfig.runtime.value, prepareParams.nativeToolsEnabled);
|
|
390
397
|
const fastEnabled = resolvedConfig.runtime.value === "cloud" ? undefined : getEffectiveFastForModelId(model.id, params.scope.scopeKey);
|
|
391
398
|
const selection = buildCursorModelSelection(model.id, options?.reasoning ?? "off", fastEnabled);
|
|
392
399
|
if (resolvedConfig.runtime.value === "local" && params.request.purpose !== "normal") {
|