/** * Public Docs: https://cursor.com/docs/cli/acp#cursor-extension-methods * Additional reference provided by the Cursor team: https://anysphere.enterprise.slack.com/files/U068SSJE141/F0APT1HSZRP/cursor-acp-extension-method-schemas.md */import type { UserInputQuestion } from "@t3tools/contracts";import { Schema } from "effect"; const CursorAskQuestionOption = Schema.Struct({ id: Schema.String, label: Schema.String,}); const CursorAskQuestion = Schema.Struct({ id: Schema.String, prompt: Schema.String, options: Schema.Array(CursorAskQuestionOption), allowMultiple: Schema.optional(Schema.Boolean),}); export const CursorAskQuestionRequest = Schema.Struct({ toolCallId: Schema.String, title: Schema.optional(Schema.String), questions: Schema.Array(CursorAskQuestion),}); const CursorTodoStatus = Schema.String; const CursorTodo = Schema.Struct({ id: Schema.optional(Schema.String), content: Schema.optional(Schema.String), title: Schema.optional(Schema.String), status: Schema.optional(CursorTodoStatus),}); const CursorPlanPhase = Schema.Struct({ name: Schema.String, todos: Schema.Array(CursorTodo),}); export const CursorCreatePlanRequest = Schema.Struct({ toolCallId: Schema.String, name: Schema.optional(Schema.String), overview: Schema.optional(Schema.String), plan: Schema.String, todos: Schema.Array(CursorTodo), isProject: Schema.optional(Schema.Boolean), phases: Schema.optional(Schema.Array(CursorPlanPhase)),}); export const CursorUpdateTodosRequest = Schema.Struct({ toolCallId: Schema.String, todos: Schema.Array(CursorTodo), merge: Schema.Boolean,}); export function extractAskQuestions( params: typeof CursorAskQuestionRequest.Type,): ReadonlyArray<UserInputQuestion> { return params.questions.map((question) => ({ id: question.id, header: "Question", question: question.prompt, multiSelect: question.allowMultiple === true, options: question.options.length > 0 ? question.options.map((option) => ({ label: option.label, description: option.label, })) : [{ label: "OK", description: "Continue" }], }));} export function extractPlanMarkdown(params: typeof CursorCreatePlanRequest.Type): string { return params.plan || "# Plan\n\n(Cursor did not supply plan text.)";} export function extractTodosAsPlan(params: typeof CursorUpdateTodosRequest.Type): { readonly explanation?: string; readonly plan: ReadonlyArray<{ readonly step: string; readonly status: "pending" | "inProgress" | "completed"; }>;} { const plan = params.todos.flatMap((todo) => { const step = todo.content?.trim() ?? todo.title?.trim() ?? ""; if (step === "") { return []; } const status: "pending" | "inProgress" | "completed" = todo.status === "completed" ? "completed" : todo.status === "in_progress" || todo.status === "inProgress" ? "inProgress" : "pending"; return [{ step, status }]; }); return { plan };}
/**
* Public Docs: https://cursor.com/docs/cli/acp#cursor-extension-methods
* Additional reference provided by the Cursor team: https://anysphere.enterprise.slack.com/files/U068SSJE141/F0APT1HSZRP/cursor-acp-extension-method-schemas.md
*/
import type { UserInputQuestion } from "@t3tools/contracts";
import { Schema } from "effect";
const CursorAskQuestionOption = Schema.Struct({
id: Schema.String,
label: Schema.String,
});
const CursorAskQuestion = Schema.Struct({
id: Schema.String,
prompt: Schema.String,
options: Schema.Array(CursorAskQuestionOption),
allowMultiple: Schema.optional(Schema.Boolean),
});
export const CursorAskQuestionRequest = Schema.Struct({
toolCallId: Schema.String,
title: Schema.optional(Schema.String),
questions: Schema.Array(CursorAskQuestion),
});
const CursorTodoStatus = Schema.String;
const CursorTodo = Schema.Struct({
id: Schema.optional(Schema.String),
content: Schema.optional(Schema.String),
title: Schema.optional(Schema.String),
status: Schema.optional(CursorTodoStatus),
});
const CursorPlanPhase = Schema.Struct({
name: Schema.String,
todos: Schema.Array(CursorTodo),
});
export const CursorCreatePlanRequest = Schema.Struct({
toolCallId: Schema.String,
name: Schema.optional(Schema.String),
overview: Schema.optional(Schema.String),
plan: Schema.String,
todos: Schema.Array(CursorTodo),
isProject: Schema.optional(Schema.Boolean),
phases: Schema.optional(Schema.Array(CursorPlanPhase)),
});
export const CursorUpdateTodosRequest = Schema.Struct({
toolCallId: Schema.String,
todos: Schema.Array(CursorTodo),
merge: Schema.Boolean,
});
export function extractAskQuestions(
params: typeof CursorAskQuestionRequest.Type,
): ReadonlyArray<UserInputQuestion> {
return params.questions.map((question) => ({
id: question.id,
header: "Question",
question: question.prompt,
multiSelect: question.allowMultiple === true,
options:
question.options.length > 0
? question.options.map((option) => ({
label: option.label,
description: option.label,
}))
: [{ label: "OK", description: "Continue" }],
}));
}
export function extractPlanMarkdown(params: typeof CursorCreatePlanRequest.Type): string {
return params.plan || "# Plan\n\n(Cursor did not supply plan text.)";
}
export function extractTodosAsPlan(params: typeof CursorUpdateTodosRequest.Type): {
readonly explanation?: string;
readonly plan: ReadonlyArray<{
readonly step: string;
readonly status: "pending" | "inProgress" | "completed";
}>;
} {
const plan = params.todos.flatMap((todo) => {
const step = todo.content?.trim() ?? todo.title?.trim() ?? "";
if (step === "") {
return [];
}
const status: "pending" | "inProgress" | "completed" =
todo.status === "completed"
? "completed"
: todo.status === "in_progress" || todo.status === "inProgress"
? "inProgress"
: "pending";
return [{ step, status }];
});
return { plan };
}