Fix Cursor SDK invocation to use static Agent.prompt API.

Use cloud repo config with Gitea URL and head ref, parse RunResult output correctly, and log cursor run IDs for debugging.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Daan Schouteden
2026-06-03 10:42:54 +02:00
parent e9aafdf8c4
commit 376c499c81
2 changed files with 61 additions and 43 deletions
+49 -36
View File
@@ -1,3 +1,5 @@
import { Agent, CursorAgentError } from "@cursor/sdk";
import { log } from "../logging/logger.js";
import { parseReviewResult, ReviewResult } from "./review-schema.js"; import { parseReviewResult, ReviewResult } from "./review-schema.js";
type RunReviewInput = { type RunReviewInput = {
@@ -5,57 +7,68 @@ type RunReviewInput = {
prompt: string; prompt: string;
timeoutMs: number; timeoutMs: number;
model?: string; model?: string;
giteaBaseUrl: string;
owner: string;
repo: string;
headRef: string;
correlationId?: string;
}; };
export async function runCursorReview(input: RunReviewInput): Promise<ReviewResult> { export async function runCursorReview(input: RunReviewInput): Promise<ReviewResult> {
const sdk = (await import("@cursor/sdk")) as any; const repoUrl = buildRepoUrl(input.giteaBaseUrl, input.owner, input.repo);
const Agent = sdk.Agent;
if (!Agent) {
throw new Error("Cursor SDK Agent API is unavailable");
}
const agent = new Agent({
apiKey: input.apiKey,
runtime: "cloud",
model: input.model
});
try {
const result = await withTimeout( const result = await withTimeout(
agent.prompt({ Agent.prompt(input.prompt, {
prompt: input.prompt apiKey: input.apiKey,
model: { id: input.model ?? "composer-2.5" },
cloud: {
repos: [{ url: repoUrl, startingRef: input.headRef }],
skipReviewerRequest: true
}
}), }),
input.timeoutMs, input.timeoutMs,
"Cursor review timed out" "Cursor review timed out"
); );
const text = extractText(result); log("info", "Cursor review run finished", {
const parsed = JSON.parse(text) as unknown; correlation_id: input.correlationId,
cursor_run_id: result.id,
status: result.status,
duration_ms: result.durationMs
});
if (result.status === "error") {
throw new Error(`Cursor review run failed (${result.id})`);
}
const text = extractResponseText(result.result);
const parsed = parseJsonFromText(text);
return parseReviewResult(parsed); return parseReviewResult(parsed);
} catch (error) {
if (error instanceof CursorAgentError) {
throw new Error(`Cursor SDK startup failed: ${error.message}`);
}
throw error;
}
} }
function extractText(result: unknown): string { function buildRepoUrl(baseUrl: string, owner: string, repo: string): string {
if (typeof result === "string") { return `${baseUrl.replace(/\/$/, "")}/${owner}/${repo}`;
}
function extractResponseText(result: string | undefined): string {
if (!result?.trim()) {
throw new Error("Cursor review returned empty result text");
}
return result; return result;
} }
if (result && typeof result === "object") { function parseJsonFromText(text: string): unknown {
const maybe = result as Record<string, unknown>; const trimmed = text.trim();
if (typeof maybe.output_text === "string") { const fenceMatch = trimmed.match(/^```(?:json)?\s*([\s\S]*?)\s*```$/i);
return maybe.output_text; const candidate = fenceMatch?.[1]?.trim() ?? trimmed;
} return JSON.parse(candidate);
if (typeof maybe.text === "string") {
return maybe.text;
}
if (Array.isArray(maybe.messages)) {
const last = maybe.messages.at(-1) as any;
const content = last?.content;
if (typeof content === "string") {
return content;
}
}
}
throw new Error("Could not extract Cursor response text");
} }
async function withTimeout<T>(promise: Promise<T>, timeoutMs: number, message: string): Promise<T> { async function withTimeout<T>(promise: Promise<T>, timeoutMs: number, message: string): Promise<T> {
+6 -1
View File
@@ -102,7 +102,12 @@ export async function runReview(input: {
apiKey: input.env.CURSOR_API_KEY, apiKey: input.env.CURSOR_API_KEY,
prompt, prompt,
timeoutMs: input.env.REVIEW_TIMEOUT_MS, timeoutMs: input.env.REVIEW_TIMEOUT_MS,
model: repoConfig.model model: repoConfig.model,
giteaBaseUrl: input.env.GITEA_BASE_URL,
owner,
repo,
headRef: pull.head.ref,
correlationId: input.correlationId
}), }),
retries: 2, retries: 2,
initialDelayMs: 500, initialDelayMs: 500,