cursor-opencode-provider 0.4.3 → 0.6.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/README.md +33 -23
- package/dist/context/agents.d.ts +1 -1
- package/dist/context/agents.js +1 -1
- package/dist/context/auth-store.js +2 -2
- package/dist/context/build.d.ts +11 -1
- package/dist/context/build.js +76 -25
- package/dist/context/frozen.d.ts +29 -0
- package/dist/context/frozen.js +168 -0
- package/dist/context/index.d.ts +3 -2
- package/dist/context/index.js +3 -2
- package/dist/context/paths.d.ts +34 -53
- package/dist/context/paths.js +54 -118
- package/dist/context/rules.d.ts +1 -0
- package/dist/context/rules.js +2 -2
- package/dist/image-save-tool.d.ts +19 -0
- package/dist/image-save-tool.js +25 -0
- package/dist/image-save.d.ts +58 -0
- package/dist/image-save.js +189 -0
- package/dist/image-staging.d.ts +59 -0
- package/dist/image-staging.js +91 -0
- package/dist/index.d.ts +1 -1
- package/dist/language-model.d.ts +13 -11
- package/dist/language-model.js +957 -150
- package/dist/plan-execution-kickoff.d.ts +55 -0
- package/dist/plan-execution-kickoff.js +162 -0
- package/dist/plugin-opencode2.js +52 -3
- package/dist/plugin-v2.d.ts +11 -9
- package/dist/plugin-v2.js +6 -13
- package/dist/plugin.d.ts +9 -0
- package/dist/plugin.js +61 -5
- package/dist/pricing-data.d.ts +16 -4
- package/dist/pricing-data.js +16 -4
- package/dist/protocol/ask-question.d.ts +133 -0
- package/dist/protocol/ask-question.js +320 -0
- package/dist/protocol/blob-reachability.d.ts +15 -0
- package/dist/protocol/blob-reachability.js +140 -0
- package/dist/protocol/blob-store.d.ts +37 -0
- package/dist/protocol/blob-store.js +115 -0
- package/dist/protocol/conversation-bind.d.ts +13 -0
- package/dist/protocol/conversation-bind.js +27 -1
- package/dist/protocol/conversation-persistence.d.ts +32 -0
- package/dist/protocol/conversation-persistence.js +504 -0
- package/dist/protocol/conversation-state.d.ts +16 -0
- package/dist/protocol/conversation-state.js +62 -0
- package/dist/protocol/create-plan.d.ts +141 -0
- package/dist/protocol/create-plan.js +373 -0
- package/dist/protocol/generate-image.d.ts +54 -0
- package/dist/protocol/generate-image.js +101 -0
- package/dist/protocol/interactions.d.ts +110 -8
- package/dist/protocol/interactions.js +295 -35
- package/dist/protocol/kv.d.ts +1 -0
- package/dist/protocol/kv.js +2 -0
- package/dist/protocol/messages.d.ts +2 -0
- package/dist/protocol/messages.js +148 -8
- package/dist/protocol/request.d.ts +2 -0
- package/dist/protocol/request.js +2 -0
- package/dist/protocol/switch-mode.d.ts +178 -0
- package/dist/protocol/switch-mode.js +417 -0
- package/dist/protocol/token-details.d.ts +33 -0
- package/dist/protocol/token-details.js +155 -0
- package/dist/protocol/tool-call-bridge.js +10 -5
- package/dist/protocol/tools.d.ts +44 -11
- package/dist/protocol/tools.js +171 -41
- package/dist/session.d.ts +39 -11
- package/dist/shared.d.ts +3 -0
- package/dist/shared.js +3 -0
- package/dist/usage.d.ts +42 -37
- package/dist/usage.js +201 -70
- package/dist/web-search-tool.d.ts +22 -26
- package/dist/web-search-tool.js +25 -15
- package/package.json +7 -4
|
@@ -174,6 +174,12 @@ export function createMessageTypes() {
|
|
|
174
174
|
addType(root, "AskQuestionArgs", [
|
|
175
175
|
{ id: 1, name: "title", type: "string" },
|
|
176
176
|
{ id: 2, name: "questions", type: "AskQuestionItem", repeated: true },
|
|
177
|
+
// #5/#6 drive Cursor's non-blocking AskQuestion: the client answers the
|
|
178
|
+
// interaction with `async` right away and delivers the real answers later
|
|
179
|
+
// through ConversationAction.async_ask_question_completion_action, keyed by
|
|
180
|
+
// the originating tool call id. See protocol/ask-question.ts.
|
|
181
|
+
{ id: 5, name: "run_async", type: "bool" },
|
|
182
|
+
{ id: 6, name: "async_original_tool_call_id", type: "string" },
|
|
177
183
|
]);
|
|
178
184
|
addType(root, "AskQuestionToolCall", [{ id: 1, name: "args", type: "AskQuestionArgs" }]);
|
|
179
185
|
addType(root, "FetchToolArgs", [
|
|
@@ -187,7 +193,23 @@ export function createMessageTypes() {
|
|
|
187
193
|
{ id: 2, name: "explanation", type: "string" },
|
|
188
194
|
{ id: 3, name: "tool_call_id", type: "string" },
|
|
189
195
|
]);
|
|
190
|
-
|
|
196
|
+
// Display ToolCall.result (CLI success hardcodes from_mode_id=""); distinct from
|
|
197
|
+
// InteractionResponse approved/rejected, which is the consent gate.
|
|
198
|
+
addType(root, "SwitchModeSuccess", [
|
|
199
|
+
{ id: 1, name: "from_mode_id", type: "string" },
|
|
200
|
+
{ id: 2, name: "to_mode_id", type: "string" },
|
|
201
|
+
]);
|
|
202
|
+
addType(root, "SwitchModeError", [{ id: 1, name: "error", type: "string" }]);
|
|
203
|
+
addType(root, "SwitchModeRejected", [{ id: 1, name: "reason", type: "string" }]);
|
|
204
|
+
addType(root, "SwitchModeResult", [
|
|
205
|
+
{ id: 1, name: "success", type: "SwitchModeSuccess" },
|
|
206
|
+
{ id: 2, name: "error", type: "SwitchModeError" },
|
|
207
|
+
{ id: 3, name: "rejected", type: "SwitchModeRejected" },
|
|
208
|
+
], [{ name: "result", fields: ["success", "error", "rejected"] }]);
|
|
209
|
+
addType(root, "SwitchModeToolCall", [
|
|
210
|
+
{ id: 1, name: "args", type: "SwitchModeToolArgs" },
|
|
211
|
+
{ id: 2, name: "result", type: "SwitchModeResult" },
|
|
212
|
+
]);
|
|
191
213
|
addType(root, "PiReadToolArgs", [
|
|
192
214
|
{ id: 1, name: "path", type: "string" },
|
|
193
215
|
{ id: 2, name: "offset", type: "int32" },
|
|
@@ -258,7 +280,27 @@ export function createMessageTypes() {
|
|
|
258
280
|
{ id: 2, name: "file_path", type: "string" },
|
|
259
281
|
{ id: 5, name: "reference_image_paths", type: "string", repeated: true },
|
|
260
282
|
]);
|
|
261
|
-
|
|
283
|
+
// Cursor generates the image server-side and returns it on the display
|
|
284
|
+
// tool_call frame. `image_data` is a base64 string, not bytes.
|
|
285
|
+
addType(root, "GenerateImageSuccess", [
|
|
286
|
+
{ id: 1, name: "file_path", type: "string" },
|
|
287
|
+
{ id: 2, name: "image_data", type: "string" },
|
|
288
|
+
]);
|
|
289
|
+
addType(root, "GenerateImageError", [{ id: 1, name: "error", type: "string" }]);
|
|
290
|
+
addType(root, "GenerateImageResult", [
|
|
291
|
+
{ id: 1, name: "success", type: "GenerateImageSuccess" },
|
|
292
|
+
{ id: 2, name: "error", type: "GenerateImageError" },
|
|
293
|
+
], [{ name: "result", fields: ["success", "error"] }]);
|
|
294
|
+
addType(root, "GenerateImageToolCall", [
|
|
295
|
+
{ id: 1, name: "args", type: "GenerateImageToolArgs" },
|
|
296
|
+
{ id: 2, name: "result", type: "GenerateImageResult" },
|
|
297
|
+
]);
|
|
298
|
+
// InteractionQuery keeps query bodies opaque; decoded on demand like
|
|
299
|
+
// AskQuestionInteractionQuery.
|
|
300
|
+
addType(root, "GenerateImageRequestQuery", [
|
|
301
|
+
{ id: 1, name: "args", type: "GenerateImageToolArgs" },
|
|
302
|
+
{ id: 2, name: "tool_call_id", type: "string" },
|
|
303
|
+
]);
|
|
262
304
|
addType(root, "ToolCall", [
|
|
263
305
|
{ id: 57, name: "tool_call_id", type: "string" },
|
|
264
306
|
{ id: 1, name: "shell_tool_call", type: "ShellToolCall" },
|
|
@@ -455,10 +497,20 @@ export function createMessageTypes() {
|
|
|
455
497
|
{ id: 1, name: "path", type: "string" },
|
|
456
498
|
{ id: 2, name: "error", type: "string" },
|
|
457
499
|
]);
|
|
500
|
+
// Cursor's own executor reports a refused write with this variant rather than
|
|
501
|
+
// a generic error, and its agent reads `operation` / `is_readonly` back.
|
|
502
|
+
addType(root, "WritePermissionDenied", [
|
|
503
|
+
{ id: 1, name: "path", type: "string" },
|
|
504
|
+
{ id: 2, name: "directory", type: "string" },
|
|
505
|
+
{ id: 3, name: "operation", type: "string" },
|
|
506
|
+
{ id: 4, name: "error", type: "string" },
|
|
507
|
+
{ id: 5, name: "is_readonly", type: "bool" },
|
|
508
|
+
]);
|
|
458
509
|
addType(root, "WriteResult", [
|
|
459
510
|
{ id: 1, name: "success", type: "WriteSuccess" },
|
|
511
|
+
{ id: 3, name: "permission_denied", type: "WritePermissionDenied" },
|
|
460
512
|
{ id: 5, name: "error", type: "WriteError" },
|
|
461
|
-
], [{ name: "result", fields: ["success", "error"] }]);
|
|
513
|
+
], [{ name: "result", fields: ["success", "permission_denied", "error"] }]);
|
|
462
514
|
// agent.v1 Pi exec results. Cursor places Pi requests at ExecServerMessage
|
|
463
515
|
// fields #45..#51 and their corresponding ExecClientMessage results at
|
|
464
516
|
// #46..#52 (the result fields are deliberately offset by one).
|
|
@@ -1069,11 +1121,29 @@ export function createMessageTypes() {
|
|
|
1069
1121
|
addType(root, "CancelAction", [
|
|
1070
1122
|
{ id: 1, name: "conversation_id", type: "string" },
|
|
1071
1123
|
]);
|
|
1124
|
+
// Deferred answers for an AskQuestion the client already replied to with
|
|
1125
|
+
// `async`. `original_args` is kept as raw bytes so the exact AskQuestionArgs
|
|
1126
|
+
// Cursor sent — including fields this schema does not model — is echoed back
|
|
1127
|
+
// verbatim rather than re-encoded from a lossy decode.
|
|
1128
|
+
addType(root, "AsyncAskQuestionCompletionAction", [
|
|
1129
|
+
{ id: 1, name: "original_tool_call_id", type: "string" },
|
|
1130
|
+
{ id: 2, name: "original_args", type: "bytes" },
|
|
1131
|
+
{ id: 3, name: "result", type: "AskQuestionResult" },
|
|
1132
|
+
]);
|
|
1072
1133
|
addType(root, "ConversationAction", [
|
|
1073
1134
|
{ id: 1, name: "user_message_action", type: "UserMessageAction" },
|
|
1074
1135
|
{ id: 2, name: "resume_action", type: "ResumeAction" },
|
|
1075
1136
|
{ id: 3, name: "cancel_action", type: "CancelAction" },
|
|
1076
|
-
|
|
1137
|
+
{ id: 8, name: "async_ask_question_completion_action", type: "AsyncAskQuestionCompletionAction" },
|
|
1138
|
+
], [{
|
|
1139
|
+
name: "action",
|
|
1140
|
+
fields: [
|
|
1141
|
+
"user_message_action",
|
|
1142
|
+
"resume_action",
|
|
1143
|
+
"cancel_action",
|
|
1144
|
+
"async_ask_question_completion_action",
|
|
1145
|
+
],
|
|
1146
|
+
}]);
|
|
1077
1147
|
// Seed ConversationStateStructure for turn 1 (system prompt as JSON strings
|
|
1078
1148
|
// in #1). After the first conversation_checkpoint_update we echo opaque
|
|
1079
1149
|
// server bytes instead — CLI's live structure uses blob-id bytes in #1/#8.
|
|
@@ -1090,10 +1160,30 @@ export function createMessageTypes() {
|
|
|
1090
1160
|
addType(root, "ConversationTurn", [
|
|
1091
1161
|
{ id: 1, name: "agent_conversation_turn", type: "AgentConversationTurn" },
|
|
1092
1162
|
], [{ name: "turn", fields: ["agent_conversation_turn"] }]);
|
|
1093
|
-
//
|
|
1094
|
-
//
|
|
1163
|
+
// Cursor checkpoints carry authoritative context occupancy in field #5. The
|
|
1164
|
+
// provider still transports the complete checkpoint as opaque bytes; these
|
|
1165
|
+
// declarations support targeted diagnostics/tests without a re-encode.
|
|
1166
|
+
addType(root, "PromptTokenBreakdownCategory", [
|
|
1167
|
+
{ id: 1, name: "id", type: "string" },
|
|
1168
|
+
{ id: 2, name: "label", type: "string" },
|
|
1169
|
+
{ id: 3, name: "estimated_tokens", type: "uint32" },
|
|
1170
|
+
{ id: 4, name: "character_count", type: "uint32" },
|
|
1171
|
+
]);
|
|
1172
|
+
addType(root, "PromptTokenBreakdownSnapshot", [
|
|
1173
|
+
{ id: 1, name: "total_used_tokens", type: "uint32" },
|
|
1174
|
+
{ id: 2, name: "max_tokens", type: "uint32" },
|
|
1175
|
+
{ id: 3, name: "categories", type: "PromptTokenBreakdownCategory", repeated: true },
|
|
1176
|
+
]);
|
|
1177
|
+
addType(root, "ConversationTokenDetails", [
|
|
1178
|
+
{ id: 1, name: "used_tokens", type: "uint32" },
|
|
1179
|
+
{ id: 2, name: "max_tokens", type: "uint32" },
|
|
1180
|
+
{ id: 3, name: "breakdown", type: "PromptTokenBreakdownSnapshot" },
|
|
1181
|
+
]);
|
|
1182
|
+
// Seed schema (JSON strings). AgentRunRequest.conversation_state remains
|
|
1183
|
+
// bytes, so live checkpoints are never decoded/re-encoded on transport.
|
|
1095
1184
|
addType(root, "ConversationStateStructure", [
|
|
1096
1185
|
{ id: 1, name: "root_prompt_messages_json", type: "string", repeated: true },
|
|
1186
|
+
{ id: 5, name: "token_details", type: "ConversationTokenDetails" },
|
|
1097
1187
|
{ id: 8, name: "turns", type: "ConversationTurn", repeated: true },
|
|
1098
1188
|
]);
|
|
1099
1189
|
addType(root, "ModelDetails", [
|
|
@@ -1113,6 +1203,7 @@ export function createMessageTypes() {
|
|
|
1113
1203
|
{ id: 12, name: "field_12", type: "uint32" },
|
|
1114
1204
|
{ id: 14, name: "selected_subagent_models", type: "RequestedModel", repeated: true },
|
|
1115
1205
|
{ id: 16, name: "conversation_group_id", type: "string" },
|
|
1206
|
+
{ id: 25, name: "run_id", type: "string" },
|
|
1116
1207
|
]);
|
|
1117
1208
|
// ── Client heartbeat ──
|
|
1118
1209
|
addType(root, "ClientHeartbeat", []);
|
|
@@ -1151,18 +1242,42 @@ export function createMessageTypes() {
|
|
|
1151
1242
|
{ id: 2, name: "rejected", type: "WebSearchRequestRejected" },
|
|
1152
1243
|
], [{ name: "result", fields: ["approved", "rejected"] }]);
|
|
1153
1244
|
addType(root, "AskQuestionRejected", [{ id: 1, name: "reason", type: "string" }]);
|
|
1245
|
+
addType(root, "AskQuestionError", [{ id: 1, name: "error_message", type: "string" }]);
|
|
1246
|
+
addType(root, "AskQuestionAsync", []);
|
|
1247
|
+
addType(root, "AskQuestionSuccessAnswer", [
|
|
1248
|
+
{ id: 1, name: "question_id", type: "string" },
|
|
1249
|
+
{ id: 2, name: "selected_option_ids", type: "string", repeated: true },
|
|
1250
|
+
{ id: 3, name: "freeform_text", type: "string" },
|
|
1251
|
+
]);
|
|
1252
|
+
addType(root, "AskQuestionSuccess", [
|
|
1253
|
+
{ id: 1, name: "answers", type: "AskQuestionSuccessAnswer", repeated: true },
|
|
1254
|
+
]);
|
|
1154
1255
|
addType(root, "AskQuestionResult", [
|
|
1256
|
+
{ id: 1, name: "success", type: "AskQuestionSuccess" },
|
|
1257
|
+
{ id: 2, name: "error", type: "AskQuestionError" },
|
|
1155
1258
|
{ id: 3, name: "rejected", type: "AskQuestionRejected" },
|
|
1156
|
-
|
|
1259
|
+
{ id: 4, name: "async", type: "AskQuestionAsync" },
|
|
1260
|
+
], [{ name: "result", fields: ["success", "error", "rejected", "async"] }]);
|
|
1157
1261
|
addType(root, "AskQuestionInteractionResponse", [
|
|
1158
1262
|
{ id: 1, name: "result", type: "AskQuestionResult" },
|
|
1159
1263
|
]);
|
|
1264
|
+
// InteractionQuery keeps query bodies opaque (see the InteractionQuery
|
|
1265
|
+
// comment); this type decodes only the ask-question body on demand.
|
|
1266
|
+
addType(root, "AskQuestionInteractionQuery", [
|
|
1267
|
+
{ id: 1, name: "args", type: "AskQuestionArgs" },
|
|
1268
|
+
{ id: 2, name: "tool_call_id", type: "string" },
|
|
1269
|
+
]);
|
|
1160
1270
|
addType(root, "SwitchModeRequestApproved", []);
|
|
1161
1271
|
addType(root, "SwitchModeRequestRejected", [{ id: 1, name: "reason", type: "string" }]);
|
|
1162
1272
|
addType(root, "SwitchModeRequestResponse", [
|
|
1163
1273
|
{ id: 1, name: "approved", type: "SwitchModeRequestApproved" },
|
|
1164
1274
|
{ id: 2, name: "rejected", type: "SwitchModeRequestRejected" },
|
|
1165
1275
|
], [{ name: "result", fields: ["approved", "rejected"] }]);
|
|
1276
|
+
// InteractionQuery keeps query bodies opaque; decoded on demand like
|
|
1277
|
+
// AskQuestionInteractionQuery / CreatePlanRequestQuery.
|
|
1278
|
+
addType(root, "SwitchModeRequestQuery", [
|
|
1279
|
+
{ id: 1, name: "args", type: "SwitchModeToolArgs" },
|
|
1280
|
+
]);
|
|
1166
1281
|
addType(root, "CreatePlanSuccess", []);
|
|
1167
1282
|
addType(root, "CreatePlanError", [{ id: 1, name: "error", type: "string" }]);
|
|
1168
1283
|
addType(root, "CreatePlanResult", [
|
|
@@ -1173,6 +1288,12 @@ export function createMessageTypes() {
|
|
|
1173
1288
|
addType(root, "CreatePlanRequestResponse", [
|
|
1174
1289
|
{ id: 1, name: "result", type: "CreatePlanResult" },
|
|
1175
1290
|
]);
|
|
1291
|
+
// InteractionQuery keeps query bodies opaque; decoded on demand like
|
|
1292
|
+
// AskQuestionInteractionQuery / GenerateImageRequestQuery.
|
|
1293
|
+
addType(root, "CreatePlanRequestQuery", [
|
|
1294
|
+
{ id: 1, name: "args", type: "CreatePlanArgs" },
|
|
1295
|
+
{ id: 2, name: "tool_call_id", type: "string" },
|
|
1296
|
+
]);
|
|
1176
1297
|
addType(root, "SetupVmEnvironmentSuccess", []);
|
|
1177
1298
|
addType(root, "SetupVmEnvironmentResult", [
|
|
1178
1299
|
{ id: 1, name: "success", type: "SetupVmEnvironmentSuccess" },
|
|
@@ -1273,10 +1394,22 @@ export function createMessageTypes() {
|
|
|
1273
1394
|
{ id: 1, name: "run_request", type: "AgentRunRequest" },
|
|
1274
1395
|
{ id: 2, name: "exec_client_message", type: "ExecClientMessage" },
|
|
1275
1396
|
{ id: 3, name: "kv_client_message", type: "KvClientMessage" },
|
|
1397
|
+
{ id: 4, name: "conversation_action", type: "ConversationAction" },
|
|
1276
1398
|
{ id: 5, name: "exec_client_control_message", type: "ExecClientControlMessage" },
|
|
1277
1399
|
{ id: 6, name: "interaction_response", type: "InteractionResponse" },
|
|
1278
1400
|
{ id: 7, name: "client_heartbeat", type: "ClientHeartbeat" },
|
|
1279
|
-
], [{
|
|
1401
|
+
], [{
|
|
1402
|
+
name: "message",
|
|
1403
|
+
fields: [
|
|
1404
|
+
"run_request",
|
|
1405
|
+
"exec_client_message",
|
|
1406
|
+
"kv_client_message",
|
|
1407
|
+
"conversation_action",
|
|
1408
|
+
"exec_client_control_message",
|
|
1409
|
+
"interaction_response",
|
|
1410
|
+
"client_heartbeat",
|
|
1411
|
+
],
|
|
1412
|
+
}]);
|
|
1280
1413
|
// ── AgentServerMessage ──
|
|
1281
1414
|
addType(root, "AgentServerMessage", [
|
|
1282
1415
|
{ id: 1, name: "interaction_update", type: "InteractionUpdate" },
|
|
@@ -1343,6 +1476,13 @@ export function decodeMessage(typeName, data) {
|
|
|
1343
1476
|
const decoded = type.decode(data);
|
|
1344
1477
|
return type.toObject(decoded, { defaults: true, json: true, longs: Number });
|
|
1345
1478
|
}
|
|
1479
|
+
/** Decode without inventing absent proto defaults (used for byte-stable snapshots). */
|
|
1480
|
+
export function decodeMessageSparse(typeName, data) {
|
|
1481
|
+
const root = getMessageTypes();
|
|
1482
|
+
const type = root.lookupType(typeName);
|
|
1483
|
+
const decoded = type.decode(data);
|
|
1484
|
+
return type.toObject(decoded, { json: true, longs: Number });
|
|
1485
|
+
}
|
|
1346
1486
|
/** Schema-free walk of one message's top-level wire fields (no nested descent). */
|
|
1347
1487
|
function readRawFields(buf) {
|
|
1348
1488
|
const reader = protobuf.Reader.create(buf);
|
|
@@ -9,6 +9,8 @@ export type RunRequestInput = {
|
|
|
9
9
|
images?: CursorImageInput[];
|
|
10
10
|
modelId: string;
|
|
11
11
|
conversationId: string;
|
|
12
|
+
/** Stable parent group; unlike conversationId, this survives compaction/rebase. */
|
|
13
|
+
conversationGroupId?: string;
|
|
12
14
|
systemPrompt?: string;
|
|
13
15
|
/**
|
|
14
16
|
* Prior chat turns for a seed ConversationStateStructure (no checkpoint).
|
package/dist/protocol/request.js
CHANGED
|
@@ -76,6 +76,8 @@ export function buildRunRequest(input) {
|
|
|
76
76
|
});
|
|
77
77
|
const runRequest = {
|
|
78
78
|
conversation_id: input.conversationId,
|
|
79
|
+
conversation_group_id: input.conversationGroupId ?? input.conversationId,
|
|
80
|
+
run_id: msgId,
|
|
79
81
|
action,
|
|
80
82
|
requested_model: {
|
|
81
83
|
// The provider always selects a concrete model. Cursor's "default"
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cursor-native SwitchMode ⇄ OpenCode `plan_enter` / `plan_exit`.
|
|
3
|
+
*
|
|
4
|
+
* Cursor raises `switch_mode_request_query` (#4) with SwitchModeArgs and blocks
|
|
5
|
+
* until the client replies approved{} or rejected{reason} (no async variant).
|
|
6
|
+
* Cursor CLI prompts the user, then flips unifiedMode and injects a
|
|
7
|
+
* `<system_reminder>` describing how to behave in that mode.
|
|
8
|
+
* Display `switch_mode_tool_call` (#25) is a separate transcript record and is
|
|
9
|
+
* not replayed as a host tool.
|
|
10
|
+
*
|
|
11
|
+
* Host mapping (advertisement-gated):
|
|
12
|
+
* - plan, spec → plan_enter
|
|
13
|
+
* - every other non-empty target → plan_exit (leave plan for OpenCode build),
|
|
14
|
+
* then inject the Cursor CLI-shaped mode reminder for that target
|
|
15
|
+
*
|
|
16
|
+
* Upstream OpenCode's plan tools are not mode flags. Each one asks the user
|
|
17
|
+
* through the Question service and, on "Yes", injects a synthetic user message
|
|
18
|
+
* carrying `agent: "plan" | "build"`, which `createUserMessage` turns into a
|
|
19
|
+
* `setAgentModel` primary-agent switch (opencode `tool/plan.ts`,
|
|
20
|
+
* `session/prompt.ts`). A provider cannot reach `setAgentModel`, but the two
|
|
21
|
+
* observable halves — the approval gate and the behavioural contract — are both
|
|
22
|
+
* reachable, so the bridge degrades instead of refusing when the host tool is
|
|
23
|
+
* missing (`plan_enter` is commented out upstream and `plan_exit` is gated
|
|
24
|
+
* behind OPENCODE_EXPERIMENTAL_PLAN_MODE + CLI client):
|
|
25
|
+
*
|
|
26
|
+
* - entering plan/spec needs no host tool at all — approve immediately and let
|
|
27
|
+
* the injected <system_reminder> carry the contract, exactly as Cursor CLI's
|
|
28
|
+
* own plan mode is prompt-enforced.
|
|
29
|
+
* - leaving plan mode is the gate the user must actually see, so it falls back
|
|
30
|
+
* to the host `question` tool and only rejects when that is absent too.
|
|
31
|
+
*
|
|
32
|
+
* Resolution is keyed solely on the advertised catalog under canonical tool
|
|
33
|
+
* names — never on host identity or model id. A compatibility layer may restate
|
|
34
|
+
* alternate host vocabulary under canonical names before the provider sees the
|
|
35
|
+
* catalog, so the bridge itself remains OpenCode-shaped.
|
|
36
|
+
*
|
|
37
|
+
* CLI reject reason for user declines: "Mode switch rejected by user"
|
|
38
|
+
* (chunk-7076/dist/ui.js onSwitchModeReject).
|
|
39
|
+
*/
|
|
40
|
+
import { type OpencodeQuestionInput } from "./ask-question.js";
|
|
41
|
+
/**
|
|
42
|
+
* `PendingExec.resultField` for a held-open SwitchMode InteractionQuery.
|
|
43
|
+
* Continuation writes an InteractionResponse, not an ExecClientMessage.
|
|
44
|
+
*/
|
|
45
|
+
export declare const SWITCH_MODE_RESULT_FIELD = "switch_mode_request_response";
|
|
46
|
+
/** CLI verbatim when the user rejects the mode switch. */
|
|
47
|
+
export declare const USER_REJECTED_REASON = "Mode switch rejected by user";
|
|
48
|
+
export declare const MISSING_QUERY_REASON = "Missing switch-mode query";
|
|
49
|
+
export declare const MISSING_ARGS_REASON = "Missing switch-mode arguments";
|
|
50
|
+
export declare const MISSING_TARGET_REASON = "Missing targetModeId";
|
|
51
|
+
/**
|
|
52
|
+
* A turn that cannot call host tools is a lifecycle turn (title generation,
|
|
53
|
+
* compaction, summarization). OpenCode opens one alongside the real Run, and it
|
|
54
|
+
* must not mutate session state — approving there flips plan mode twice and
|
|
55
|
+
* lets the throwaway turn write its own plan file.
|
|
56
|
+
*
|
|
57
|
+
* Soft-ack with `approved{}` instead of `rejected{reason}`: a hard reject lands
|
|
58
|
+
* in the transcript and the real turn narrates "mode switches are blocked"
|
|
59
|
+
* even though the next Run will succeed. CreatePlan already soft-acks the same
|
|
60
|
+
* case; SwitchMode must match.
|
|
61
|
+
*/
|
|
62
|
+
export declare const PLAN_EXIT_UNAVAILABLE_REASON: string;
|
|
63
|
+
/** Upstream `PlanExitTool` wording, minus a plan path the provider cannot verify. */
|
|
64
|
+
export declare const SWITCH_MODE_EXIT_QUESTION = "Planning is complete. Would you like to switch to the build agent and start implementing?";
|
|
65
|
+
export declare const SWITCH_MODE_EXIT_HEADER = "Build Agent";
|
|
66
|
+
export type SwitchModeHostTool = "plan_enter" | "plan_exit";
|
|
67
|
+
/**
|
|
68
|
+
* How a SwitchMode query is satisfied this turn.
|
|
69
|
+
*
|
|
70
|
+
* - `native` the host advertises the matching plan tool; bridge to it.
|
|
71
|
+
* - `approve` entering plan mode needs no host tool — approve immediately.
|
|
72
|
+
* - `ack` lifecycle turn (`allowTools=false`): approve on the wire without
|
|
73
|
+
* mutating session mode (same soft-ack pattern as CreatePlan).
|
|
74
|
+
* - `question` leaving plan mode falls back to the host `question` prompt.
|
|
75
|
+
* - `reject` nothing can satisfy it; `reason` names the real cause.
|
|
76
|
+
*/
|
|
77
|
+
export type SwitchModeBridge = {
|
|
78
|
+
kind: "native";
|
|
79
|
+
toolName: SwitchModeHostTool;
|
|
80
|
+
} | {
|
|
81
|
+
kind: "approve";
|
|
82
|
+
} | {
|
|
83
|
+
kind: "ack";
|
|
84
|
+
} | {
|
|
85
|
+
kind: "question";
|
|
86
|
+
input: OpencodeQuestionInput;
|
|
87
|
+
} | {
|
|
88
|
+
kind: "reject";
|
|
89
|
+
reason: string;
|
|
90
|
+
};
|
|
91
|
+
export type CursorSwitchModeArgs = {
|
|
92
|
+
targetModeId: string;
|
|
93
|
+
explanation: string;
|
|
94
|
+
toolCallId: string;
|
|
95
|
+
};
|
|
96
|
+
export type DecodedSwitchModeQuery = {
|
|
97
|
+
args: CursorSwitchModeArgs;
|
|
98
|
+
toolCallId: string;
|
|
99
|
+
};
|
|
100
|
+
export type SwitchModeMapping = {
|
|
101
|
+
ok: true;
|
|
102
|
+
toolName: SwitchModeHostTool;
|
|
103
|
+
} | {
|
|
104
|
+
ok: false;
|
|
105
|
+
reason: string;
|
|
106
|
+
};
|
|
107
|
+
/** Normalize Cursor mode ids for comparison. */
|
|
108
|
+
export declare function normalizeSwitchModeId(targetModeId: string): string;
|
|
109
|
+
/**
|
|
110
|
+
* Map a Cursor unified-mode id onto an OpenCode plan enter/exit tool.
|
|
111
|
+
* Does not check advertisement — callers gate on the host catalog.
|
|
112
|
+
*
|
|
113
|
+
* plan/spec enter plan; every other non-empty target leaves plan via plan_exit
|
|
114
|
+
* (host-portable primary switch) and relies on {@link cursorModeSystemReminder}
|
|
115
|
+
* for CLI-shaped behavioral guidance.
|
|
116
|
+
*/
|
|
117
|
+
export declare function mapSwitchModeTarget(targetModeId: string): SwitchModeMapping;
|
|
118
|
+
/** Host `question` input mirroring upstream `PlanExitTool`'s own prompt. */
|
|
119
|
+
export declare function switchModeExitQuestionInput(): OpencodeQuestionInput;
|
|
120
|
+
/**
|
|
121
|
+
* Resolve how this SwitchMode target is satisfied, keyed only on the advertised
|
|
122
|
+
* catalog under canonical tool names.
|
|
123
|
+
*
|
|
124
|
+
* A missing plan tool is not a refusal: entering plan mode needs no host tool,
|
|
125
|
+
* and leaving it degrades to the `question` prompt so the user still approves
|
|
126
|
+
* before any execution starts.
|
|
127
|
+
*/
|
|
128
|
+
export declare function resolveSwitchModeBridge(targetModeId: string, options: {
|
|
129
|
+
allowTools: boolean;
|
|
130
|
+
advertised: ReadonlySet<string> | Iterable<string>;
|
|
131
|
+
/** Cursor unified mode currently recorded for this session, if any. */
|
|
132
|
+
activeModeId?: string;
|
|
133
|
+
}): SwitchModeBridge;
|
|
134
|
+
/** Decode a `switch_mode_request_query` body, or undefined when unusable. */
|
|
135
|
+
export declare function decodeSwitchModeQuery(queryBytes: Uint8Array): DecodedSwitchModeQuery | undefined;
|
|
136
|
+
/** OpenCode plan_enter / plan_exit advertise empty input. */
|
|
137
|
+
export declare function switchModeToolInput(): Record<string, never>;
|
|
138
|
+
/** Shape `approved{}` / `rejected{reason}` for encodeMessage. */
|
|
139
|
+
export declare function switchModeApprovedResult(): Record<string, unknown>;
|
|
140
|
+
export declare function switchModeRejectedResult(reason: string): Record<string, unknown>;
|
|
141
|
+
/**
|
|
142
|
+
* Host tool outcome → Cursor SwitchModeRequestResponse.
|
|
143
|
+
* Question/permission declines and empty failures use the CLI user-reject string.
|
|
144
|
+
*/
|
|
145
|
+
export declare function switchModeResultFromToolOutput(output: string, isError: boolean): Record<string, unknown>;
|
|
146
|
+
/**
|
|
147
|
+
* Host `question` outcome → Cursor SwitchModeRequestResponse, for the emulated
|
|
148
|
+
* plan exit. Only an explicit "Yes" approves; an unanswered or dismissed prompt
|
|
149
|
+
* keeps the model in plan mode rather than silently starting execution.
|
|
150
|
+
*/
|
|
151
|
+
export declare function switchModeResultFromQuestionOutput(output: string, isError: boolean): Record<string, unknown>;
|
|
152
|
+
/** Record the approved Cursor mode for later system-reminder injection. */
|
|
153
|
+
export declare function setActiveCursorMode(sessionKey: string | undefined, targetModeId: string, options?: {
|
|
154
|
+
bridgedPlanEntered?: boolean;
|
|
155
|
+
}): void;
|
|
156
|
+
export declare function getActiveCursorMode(sessionKey: string | undefined): string | undefined;
|
|
157
|
+
/** True while the provider has an approved Cursor plan/spec mode for this session. */
|
|
158
|
+
export declare function isCursorPlanModeActive(sessionKey: string | undefined): boolean;
|
|
159
|
+
/** True after a bridged plan/spec Run has observed plan_enter removed by the host. */
|
|
160
|
+
export declare function isBridgedCursorPlanModeActive(sessionKey: string | undefined): boolean;
|
|
161
|
+
export declare function clearActiveCursorMode(sessionKey: string | undefined): void;
|
|
162
|
+
export declare function resetActiveCursorModesForTests(): void;
|
|
163
|
+
/**
|
|
164
|
+
* Cursor CLI-shaped mode reminder for the active unified mode.
|
|
165
|
+
* Tool names are adapted to OpenCode (`task`, `question`) where the CLI names
|
|
166
|
+
* Cursor-only tools; the behavioral contract matches the CLI reminders.
|
|
167
|
+
*/
|
|
168
|
+
export declare function cursorModeSystemReminder(targetModeId: string, options?: {
|
|
169
|
+
firstTurn?: boolean;
|
|
170
|
+
planExitAdvertised?: boolean;
|
|
171
|
+
}): string | undefined;
|
|
172
|
+
/**
|
|
173
|
+
* Consume the active-mode reminder for this OpenCode session.
|
|
174
|
+
* Marks the mode as no longer first-turn after the first successful read.
|
|
175
|
+
*/
|
|
176
|
+
export declare function takeActiveCursorModeReminder(sessionKey: string | undefined, options?: {
|
|
177
|
+
advertisedTools?: ReadonlySet<string> | Iterable<string>;
|
|
178
|
+
}): string | undefined;
|