pi-cursor-sdk 0.5.3 → 0.5.5
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 +61 -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 +424 -1
- package/dist/cursor-live-run-coordinator.js +1 -0
- package/dist/cursor-local-run-terminal-error.js +57 -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-finalize.js +2 -0
- package/dist/cursor-provider-turn-prepare.js +8 -1
- package/dist/cursor-provider-turn-runner.js +17 -6
- package/dist/cursor-provider-turn-send.js +3 -0
- 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 +3 -2
- 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 +4 -4
- package/docs/cursor-model-ux-spec.md +18 -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 +22 -4
- package/docs/cursor-tool-surfaces.md +32 -3
- package/docs/development.md +101 -0
- package/docs/platform-smoke-implementation.md +3 -2
- package/docs/reference.md +810 -0
- package/node_modules/@modelcontextprotocol/core/node_modules/zod/package.json +1 -1
- package/node_modules/@modelcontextprotocol/core/node_modules/zod/src/v4/classic/tests/array.test.ts +27 -0
- package/node_modules/@modelcontextprotocol/core/node_modules/zod/src/v4/classic/tests/map.test.ts +7 -0
- package/node_modules/@modelcontextprotocol/core/node_modules/zod/src/v4/classic/tests/set.test.ts +6 -0
- package/node_modules/@modelcontextprotocol/core/node_modules/zod/src/v4/core/compile.ts +9 -0
- package/node_modules/@modelcontextprotocol/core/node_modules/zod/src/v4/core/schemas.ts +35 -1
- package/node_modules/@modelcontextprotocol/core/node_modules/zod/src/v4/core/util.ts +13 -0
- package/node_modules/@modelcontextprotocol/core/node_modules/zod/src/v4/core/versions.ts +1 -1
- package/node_modules/@modelcontextprotocol/core/node_modules/zod/v4/core/compile.cjs +8 -0
- package/node_modules/@modelcontextprotocol/core/node_modules/zod/v4/core/compile.js +8 -0
- package/node_modules/@modelcontextprotocol/core/node_modules/zod/v4/core/schemas.cjs +30 -0
- package/node_modules/@modelcontextprotocol/core/node_modules/zod/v4/core/schemas.d.cts +3 -1
- package/node_modules/@modelcontextprotocol/core/node_modules/zod/v4/core/schemas.d.ts +3 -1
- package/node_modules/@modelcontextprotocol/core/node_modules/zod/v4/core/schemas.js +30 -0
- package/node_modules/@modelcontextprotocol/core/node_modules/zod/v4/core/util.cjs +15 -0
- package/node_modules/@modelcontextprotocol/core/node_modules/zod/v4/core/util.d.cts +2 -0
- package/node_modules/@modelcontextprotocol/core/node_modules/zod/v4/core/util.d.ts +2 -0
- package/node_modules/@modelcontextprotocol/core/node_modules/zod/v4/core/util.js +14 -0
- package/node_modules/@modelcontextprotocol/core/node_modules/zod/v4/core/versions.cjs +1 -1
- package/node_modules/@modelcontextprotocol/core/node_modules/zod/v4/core/versions.js +1 -1
- package/node_modules/@modelcontextprotocol/server/node_modules/zod/package.json +1 -1
- package/node_modules/@modelcontextprotocol/server/node_modules/zod/src/v4/classic/tests/array.test.ts +27 -0
- package/node_modules/@modelcontextprotocol/server/node_modules/zod/src/v4/classic/tests/map.test.ts +7 -0
- package/node_modules/@modelcontextprotocol/server/node_modules/zod/src/v4/classic/tests/set.test.ts +6 -0
- package/node_modules/@modelcontextprotocol/server/node_modules/zod/src/v4/core/compile.ts +9 -0
- package/node_modules/@modelcontextprotocol/server/node_modules/zod/src/v4/core/schemas.ts +35 -1
- package/node_modules/@modelcontextprotocol/server/node_modules/zod/src/v4/core/util.ts +13 -0
- package/node_modules/@modelcontextprotocol/server/node_modules/zod/src/v4/core/versions.ts +1 -1
- package/node_modules/@modelcontextprotocol/server/node_modules/zod/v4/core/compile.cjs +8 -0
- package/node_modules/@modelcontextprotocol/server/node_modules/zod/v4/core/compile.js +8 -0
- package/node_modules/@modelcontextprotocol/server/node_modules/zod/v4/core/schemas.cjs +30 -0
- package/node_modules/@modelcontextprotocol/server/node_modules/zod/v4/core/schemas.d.cts +3 -1
- package/node_modules/@modelcontextprotocol/server/node_modules/zod/v4/core/schemas.d.ts +3 -1
- package/node_modules/@modelcontextprotocol/server/node_modules/zod/v4/core/schemas.js +30 -0
- package/node_modules/@modelcontextprotocol/server/node_modules/zod/v4/core/util.cjs +15 -0
- package/node_modules/@modelcontextprotocol/server/node_modules/zod/v4/core/util.d.cts +2 -0
- package/node_modules/@modelcontextprotocol/server/node_modules/zod/v4/core/util.d.ts +2 -0
- package/node_modules/@modelcontextprotocol/server/node_modules/zod/v4/core/util.js +14 -0
- package/node_modules/@modelcontextprotocol/server/node_modules/zod/v4/core/versions.cjs +1 -1
- package/node_modules/@modelcontextprotocol/server/node_modules/zod/v4/core/versions.js +1 -1
- package/package.json +14 -6
- 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/scripts/steering-rpc-smoke.mjs +1 -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 +424 -1
- package/src/cursor-live-run-coordinator.ts +5 -0
- package/src/cursor-local-run-terminal-error.ts +58 -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-finalize.ts +2 -0
- package/src/cursor-provider-turn-prepare.ts +10 -1
- package/src/cursor-provider-turn-runner.ts +17 -6
- package/src/cursor-provider-turn-send.ts +2 -0
- 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 +3 -2
- 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
|
@@ -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,
|
|
@@ -1,18 +1,12 @@
|
|
|
1
1
|
import { spawnSync } from "node:child_process";
|
|
2
2
|
import {
|
|
3
|
-
CURSOR_PI_TOOL_BRIDGE_DEBUG_ENV,
|
|
4
3
|
CURSOR_PI_TOOL_BRIDGE_DIAGNOSTIC_PREFIX,
|
|
5
4
|
type CursorPiToolBridgeDiagnosticEvent,
|
|
6
5
|
serializeCursorPiToolBridgeDiagnostic,
|
|
7
6
|
} from "./cursor-pi-tool-bridge-diagnostics.js";
|
|
8
|
-
import {
|
|
9
|
-
CURSOR_PI_TOOL_BRIDGE_BUILTINS_ENV,
|
|
10
|
-
CURSOR_PI_TOOL_BRIDGE_CALL_TIMEOUT_MS_ENV,
|
|
11
|
-
CURSOR_PI_TOOL_BRIDGE_ENV,
|
|
12
|
-
} from "./cursor-pi-tool-bridge-env.js";
|
|
13
7
|
import { bridgeToolExecutionAbortTracker } from "./cursor-pi-tool-bridge-abort.js";
|
|
14
|
-
import { isCursorPiBridgeToolCallId
|
|
15
|
-
import {
|
|
8
|
+
import { isCursorPiBridgeToolCallId } from "./cursor-pi-tool-bridge-constants.js";
|
|
9
|
+
import { CursorPiToolBridgeRegistry } from "./cursor-pi-tool-bridge-server.js";
|
|
16
10
|
import type {
|
|
17
11
|
CursorPiToolBridge,
|
|
18
12
|
CursorPiToolBridgeExtensionApi,
|
|
@@ -34,9 +28,6 @@ export type {
|
|
|
34
28
|
export type { CursorPiToolBridgeDiagnosticEvent } from "./cursor-pi-tool-bridge-diagnostics.js";
|
|
35
29
|
export { resolveCursorPiToolBridgeDebugEnabled } from "./cursor-pi-tool-bridge-diagnostics.js";
|
|
36
30
|
export {
|
|
37
|
-
CURSOR_PI_TOOL_BRIDGE_BUILTINS_ENV,
|
|
38
|
-
CURSOR_PI_TOOL_BRIDGE_CALL_TIMEOUT_MS_ENV,
|
|
39
|
-
CURSOR_PI_TOOL_BRIDGE_ENV,
|
|
40
31
|
resolveCursorPiToolBridgeBuiltinsEnabled,
|
|
41
32
|
resolveCursorPiToolBridgeCallTimeoutMs,
|
|
42
33
|
resolveCursorPiToolBridgeEnabled,
|
|
@@ -145,13 +136,7 @@ export function getRegisteredCursorPiToolBridge(pi?: object): CursorPiToolBridge
|
|
|
145
136
|
}
|
|
146
137
|
|
|
147
138
|
export const __testUtils = {
|
|
148
|
-
CURSOR_PI_TOOL_BRIDGE_ENV,
|
|
149
|
-
CURSOR_PI_TOOL_BRIDGE_BUILTINS_ENV,
|
|
150
|
-
CURSOR_PI_TOOL_BRIDGE_CALL_TIMEOUT_MS_ENV,
|
|
151
|
-
CURSOR_PI_TOOL_BRIDGE_DEBUG_ENV,
|
|
152
139
|
CURSOR_PI_TOOL_BRIDGE_DIAGNOSTIC_PREFIX,
|
|
153
|
-
LOOPBACK_HOST,
|
|
154
|
-
MCP_SERVER_NAME,
|
|
155
140
|
createRegistry(
|
|
156
141
|
pi: CursorPiToolBridgeSnapshotApi,
|
|
157
142
|
env: Record<string, string | undefined> = process.env,
|
|
@@ -168,7 +153,6 @@ export const __testUtils = {
|
|
|
168
153
|
return bridgeToolExecutionAbortTracker.getActiveCount();
|
|
169
154
|
},
|
|
170
155
|
buildWindowsBridgeBashAbortCommandForTests: buildWindowsBridgeBashAbortCommand,
|
|
171
|
-
installWindowsBridgeBashAbortMarkerForTests: installWindowsBridgeBashAbortMarker,
|
|
172
156
|
emitBridgeToolExecutionProcessAbortSignalForTests(signal: NodeJS.Signals) {
|
|
173
157
|
bridgeToolExecutionAbortTracker.emitProcessAbortSignalForTests(signal);
|
|
174
158
|
},
|
|
@@ -258,14 +258,6 @@ export function classifyCursorConnectError(error: unknown): CursorConnectErrorCl
|
|
|
258
258
|
return undefined;
|
|
259
259
|
}
|
|
260
260
|
|
|
261
|
-
export function isCursorSdkAbortConnectError(error: unknown): boolean {
|
|
262
|
-
return classifyCursorConnectError(error)?.kind === "abort";
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
export function isUnauthenticatedConnectError(error: unknown): boolean {
|
|
266
|
-
return classifyCursorConnectError(error)?.kind === "unauthenticated";
|
|
267
|
-
}
|
|
268
|
-
|
|
269
261
|
function isLikelyNetworkTimeout(message: string): boolean {
|
|
270
262
|
return (
|
|
271
263
|
/\b(ETIMEDOUT|ECONNRESET|ECONNREFUSED|ENETUNREACH|EAI_AGAIN|NGHTTP2_ENHANCE_YOUR_CALM|ERR_HTTP2_STREAM_ERROR|ERR_HTTP2_SESSION_ERROR)\b/i.test(
|
|
@@ -29,7 +29,7 @@ import { formatInactiveCursorReplayTrace } from "./cursor-native-replay-trace.js
|
|
|
29
29
|
import { partitionNativeToolsByActiveContext } from "./cursor-native-replay-routing.js";
|
|
30
30
|
import type { CursorSdkEventDebugRecorder } from "./cursor-sdk-event-debug.js";
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
const DEFAULT_CURSOR_NATIVE_REPLAY_IDLE_DISPOSE_MS = 5 * 60 * 1000;
|
|
33
33
|
function parseCursorNativeReplayIdleDisposeMs(raw: string | undefined): number {
|
|
34
34
|
if (!raw || /[^0-9]/.test(raw)) return DEFAULT_CURSOR_NATIVE_REPLAY_IDLE_DISPOSE_MS;
|
|
35
35
|
const value = Number(raw);
|
|
@@ -69,7 +69,7 @@ function getCursorNativeReplayIdFromToolCallId(toolCallId: string): string | und
|
|
|
69
69
|
return CURSOR_NATIVE_REPLAY_TOOL_ID_PATTERN.exec(toolCallId)?.[1];
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
-
|
|
72
|
+
function getPendingCursorLiveRun(context: Context, scopeKey: string): CursorLiveRun | undefined {
|
|
73
73
|
return cursorLiveRuns.getPendingFromContext(context, getCursorNativeReplayIdFromToolCallId, scopeKey);
|
|
74
74
|
}
|
|
75
75
|
|
|
@@ -449,6 +449,7 @@ export async function drainExistingCursorLiveRunBeforeSend(
|
|
|
449
449
|
turnDebugRecorder: CursorSdkEventDebugRecorder | undefined,
|
|
450
450
|
scopeKey: string,
|
|
451
451
|
occupancyFloor?: number,
|
|
452
|
+
nativeToolsEnabled = true,
|
|
452
453
|
): Promise<LiveRunPreSendOutcome> {
|
|
453
454
|
turnDebugRecorder?.recordDrainEvent("pre_send_start", {});
|
|
454
455
|
while (true) {
|
|
@@ -461,6 +462,11 @@ export async function drainExistingCursorLiveRunBeforeSend(
|
|
|
461
462
|
try {
|
|
462
463
|
const outcome = await cursorLiveRuns.withRunLease(run, signal, async () => {
|
|
463
464
|
if (run.disposed) return "continue_send" as const;
|
|
465
|
+
// Check under the drain lease, before consuming/delivering Pi results:
|
|
466
|
+
// resolving a bridge waiter can resume the SDK's old tool surface.
|
|
467
|
+
if ((run.nativeToolsEnabled !== false) !== nativeToolsEnabled) {
|
|
468
|
+
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.");
|
|
469
|
+
}
|
|
464
470
|
const consumed = cursorLiveRuns.consumeToolResults(run, context, getCursorNativeReplayIdFromToolCallId);
|
|
465
471
|
await run.bridgeRun?.resolveToolResults(consumed.toolResults);
|
|
466
472
|
const shouldChainUserInput = run.chainUserInputAfterCompletion || hasTrailingUserMessagesAfterToolResults(context);
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
import type { AssistantMessage } from "@earendil-works/pi-ai";
|
|
3
2
|
import { CURSOR_PROVIDER } from "./cursor-model.js";
|
|
4
3
|
|
|
5
4
|
/**
|
|
@@ -15,7 +14,7 @@ import { CURSOR_PROVIDER } from "./cursor-model.js";
|
|
|
15
14
|
*
|
|
16
15
|
* See pi `docs/custom-provider.md` -> "Context Overflow Errors".
|
|
17
16
|
*/
|
|
18
|
-
|
|
17
|
+
const CURSOR_OVERFLOW_MARKER = "context_length_exceeded";
|
|
19
18
|
|
|
20
19
|
/**
|
|
21
20
|
* Context-overflow phrases. These overlap pi's own `OVERFLOW_PATTERNS` so the
|
|
@@ -43,9 +42,9 @@ const CURSOR_THROTTLE_PATTERN = /too many requests|rate.?limit|throttl|retry.?af
|
|
|
43
42
|
* Map a finalized Cursor assistant `errorMessage` to pi's overflow form.
|
|
44
43
|
* Returns `undefined` when the message is not a Cursor context-overflow failure.
|
|
45
44
|
*
|
|
46
|
-
*
|
|
45
|
+
* Idempotent so repeated message delivery does not add another prefix.
|
|
47
46
|
*/
|
|
48
|
-
|
|
47
|
+
function normalizeCursorOverflowErrorMessage(errorMessage: string | undefined): string | undefined {
|
|
49
48
|
const message = errorMessage?.trim();
|
|
50
49
|
if (!message) return undefined;
|
|
51
50
|
// Idempotent: never double-prefix an already-normalized message.
|
|
@@ -57,24 +56,6 @@ export function normalizeCursorOverflowErrorMessage(errorMessage: string | undef
|
|
|
57
56
|
return undefined;
|
|
58
57
|
}
|
|
59
58
|
|
|
60
|
-
/**
|
|
61
|
-
* Rewrite a finalized assistant message into pi's overflow form when it is a
|
|
62
|
-
* Cursor context-overflow failure. Returns the replacement message (for the
|
|
63
|
-
* `message_end` result) or `undefined` to leave it unchanged.
|
|
64
|
-
*
|
|
65
|
-
* `isCursorProvider` carries the provider scoping decision out of the handler so
|
|
66
|
-
* the full logic is unit-testable without a pi event harness.
|
|
67
|
-
*/
|
|
68
|
-
export function rewriteCursorOverflowAssistantMessage(
|
|
69
|
-
message: AssistantMessage,
|
|
70
|
-
isCursorProvider: boolean,
|
|
71
|
-
): AssistantMessage | undefined {
|
|
72
|
-
if (!isCursorProvider || message.stopReason !== "error") return undefined;
|
|
73
|
-
const rewritten = normalizeCursorOverflowErrorMessage(message.errorMessage);
|
|
74
|
-
if (!rewritten) return undefined;
|
|
75
|
-
return { ...message, errorMessage: rewritten };
|
|
76
|
-
}
|
|
77
|
-
|
|
78
59
|
export type CursorOverflowExtensionApi = Pick<ExtensionAPI, "on">;
|
|
79
60
|
|
|
80
61
|
/**
|
|
@@ -89,8 +70,9 @@ export function registerCursorOverflowNormalization(pi: CursorOverflowExtensionA
|
|
|
89
70
|
const message = event.message;
|
|
90
71
|
if (message.role !== "assistant") return undefined;
|
|
91
72
|
const isCursorProvider = message.provider === CURSOR_PROVIDER || ctx.model?.provider === CURSOR_PROVIDER;
|
|
92
|
-
|
|
73
|
+
if (!isCursorProvider || message.stopReason !== "error") return undefined;
|
|
74
|
+
const rewritten = normalizeCursorOverflowErrorMessage(message.errorMessage);
|
|
93
75
|
if (!rewritten) return undefined;
|
|
94
|
-
return { message: rewritten };
|
|
76
|
+
return { message: { ...message, errorMessage: rewritten } };
|
|
95
77
|
});
|
|
96
78
|
}
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
} from "./cursor-provider-run-outcome.js";
|
|
17
17
|
import type { StartedCursorProviderTurn } from "./cursor-provider-turn-types.js";
|
|
18
18
|
import { loadCursorSdk } from "./cursor-sdk-runtime.js";
|
|
19
|
+
import { resolveCursorLocalRunTerminalError } from "./cursor-local-run-terminal-error.js";
|
|
19
20
|
|
|
20
21
|
export async function cacheSdkContextWindow(
|
|
21
22
|
agentId: string,
|
|
@@ -157,6 +158,7 @@ export async function awaitFinalizeCursorRunOutcome(params: AwaitFinalizeCursorR
|
|
|
157
158
|
let waitResult: Awaited<ReturnType<typeof params.run.wait>>;
|
|
158
159
|
try {
|
|
159
160
|
waitResult = params.waitResult ?? (await params.run.wait());
|
|
161
|
+
waitResult = resolveCursorLocalRunTerminalError(params.run, waitResult, params.signal?.aborted === true);
|
|
160
162
|
} catch (error) {
|
|
161
163
|
await recordCursorProviderTerminalUsage(params.prepared, params.signal?.aborted ? "abort" : "error");
|
|
162
164
|
throw error;
|
|
@@ -59,6 +59,7 @@ import type {
|
|
|
59
59
|
} from "./cursor-provider-turn-types.js";
|
|
60
60
|
import type { CursorSdkEventDebugSink } from "./cursor-sdk-event-debug.js";
|
|
61
61
|
import type { SessionCursorAgentLease } from "./cursor-session-agent.js";
|
|
62
|
+
import { assertCursorNativeToolsRuntime } from "./cursor-native-tools.js";
|
|
62
63
|
import { prepareCursorSummaryProviderTurn } from "./cursor-provider-turn-summary.js";
|
|
63
64
|
|
|
64
65
|
export interface PrepareCursorProviderTurnParams {
|
|
@@ -69,6 +70,8 @@ export interface PrepareCursorProviderTurnParams {
|
|
|
69
70
|
throwIfAborted: () => void;
|
|
70
71
|
/** Snapshot resolved once by the runner before draining; reused unchanged through prepare. */
|
|
71
72
|
resolvedConfig: CursorResolvedSdkConfig;
|
|
73
|
+
/** Snapshotted before drain/acquisition, never re-read during creation/resume. */
|
|
74
|
+
nativeToolsEnabled: boolean;
|
|
72
75
|
}
|
|
73
76
|
|
|
74
77
|
interface PrepareCursorProviderTurnContext extends PrepareCursorProviderTurnParams {
|
|
@@ -252,7 +255,7 @@ async function prepareCursorCloudProviderTurn(
|
|
|
252
255
|
async function prepareCursorLocalProviderTurn(
|
|
253
256
|
prepareParams: PrepareCursorProviderTurnContext,
|
|
254
257
|
): Promise<LocalCursorProviderTurnPrepareResult> {
|
|
255
|
-
const { params, cwd, resolvedApiKey, sdkEventDebug, throwIfAborted, resolvedConfig, agentMode, selection, fastEnabled, agents, customSubagentNames } = prepareParams;
|
|
258
|
+
const { params, cwd, resolvedApiKey, sdkEventDebug, throwIfAborted, resolvedConfig, agentMode, selection, fastEnabled, agents, customSubagentNames, nativeToolsEnabled } = prepareParams;
|
|
256
259
|
const { model, context, options } = params;
|
|
257
260
|
|
|
258
261
|
let restoreCursorSdkOutputFilter: (() => void) | undefined;
|
|
@@ -289,6 +292,7 @@ async function prepareCursorLocalProviderTurn(
|
|
|
289
292
|
settingSources,
|
|
290
293
|
localSafety,
|
|
291
294
|
agents,
|
|
295
|
+
nativeToolsEnabled,
|
|
292
296
|
localResume: resolvedConfig.local.resume.value,
|
|
293
297
|
storeRoot: resolvedConfig.local.storeRoot.value,
|
|
294
298
|
useHttp1ForAgent,
|
|
@@ -314,6 +318,7 @@ async function prepareCursorLocalProviderTurn(
|
|
|
314
318
|
...getCursorPromptOptions(model),
|
|
315
319
|
nativeSourceRoles: params.request.nativeSourceRoles,
|
|
316
320
|
agentMode,
|
|
321
|
+
nativeToolsEnabled,
|
|
317
322
|
includePiBridgeGuidance,
|
|
318
323
|
includePiAskQuestionGuidance: false,
|
|
319
324
|
};
|
|
@@ -323,6 +328,7 @@ async function prepareCursorLocalProviderTurn(
|
|
|
323
328
|
return {
|
|
324
329
|
...promptOptions,
|
|
325
330
|
toolManifest: buildCursorToolManifestText({
|
|
331
|
+
nativeToolsEnabled,
|
|
326
332
|
bridgeSnapshot: sessionAgentLease.bridgeRun?.snapshot,
|
|
327
333
|
piBridgeEnabled: resolveCursorPiToolBridgeEnabled(),
|
|
328
334
|
includePiBridgeGuidance,
|
|
@@ -373,6 +379,7 @@ async function prepareCursorLocalProviderTurn(
|
|
|
373
379
|
agentMode,
|
|
374
380
|
localForce: resolvedConfig.local.force.value,
|
|
375
381
|
localResume: resolvedConfig.local.resume.value,
|
|
382
|
+
...(nativeToolsEnabled ? {} : { nativeToolsEnabled: false, tools: ["mcp"] }),
|
|
376
383
|
resumedAgent: sessionAgentLease.resumed,
|
|
377
384
|
activeToolNames: activeToolNames ? [...activeToolNames] : [],
|
|
378
385
|
sessionAgentScopeKey,
|
|
@@ -386,6 +393,7 @@ async function prepareCursorLocalProviderTurn(
|
|
|
386
393
|
? cursorLiveRuns.start({
|
|
387
394
|
id: useNativeToolReplay ? nativeReplayId : bridgeRun?.id ?? nativeReplayId,
|
|
388
395
|
agent,
|
|
396
|
+
nativeToolsEnabled,
|
|
389
397
|
bridgeRun,
|
|
390
398
|
sessionBridgeRun,
|
|
391
399
|
sessionAgentScopeKey,
|
|
@@ -467,6 +475,7 @@ export async function prepareCursorProviderTurn(
|
|
|
467
475
|
): Promise<CursorProviderTurnPrepareResult> {
|
|
468
476
|
const { params, resolvedConfig } = prepareParams;
|
|
469
477
|
const { model, options } = params;
|
|
478
|
+
assertCursorNativeToolsRuntime(resolvedConfig.runtime.value, prepareParams.nativeToolsEnabled);
|
|
470
479
|
|
|
471
480
|
const fastEnabled = resolvedConfig.runtime.value === "cloud" ? undefined : getEffectiveFastForModelId(model.id, params.scope.scopeKey);
|
|
472
481
|
const selection = buildCursorModelSelection(model.id, options?.reasoning ?? "off", fastEnabled);
|