import type { ListRunsOptions } from "@cursor/sdk"; import * as NodeServices from "@effect/platform-node/NodeServices"; import { assert, describe, it } from "@effect/vitest"; import { ProviderSessionId, ThreadId } from "@t3tools/contracts"; import * as Effect from "effect/Effect"; import * as Layer from "effect/Layer"; import * as Exit from "effect/Exit"; import * as Cause from "effect/Cause"; import { vi } from "vite-plus/test"; import * as ProviderEventLoggers from "@t3tools/provider-core/server/ProviderEventLoggers"; import * as CursorAgentSdk from "./CursorAgentSdk.ts"; import * as CursorSdk from "./CursorSdk.ts"; const cursorSdkMock = vi.hoisted(() => { const closeExecutionOrder: Array = []; const runOperations: Array = []; let agentCloseFailure: unknown; // The local store lists runs oldest first, like the real SDK. let storedRuns: Array<{ readonly id: string; readonly status: string }> = []; const runWait = vi.fn(async () => ({ id: "run-cursor-agent-sdk-test", requestId: "request-cursor-agent-sdk-test", status: "finished", result: "ok", model: { id: "default" }, durationMs: 1, })); const runCancel = vi.fn(async () => {}); const runSteer = vi.fn(async () => "complete_delivered" as const); const agentClose = vi.fn(() => { closeExecutionOrder.push("sdk:agent.close"); if (agentCloseFailure !== undefined) { throw agentCloseFailure; } }); const send = vi.fn( async ( _message: unknown, options: { readonly onDelta?: (input: { readonly update: unknown }) => Promise }, ) => { runOperations.push("run.start"); if (storedRuns.some((run) => run.status === "running")) { throw new Error("Agent agent-cursor-agent-sdk-test already has active run"); } await options.onDelta?.({ update: { type: "assistant-message-chunk", text: "hello", }, }); return { id: "run-cursor-agent-sdk-test", agentId: "agent-cursor-agent-sdk-test", wait: runWait, cancel: runCancel, steer: runSteer as typeof runSteer | undefined, }; }, ); const create = vi.fn(async () => ({ agentId: "agent-cursor-agent-sdk-test", send, close: agentClose, })); const resume = vi.fn(async () => ({ agentId: "agent-cursor-agent-sdk-test", send, close: agentClose, })); const listRuns = vi.fn(async (_agentId: string, options?: ListRunsOptions) => { runOperations.push("agent.listRuns"); // Mirrors the SDK's opaque cursor pagination with its default page size of 50. const offset = options?.cursor === undefined ? 0 : Number(options.cursor); const end = offset + (options?.limit ?? 50); return { items: storedRuns.slice(offset, end), ...(end < storedRuns.length ? { nextCursor: String(end) } : {}), }; }); const cancelRun = vi.fn(async (runId: string) => { runOperations.push("agent.cancelRun"); storedRuns = storedRuns.map((run) => run.id === runId ? { ...run, status: "cancelled" } : run, ); }); return { agentClose, cancelRun, closeExecutionOrder, create, listRuns, resume, runOperations, runCancel, runSteer, runWait, send, setAgentCloseFailure: (failure: unknown) => { agentCloseFailure = failure; }, setStoredRuns: (runs: Array<{ readonly id: string; readonly status: string }>) => { storedRuns = runs; }, }; }); vi.mock("./sdk.ts", () => ({ loadCursorSdk: () => ({ Agent: { create: cursorSdkMock.create, resume: cursorSdkMock.resume, listRuns: cursorSdkMock.listRuns, cancelRun: cursorSdkMock.cancelRun, messages: { list: vi.fn(async () => []), }, }, createAgentPlatform: vi.fn(), }), loadCursorSqlite: () => ({ SqliteLocalAgentStore: { open: vi.fn() } }), })); const layerTest = CursorAgentSdk.layer.pipe( Layer.provide( Layer.succeed(ProviderEventLoggers.ProviderEventLoggers, { native: { filePath: "cursor-agent-sdk-test.log", write: (event: unknown) => Effect.sync(() => { if ( typeof event === "object" && event !== null && Reflect.get(Reflect.get(event, "event"), "payload")?.type === "agent.close" ) { cursorSdkMock.closeExecutionOrder.push("protocol:agent.close"); } }), close: () => Effect.void, }, canonical: undefined, }), ), Layer.provide(CursorSdk.layer), Layer.provide(NodeServices.layer), ); describe("CursorAgentSdkRunner", () => { it.effect("constructs without loading and reports first-use load failures as runner errors", () => Effect.gen(function* () { const failure = new Error("missing SDK chunk"); const load = vi.fn(() => { throw failure; }); const runner = yield* CursorAgentSdk.makeCursorAgentSdkRunner(() => undefined).pipe( Effect.provideService(CursorSdk.CursorSdk, { load, loadSqlite: () => { throw new Error("unused SQLite"); }, }), ); assert.strictEqual(load.mock.calls.length, 0); const result = yield* runner .open({ operation: "create", options: {}, threadId: ThreadId.make("lazy-load"), providerSessionId: ProviderSessionId.make("lazy-load"), }) .pipe(Effect.exit); assert(Exit.isFailure(result)); assert.deepStrictEqual( Cause.squash(result.cause), new CursorAgentSdk.CursorAgentSdkRunnerError({ method: "agent.create", cause: failure, }), ); assert.strictEqual(load.mock.calls.length, 1); }), ); it.effect("retries a resumed send after cancelling its abandoned local run", () => Effect.gen(function* () { cursorSdkMock.runOperations.length = 0; cursorSdkMock.listRuns.mockClear(); cursorSdkMock.cancelRun.mockClear(); cursorSdkMock.setStoredRuns([{ id: "run-stale", status: "running" }]); const runner = yield* CursorAgentSdk.CursorAgentSdkRunner; const session = yield* runner.open({ operation: "resume", agentId: "agent-cursor-agent-sdk-test", options: { model: { id: "default" }, mode: "agent", local: { cwd: process.cwd() } }, threadId: ThreadId.make("thread-cursor-agent-sdk-stale-run-test"), providerSessionId: ProviderSessionId.make( "provider-session-cursor-agent-sdk-stale-run-test", ), }); assert.deepStrictEqual(cursorSdkMock.runOperations, []); const run = yield* session.send({ message: "continue" }); yield* run.wait; assert.deepStrictEqual(cursorSdkMock.runOperations, [ "run.start", "agent.listRuns", "agent.cancelRun", "run.start", ]); assert.deepStrictEqual(cursorSdkMock.listRuns.mock.calls[0], [ "agent-cursor-agent-sdk-test", { runtime: "local", cwd: process.cwd() }, ]); assert.deepStrictEqual(cursorSdkMock.cancelRun.mock.calls[0], [ "run-stale", { runtime: "local", cwd: process.cwd(), }, ]); }).pipe(Effect.provide(layerTest)), ); it.effect("cancels the abandoned run on a later page behind older terminal runs", () => Effect.gen(function* () { cursorSdkMock.runOperations.length = 0; cursorSdkMock.listRuns.mockClear(); cursorSdkMock.cancelRun.mockClear(); cursorSdkMock.setStoredRuns([ ...Array.from({ length: 50 }, (_, index) => ({ id: `run-terminal-${index}`, status: index === 0 ? "cancelled" : "finished", })), { id: "run-abandoned-on-second-restart", status: "running" }, ]); const runner = yield* CursorAgentSdk.CursorAgentSdkRunner; const session = yield* runner.open({ operation: "resume", agentId: "agent-cursor-agent-sdk-test", options: { model: { id: "default" }, mode: "agent", local: { cwd: process.cwd() } }, threadId: ThreadId.make("thread-cursor-agent-sdk-second-stale-run-test"), providerSessionId: ProviderSessionId.make( "provider-session-cursor-agent-sdk-second-stale-run-test", ), }); const run = yield* session.send({ message: "continue" }); yield* run.wait; assert.deepStrictEqual(cursorSdkMock.listRuns.mock.calls.length, 2); assert.deepStrictEqual( cursorSdkMock.cancelRun.mock.calls[0]?.[0], "run-abandoned-on-second-restart", ); assert.deepStrictEqual(cursorSdkMock.runOperations.at(-1), "run.start"); }).pipe(Effect.provide(layerTest)), ); it.effect("forwards Run.steer and reports a missing method as a follow-up", () => Effect.gen(function* () { cursorSdkMock.runSteer.mockClear(); const runner = yield* CursorAgentSdk.CursorAgentSdkRunner; const session = yield* runner.open({ operation: "create", options: {}, threadId: ThreadId.make("thread-cursor-agent-sdk-steer"), providerSessionId: ProviderSessionId.make("provider-session-cursor-agent-sdk-steer"), }); const run = yield* session.send({ message: "continue" }); assert.equal(yield* run.steer!("keep going"), "complete_delivered"); assert.deepStrictEqual(cursorSdkMock.runSteer.mock.calls, [["keep going"]]); cursorSdkMock.runSteer.mockImplementationOnce(async () => { throw new Error("steer transport failed"); }); const failed = yield* run.steer!("again").pipe(Effect.exit); assert(Exit.isFailure(failed)); cursorSdkMock.send.mockImplementationOnce(async () => ({ id: "run-without-steer", agentId: "agent-cursor-agent-sdk-test", wait: cursorSdkMock.runWait, cancel: cursorSdkMock.runCancel, steer: undefined, })); const plain = yield* session.send({ message: "plain" }); assert.equal(yield* plain.steer!("later"), "revert_to_followup"); }).pipe(Effect.provide(layerTest)), ); });