import type { SendOptions } from "@cursor/sdk"; import { countCursorAgentMessages } from "./agent-message-web-tools.js"; import { CursorLiveRunAbortError } from "./live-run-coordinator.js"; import { cursorLiveRuns } from "./provider-live-run-drain.js"; import { consumeCursorLocalForceOverride } from "./runtime-state.js"; import { recordCursorSessionAgentLineage } from "./session-agent-lineage.js"; import type { installCursorSdkProcessErrorGuard } from "./sdk-process-error-guard.js"; import type { CursorProviderTurnRunnerParams, CursorProviderTurnPrepareResult, CursorProviderTurnSendResult, } from "./provider-turn-types.js"; import type { CursorSdkEventDebugSink } from "./sdk-event-debug.js"; export interface SendCursorProviderTurnParams { params: CursorProviderTurnRunnerParams; prepared: CursorProviderTurnPrepareResult; sdkEventDebug: CursorSdkEventDebugSink | undefined; sdkProcessErrorGuard: ReturnType; throwIfAborted: () => void; } function recordDebug(action: () => void): void { try { action(); } catch { // Debug capture is optional and must never change provider execution. } } export async function sendCursorProviderTurn(sendParams: SendCursorProviderTurnParams): Promise { const { params, prepared, sdkEventDebug, sdkProcessErrorGuard, throwIfAborted } = sendParams; const { options } = params; const { cwd, payload, meta, runtime, sessionAgentLease } = prepared; const { turnCoordinator, liveRun } = runtime; const agent = sessionAgentLease.agent; let completed = false; let sdkRun: Awaited> | null = null; const abortListener = () => { sdkProcessErrorGuard.suppressAbortErrors(); liveRun?.bridgeRun?.cancel("Cursor SDK run aborted"); if (sdkRun) { sdkRun.cancel().catch(() => {}); } }; const abortSignal = options?.signal; const abortRegistration = abortSignal ? { signal: abortSignal, listener: abortListener } : undefined; try { abortRegistration?.signal.addEventListener("abort", abortListener, { once: true }); throwIfAborted(); let cursorAgentMessageOffset: number | undefined; try { cursorAgentMessageOffset = await countCursorAgentMessages(agent.agentId, cwd, sessionAgentLease.store); } catch (error) { recordDebug(() => sdkEventDebug?.recordError("cursor_agent_message_count", error)); } throwIfAborted(); recordDebug(() => sdkEventDebug?.recordSendMeta({ mode: meta.sendPlan.mode, reason: meta.sendPlan.reason, resetAgent: meta.sendPlan.resetAgent, bootstrap: meta.bootstrap, promptText: meta.prompt.text, imageCount: meta.prompt.images.length, useNativeToolReplay: meta.useNativeToolReplay, bridgeEnabled: meta.bridgeEnabled, nativeReplayId: meta.nativeReplayId, promptInputTokens: meta.promptInputTokens, agentMode: meta.agentMode, })); recordDebug(() => sdkEventDebug?.recordSendPayload(payload)); recordDebug(() => sdkEventDebug?.recordProviderEvent("agent_send_start", payload)); const sendOptions: SendOptions = { mode: meta.agentMode, model: meta.modelSelection, onDelta: (args) => { recordDebug(() => sdkEventDebug?.recordOnDelta(args.update)); turnCoordinator.handleDelta(args.update); }, onStep: (args) => { recordDebug(() => sdkEventDebug?.recordOnStep(args.step)); turnCoordinator.handleStep(args.step); }, }; throwIfAborted(); if (consumeCursorLocalForceOverride(prepared.localForce)) { sendOptions.local = { force: true }; } const runPromise = agent.send(payload, sendOptions); // Record at send initiation (promise created), including later reject/cancel paths. recordCursorSessionAgentLineage(agent.agentId); const run = await runPromise; sdkRun = run; recordDebug(() => sdkEventDebug?.recordRunMeta({ runId: run.id, requestId: run.requestId, agentId: run.agentId, status: run.status, })); recordDebug(() => sdkEventDebug?.attachRunStream(run)); recordDebug(() => sdkEventDebug?.recordProviderEvent("agent_send_returned", { runId: run.id, requestId: run.requestId, agentId: run.agentId, status: run.status, })); if (liveRun) cursorLiveRuns.attachSdkRun(liveRun, run); if (options?.signal?.aborted) { sdkProcessErrorGuard.suppressAbortErrors(); liveRun?.bridgeRun?.cancel("Cursor SDK run aborted"); await run.cancel().catch(() => {}); throw new CursorLiveRunAbortError(); } completed = true; return { send: { run, cursorAgentMessageOffset }, abortRegistration, }; } finally { if (!completed && abortRegistration) { abortRegistration.signal.removeEventListener("abort", abortRegistration.listener); } } }