pi-cursor-agent 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +19 -0
- package/README.md +45 -0
- package/package.json +50 -0
- package/src/__generated__/agent/v1/agent_pb.ts +4642 -0
- package/src/__generated__/agent/v1/agent_service_connect.ts +71 -0
- package/src/__generated__/agent/v1/apply_agent_diff_tool_pb.ts +317 -0
- package/src/__generated__/agent/v1/ask_question_tool_pb.ts +588 -0
- package/src/__generated__/agent/v1/background_shell_exec_pb.ts +245 -0
- package/src/__generated__/agent/v1/computer_use_tool_pb.ts +959 -0
- package/src/__generated__/agent/v1/control_service_connect.ts +144 -0
- package/src/__generated__/agent/v1/control_service_pb.ts +1308 -0
- package/src/__generated__/agent/v1/create_plan_tool_pb.ts +366 -0
- package/src/__generated__/agent/v1/cursor_packages_pb.ts +278 -0
- package/src/__generated__/agent/v1/cursor_rules_pb.ts +301 -0
- package/src/__generated__/agent/v1/delete_exec_pb.ts +443 -0
- package/src/__generated__/agent/v1/delete_tool_pb.ts +52 -0
- package/src/__generated__/agent/v1/diagnostics_exec_pb.ts +399 -0
- package/src/__generated__/agent/v1/edit_tool_pb.ts +497 -0
- package/src/__generated__/agent/v1/exa_fetch_tool_pb.ts +472 -0
- package/src/__generated__/agent/v1/exa_search_tool_pb.ts +484 -0
- package/src/__generated__/agent/v1/exec_pb.ts +1271 -0
- package/src/__generated__/agent/v1/exec_service_connect.ts +14 -0
- package/src/__generated__/agent/v1/fetch_tool_pb.ts +242 -0
- package/src/__generated__/agent/v1/generate_image_tool_pb.ts +230 -0
- package/src/__generated__/agent/v1/glob_tool_pb.ts +248 -0
- package/src/__generated__/agent/v1/grep_exec_pb.ts +690 -0
- package/src/__generated__/agent/v1/grep_tool_pb.ts +52 -0
- package/src/__generated__/agent/v1/kv_pb.ts +281 -0
- package/src/__generated__/agent/v1/ls_exec_pb.ts +295 -0
- package/src/__generated__/agent/v1/ls_tool_pb.ts +52 -0
- package/src/__generated__/agent/v1/mcp_pb.ts +302 -0
- package/src/__generated__/agent/v1/mcp_resource_tool_pb.ts +688 -0
- package/src/__generated__/agent/v1/mcp_tool_pb.ts +630 -0
- package/src/__generated__/agent/v1/private_worker_bridge_external_connect.ts +26 -0
- package/src/__generated__/agent/v1/read_exec_pb.ts +412 -0
- package/src/__generated__/agent/v1/read_lints_tool_pb.ts +384 -0
- package/src/__generated__/agent/v1/read_tool_pb.ts +342 -0
- package/src/__generated__/agent/v1/record_screen_tool_pb.ts +376 -0
- package/src/__generated__/agent/v1/reflect_tool_pb.ts +236 -0
- package/src/__generated__/agent/v1/repo_pb.ts +154 -0
- package/src/__generated__/agent/v1/report_bugfix_results_tool_pb.ts +305 -0
- package/src/__generated__/agent/v1/request_context_exec_pb.ts +528 -0
- package/src/__generated__/agent/v1/sandbox_pb.ts +125 -0
- package/src/__generated__/agent/v1/selected_context_pb.ts +2272 -0
- package/src/__generated__/agent/v1/semsearch_tool_pb.ts +230 -0
- package/src/__generated__/agent/v1/setup_vm_environment_tool_pb.ts +168 -0
- package/src/__generated__/agent/v1/shell_exec_pb.ts +1195 -0
- package/src/__generated__/agent/v1/shell_tool_pb.ts +176 -0
- package/src/__generated__/agent/v1/start_grind_execution_tool_pb.ts +212 -0
- package/src/__generated__/agent/v1/start_grind_planning_tool_pb.ts +212 -0
- package/src/__generated__/agent/v1/subagents_pb.ts +1106 -0
- package/src/__generated__/agent/v1/switch_mode_tool_pb.ts +429 -0
- package/src/__generated__/agent/v1/todo_tool_pb.ts +551 -0
- package/src/__generated__/agent/v1/utils_pb.ts +348 -0
- package/src/__generated__/agent/v1/web_fetch_tool_pb.ts +429 -0
- package/src/__generated__/agent/v1/web_search_tool_pb.ts +466 -0
- package/src/__generated__/agent/v1/write_exec_pb.ts +379 -0
- package/src/__generated__/agent/v1/write_shell_stdin_tool_pb.ts +224 -0
- package/src/__generated__/aiserver/v1/aiserver_service_connect.ts +40 -0
- package/src/api/agent-service.ts +55 -0
- package/src/api/ai-service.ts +42 -0
- package/src/api/auth.ts +74 -0
- package/src/index.ts +101 -0
- package/src/lib/agent-store/disk.ts +139 -0
- package/src/lib/agent-store/index.ts +72 -0
- package/src/lib/agent-store/json-blob-store.ts +47 -0
- package/src/lib/auth.ts +135 -0
- package/src/lib/backoff.ts +32 -0
- package/src/lib/env.ts +3 -0
- package/src/lib/heartbeat.ts +21 -0
- package/src/pi/agent-store.ts +102 -0
- package/src/pi/env.ts +11 -0
- package/src/pi/executors/delete.ts +129 -0
- package/src/pi/executors/grep.ts +238 -0
- package/src/pi/executors/hook.ts +64 -0
- package/src/pi/executors/ls.ts +107 -0
- package/src/pi/executors/read.ts +73 -0
- package/src/pi/executors/request-context.ts +120 -0
- package/src/pi/executors/shell-stream.ts +136 -0
- package/src/pi/executors/shell.ts +157 -0
- package/src/pi/executors/stubs.ts +173 -0
- package/src/pi/executors/write.ts +189 -0
- package/src/pi/local-resource-provider/index.ts +10 -0
- package/src/pi/local-resource-provider/provider.ts +98 -0
- package/src/pi/local-resource-provider/types.ts +110 -0
- package/src/pi/model-mapping.ts +115 -0
- package/src/pi/model-override.ts +110 -0
- package/src/pi/model.ts +61 -0
- package/src/pi/request-builder.ts +279 -0
- package/src/pi/utils/tool-result.ts +35 -0
- package/src/stream.ts +386 -0
- package/src/tool-host.ts +44 -0
- package/src/vendor/agent-client/checkpoint-controller.ts +34 -0
- package/src/vendor/agent-client/connect.ts +348 -0
- package/src/vendor/agent-client/exec-controller.ts +102 -0
- package/src/vendor/agent-client/index.ts +25 -0
- package/src/vendor/agent-client/interaction-controller.ts +96 -0
- package/src/vendor/agent-client/split-stream.ts +143 -0
- package/src/vendor/agent-core/index.ts +9 -0
- package/src/vendor/agent-core/interaction-conversion.ts +558 -0
- package/src/vendor/agent-exec/controlled.ts +104 -0
- package/src/vendor/agent-exec/index.ts +45 -0
- package/src/vendor/agent-exec/registry-resource-accessor.ts +39 -0
- package/src/vendor/agent-exec/resources.ts +121 -0
- package/src/vendor/agent-exec/serialization.ts +22 -0
- package/src/vendor/agent-exec/simple-controlled-exec-manager.ts +161 -0
- package/src/vendor/agent-kv/agent-store.ts +115 -0
- package/src/vendor/agent-kv/blob-store.ts +36 -0
- package/src/vendor/agent-kv/controlled.ts +117 -0
- package/src/vendor/agent-kv/index.ts +15 -0
- package/src/vendor/agent-kv/serde.ts +44 -0
- package/src/vendor/local-exec/common.ts +19 -0
- package/src/vendor/local-exec/git-executor.ts +37 -0
- package/src/vendor/local-exec/git-helpers.ts +79 -0
- package/src/vendor/local-exec/index.ts +8 -0
- package/src/vendor/utils/index.ts +5 -0
- package/src/vendor/utils/map-writable.ts +34 -0
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
Api,
|
|
3
|
+
Context,
|
|
4
|
+
Message,
|
|
5
|
+
Model,
|
|
6
|
+
TextContent,
|
|
7
|
+
Tool,
|
|
8
|
+
ToolResultMessage,
|
|
9
|
+
} from "@mariozechner/pi-ai";
|
|
10
|
+
import { type JsonValue, Value } from "@bufbuild/protobuf";
|
|
11
|
+
import {
|
|
12
|
+
AgentClientMessage,
|
|
13
|
+
AgentConversationTurnStructure,
|
|
14
|
+
AgentRunRequest,
|
|
15
|
+
AssistantMessage as AssistantMessageProto,
|
|
16
|
+
ConversationAction,
|
|
17
|
+
type ConversationStateStructure,
|
|
18
|
+
ConversationStep,
|
|
19
|
+
ConversationTurnStructure,
|
|
20
|
+
ModelDetails,
|
|
21
|
+
UserMessage,
|
|
22
|
+
UserMessageAction,
|
|
23
|
+
ConversationStateStructure as ConversationStateStructureClass,
|
|
24
|
+
} from "../__generated__/agent/v1/agent_pb";
|
|
25
|
+
import {
|
|
26
|
+
type McpToolDefinition,
|
|
27
|
+
McpToolDefinition as McpToolDefinitionClass,
|
|
28
|
+
McpTools,
|
|
29
|
+
} from "../__generated__/agent/v1/mcp_pb";
|
|
30
|
+
import { toolResultToText } from "./utils/tool-result";
|
|
31
|
+
import { getBlobId, type BlobStore } from "../vendor/agent-kv";
|
|
32
|
+
|
|
33
|
+
const CURSOR_NATIVE_TOOL_NAMES = new Set([
|
|
34
|
+
"bash",
|
|
35
|
+
"read",
|
|
36
|
+
"write",
|
|
37
|
+
"delete",
|
|
38
|
+
"ls",
|
|
39
|
+
"grep",
|
|
40
|
+
"lsp",
|
|
41
|
+
"todo_write",
|
|
42
|
+
]);
|
|
43
|
+
|
|
44
|
+
type ContextWithTools = Context & { tools?: Tool[] };
|
|
45
|
+
|
|
46
|
+
export function extractUserMessageText(msg: Message): string {
|
|
47
|
+
if (msg.role !== "user") return "";
|
|
48
|
+
if (typeof msg.content === "string") return msg.content.trim();
|
|
49
|
+
return msg.content
|
|
50
|
+
.filter((c): c is TextContent => c.type === "text")
|
|
51
|
+
.map((c) => c.text)
|
|
52
|
+
.join("\n")
|
|
53
|
+
.trim();
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function extractAssistantMessageText(msg: Message): string {
|
|
57
|
+
if (msg.role !== "assistant") return "";
|
|
58
|
+
if (!Array.isArray(msg.content)) return "";
|
|
59
|
+
return msg.content
|
|
60
|
+
.filter((c): c is TextContent => c.type === "text")
|
|
61
|
+
.map((c) => c.text)
|
|
62
|
+
.join("\n");
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function buildConversationTurns(messages: Message[]): Uint8Array[] {
|
|
66
|
+
const turns: Uint8Array[] = [];
|
|
67
|
+
|
|
68
|
+
let i = 0;
|
|
69
|
+
while (i < messages.length) {
|
|
70
|
+
const msg = messages[i];
|
|
71
|
+
if (!msg || msg.role !== "user") {
|
|
72
|
+
i++;
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
let isLastUserMessage = true;
|
|
77
|
+
for (let j = i + 1; j < messages.length; j++) {
|
|
78
|
+
if (messages[j]?.role === "user") {
|
|
79
|
+
isLastUserMessage = false;
|
|
80
|
+
break;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
if (isLastUserMessage) break;
|
|
84
|
+
|
|
85
|
+
const userText = extractUserMessageText(msg);
|
|
86
|
+
if (!userText) {
|
|
87
|
+
i++;
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const userMessage = new UserMessage({
|
|
92
|
+
text: userText,
|
|
93
|
+
messageId: crypto.randomUUID(),
|
|
94
|
+
});
|
|
95
|
+
const userMessageBytes = userMessage.toBinary();
|
|
96
|
+
|
|
97
|
+
const stepBytes: Uint8Array[] = [];
|
|
98
|
+
i++;
|
|
99
|
+
while (i < messages.length && messages[i]?.role !== "user") {
|
|
100
|
+
const stepMsg = messages[i];
|
|
101
|
+
if (!stepMsg) {
|
|
102
|
+
i++;
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (stepMsg.role === "assistant") {
|
|
107
|
+
const text = extractAssistantMessageText(stepMsg);
|
|
108
|
+
if (text) {
|
|
109
|
+
const step = new ConversationStep({
|
|
110
|
+
message: {
|
|
111
|
+
case: "assistantMessage",
|
|
112
|
+
value: new AssistantMessageProto({ text }),
|
|
113
|
+
},
|
|
114
|
+
});
|
|
115
|
+
stepBytes.push(step.toBinary());
|
|
116
|
+
}
|
|
117
|
+
} else if (stepMsg.role === "toolResult") {
|
|
118
|
+
const text = toolResultToText(stepMsg as ToolResultMessage);
|
|
119
|
+
if (text) {
|
|
120
|
+
const step = new ConversationStep({
|
|
121
|
+
message: {
|
|
122
|
+
case: "assistantMessage",
|
|
123
|
+
value: new AssistantMessageProto({
|
|
124
|
+
text: `[Tool Result]\n${text}`,
|
|
125
|
+
}),
|
|
126
|
+
},
|
|
127
|
+
});
|
|
128
|
+
stepBytes.push(step.toBinary());
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
i++;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const agentTurn = new AgentConversationTurnStructure({
|
|
136
|
+
userMessage: new Uint8Array(userMessageBytes),
|
|
137
|
+
steps: stepBytes,
|
|
138
|
+
});
|
|
139
|
+
const turn = new ConversationTurnStructure({
|
|
140
|
+
turn: { case: "agentConversationTurn", value: agentTurn },
|
|
141
|
+
});
|
|
142
|
+
turns.push(turn.toBinary());
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return turns;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export function buildMcpToolDefinitions(
|
|
149
|
+
tools: Tool[] | undefined,
|
|
150
|
+
): McpToolDefinition[] {
|
|
151
|
+
if (!tools || tools.length === 0) {
|
|
152
|
+
return [];
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const advertisedTools = tools.filter(
|
|
156
|
+
(tool) => !CURSOR_NATIVE_TOOL_NAMES.has(tool.name),
|
|
157
|
+
);
|
|
158
|
+
if (advertisedTools.length === 0) {
|
|
159
|
+
return [];
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return advertisedTools.map((tool) => {
|
|
163
|
+
const jsonSchema = tool.parameters as Record<string, unknown> | undefined;
|
|
164
|
+
const schemaValue: JsonValue =
|
|
165
|
+
jsonSchema && typeof jsonSchema === "object"
|
|
166
|
+
? (jsonSchema as JsonValue)
|
|
167
|
+
: { type: "object", properties: {}, required: [] };
|
|
168
|
+
const inputSchema = new Uint8Array(Value.fromJson(schemaValue).toBinary());
|
|
169
|
+
return new McpToolDefinitionClass({
|
|
170
|
+
name: tool.name,
|
|
171
|
+
description: tool.description,
|
|
172
|
+
providerIdentifier: "pi-agent",
|
|
173
|
+
toolName: tool.name,
|
|
174
|
+
inputSchema,
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export interface BuildRunRequestParams {
|
|
180
|
+
model: Model<Api>;
|
|
181
|
+
context: Context;
|
|
182
|
+
conversationId: string;
|
|
183
|
+
blobStore: BlobStore;
|
|
184
|
+
conversationState: ConversationStateStructure | undefined;
|
|
185
|
+
mcpToolDefinitions?: McpToolDefinition[];
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export interface BuildRunRequestResult {
|
|
189
|
+
initialRequest: AgentClientMessage;
|
|
190
|
+
conversationState: ConversationStateStructure;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export function buildRunRequest(
|
|
194
|
+
params: BuildRunRequestParams,
|
|
195
|
+
): BuildRunRequestResult {
|
|
196
|
+
const systemPromptJson = JSON.stringify({
|
|
197
|
+
role: "system",
|
|
198
|
+
content: params.context.systemPrompt || "You are a helpful assistant.",
|
|
199
|
+
});
|
|
200
|
+
const systemPromptBytes = new TextEncoder().encode(systemPromptJson);
|
|
201
|
+
const systemPromptId = getBlobId(systemPromptBytes);
|
|
202
|
+
void params.blobStore.setBlob(null, systemPromptId, systemPromptBytes);
|
|
203
|
+
|
|
204
|
+
const lastMessage = params.context.messages.at(-1);
|
|
205
|
+
const userText = lastMessage ? extractUserMessageText(lastMessage) : "";
|
|
206
|
+
if (!userText) {
|
|
207
|
+
throw new Error("Cannot send empty user message to Cursor API");
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const userMessage = new UserMessage({
|
|
211
|
+
text: userText,
|
|
212
|
+
messageId: crypto.randomUUID(),
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
const action = new ConversationAction({
|
|
216
|
+
action: {
|
|
217
|
+
case: "userMessageAction",
|
|
218
|
+
value: new UserMessageAction({ userMessage }),
|
|
219
|
+
},
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
const cached = params.conversationState;
|
|
223
|
+
const hasMatchingPrompt = cached?.rootPromptMessagesJson?.some((entry) =>
|
|
224
|
+
Buffer.from(entry).equals(systemPromptId),
|
|
225
|
+
);
|
|
226
|
+
|
|
227
|
+
const turns = buildConversationTurns(params.context.messages);
|
|
228
|
+
|
|
229
|
+
const baseState =
|
|
230
|
+
cached && hasMatchingPrompt
|
|
231
|
+
? cached
|
|
232
|
+
: new ConversationStateStructureClass({
|
|
233
|
+
rootPromptMessagesJson: [systemPromptId],
|
|
234
|
+
turns: [],
|
|
235
|
+
todos: [],
|
|
236
|
+
pendingToolCalls: [],
|
|
237
|
+
previousWorkspaceUris: [],
|
|
238
|
+
fileStates: {},
|
|
239
|
+
fileStatesV2: {},
|
|
240
|
+
summaryArchives: [],
|
|
241
|
+
turnTimings: [],
|
|
242
|
+
subagentStates: {},
|
|
243
|
+
selfSummaryCount: 0,
|
|
244
|
+
readPaths: [],
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
const conversationState = new ConversationStateStructureClass({
|
|
248
|
+
...baseState,
|
|
249
|
+
turns: turns.length > 0 ? turns : baseState.turns,
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
const modelDetails = new ModelDetails({
|
|
253
|
+
modelId: params.model.id,
|
|
254
|
+
displayModelId: params.model.id,
|
|
255
|
+
displayName: params.model.name,
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
const mcpToolDefinitions = params.mcpToolDefinitions ?? [];
|
|
259
|
+
const runRequest = new AgentRunRequest({
|
|
260
|
+
conversationState,
|
|
261
|
+
action,
|
|
262
|
+
modelDetails,
|
|
263
|
+
conversationId: params.conversationId,
|
|
264
|
+
mcpTools: new McpTools({ mcpTools: mcpToolDefinitions }),
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
const initialRequest = new AgentClientMessage({
|
|
268
|
+
message: { case: "runRequest", value: runRequest },
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
return {
|
|
272
|
+
initialRequest,
|
|
273
|
+
conversationState,
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
export function getContextTools(context: Context): McpToolDefinition[] {
|
|
278
|
+
return buildMcpToolDefinitions((context as ContextWithTools).tools);
|
|
279
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ImageContent,
|
|
3
|
+
TextContent,
|
|
4
|
+
ToolResultMessage,
|
|
5
|
+
} from "@mariozechner/pi-ai";
|
|
6
|
+
|
|
7
|
+
export function toolResultToText(result: ToolResultMessage): string {
|
|
8
|
+
return result.content
|
|
9
|
+
.map((item: TextContent | ImageContent) => {
|
|
10
|
+
if (item.type === "text") return item.text;
|
|
11
|
+
return `[${item.mimeType} image]`;
|
|
12
|
+
})
|
|
13
|
+
.join("\n");
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function toolResultWasTruncated(result: ToolResultMessage): boolean {
|
|
17
|
+
if (!result.details || typeof result.details !== "object") {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
const truncation = (
|
|
21
|
+
result.details as { truncation?: { truncated?: boolean } }
|
|
22
|
+
).truncation;
|
|
23
|
+
return !!truncation?.truncated;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function toolResultDetailBoolean(
|
|
27
|
+
result: ToolResultMessage,
|
|
28
|
+
key: string,
|
|
29
|
+
): boolean {
|
|
30
|
+
if (!result.details || typeof result.details !== "object") {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
const value = (result.details as Record<string, unknown>)[key];
|
|
34
|
+
return typeof value === "boolean" ? value : false;
|
|
35
|
+
}
|
package/src/stream.ts
ADDED
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ExtensionAPI,
|
|
3
|
+
ExtensionContext,
|
|
4
|
+
} from "@mariozechner/pi-coding-agent";
|
|
5
|
+
import {
|
|
6
|
+
createAssistantMessageEventStream,
|
|
7
|
+
type Api,
|
|
8
|
+
type AssistantMessage,
|
|
9
|
+
type AssistantMessageEventStream,
|
|
10
|
+
type Context,
|
|
11
|
+
type Model,
|
|
12
|
+
type SimpleStreamOptions,
|
|
13
|
+
type TextContent,
|
|
14
|
+
type ThinkingContent,
|
|
15
|
+
} from "@mariozechner/pi-ai";
|
|
16
|
+
import type { ConversationStateStructure } from "./__generated__/agent/v1/agent_pb";
|
|
17
|
+
import {
|
|
18
|
+
AskQuestionRejected,
|
|
19
|
+
AskQuestionResult,
|
|
20
|
+
} from "./__generated__/agent/v1/ask_question_tool_pb";
|
|
21
|
+
import AgentService from "./api/agent-service";
|
|
22
|
+
import { toCursorId } from "./pi/model-mapping";
|
|
23
|
+
import { buildRunRequest, getContextTools } from "./pi/request-builder";
|
|
24
|
+
import {
|
|
25
|
+
CURSOR_STATE_ENTRY_TYPE,
|
|
26
|
+
ensureAgentStore,
|
|
27
|
+
persistAgentStore,
|
|
28
|
+
} from "./pi/agent-store";
|
|
29
|
+
import {
|
|
30
|
+
LocalResourceProvider,
|
|
31
|
+
type PiToolContext,
|
|
32
|
+
type ToolExecEvent,
|
|
33
|
+
} from "./pi/local-resource-provider";
|
|
34
|
+
import {
|
|
35
|
+
AgentConnectClient,
|
|
36
|
+
type CheckpointHandler,
|
|
37
|
+
type InteractionListener,
|
|
38
|
+
} from "./vendor/agent-client";
|
|
39
|
+
import type {
|
|
40
|
+
CoreInteractionUpdate,
|
|
41
|
+
CoreInteractionQuery,
|
|
42
|
+
CoreInteractionResponse,
|
|
43
|
+
} from "./vendor/agent-core";
|
|
44
|
+
|
|
45
|
+
export const CURSOR_API_URL = "https://api2.cursor.sh";
|
|
46
|
+
const CURSOR_CLIENT_VERSION = "cli-2026.01.17-d239e66";
|
|
47
|
+
|
|
48
|
+
function createCheckpointHandler(
|
|
49
|
+
handler: (checkpoint: ConversationStateStructure) => void,
|
|
50
|
+
): CheckpointHandler {
|
|
51
|
+
return {
|
|
52
|
+
handleCheckpoint(
|
|
53
|
+
_ctx: unknown,
|
|
54
|
+
checkpoint: ConversationStateStructure,
|
|
55
|
+
): Promise<void> {
|
|
56
|
+
handler(checkpoint);
|
|
57
|
+
return Promise.resolve();
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const QUERY_REJECTION_REASON = "Not supported by pi-cursor-agent";
|
|
63
|
+
|
|
64
|
+
function createInteractionListenerAdapter(
|
|
65
|
+
onUpdate: (update: CoreInteractionUpdate) => void,
|
|
66
|
+
): InteractionListener {
|
|
67
|
+
return {
|
|
68
|
+
async sendUpdate(
|
|
69
|
+
_ctx: unknown,
|
|
70
|
+
update: CoreInteractionUpdate,
|
|
71
|
+
): Promise<void> {
|
|
72
|
+
onUpdate(update);
|
|
73
|
+
},
|
|
74
|
+
|
|
75
|
+
async query(
|
|
76
|
+
_ctx: unknown,
|
|
77
|
+
query: CoreInteractionQuery,
|
|
78
|
+
): Promise<CoreInteractionResponse> {
|
|
79
|
+
switch (query.type) {
|
|
80
|
+
case "ask-question-request":
|
|
81
|
+
return {
|
|
82
|
+
result: new AskQuestionResult({
|
|
83
|
+
result: {
|
|
84
|
+
case: "rejected",
|
|
85
|
+
value: new AskQuestionRejected({
|
|
86
|
+
reason: QUERY_REJECTION_REASON,
|
|
87
|
+
}),
|
|
88
|
+
},
|
|
89
|
+
}),
|
|
90
|
+
};
|
|
91
|
+
case "web-search-request":
|
|
92
|
+
case "web-fetch-request":
|
|
93
|
+
case "exa-search-request":
|
|
94
|
+
case "exa-fetch-request":
|
|
95
|
+
case "switch-mode-request":
|
|
96
|
+
return { approved: false, reason: QUERY_REJECTION_REASON };
|
|
97
|
+
case "create-plan-request":
|
|
98
|
+
return {
|
|
99
|
+
result: {
|
|
100
|
+
planUri: "",
|
|
101
|
+
result: {
|
|
102
|
+
case: "error",
|
|
103
|
+
value: { error: QUERY_REJECTION_REASON },
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
} as CoreInteractionResponse;
|
|
107
|
+
case "setup-vm-environment-request":
|
|
108
|
+
return {} as CoreInteractionResponse;
|
|
109
|
+
default:
|
|
110
|
+
return { approved: false, reason: QUERY_REJECTION_REASON };
|
|
111
|
+
}
|
|
112
|
+
},
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function streamCursorAgent(
|
|
117
|
+
pi: ExtensionAPI,
|
|
118
|
+
getCtx: () => ExtensionContext | null,
|
|
119
|
+
model: Model<Api>,
|
|
120
|
+
context: Context,
|
|
121
|
+
options?: SimpleStreamOptions,
|
|
122
|
+
): AssistantMessageEventStream {
|
|
123
|
+
const stream = createAssistantMessageEventStream();
|
|
124
|
+
|
|
125
|
+
(async () => {
|
|
126
|
+
const startTime = Date.now();
|
|
127
|
+
let firstTokenTime: number | undefined;
|
|
128
|
+
|
|
129
|
+
const output: AssistantMessage = {
|
|
130
|
+
role: "assistant",
|
|
131
|
+
content: [],
|
|
132
|
+
api: model.api,
|
|
133
|
+
provider: model.provider,
|
|
134
|
+
model: model.id,
|
|
135
|
+
usage: {
|
|
136
|
+
input: 0,
|
|
137
|
+
output: 0,
|
|
138
|
+
cacheRead: 0,
|
|
139
|
+
cacheWrite: 0,
|
|
140
|
+
totalTokens: 0,
|
|
141
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
|
|
142
|
+
},
|
|
143
|
+
stopReason: "stop",
|
|
144
|
+
timestamp: Date.now(),
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
const sessionId = options?.sessionId ?? "default";
|
|
148
|
+
|
|
149
|
+
try {
|
|
150
|
+
const apiKey = options?.apiKey;
|
|
151
|
+
if (!apiKey) {
|
|
152
|
+
throw new Error(
|
|
153
|
+
"Cursor API key (access token) is required. Run /login cursor or set CURSOR_ACCESS_TOKEN.",
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const agentStore = await ensureAgentStore(sessionId);
|
|
158
|
+
const cwd = getCtx()?.cwd ?? process.cwd();
|
|
159
|
+
const requestContextTools = getContextTools(context);
|
|
160
|
+
|
|
161
|
+
let onToolExec: ((event: ToolExecEvent) => void) | undefined;
|
|
162
|
+
|
|
163
|
+
const piToolCtx: PiToolContext = {
|
|
164
|
+
cwd,
|
|
165
|
+
...(options?.signal ? { signal: options.signal } : {}),
|
|
166
|
+
getActiveTools: () => new Set(pi.getActiveTools()),
|
|
167
|
+
getCtx,
|
|
168
|
+
onToolExec: (event) => onToolExec?.(event),
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
const resources = new LocalResourceProvider({
|
|
172
|
+
ctx: piToolCtx,
|
|
173
|
+
requestContextTools,
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
const blobStore = agentStore.getBlobStore();
|
|
177
|
+
const cursorModelId = toCursorId(model.id, options?.reasoning);
|
|
178
|
+
const { initialRequest, conversationState } = buildRunRequest({
|
|
179
|
+
model: { ...model, id: cursorModelId },
|
|
180
|
+
context,
|
|
181
|
+
conversationId: agentStore.getId(),
|
|
182
|
+
blobStore,
|
|
183
|
+
conversationState: agentStore.getConversationStateStructure(),
|
|
184
|
+
mcpToolDefinitions: requestContextTools,
|
|
185
|
+
});
|
|
186
|
+
agentStore.conversationStateStructure = conversationState;
|
|
187
|
+
|
|
188
|
+
stream.push({ type: "start", partial: output });
|
|
189
|
+
|
|
190
|
+
let currentTextBlock: TextContent | null = null;
|
|
191
|
+
let currentThinkingBlock: ThinkingContent | null = null;
|
|
192
|
+
const usageState = { sawTokenDelta: false };
|
|
193
|
+
|
|
194
|
+
const finalizeTextBlock = () => {
|
|
195
|
+
if (!currentTextBlock) return;
|
|
196
|
+
stream.push({
|
|
197
|
+
type: "text_end",
|
|
198
|
+
contentIndex: output.content.indexOf(currentTextBlock),
|
|
199
|
+
content: currentTextBlock.text,
|
|
200
|
+
partial: output,
|
|
201
|
+
});
|
|
202
|
+
currentTextBlock = null;
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
const finalizeThinkingBlock = () => {
|
|
206
|
+
if (!currentThinkingBlock) return;
|
|
207
|
+
stream.push({
|
|
208
|
+
type: "thinking_end",
|
|
209
|
+
contentIndex: output.content.indexOf(currentThinkingBlock),
|
|
210
|
+
content: currentThinkingBlock.thinking,
|
|
211
|
+
partial: output,
|
|
212
|
+
});
|
|
213
|
+
currentThinkingBlock = null;
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
onToolExec = (event: ToolExecEvent) => {
|
|
217
|
+
if (event.type === "start") {
|
|
218
|
+
finalizeTextBlock();
|
|
219
|
+
finalizeThinkingBlock();
|
|
220
|
+
(stream as any).push({
|
|
221
|
+
type: "tool_exec_start",
|
|
222
|
+
toolCallId: event.toolCallId,
|
|
223
|
+
toolName: event.toolName,
|
|
224
|
+
args: event.args,
|
|
225
|
+
});
|
|
226
|
+
} else {
|
|
227
|
+
(stream as any).push({
|
|
228
|
+
type: "tool_exec_end",
|
|
229
|
+
toolCallId: event.toolCallId,
|
|
230
|
+
toolName: event.toolName,
|
|
231
|
+
result: {
|
|
232
|
+
content: event.result.content,
|
|
233
|
+
details: event.result.details,
|
|
234
|
+
},
|
|
235
|
+
isError: event.result.isError,
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
const handleInteractionUpdate = (update: CoreInteractionUpdate) => {
|
|
241
|
+
switch (update.type) {
|
|
242
|
+
case "text-delta": {
|
|
243
|
+
if (!firstTokenTime) firstTokenTime = Date.now();
|
|
244
|
+
finalizeThinkingBlock();
|
|
245
|
+
const delta = update.text;
|
|
246
|
+
if (!currentTextBlock) {
|
|
247
|
+
currentTextBlock = { type: "text", text: "" };
|
|
248
|
+
output.content.push(currentTextBlock);
|
|
249
|
+
stream.push({
|
|
250
|
+
type: "text_start",
|
|
251
|
+
contentIndex: output.content.length - 1,
|
|
252
|
+
partial: output,
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
currentTextBlock.text += delta;
|
|
256
|
+
stream.push({
|
|
257
|
+
type: "text_delta",
|
|
258
|
+
contentIndex: output.content.indexOf(currentTextBlock),
|
|
259
|
+
delta,
|
|
260
|
+
partial: output,
|
|
261
|
+
});
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
case "thinking-delta": {
|
|
266
|
+
if (!firstTokenTime) firstTokenTime = Date.now();
|
|
267
|
+
finalizeTextBlock();
|
|
268
|
+
const delta = update.text;
|
|
269
|
+
if (!currentThinkingBlock) {
|
|
270
|
+
currentThinkingBlock = { type: "thinking", thinking: "" };
|
|
271
|
+
output.content.push(currentThinkingBlock);
|
|
272
|
+
stream.push({
|
|
273
|
+
type: "thinking_start",
|
|
274
|
+
contentIndex: output.content.length - 1,
|
|
275
|
+
partial: output,
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
currentThinkingBlock.thinking += delta;
|
|
279
|
+
stream.push({
|
|
280
|
+
type: "thinking_delta",
|
|
281
|
+
contentIndex: output.content.indexOf(currentThinkingBlock),
|
|
282
|
+
delta,
|
|
283
|
+
partial: output,
|
|
284
|
+
});
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
case "thinking-completed": {
|
|
289
|
+
finalizeThinkingBlock();
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
case "turn-ended": {
|
|
294
|
+
output.stopReason = "stop";
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
case "token-delta": {
|
|
299
|
+
usageState.sawTokenDelta = true;
|
|
300
|
+
output.usage.output += update.tokens;
|
|
301
|
+
output.usage.totalTokens = output.usage.input + output.usage.output;
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
};
|
|
306
|
+
|
|
307
|
+
const baseUrl = model.baseUrl || CURSOR_API_URL;
|
|
308
|
+
const agentService = new AgentService(baseUrl, {
|
|
309
|
+
accessToken: apiKey,
|
|
310
|
+
clientVersion: CURSOR_CLIENT_VERSION,
|
|
311
|
+
clientType: "cli",
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
const connectClient = new AgentConnectClient(agentService.rpcClient);
|
|
315
|
+
|
|
316
|
+
const interactionListener = createInteractionListenerAdapter(
|
|
317
|
+
handleInteractionUpdate,
|
|
318
|
+
);
|
|
319
|
+
|
|
320
|
+
const checkpointHandler = createCheckpointHandler(
|
|
321
|
+
(checkpoint: ConversationStateStructure) => {
|
|
322
|
+
void agentStore.handleCheckpoint(null, checkpoint);
|
|
323
|
+
if (usageState.sawTokenDelta) return;
|
|
324
|
+
const usedTokens = checkpoint.tokenDetails?.usedTokens ?? 0;
|
|
325
|
+
if (usedTokens > 0 && output.usage.output !== usedTokens) {
|
|
326
|
+
output.usage.output = usedTokens;
|
|
327
|
+
output.usage.totalTokens = output.usage.input + output.usage.output;
|
|
328
|
+
}
|
|
329
|
+
},
|
|
330
|
+
);
|
|
331
|
+
checkpointHandler.getLatestCheckpoint = () =>
|
|
332
|
+
agentStore.getConversationStateStructure();
|
|
333
|
+
|
|
334
|
+
const runOptions: Parameters<typeof connectClient.run>[1] = {
|
|
335
|
+
interactionListener,
|
|
336
|
+
resources,
|
|
337
|
+
blobStore,
|
|
338
|
+
checkpointHandler,
|
|
339
|
+
};
|
|
340
|
+
if (options?.signal) runOptions.signal = options.signal;
|
|
341
|
+
|
|
342
|
+
await connectClient.run(initialRequest, runOptions);
|
|
343
|
+
|
|
344
|
+
finalizeTextBlock();
|
|
345
|
+
finalizeThinkingBlock();
|
|
346
|
+
|
|
347
|
+
output.usage.cost = {
|
|
348
|
+
input: 0,
|
|
349
|
+
output: 0,
|
|
350
|
+
cacheRead: 0,
|
|
351
|
+
cacheWrite: 0,
|
|
352
|
+
total: 0,
|
|
353
|
+
};
|
|
354
|
+
(output as any).duration = Date.now() - startTime;
|
|
355
|
+
if (firstTokenTime) {
|
|
356
|
+
(output as any).ttft = firstTokenTime - startTime;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
stream.push({ type: "done", reason: "stop", message: output });
|
|
360
|
+
stream.end();
|
|
361
|
+
} catch (error) {
|
|
362
|
+
output.stopReason = options?.signal?.aborted ? "aborted" : "error";
|
|
363
|
+
output.errorMessage =
|
|
364
|
+
error instanceof Error ? error.message : String(error);
|
|
365
|
+
(output as any).duration = Date.now() - startTime;
|
|
366
|
+
if (firstTokenTime) (output as any).ttft = firstTokenTime - startTime;
|
|
367
|
+
stream.push({
|
|
368
|
+
type: "error",
|
|
369
|
+
reason: output.stopReason as any,
|
|
370
|
+
error: output,
|
|
371
|
+
});
|
|
372
|
+
stream.end();
|
|
373
|
+
} finally {
|
|
374
|
+
try {
|
|
375
|
+
const snapshot = await persistAgentStore(sessionId);
|
|
376
|
+
if (snapshot) {
|
|
377
|
+
pi.appendEntry(CURSOR_STATE_ENTRY_TYPE, snapshot);
|
|
378
|
+
}
|
|
379
|
+
} catch {
|
|
380
|
+
// ignore persistence errors
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
})();
|
|
384
|
+
|
|
385
|
+
return stream;
|
|
386
|
+
}
|