pi-cursor-sdk 0.1.5 → 0.1.6
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 +7 -0
- package/README.md +11 -1
- package/docs/cursor-model-ux-spec.md +1 -1
- package/package.json +1 -1
- package/src/cursor-native-tool-display.ts +78 -13
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.1.6 - 2026-05-10
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- Avoid loading failures when another extension already owns `read`, `bash`, or `ls`; Cursor native replay now registers only non-conflicting wrappers and falls back to scrubbed activity transcripts for skipped tools.
|
|
10
|
+
- `PI_CURSOR_NATIVE_TOOL_DISPLAY=0` now skips Cursor native replay tool registration instead of only disabling replay at runtime.
|
|
11
|
+
|
|
5
12
|
## 0.1.5 - 2026-05-09
|
|
6
13
|
|
|
7
14
|
### Changed
|
package/README.md
CHANGED
|
@@ -210,7 +210,7 @@ Fallback models are a conservative startup model list. Actual Cursor runs still
|
|
|
210
210
|
## Limits
|
|
211
211
|
|
|
212
212
|
- **Local Cursor SDK agents only.** This extension does not use Cursor cloud agents.
|
|
213
|
-
- **Cursor-side tool use is not re-executed by pi.** Cursor still uses its own internal SDK tools. The extension records completed Cursor tool activity and, in interactive TTY sessions, replays supported `read`, `bash`, and `ls` activity through pi's native tool-call path with recorded results (for example green `read` and `$ ...` cards) without forcing Cursor to call pi tools or rerun commands. As Cursor SDK tool completions arrive, the extension mirrors native Codex ordering by ending a tool-use turn, letting pi render the recorded tool results immediately, then continuing with live post-tool Cursor thinking/text, any later Cursor tool batches, or Cursor's final answer as the next assistant turn. Non-interactive/session consumers still get bounded scrubbed transcript data so `pi -p` keeps printing normal assistant text.
|
|
213
|
+
- **Cursor-side tool use is not re-executed by pi.** Cursor still uses its own internal SDK tools. The extension records completed Cursor tool activity and, in interactive TTY sessions, replays supported `read`, `bash`, and `ls` activity through pi's native tool-call path with recorded results (for example green `read` and `$ ...` cards) without forcing Cursor to call pi tools or rerun commands. Native replay wrappers are registered only for tool names not already owned by another extension; skipped tools fall back to the scrubbed Cursor activity transcript. As Cursor SDK tool completions arrive, the extension mirrors native Codex ordering by ending a tool-use turn, letting pi render the recorded tool results immediately, then continuing with live post-tool Cursor thinking/text, any later Cursor tool batches, or Cursor's final answer as the next assistant turn. Non-interactive/session consumers still get bounded scrubbed transcript data so `pi -p` keeps printing normal assistant text.
|
|
214
214
|
- **Pi tool schemas are not passed through to Cursor.** This extension is a Cursor provider, not a bridge that forwards pi's tool system into Cursor.
|
|
215
215
|
- **One fresh Cursor agent is created per provider call.** Cursor agent state is not reused between pi provider calls.
|
|
216
216
|
- **Ambient Cursor setting/rule layers are not loaded by default.** The current Cursor SDK writes setting/rule loading logs directly to terminal output, which corrupts pi's TUI, so the extension leaves those layers out.
|
|
@@ -263,6 +263,16 @@ Fast mode is currently off. The footer only shows `cursor fast` when fast mode i
|
|
|
263
263
|
|
|
264
264
|
They are not loaded by default in this extension. See [Limits](#limits).
|
|
265
265
|
|
|
266
|
+
### Cursor native tool cards conflict with another extension
|
|
267
|
+
|
|
268
|
+
Cursor native replay is a UI enhancement for interactive TTY sessions. If another extension already owns `read`, `bash`, or `ls`, this extension skips only the conflicting native replay wrapper and uses the scrubbed Cursor activity transcript for that tool instead. To disable Cursor native replay registration entirely, start pi with:
|
|
269
|
+
|
|
270
|
+
```bash
|
|
271
|
+
PI_CURSOR_NATIVE_TOOL_DISPLAY=0 pi --model cursor/composer-2
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
`PI_CURSOR_REGISTER_NATIVE_TOOLS=0` is also accepted as a registration-only opt-out.
|
|
275
|
+
|
|
266
276
|
## Development
|
|
267
277
|
|
|
268
278
|
Run checks:
|
|
@@ -17,7 +17,7 @@ Current implementation notes:
|
|
|
17
17
|
- Cursor auth uses pi-native API-key resolution for provider `cursor`: CLI `--api-key`, stored `~/.pi/agent/auth.json` API key from `/login`, then `CURSOR_API_KEY`. The extension config file stores only non-secret Cursor-only state such as fast defaults.
|
|
18
18
|
- Local agents do not pass `settingSources` by default because the current Cursor SDK writes setting/rule loading INFO logs directly to terminal output, which corrupts pi's TUI.
|
|
19
19
|
- Cursor SDK models are treated as thinking-capable even when pi reports `thinking=no`; that pi column only means the SDK did not expose a pi-controllable thinking parameter for that model.
|
|
20
|
-
- Cursor-side thinking remains visible. Cursor internal tool activity is recorded from SDK events and scrubbed. In interactive TTY sessions, supported completed `read`, `bash`, and `ls` activity is replayed through pi's native tool-call rendering path with recorded Cursor results, so the TUI can show native green cards without forcing Cursor to call pi tools or rerunning Cursor's reads/shell commands. When these native cards are emitted, the provider mirrors Codex's turn shape as Cursor SDK completions arrive: assistant `toolUse`, pi `toolResult`s, live post-tool Cursor thinking/text, any later Cursor tool batches as further `toolUse` turns, then Cursor's final assistant answer. Non-interactive runs keep bounded scrubbed transcript output instead, preserving `pi -p` assistant text output. Cursor text deltas stream live when native tool replay is not active.
|
|
20
|
+
- Cursor-side thinking remains visible. Cursor internal tool activity is recorded from SDK events and scrubbed. In interactive TTY sessions, supported completed `read`, `bash`, and `ls` activity is replayed through pi's native tool-call rendering path with recorded Cursor results, so the TUI can show native green cards without forcing Cursor to call pi tools or rerunning Cursor's reads/shell commands. Native replay wrappers are registered only for tool names not already owned by another extension; conflicting tools use the bounded scrubbed transcript fallback. When these native cards are emitted, the provider mirrors Codex's turn shape as Cursor SDK completions arrive: assistant `toolUse`, pi `toolResult`s, live post-tool Cursor thinking/text, any later Cursor tool batches as further `toolUse` turns, then Cursor's final assistant answer. Non-interactive runs keep bounded scrubbed transcript output instead, preserving `pi -p` assistant text output. Cursor text deltas stream live when native tool replay is not active.
|
|
21
21
|
- Cursor SDK usage events report cumulative internal agent/tool/cache work, not the replayable pi prompt context. The extension reports approximate prompt/output usage for pi context display and compaction decisions instead of copying raw Cursor SDK usage.
|
|
22
22
|
- For models without a catalog `context` parameter, context windows are not hardcoded. The extension ships a bundled SDK-derived default/non-Max cache generated from `createAgentPlatform().checkpointStore.loadLatest(agentId).tokenDetails.maxTokens`. Successful runs can update a local override cache, but model discovery does not probe models at startup.
|
|
23
23
|
- Max Mode context windows are distinct from default/non-Max context windows. `@cursor/sdk` 1.0.12 exposes internal protobuf fields named `maxMode`/`max_mode`, but the public `ModelSelection` type and the local executor path do not pass a Max Mode selector for local agent runs. Do not advertise Max Mode context windows unless the SDK catalog exposes an exact parameter/variant or the SDK public API adds a Max Mode selector that the extension actually sends.
|
package/package.json
CHANGED
|
@@ -3,12 +3,16 @@ import {
|
|
|
3
3
|
createLsToolDefinition,
|
|
4
4
|
createReadToolDefinition,
|
|
5
5
|
type ExtensionAPI,
|
|
6
|
+
type ExtensionContext,
|
|
6
7
|
type ToolDefinition,
|
|
7
8
|
} from "@earendil-works/pi-coding-agent";
|
|
8
9
|
import type { TSchema } from "typebox";
|
|
9
10
|
import type { CursorPiToolDisplay } from "./cursor-tool-transcript.js";
|
|
10
11
|
|
|
11
|
-
const NATIVE_CURSOR_TOOL_NAMES =
|
|
12
|
+
const NATIVE_CURSOR_TOOL_NAMES = ["read", "bash", "ls"] as const;
|
|
13
|
+
type NativeCursorToolName = (typeof NATIVE_CURSOR_TOOL_NAMES)[number];
|
|
14
|
+
const NATIVE_CURSOR_TOOL_DISPLAY_ENV = "PI_CURSOR_NATIVE_TOOL_DISPLAY";
|
|
15
|
+
const NATIVE_CURSOR_TOOL_REGISTRATION_ENV = "PI_CURSOR_REGISTER_NATIVE_TOOLS";
|
|
12
16
|
|
|
13
17
|
export interface CursorNativeToolDisplayItem extends CursorPiToolDisplay {
|
|
14
18
|
id: string;
|
|
@@ -16,26 +20,41 @@ export interface CursorNativeToolDisplayItem extends CursorPiToolDisplay {
|
|
|
16
20
|
}
|
|
17
21
|
|
|
18
22
|
let nativeToolDisplayEnabled = false;
|
|
23
|
+
const registeredNativeToolNames = new Set<string>();
|
|
19
24
|
const nativeToolResults = new Map<string, CursorNativeToolDisplayItem>();
|
|
20
25
|
|
|
26
|
+
function readBooleanEnv(name: string): boolean | undefined {
|
|
27
|
+
const value = process.env[name]?.trim().toLowerCase();
|
|
28
|
+
if (value === "1" || value === "true" || value === "yes" || value === "on") return true;
|
|
29
|
+
if (value === "0" || value === "false" || value === "no" || value === "off") return false;
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function isCursorNativeToolDisplayRequested(): boolean {
|
|
34
|
+
const override = readBooleanEnv(NATIVE_CURSOR_TOOL_DISPLAY_ENV);
|
|
35
|
+
if (override !== undefined) return override;
|
|
36
|
+
return process.stdout.isTTY === true;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function isCursorNativeToolRegistrationRequested(): boolean {
|
|
40
|
+
if (readBooleanEnv(NATIVE_CURSOR_TOOL_REGISTRATION_ENV) === false) return false;
|
|
41
|
+
return isCursorNativeToolDisplayRequested();
|
|
42
|
+
}
|
|
43
|
+
|
|
21
44
|
export function isCursorNativeToolDisplayEnabled(): boolean {
|
|
22
45
|
return nativeToolDisplayEnabled;
|
|
23
46
|
}
|
|
24
47
|
|
|
25
48
|
export function isCursorNativeToolDisplayRuntimeEnabled(): boolean {
|
|
26
|
-
|
|
27
|
-
const override = process.env.PI_CURSOR_NATIVE_TOOL_DISPLAY;
|
|
28
|
-
if (override === "1" || override === "true") return true;
|
|
29
|
-
if (override === "0" || override === "false") return false;
|
|
30
|
-
return process.stdout.isTTY === true;
|
|
49
|
+
return isCursorNativeToolDisplayRequested() && registeredNativeToolNames.size > 0;
|
|
31
50
|
}
|
|
32
51
|
|
|
33
52
|
export function canRenderCursorToolNatively(toolName: string): boolean {
|
|
34
|
-
return
|
|
53
|
+
return registeredNativeToolNames.has(toolName);
|
|
35
54
|
}
|
|
36
55
|
|
|
37
56
|
export function recordCursorNativeToolDisplay(item: CursorNativeToolDisplayItem): void {
|
|
38
|
-
if (!
|
|
57
|
+
if (!canRenderCursorToolNatively(item.toolName)) return;
|
|
39
58
|
nativeToolResults.set(item.id, item);
|
|
40
59
|
}
|
|
41
60
|
|
|
@@ -48,6 +67,7 @@ function consumeCursorNativeToolDisplay(id: string): CursorNativeToolDisplayItem
|
|
|
48
67
|
export const __testUtils = {
|
|
49
68
|
reset(): void {
|
|
50
69
|
nativeToolDisplayEnabled = false;
|
|
70
|
+
registeredNativeToolNames.clear();
|
|
51
71
|
nativeToolResults.clear();
|
|
52
72
|
},
|
|
53
73
|
};
|
|
@@ -71,10 +91,55 @@ function wrapNativeCursorTool<TParams extends TSchema, TDetails, TState>(
|
|
|
71
91
|
};
|
|
72
92
|
}
|
|
73
93
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
94
|
+
function registerNativeCursorTool(pi: ExtensionAPI, toolName: NativeCursorToolName, cwd: string): void {
|
|
95
|
+
if (toolName === "read") {
|
|
96
|
+
pi.registerTool(wrapNativeCursorTool(createReadToolDefinition(cwd)));
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
if (toolName === "bash") {
|
|
100
|
+
pi.registerTool(wrapNativeCursorTool(createBashToolDefinition(cwd)));
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
79
103
|
pi.registerTool(wrapNativeCursorTool(createLsToolDefinition(cwd)));
|
|
80
104
|
}
|
|
105
|
+
|
|
106
|
+
function getExistingToolOwner(pi: ExtensionAPI, toolName: NativeCursorToolName): string | undefined {
|
|
107
|
+
const existingTool = pi.getAllTools().find((tool) => tool.name === toolName);
|
|
108
|
+
if (!existingTool || existingTool.sourceInfo.source === "builtin") return undefined;
|
|
109
|
+
return existingTool.sourceInfo.path ?? existingTool.sourceInfo.source;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function registerAvailableNativeCursorTools(pi: ExtensionAPI, ctx: ExtensionContext): void {
|
|
113
|
+
if (!isCursorNativeToolRegistrationRequested()) {
|
|
114
|
+
nativeToolDisplayEnabled = false;
|
|
115
|
+
registeredNativeToolNames.clear();
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const cwd = process.cwd();
|
|
120
|
+
const skippedToolNames: string[] = [];
|
|
121
|
+
for (const toolName of NATIVE_CURSOR_TOOL_NAMES) {
|
|
122
|
+
if (registeredNativeToolNames.has(toolName)) continue;
|
|
123
|
+
const existingOwner = getExistingToolOwner(pi, toolName);
|
|
124
|
+
if (existingOwner) {
|
|
125
|
+
skippedToolNames.push(toolName);
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
registerNativeCursorTool(pi, toolName, cwd);
|
|
129
|
+
registeredNativeToolNames.add(toolName);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
nativeToolDisplayEnabled = registeredNativeToolNames.size > 0;
|
|
133
|
+
if (skippedToolNames.length > 0 && readBooleanEnv(NATIVE_CURSOR_TOOL_DISPLAY_ENV) === true && ctx.hasUI) {
|
|
134
|
+
ctx.ui.notify(
|
|
135
|
+
`Cursor native tool replay skipped for ${skippedToolNames.join(", ")} because another extension already provides ${skippedToolNames.length === 1 ? "that tool" : "those tools"}. Cursor will use scrubbed activity transcripts for skipped tools.`,
|
|
136
|
+
"warning",
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export function registerCursorNativeToolDisplay(pi: ExtensionAPI): void {
|
|
142
|
+
pi.on("session_start", async (_event, ctx) => {
|
|
143
|
+
registerAvailableNativeCursorTools(pi, ctx);
|
|
144
|
+
});
|
|
145
|
+
}
|